mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce2a3bd8eb | ||
|
|
b81d97c2da | ||
|
|
038019a4e7 |
No files matched your search
@@ -6,6 +6,18 @@ figure out what that actually means in terms of OpenRig topology, what
|
||||
the trade-offs are, who should do which part of the work, and what to
|
||||
hand off.
|
||||
|
||||
## First orientation
|
||||
|
||||
In Claude Code, invoke the native Skill tool with
|
||||
`skill: "openrig-skills"` before running `rig whoami --json`
|
||||
in the same turn. This loads the installed OpenRig index for orientation;
|
||||
reading its file or mentioning its name is not a Skill invocation.
|
||||
|
||||
Respect explicit ask/deny rules and managed restrictions. If the skill is
|
||||
unavailable or Claude still requires approval, report that boundary; do not
|
||||
change permissions or try a different command form to get around it.
|
||||
Other runtimes keep their ordinary identity and orientation flow.
|
||||
|
||||
## You advise; you do not run things
|
||||
|
||||
- Bringing rigs up / down / restarting / inspecting health is the
|
||||
|
||||
@@ -5,6 +5,14 @@ this through their terminal or through the Mission Control UI.
|
||||
|
||||
## First action
|
||||
|
||||
Use the identity from your first orientation turn. If it has not been
|
||||
verified yet, recover it before introducing yourself. In Claude Code, if
|
||||
this is a new turn, invoke the native Skill tool again with
|
||||
`skill: "openrig-skills"` before `rig whoami --json` in that
|
||||
same turn. Respect the permission boundary described in your role; the
|
||||
previous turn's invocation does not carry forward. Other runtimes use
|
||||
their ordinary identity flow.
|
||||
|
||||
Introduce yourself in one short paragraph. Tell the user:
|
||||
|
||||
1. Who you are (`advisor.lead`) and what you can do for them
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import type Database from "better-sqlite3";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parse } from "yaml";
|
||||
import { RigSpecSchema } from "../src/domain/rigspec-schema.js";
|
||||
import { resolveAgentRef } from "../src/domain/agent-resolver.js";
|
||||
import { createFullTestDb } from "./helpers/test-app.js";
|
||||
import { SessionRegistry } from "../src/domain/session-registry.js";
|
||||
import { EventBus } from "../src/domain/event-bus.js";
|
||||
@@ -719,6 +724,45 @@ describe("StartupOrchestrator", () => {
|
||||
expect(deliverStartup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["rig.yaml", "rig-claude-only.yaml"])("delivers native skill orientation in the advisor's first turn (%s)", async rigFile => {
|
||||
const kernel = path.resolve(import.meta.dirname, "../specs/rigs/launch/kernel");
|
||||
const readFile = (p: string) => fs.readFileSync(p, "utf8");
|
||||
const rig = RigSpecSchema.normalize(parse(readFile(path.join(kernel, rigFile))));
|
||||
const advisor = rig.pods.find(p => p.id === "advisor")!.members.find(m => m.id === "lead")!;
|
||||
expect(advisor.runtime).toBe("claude-code");
|
||||
const resolved = resolveAgentRef(advisor.agentRef, kernel, { readFile, exists: fs.existsSync });
|
||||
if (!resolved.ok) throw Error(JSON.stringify(resolved));
|
||||
const { spec, sourcePath } = resolved.resolved;
|
||||
const files = spec.startup.files.map(file => ({ ...file,
|
||||
absolutePath: path.resolve(sourcePath, file.path), ownerRoot: sourcePath }));
|
||||
const deliverStartup = vi.fn(async (_files: ResolvedStartupFile[]) => ({ delivered: 0, failed: [] }));
|
||||
const seed = seedSession();
|
||||
const result = await createOrchestrator({ readFile }).startNode(makeInput(seed, {
|
||||
adapter: mockAdapter({ deliverStartup }), resolvedStartupFiles: files,
|
||||
startupActions: [makeIdentityAction()],
|
||||
}));
|
||||
expect(result.ok).toBe(true);
|
||||
const firstPrompt = vi.mocked(tmux.sendText).mock.calls[0][1];
|
||||
expect(firstPrompt).toContain(makeIdentityAction().value);
|
||||
expect(firstPrompt).toContain("In Claude Code, invoke the native Skill tool");
|
||||
expect(firstPrompt).toContain('skill: "openrig-skills"');
|
||||
expect(firstPrompt.indexOf('skill: "openrig-skills"')).toBeLessThan(firstPrompt.indexOf("rig whoami --json"));
|
||||
expect(firstPrompt).toContain("in the same turn");
|
||||
expect(firstPrompt).toContain("explicit ask/deny rules and managed restrictions");
|
||||
// Context is a later message. It must retain the same-turn invocation rule,
|
||||
// rather than relying on the previous turn's skill grant.
|
||||
// The adapter first provisions pre-launch files, even when that list is empty.
|
||||
expect(deliverStartup).toHaveBeenCalledTimes(2);
|
||||
expect(deliverStartup.mock.calls[0][0]).toEqual([]);
|
||||
const later = deliverStartup.mock.calls[1][0];
|
||||
expect(later.map(f => f.path)).toEqual(["startup/context.md"]);
|
||||
const context = readFile(later[0].absolutePath);
|
||||
expect(context).toContain("invoke the native Skill tool again");
|
||||
expect(context).toContain('skill: "openrig-skills"');
|
||||
expect(context.indexOf("invoke the native Skill tool again")).toBeLessThan(context.indexOf("rig whoami --json"));
|
||||
// This checks delivered instructions, not native Skill execution or permission effects.
|
||||
});
|
||||
|
||||
it("does not replay the session identity on a resumed restore", async () => {
|
||||
const seed = seedSession();
|
||||
const sendText = vi.fn(async () => ({ ok: true as const }));
|
||||
|
||||
Reference in new issue
Block a user