@akagilnc/pi-workflow-roles 0.1.4422 → 0.1.4489
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 +333 -258
- package/dist/auditor-soul.js +8 -1
- package/dist/diarist-contracts.js +2 -11
- package/dist/headless-host/description.js +3 -3
- package/dist/headless-host/production-host.js +340 -227
- 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/main.js +14 -21
- package/dist/session-opening-materials.js +17 -5
- package/dist/ticket-provenance-contracts.js +6 -25
- package/dist/ticket-provenance.js +223 -90
- 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/diarist-contracts.ts +1 -19
- package/src/diarist-role.ts +3 -31
- package/src/diarist.ts +5 -19
- 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/ledger-session-read.ts +1 -2
- package/src/role-envelope.ts +17 -47
- package/src/role-runtime-dependencies.ts +17 -2
- package/src/role-runtime.ts +14 -31
- package/src/session-opening-materials.ts +27 -11
- package/src/ticket-provenance-contracts.ts +11 -38
- package/src/ticket-provenance.ts +228 -109
package/src/diarist-contracts.ts
CHANGED
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
* Machine facts about the volume come from the mechanical reproject seam, never
|
|
5
5
|
* from model self-report (锚定宪法); this module owns the receipt shape only.
|
|
6
6
|
*
|
|
7
|
-
* #901: LLM submits bounds
|
|
7
|
+
* #901: LLM submits bounds; the mechanical layer projects source bytes.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
projectTicketProvenanceAmendments,
|
|
12
11
|
projectTicketProvenanceSessions,
|
|
13
|
-
type TicketProvenanceAmendment,
|
|
14
12
|
type TicketProvenanceSession,
|
|
15
13
|
} from "./ticket-provenance-contracts.ts";
|
|
16
14
|
import {
|
|
@@ -45,11 +43,6 @@ export type DiaristOutput =
|
|
|
45
43
|
* Malformed endpoints → accept hook reasks (`reask-not-explode`).
|
|
46
44
|
*/
|
|
47
45
|
readonly sessions?: readonly TicketProvenanceSession[];
|
|
48
|
-
/**
|
|
49
|
-
* Optional amendments for unparsable source lines (s + line + speaker + text).
|
|
50
|
-
* Absent = no amendments this turn (not a shape rejection).
|
|
51
|
-
*/
|
|
52
|
-
readonly amendments?: readonly TicketProvenanceAmendment[];
|
|
53
46
|
}
|
|
54
47
|
| {
|
|
55
48
|
/** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
|
|
@@ -187,14 +180,3 @@ export function projectDiaristSessions(
|
|
|
187
180
|
if (raw === undefined) return [];
|
|
188
181
|
return projectTicketProvenanceSessions(raw);
|
|
189
182
|
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Project optional amendments. Absent / non-array → empty (not a rejection).
|
|
193
|
-
*/
|
|
194
|
-
export function projectDiaristAmendments(
|
|
195
|
-
value: unknown,
|
|
196
|
-
): readonly TicketProvenanceAmendment[] {
|
|
197
|
-
const raw = (value as { amendments?: unknown } | null)?.amendments;
|
|
198
|
-
if (raw === undefined) return [];
|
|
199
|
-
return projectTicketProvenanceAmendments(raw);
|
|
200
|
-
}
|
package/src/diarist-role.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { validateRecordedDiaristOutput };
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* 起居郎交卷形状;形状指引,非 schema 闸。
|
|
21
|
-
* #901:交边界(sessions
|
|
21
|
+
* #901:交边界(sessions);正文与不可解析原字节由机械投影。
|
|
22
22
|
*/
|
|
23
23
|
export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
24
24
|
openToolObject(
|
|
@@ -93,34 +93,6 @@ export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
|
93
93
|
},
|
|
94
94
|
),
|
|
95
95
|
),
|
|
96
|
-
amendments: Type.Optional(
|
|
97
|
-
Type.Array(
|
|
98
|
-
Type.Object(
|
|
99
|
-
{
|
|
100
|
-
s: Type.Optional(
|
|
101
|
-
Type.Unknown({ description: "卷下标(sessions 位置)" }),
|
|
102
|
-
),
|
|
103
|
-
line: Type.Optional(
|
|
104
|
-
Type.Unknown({ description: "源卷 1-based 行号" }),
|
|
105
|
-
),
|
|
106
|
-
speaker: Type.Optional(
|
|
107
|
-
Type.String({ description: "owner | runner" }),
|
|
108
|
-
),
|
|
109
|
-
text: Type.Optional(
|
|
110
|
-
Type.String({ description: "补写正文(原话)" }),
|
|
111
|
-
),
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
additionalProperties: true,
|
|
115
|
-
description: "一条坏行补写;缺字段的成员机械跳过",
|
|
116
|
-
},
|
|
117
|
-
),
|
|
118
|
-
{
|
|
119
|
-
description:
|
|
120
|
-
"可选;机械解析不了的行交此补写。缺本字段=无补写,不拒收。",
|
|
121
|
-
},
|
|
122
|
-
),
|
|
123
|
-
),
|
|
124
96
|
}),
|
|
125
97
|
),
|
|
126
98
|
);
|
|
@@ -138,7 +110,7 @@ export const DIARIST_TOOL_SPEC = {
|
|
|
138
110
|
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
139
111
|
label: "起居郎输出",
|
|
140
112
|
description:
|
|
141
|
-
"起居郎交本票对话边界(sessions
|
|
142
|
-
promptSnippet: "
|
|
113
|
+
"起居郎交本票对话边界(sessions);认不出本庭对象则 escalate。",
|
|
114
|
+
promptSnippet: "起居郎交边界",
|
|
143
115
|
parameters: diaristOutputSchema,
|
|
144
116
|
} as const;
|
package/src/diarist.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 起居郎 volume commit — ADR 0075 `diarist-is-role` / #901.
|
|
3
|
-
* LLM submits bounds
|
|
3
|
+
* LLM submits bounds; mechanical layer reprojects the
|
|
4
4
|
* unique records.jsonl from session volumes. No human view. No lifecycle here
|
|
5
5
|
* (ADR 0018): the seat prepares, the role envelope commits.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type UnparsableSessionLine,
|
|
10
|
-
} from "./ticket-provenance.ts";
|
|
11
|
-
import type {
|
|
12
|
-
TicketProvenanceAmendment,
|
|
13
|
-
TicketProvenanceSession,
|
|
14
|
-
} from "./ticket-provenance-contracts.ts";
|
|
7
|
+
import { reprojectTicketProvenance } from "./ticket-provenance.ts";
|
|
8
|
+
import type { TicketProvenanceSession } from "./ticket-provenance-contracts.ts";
|
|
15
9
|
|
|
16
10
|
/**
|
|
17
11
|
* Honest machine facts about what this turn actually committed to the volume.
|
|
@@ -22,16 +16,11 @@ export type DiaristCommitFacts = {
|
|
|
22
16
|
readonly volumeRecordFile: string;
|
|
23
17
|
/** Dialogue lines written by this reproject. */
|
|
24
18
|
readonly lineCount: number;
|
|
25
|
-
/**
|
|
26
|
-
* Session lines inside the bounds that still have no amendment.
|
|
27
|
-
* Accept hook turns a non-empty list into ParentQueueReaskError.
|
|
28
|
-
*/
|
|
29
|
-
readonly unparsable: readonly UnparsableSessionLine[];
|
|
30
19
|
};
|
|
31
20
|
|
|
32
21
|
/**
|
|
33
|
-
* Reproject the unique diary from LLM-submitted bounds
|
|
34
|
-
* Shape projection of sessions
|
|
22
|
+
* Reproject the unique diary from LLM-submitted bounds.
|
|
23
|
+
* Shape projection of sessions is the caller's job (accept hook);
|
|
35
24
|
* this seam only copies source facts into the volume.
|
|
36
25
|
*/
|
|
37
26
|
export async function commitDiaristProjection(input: {
|
|
@@ -39,19 +28,16 @@ export async function commitDiaristProjection(input: {
|
|
|
39
28
|
readonly cwd: string;
|
|
40
29
|
readonly home?: string;
|
|
41
30
|
readonly sessions: readonly TicketProvenanceSession[];
|
|
42
|
-
readonly amendments?: readonly TicketProvenanceAmendment[];
|
|
43
31
|
}): Promise<DiaristCommitFacts> {
|
|
44
32
|
const result = await reprojectTicketProvenance({
|
|
45
33
|
ticketNumber: input.ticketNumber,
|
|
46
34
|
cwd: input.cwd,
|
|
47
35
|
...(input.home === undefined ? {} : { home: input.home }),
|
|
48
36
|
sessions: input.sessions,
|
|
49
|
-
...(input.amendments === undefined ? {} : { amendments: input.amendments }),
|
|
50
37
|
});
|
|
51
38
|
return {
|
|
52
39
|
ticketNumber: input.ticketNumber,
|
|
53
40
|
volumeRecordFile: result.recordFile,
|
|
54
41
|
lineCount: result.lines.length,
|
|
55
|
-
unparsable: result.unparsable,
|
|
56
42
|
};
|
|
57
43
|
}
|
|
@@ -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
|
+
}
|
|
@@ -40,8 +40,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
40
40
|
* One physical JSONL line as the single kernel saw it.
|
|
41
41
|
* `row` present = syntactically complete session object at that 1-based line.
|
|
42
42
|
* `error` present = the line is complete but unreadable as a session object;
|
|
43
|
-
* `raw` carries its original bytes so
|
|
44
|
-
* projection, ADR 0075 `unparsable-line-to-diarist`) can hand the line back
|
|
43
|
+
* `raw` carries its original bytes so readers can preserve source evidence
|
|
45
44
|
* instead of inventing content. The unfinished final fragment at EOF is not a
|
|
46
45
|
* line — live tail is honest absence, never a bad line.
|
|
47
46
|
*/
|
package/src/role-envelope.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createServer, type Server, type Socket } from "node:net";
|
|
4
|
-
import {
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -13,15 +13,14 @@ import { requireGatekeeperPass } from "./gatekeeper-pass-envelope.ts";
|
|
|
13
13
|
import type {
|
|
14
14
|
HostContext,
|
|
15
15
|
HostEventRegistration,
|
|
16
|
-
HostSkillExpansionEvidence,
|
|
17
16
|
HostToolDefinition,
|
|
18
17
|
RoleEnvelopeHost,
|
|
19
18
|
RoleHost,
|
|
20
19
|
RoleTurnKnownFailure,
|
|
21
20
|
RoleTurnRequest,
|
|
22
21
|
} from "./host-contracts.ts";
|
|
22
|
+
import { hostMethodSkills, installWorkspaceMethodSkills } from "./host-native-method.ts";
|
|
23
23
|
import { packagedRoleOutputTool } from "./packaged-role-registry.ts";
|
|
24
|
-
import { stripSkillFrontmatter } from "./package-resources/method-skill.ts";
|
|
25
24
|
import {
|
|
26
25
|
createRoleRuntimeExtension,
|
|
27
26
|
type RoleRuntimeDependencies,
|
|
@@ -44,30 +43,6 @@ type RpcRequest = { readonly id: number; readonly token: string; readonly method
|
|
|
44
43
|
type ToolCallParams = { readonly name?: unknown; readonly arguments?: unknown };
|
|
45
44
|
type ContentPart = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
|
46
45
|
|
|
47
|
-
/**
|
|
48
|
-
* Build host-side Skill expansion evidence from pre-read RoleTurnRequest.methods.
|
|
49
|
-
* Non-pi hosts never parse Pi `/skill:` syntax (ADR 0082: pi is one adapter with
|
|
50
|
-
* no privilege; Pi-only seams stay inside the pi adapter);
|
|
51
|
-
* a single bound method treats the plain prompt as the preserved user message.
|
|
52
|
-
* Pi-native slash forms stay inside `src/pi/` only.
|
|
53
|
-
*/
|
|
54
|
-
export function buildSkillExpansion(
|
|
55
|
-
methodSkills: ReadonlyMap<string, { readonly path: string; readonly body: string }>,
|
|
56
|
-
prompt: string,
|
|
57
|
-
): HostSkillExpansionEvidence | undefined {
|
|
58
|
-
if (methodSkills.size !== 1) return undefined;
|
|
59
|
-
const entry = methodSkills.entries().next().value;
|
|
60
|
-
if (entry === undefined) return undefined;
|
|
61
|
-
const [name, method] = entry;
|
|
62
|
-
return Object.freeze({
|
|
63
|
-
name,
|
|
64
|
-
location: method.path,
|
|
65
|
-
content: method.body,
|
|
66
|
-
// Non-pi typed chain keeps original task bytes (ticket #822 r3); no consumer trim.
|
|
67
|
-
userMessage: prompt,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
46
|
async function listen(server: Server, path: string): Promise<void> {
|
|
72
47
|
await new Promise<void>((resolve, reject) => {
|
|
73
48
|
server.once("error", reject);
|
|
@@ -129,7 +104,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
129
104
|
const customEntries: Array<{ customType: string; data: unknown }> = [];
|
|
130
105
|
/** In-memory turn books for role lifecycle and audit subjects. */
|
|
131
106
|
const sessionEntries: Array<Record<string, unknown>> = [];
|
|
132
|
-
const methodSkills = new Map<string, { path: string; body: string }>();
|
|
133
107
|
let preferredTools: string[] = [];
|
|
134
108
|
let rejection:
|
|
135
109
|
| { readonly code: string; readonly toolCallIds: readonly string[]; readonly message: string }
|
|
@@ -140,14 +114,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
140
114
|
const runId = request.runDirectory.split("/").filter(Boolean).at(-1) ?? randomUUID();
|
|
141
115
|
await mkdir(request.runDirectory, { recursive: true });
|
|
142
116
|
|
|
143
|
-
// Canonical Skill expansion consumes RoleTurnRequest.methods (typed true source).
|
|
144
|
-
for (const method of request.methods) {
|
|
145
|
-
if (method.kind !== "skill") continue;
|
|
146
|
-
const name = basename(dirname(method.path));
|
|
147
|
-
const raw = await readFile(method.path, "utf8");
|
|
148
|
-
methodSkills.set(name, { path: method.path, body: stripSkillFrontmatter(raw).trim() });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
117
|
// Durable principal file for isAvailable / resumable settlement (public-cli).
|
|
152
118
|
// #617 DK-4: header layout only — never host conversation/tool writeback into Pi JSONL.
|
|
153
119
|
let sessionFile = options.sessionFile ?? join(request.runDirectory, "session", "session.jsonl");
|
|
@@ -219,9 +185,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
219
185
|
// #836 删 1/A4.5: do not arm closeRound retry with「终局交卷并非本轮唯一工具调用」.
|
|
220
186
|
},
|
|
221
187
|
capabilities: {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
},
|
|
188
|
+
// #922: native loaders own expansion; package does not pre-read bodies (ADR 0032).
|
|
189
|
+
skillExpansion() { return undefined; },
|
|
225
190
|
},
|
|
226
191
|
registerFlag(name, definition) { if (!flags.has(name) && definition.default !== undefined) flags.set(name, definition.default); },
|
|
227
192
|
getFlag(name) { return flags.get(name); },
|
|
@@ -625,14 +590,10 @@ export async function prepareRoleEnvelope(options: {
|
|
|
625
590
|
message: { role: "user", content: prompt },
|
|
626
591
|
});
|
|
627
592
|
}
|
|
628
|
-
//
|
|
629
|
-
// Preloading session materials duplicated soul under the role tag (#632).
|
|
630
|
-
// #879: case-dossier owner reads runDirectory from this turn's HostContext.
|
|
631
|
-
|
|
632
|
-
const methodPrompt = (await Promise.all(request.methods.map(({ path }) => readFile(path, "utf8")))).join("\n\n");
|
|
593
|
+
// Soul/materials only — forced methods use host-native loaders (#922/#632/#879).
|
|
633
594
|
const promptResults = await emit("before_agent_start", {
|
|
634
595
|
prompt,
|
|
635
|
-
systemPrompt:
|
|
596
|
+
systemPrompt: "",
|
|
636
597
|
systemPromptOptions: {},
|
|
637
598
|
});
|
|
638
599
|
const systemPromptParts = promptResults.flatMap((value) => {
|
|
@@ -640,7 +601,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
640
601
|
if (!("systemPrompt" in value) || typeof value.systemPrompt !== "string") return [];
|
|
641
602
|
return [value.systemPrompt];
|
|
642
603
|
});
|
|
643
|
-
const systemPromptBody = systemPromptParts.
|
|
604
|
+
const systemPromptBody = systemPromptParts.join("\n\n");
|
|
644
605
|
// Typed reading materials from agent-start handlers (incl. single shared
|
|
645
606
|
// case-dossier owner). Folded into provider-visible systemPrompt at send.
|
|
646
607
|
const readingMaterials: unknown[] = [];
|
|
@@ -656,6 +617,15 @@ export async function prepareRoleEnvelope(options: {
|
|
|
656
617
|
throw new Error(`terminating tool not registered after activation: ${terminatingToolName}`);
|
|
657
618
|
}
|
|
658
619
|
const jsonSchema = terminatingToolJsonSchema(terminating.parameters);
|
|
620
|
+
|
|
621
|
+
if (request.host === "codex" && hostMethodSkills(request.methods).length > 0) {
|
|
622
|
+
const packageRoot = options.dependencies.packageRoot;
|
|
623
|
+
if (typeof packageRoot !== "string" || packageRoot === "") {
|
|
624
|
+
throw new Error("codex project Skill catalog requires packageRoot");
|
|
625
|
+
}
|
|
626
|
+
await installWorkspaceMethodSkills(request.cwd, packageRoot);
|
|
627
|
+
}
|
|
628
|
+
|
|
659
629
|
return {
|
|
660
630
|
mcpServers: [{
|
|
661
631
|
name: `ak-${request.activation.role}`,
|
|
@@ -10,8 +10,15 @@ import { loadNavigatorWorkContext } from "./navigator-work-context.ts";
|
|
|
10
10
|
import { loadNotarySourceRunLocator } from "./notary-source-run.ts";
|
|
11
11
|
import { loadPackagedCanonicalSkillBinding } from "./package-resources/method-skill-binding.ts";
|
|
12
12
|
import { formatNavigatorRoleHelp, type RoleRuntimeDependencies } from "./role-runtime.ts";
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import {
|
|
14
|
+
loadAuditorReferenceMaterialsFromSubjectInput,
|
|
15
|
+
loadAuditorSoulFromSubjectInput,
|
|
16
|
+
} from "./auditor-soul.ts";
|
|
17
|
+
import {
|
|
18
|
+
loadGatekeeperSessionMaterials,
|
|
19
|
+
loadMainRoleReferenceMaterials,
|
|
20
|
+
loadMainRoleSessionMaterials,
|
|
21
|
+
} from "./session-opening-materials.ts";
|
|
15
22
|
|
|
16
23
|
const navigatorRoutePlaybookPath = fileURLToPath(
|
|
17
24
|
new URL("../resources/navigator-route-playbook.md", import.meta.url),
|
|
@@ -20,12 +27,20 @@ const collectorHandbookSeedPath = fileURLToPath(
|
|
|
20
27
|
new URL("../resources/collector-bot-handbook.md", import.meta.url),
|
|
21
28
|
);
|
|
22
29
|
|
|
30
|
+
/** Single packaged source for reference materials across production composition roots. */
|
|
31
|
+
export function loadPackagedRoleReferenceMaterials(role: Parameters<NonNullable<RoleRuntimeDependencies["loadRoleReferenceMaterials"]>>[0]): Promise<string> {
|
|
32
|
+
return role === "auditor"
|
|
33
|
+
? loadAuditorReferenceMaterialsFromSubjectInput()
|
|
34
|
+
: loadMainRoleReferenceMaterials(role);
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/** Host-neutral packaged role runtime deps for the parent-process envelope. */
|
|
24
38
|
export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeDependencies {
|
|
25
39
|
const doctorAuditor = createPiDoctorAuditor();
|
|
26
40
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
27
41
|
return {
|
|
28
42
|
packageRoot,
|
|
43
|
+
loadRoleReferenceMaterials: loadPackagedRoleReferenceMaterials,
|
|
29
44
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
30
45
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
31
46
|
loadFixPacket: (path) => readFile(path, "utf8"),
|