@akagilnc/pi-workflow-roles 0.1.4444 → 0.1.4503
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/description.js +2 -3
- package/dist/acp-host/production-host.js +512 -288
- package/dist/auditor-soul.js +8 -1
- package/dist/headless-host/description.js +3 -3
- package/dist/headless-host/production-host.js +519 -257
- package/dist/host-descriptions.js +3 -18
- package/dist/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +48 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +21 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +170 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +118 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +128 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +41 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +134 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +31 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +26 -0
- package/dist/method-host-plugin/skills/tdd/SKILL.md +38 -0
- package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/tdd/mocking.md +59 -0
- package/dist/method-host-plugin/skills/tdd/provenance.json +36 -0
- package/dist/method-host-plugin/skills/tdd/tests.md +77 -0
- package/dist/public-cli/countersign-run.js +214 -155
- package/dist/public-cli/diarist-run.js +4 -1
- package/dist/public-cli/invocation.js +129 -21
- package/dist/public-cli/main.js +386 -202
- package/dist/session-opening-materials.js +17 -5
- package/extensions/role-runtime.ts +16 -6
- package/package.json +1 -1
- package/resources/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/scripts/build-package.mjs +6 -1
- package/src/acp-host/description.ts +2 -4
- package/src/acp-host/production-host.ts +0 -1
- package/src/auditor-soul.ts +10 -1
- package/src/headless-host/description.ts +4 -3
- package/src/headless-host/role-turn-host.ts +27 -4
- package/src/host-descriptions.ts +3 -23
- package/src/host-native-method.ts +67 -0
- package/src/public-cli/countersign-run.ts +326 -218
- package/src/public-cli/diarist-run.ts +4 -0
- package/src/public-cli/invocation.ts +205 -23
- package/src/role-envelope.ts +17 -47
- package/src/role-runtime-dependencies.ts +17 -2
- package/src/role-runtime.ts +12 -7
- package/src/session-opening-materials.ts +27 -11
|
@@ -25,18 +25,29 @@ async function readPackageMaterial(relativePath) {
|
|
|
25
25
|
}
|
|
26
26
|
async function joinPackageMaterials(relativePaths) {
|
|
27
27
|
const chunks = [];
|
|
28
|
-
for (const relativePath of relativePaths)
|
|
29
|
-
chunks.push(await readPackageMaterial(relativePath));
|
|
30
|
-
}
|
|
28
|
+
for (const relativePath of relativePaths) chunks.push(await readPackageMaterial(relativePath));
|
|
31
29
|
return chunks.join("\n\n");
|
|
32
30
|
}
|
|
31
|
+
function roleSoulPath(role, materials) {
|
|
32
|
+
const suffix = `/souls/${role}.md`;
|
|
33
|
+
const path = materials.find((candidate) => `/${candidate}`.endsWith(suffix));
|
|
34
|
+
if (path === void 0) throw new Error(`session materials omit the ${role} Soul`);
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
async function loadSeparatedSessionPart(role, materials, part) {
|
|
38
|
+
const soul = roleSoulPath(role, materials);
|
|
39
|
+
return part === "soul" ? readPackageMaterial(soul) : joinPackageMaterials(materials.filter((path) => path !== soul));
|
|
40
|
+
}
|
|
33
41
|
const MAIN_ROLE_SESSION_MATERIALS = {
|
|
34
42
|
...Object.fromEntries(
|
|
35
43
|
PUBLIC_ROLE_RECORDS.map((record) => [record.role, record.sessionMaterials])
|
|
36
44
|
)
|
|
37
45
|
};
|
|
38
46
|
function loadMainRoleSessionMaterials(role) {
|
|
39
|
-
return
|
|
47
|
+
return loadSeparatedSessionPart(role, MAIN_ROLE_SESSION_MATERIALS[role], "soul");
|
|
48
|
+
}
|
|
49
|
+
function loadMainRoleReferenceMaterials(role) {
|
|
50
|
+
return loadSeparatedSessionPart(role, MAIN_ROLE_SESSION_MATERIALS[role], "references");
|
|
40
51
|
}
|
|
41
52
|
const GATEKEEPER_SESSION_MATERIALS = {
|
|
42
53
|
// #639: single authority — the public gatekeeper record owns the province list.
|
|
@@ -45,13 +56,14 @@ const GATEKEEPER_SESSION_MATERIALS = {
|
|
|
45
56
|
notary: NOTARY_SESSION_MATERIALS
|
|
46
57
|
};
|
|
47
58
|
function loadGatekeeperSessionMaterials(role) {
|
|
48
|
-
return
|
|
59
|
+
return loadSeparatedSessionPart(role, GATEKEEPER_SESSION_MATERIALS[role], "soul");
|
|
49
60
|
}
|
|
50
61
|
export {
|
|
51
62
|
GATEKEEPER_SESSION_MATERIALS,
|
|
52
63
|
MAIN_ROLE_SESSION_MATERIALS,
|
|
53
64
|
joinPackageMaterials,
|
|
54
65
|
loadGatekeeperSessionMaterials,
|
|
66
|
+
loadMainRoleReferenceMaterials,
|
|
55
67
|
loadMainRoleSessionMaterials,
|
|
56
68
|
readPackageMaterial,
|
|
57
69
|
resolvePackageRootDir
|
|
@@ -31,9 +31,14 @@ import { JUDGE_OUTPUT_TOOL_NAME } from "../src/package-contracts/judge-output.ts
|
|
|
31
31
|
import { readOAuthKeepaliveProviders } from "../src/oauth-keepalive.ts";
|
|
32
32
|
import {
|
|
33
33
|
formatNavigatorRoleHelp,
|
|
34
|
+
type RoleRuntimeDependencies,
|
|
34
35
|
} from "../src/role-runtime.ts";
|
|
35
36
|
import { loadAuditorSoulFromSubjectInput } from "../src/auditor-soul.ts";
|
|
36
|
-
import {
|
|
37
|
+
import { loadPackagedRoleReferenceMaterials } from "../src/role-runtime-dependencies.ts";
|
|
38
|
+
import {
|
|
39
|
+
loadGatekeeperSessionMaterials,
|
|
40
|
+
loadMainRoleSessionMaterials,
|
|
41
|
+
} from "../src/session-opening-materials.ts";
|
|
37
42
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
38
43
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
39
44
|
const navigatorRoutePlaybookPath = fileURLToPath(new URL("../resources/navigator-route-playbook.md", import.meta.url));
|
|
@@ -105,12 +110,11 @@ export async function loadNavigatorWorkContext(
|
|
|
105
110
|
});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
export
|
|
109
|
-
const oauthKeepaliveProviders = readOAuthKeepaliveProviders();
|
|
110
|
-
registerNavigatorModelCommand(pi);
|
|
113
|
+
export function createPiRoleRuntimeDependencies(pi: ExtensionAPI): RoleRuntimeDependencies {
|
|
111
114
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
112
|
-
|
|
115
|
+
return {
|
|
113
116
|
packageRoot,
|
|
117
|
+
loadRoleReferenceMaterials: loadPackagedRoleReferenceMaterials,
|
|
114
118
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
115
119
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
116
120
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
|
@@ -167,7 +171,13 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
167
171
|
}
|
|
168
172
|
return loadHomeCanonicalSkillBinding(name);
|
|
169
173
|
},
|
|
170
|
-
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default function roleRuntime(pi: ExtensionAPI): void {
|
|
178
|
+
const oauthKeepaliveProviders = readOAuthKeepaliveProviders();
|
|
179
|
+
registerNavigatorModelCommand(pi);
|
|
180
|
+
createPiRoleRuntimeExtension(createPiRoleRuntimeDependencies(pi), {
|
|
171
181
|
transcriptFromContext,
|
|
172
182
|
oauthKeepalive: { providers: oauthKeepaliveProviders },
|
|
173
183
|
})(pi);
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmod, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { chmod, copyFile, cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { build } from "esbuild";
|
|
@@ -200,6 +200,11 @@ export async function buildPackageArtifacts() {
|
|
|
200
200
|
await buildAcpProductionHost();
|
|
201
201
|
await buildHeadlessProductionHost();
|
|
202
202
|
await buildMigrateBookTopology();
|
|
203
|
+
const pluginDir = join("dist", "method-host-plugin");
|
|
204
|
+
await rm(pluginDir, { recursive: true, force: true });
|
|
205
|
+
await mkdir(pluginDir, { recursive: true });
|
|
206
|
+
await cp("resources/method-host-plugin/.claude-plugin", join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
207
|
+
await cp("resources/methods", join(pluginDir, "skills"), { recursive: true });
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
const isMain =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One ACP host description. Every host-specific value the generic ACP adapter
|
|
3
|
-
* needs — binary location, argv shape, resume verb, binding filename,
|
|
3
|
+
* needs — binary location, argv shape, resume verb, binding filename,
|
|
4
4
|
* optional seat-profile soul — is data here; the lifecycle in role-turn-host.ts
|
|
5
5
|
* stays one copy (#732).
|
|
6
6
|
*/
|
|
@@ -30,7 +30,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
30
30
|
boundResume: "session/load" | "session/new";
|
|
31
31
|
/** Durable ACP binding filename written beside the session principal. */
|
|
32
32
|
sessionBindingFile: string;
|
|
33
|
-
childEnv: Readonly<Record<string, string>>;
|
|
34
33
|
/**
|
|
35
34
|
* When set, the production factory ensures a seat profile whose SOUL.md is a
|
|
36
35
|
* symlink to the packaged role soul, and prefixes argv with `flag <name>`.
|
|
@@ -44,8 +43,7 @@ export function resolveAcpBinary(description: AcpHostDescription, operatorHome:
|
|
|
44
43
|
return join(operatorHome, ...description.binaryFromHome);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
/** Stdio argv: optional profile flag, thinking flag
|
|
48
|
-
* prefix, optional model flag pair, suffix. */
|
|
46
|
+
/** Stdio argv: optional profile flag, thinking flag, prefix, model, suffix. */
|
|
49
47
|
export function acpStdioArgs(
|
|
50
48
|
description: AcpHostDescription,
|
|
51
49
|
model?: { readonly model?: string; readonly thinking?: string },
|
|
@@ -55,7 +55,6 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
55
55
|
const { packageRoot, principalAuthority, description, hostName } = options;
|
|
56
56
|
const env: NodeJS.ProcessEnv = {
|
|
57
57
|
...process.env,
|
|
58
|
-
...description.childEnv,
|
|
59
58
|
AK_PACKAGE_ROOT: packageRoot,
|
|
60
59
|
};
|
|
61
60
|
|
package/src/auditor-soul.ts
CHANGED
|
@@ -85,10 +85,19 @@ export async function loadAuditorSoul(role: AuditorSoulRole): Promise<string> {
|
|
|
85
85
|
if (soul.trim().length === 0) {
|
|
86
86
|
throw new Error(`The ${role} auditor Soul is blank`);
|
|
87
87
|
}
|
|
88
|
-
return
|
|
88
|
+
return soul;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loadAuditorReferenceMaterials(role: AuditorSoulRole): Promise<string> {
|
|
92
|
+
const soulPath = auditorSoulRelativePath(role);
|
|
93
|
+
return joinPackageMaterials(AUDITOR_SESSION_MATERIALS[role].filter((path) => path !== soulPath));
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/** Runtime loader: subject input decides which soul file to assemble. */
|
|
92
97
|
export async function loadAuditorSoulFromSubjectInput(raw?: string): Promise<string> {
|
|
93
98
|
return loadAuditorSoul(resolveAuditorSubject(raw));
|
|
94
99
|
}
|
|
100
|
+
|
|
101
|
+
export function loadAuditorReferenceMaterialsFromSubjectInput(raw?: string): Promise<string> {
|
|
102
|
+
return loadAuditorReferenceMaterials(resolveAuditorSubject(raw));
|
|
103
|
+
}
|
|
@@ -90,6 +90,8 @@ export function headlessTurnArgs(options: {
|
|
|
90
90
|
readonly effort?: string;
|
|
91
91
|
/** Fresh session: pass as session id. Resume: pass as resume id. */
|
|
92
92
|
readonly session: { readonly kind: "new"; readonly id: string } | { readonly kind: "resume"; readonly id: string };
|
|
93
|
+
/** Packaged method plugin dir (`--plugin-dir`). */
|
|
94
|
+
readonly pluginDir?: string;
|
|
93
95
|
}): string[] {
|
|
94
96
|
const { description } = options;
|
|
95
97
|
const args: string[] = [
|
|
@@ -100,6 +102,7 @@ export function headlessTurnArgs(options: {
|
|
|
100
102
|
description.jsonSchemaFlag,
|
|
101
103
|
JSON.stringify(options.jsonSchema),
|
|
102
104
|
];
|
|
105
|
+
if (options.pluginDir) args.push("--plugin-dir", options.pluginDir);
|
|
103
106
|
if (options.mcpConfigPath !== undefined && options.mcpConfigPath !== "") {
|
|
104
107
|
args.push(description.mcpConfigFlag, options.mcpConfigPath);
|
|
105
108
|
}
|
|
@@ -422,9 +425,7 @@ export function codexTurnArgs(options: {
|
|
|
422
425
|
|
|
423
426
|
// JSONL event stream: thread_id + final agent_message + turn.completed/failed.
|
|
424
427
|
args.push("--json");
|
|
425
|
-
// Operator config/
|
|
426
|
-
// Project/system config and AGENTS.md have no official suppression switch.
|
|
427
|
-
args.push("--ignore-user-config", "--ignore-rules");
|
|
428
|
+
// Operator config/skills stay open (#922 host-native-loader). Auth uses CODEX_HOME.
|
|
428
429
|
const roots = (options.writableRoots ?? []).filter((root) => root !== "");
|
|
429
430
|
if (roots.length > 0) {
|
|
430
431
|
// Resume has no --add-dir; the config key keeps extra roots available on both paths.
|
|
@@ -23,6 +23,12 @@ import {
|
|
|
23
23
|
} from "../prepared-role-turn.ts";
|
|
24
24
|
|
|
25
25
|
import { reportHostSessionEvent } from "../host-session-record.ts";
|
|
26
|
+
import {
|
|
27
|
+
applyClaudeSkillInvocation,
|
|
28
|
+
applyCodexSkillInvocation,
|
|
29
|
+
hostMethodSkills,
|
|
30
|
+
packagedMethodPluginDir,
|
|
31
|
+
} from "../host-native-method.ts";
|
|
26
32
|
import {
|
|
27
33
|
closeJsonSchemaForCodex,
|
|
28
34
|
codexTurnArgs,
|
|
@@ -410,6 +416,7 @@ function buildTurnArgs(options: {
|
|
|
410
416
|
readonly sessionKind: "new" | "resume";
|
|
411
417
|
readonly cwd: string;
|
|
412
418
|
readonly writableRoots?: readonly string[];
|
|
419
|
+
readonly pluginDir?: string;
|
|
413
420
|
}): readonly string[] {
|
|
414
421
|
if (isCodexExecDescription(options.description)) {
|
|
415
422
|
if (options.sessionKind === "resume" && !options.sessionId) {
|
|
@@ -441,6 +448,7 @@ function buildTurnArgs(options: {
|
|
|
441
448
|
...(options.model === undefined ? {} : { model: options.model }),
|
|
442
449
|
...(options.effort === undefined ? {} : { effort: options.effort }),
|
|
443
450
|
session: { kind: options.sessionKind, id: options.sessionId },
|
|
451
|
+
...(options.pluginDir === undefined ? {} : { pluginDir: options.pluginDir }),
|
|
444
452
|
});
|
|
445
453
|
}
|
|
446
454
|
|
|
@@ -469,11 +477,17 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
469
477
|
await writeFile(systemPromptPath, systemPrompt, "utf8");
|
|
470
478
|
let mcpConfigPath: string | undefined;
|
|
471
479
|
let outputSchemaPath: string | undefined;
|
|
480
|
+
let pluginDir: string | undefined;
|
|
481
|
+
let applyMethodPrompt: (prompt: string) => string = codex
|
|
482
|
+
? (prompt) => applyCodexSkillInvocation(request.methods, prompt)
|
|
483
|
+
: (prompt) => prompt;
|
|
472
484
|
if (codex) {
|
|
473
|
-
// Codex --output-schema needs a closed transport projection on disk.
|
|
474
485
|
outputSchemaPath = join(request.runDirectory, "headless-output-schema.json");
|
|
475
|
-
|
|
476
|
-
|
|
486
|
+
await writeFile(
|
|
487
|
+
outputSchemaPath,
|
|
488
|
+
`${JSON.stringify(closeJsonSchemaForCodex(prepared.jsonSchema), null, 2)}\n`,
|
|
489
|
+
"utf8",
|
|
490
|
+
);
|
|
477
491
|
} else {
|
|
478
492
|
mcpConfigPath = join(request.runDirectory, "headless-mcp-config.json");
|
|
479
493
|
await writeFile(
|
|
@@ -481,6 +495,14 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
481
495
|
`${JSON.stringify(headlessMcpConfigDocument(prepared.mcpServers), null, 2)}\n`,
|
|
482
496
|
"utf8",
|
|
483
497
|
);
|
|
498
|
+
if (hostMethodSkills(request.methods).length > 0) {
|
|
499
|
+
const packageRoot = env.AK_PACKAGE_ROOT ?? process.env.AK_PACKAGE_ROOT;
|
|
500
|
+
if (typeof packageRoot !== "string" || packageRoot === "") {
|
|
501
|
+
throw new Error("claude method plugin-dir requires AK_PACKAGE_ROOT");
|
|
502
|
+
}
|
|
503
|
+
pluginDir = packagedMethodPluginDir(packageRoot);
|
|
504
|
+
applyMethodPrompt = (prompt) => applyClaudeSkillInvocation(request.methods, prompt);
|
|
505
|
+
}
|
|
484
506
|
}
|
|
485
507
|
|
|
486
508
|
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
@@ -505,6 +527,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
505
527
|
sessionKind,
|
|
506
528
|
cwd: request.cwd,
|
|
507
529
|
...(gitCommonDir === undefined ? {} : { writableRoots: [gitCommonDir] }),
|
|
530
|
+
...(pluginDir === undefined ? {} : { pluginDir }),
|
|
508
531
|
});
|
|
509
532
|
} catch (error) {
|
|
510
533
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -522,7 +545,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
522
545
|
args,
|
|
523
546
|
cwd: request.cwd,
|
|
524
547
|
env,
|
|
525
|
-
stdin: prompt,
|
|
548
|
+
stdin: applyMethodPrompt(prompt),
|
|
526
549
|
...(abortSignal === undefined ? {} : { signal: abortSignal }),
|
|
527
550
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
528
551
|
onStdoutLine(line) {
|
package/src/host-descriptions.ts
CHANGED
|
@@ -9,13 +9,6 @@
|
|
|
9
9
|
import type { AcpHostDescription } from "./acp-host/description.ts";
|
|
10
10
|
import type { HeadlessHostDescription } from "./headless-host/description.ts";
|
|
11
11
|
|
|
12
|
-
/** Grok CLI reads vendor-private compat surfaces unless each is disabled by name. */
|
|
13
|
-
const PRIVATE_COMPAT_ENV = Object.fromEntries(
|
|
14
|
-
["CLAUDE", "CURSOR", "CODEX"].flatMap((vendor) =>
|
|
15
|
-
["SKILLS", "RULES", "AGENTS", "MCPS", "HOOKS", "SESSIONS"].map((kind) =>
|
|
16
|
-
[`GROK_${vendor}_${kind}_ENABLED`, "false"] as const)),
|
|
17
|
-
);
|
|
18
|
-
|
|
19
12
|
export const DEFAULT_ROLE_TURN_HOST = "pi" as const;
|
|
20
13
|
|
|
21
14
|
export type HostFamily = "acp" | "headless";
|
|
@@ -32,11 +25,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
32
25
|
modelPassing: "argv",
|
|
33
26
|
boundResume: "session/load",
|
|
34
27
|
sessionBindingFile: "grok-acp-session.json",
|
|
35
|
-
childEnv: Object.freeze({
|
|
36
|
-
...PRIVATE_COMPAT_ENV,
|
|
37
|
-
GROK_MEMORY: "0",
|
|
38
|
-
GROK_SUBAGENTS: "0",
|
|
39
|
-
}),
|
|
40
28
|
}),
|
|
41
29
|
/**
|
|
42
30
|
* Operator home `~/.hermes`, native session/load resume, `acp` subcommand.
|
|
@@ -55,7 +43,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
55
43
|
modelPassing: "set_model",
|
|
56
44
|
boundResume: "session/load",
|
|
57
45
|
sessionBindingFile: "hermes-acp-session.json",
|
|
58
|
-
childEnv: Object.freeze({}),
|
|
59
46
|
seatProfileSoul: Object.freeze({
|
|
60
47
|
flag: "-p",
|
|
61
48
|
namePrefix: "ak-",
|
|
@@ -69,12 +56,9 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
69
56
|
* Headless CLI family (#645 / #646). Claude print-mode is the first row;
|
|
70
57
|
* codex exec (#646) adds another. Protocol-specific argv/parse live in
|
|
71
58
|
* headless-host helpers (#752 per-host impl).
|
|
72
|
-
* Claude fixedArgs: print mode,
|
|
73
|
-
*
|
|
74
|
-
* (#
|
|
75
|
-
* user/project/local CLAUDE.md/hooks/skills (role envelope is delivered via
|
|
76
|
-
* `--system-prompt` wholesale replace). `--strict-mcp-config` with no
|
|
77
|
-
* `--mcp-config` drops operator MCP + claude.ai connectors.
|
|
59
|
+
* Claude fixedArgs: print mode, full permissions. stream-json + verbose: live
|
|
60
|
+
* host events for sitian records (#811); result is last line. Forced methods
|
|
61
|
+
* ride `--plugin-dir` (#922); operator skill/setting surfaces stay open.
|
|
78
62
|
*/
|
|
79
63
|
export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDescription>> = Object.freeze({
|
|
80
64
|
"claude": Object.freeze({
|
|
@@ -87,10 +71,6 @@ export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDes
|
|
|
87
71
|
// Intermediate assistant/tool/system events require verbose with stream-json.
|
|
88
72
|
"--verbose",
|
|
89
73
|
"--permission-mode", "bypassPermissions",
|
|
90
|
-
// Empty sources: no user/project/local operator surface (envelope owns materials).
|
|
91
|
-
"--setting-sources", "",
|
|
92
|
-
// With adapter-supplied --mcp-config only (AK relay); drops operator + claude.ai MCP.
|
|
93
|
-
"--strict-mcp-config",
|
|
94
74
|
]),
|
|
95
75
|
promptFlag: "-p",
|
|
96
76
|
modelFlag: "--model",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** #922 host-native packaged method delivery. */
|
|
2
|
+
import { lstat, mkdir, readlink, realpath, symlink } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import type { MethodBinding } from "./host-contracts.ts";
|
|
5
|
+
|
|
6
|
+
type HostMethodSkill = Readonly<{ name: string }>;
|
|
7
|
+
export const HOST_METHOD_PLUGIN_NAME = "ak-methods" as const;
|
|
8
|
+
export const packagedMethodsDir = (root: string) => join(root, "resources", "methods");
|
|
9
|
+
export const packagedMethodPluginDir = (root: string) => join(root, "dist", "method-host-plugin");
|
|
10
|
+
|
|
11
|
+
export function hostMethodSkills(methods: readonly MethodBinding[]): readonly HostMethodSkill[] {
|
|
12
|
+
return Object.freeze(methods.flatMap((method) => {
|
|
13
|
+
if (method.kind !== "skill") return [];
|
|
14
|
+
const name = basename(dirname(method.path));
|
|
15
|
+
return name ? [Object.freeze({ name })] : [];
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const alreadyPrefixed = (prompt: string, token: string) => {
|
|
20
|
+
const text = prompt.trimStart();
|
|
21
|
+
return text === token || text.startsWith(`${token} `) || text.startsWith(`${token}\n`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function prefixMethodInvocation(token: string, prompt: string): string {
|
|
25
|
+
if (alreadyPrefixed(prompt, token)) return prompt;
|
|
26
|
+
return prompt ? `${token} ${prompt}` : token;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Claude's documented plugin Skill invocation syntax. */
|
|
30
|
+
export function applyClaudeSkillInvocation(methods: readonly MethodBinding[], prompt: string): string {
|
|
31
|
+
const skills = hostMethodSkills(methods);
|
|
32
|
+
if (skills.length !== 1) return prompt;
|
|
33
|
+
return prefixMethodInvocation(`/${HOST_METHOD_PLUGIN_NAME}:${skills[0]!.name}`, prompt);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Codex's documented explicit Skill invocation syntax. */
|
|
37
|
+
export function applyCodexSkillInvocation(methods: readonly MethodBinding[], prompt: string): string {
|
|
38
|
+
return hostMethodSkills(methods).reduceRight(
|
|
39
|
+
(invocation, skill) => prefixMethodInvocation(`$${skill.name}`, invocation),
|
|
40
|
+
prompt,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Install the documented project Skill catalog once and leave it in place. */
|
|
45
|
+
export async function installWorkspaceMethodSkills(cwd: string, packageRoot: string): Promise<void> {
|
|
46
|
+
const target = await realpath(packagedMethodsDir(packageRoot));
|
|
47
|
+
const link = join(cwd, ".agents", "skills");
|
|
48
|
+
try {
|
|
49
|
+
const stat = await lstat(link);
|
|
50
|
+
if (!stat.isSymbolicLink() || await realpath(link) !== target) {
|
|
51
|
+
const detail = stat.isSymbolicLink() ? `symlink to ${await readlink(link)}` : "non-symlink entry";
|
|
52
|
+
throw new Error(`workspace method catalog conflict at ${link}: ${detail}`);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
57
|
+
}
|
|
58
|
+
await mkdir(dirname(link), { recursive: true });
|
|
59
|
+
try {
|
|
60
|
+
await symlink(target, link);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
63
|
+
if ((await realpath(link).catch(() => "")) !== target) {
|
|
64
|
+
throw new Error(`workspace method catalog conflict at ${link}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|