@akagilnc/pi-workflow-roles 0.1.3558 → 0.1.3572
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 +4 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/description.js +21 -0
- package/dist/{grok → acp-host}/mcp-relay.mjs +5 -5
- package/dist/{grok → acp-host}/production-host.js +860 -162
- package/dist/diarist-contracts.js +72 -0
- package/dist/host-descriptions.js +34 -0
- package/dist/package-contracts/terminating-tools.js +20 -2
- package/dist/packaged-role-registry.js +20 -0
- package/dist/public-cli/config.js +23 -10
- package/dist/public-cli/main.js +622 -671
- package/dist/public-cli/registry.js +4 -1
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/resources/diarist-collect.md +6 -10
- package/scripts/build-package.mjs +8 -8
- package/souls/diarist.md +9 -0
- package/src/acp-host/description.ts +43 -0
- package/src/{grok → acp-host}/mcp-relay.mjs +5 -5
- package/src/{grok → acp-host}/production-host.ts +31 -37
- package/src/{grok → acp-host}/role-envelope.ts +25 -24
- package/src/{grok → acp-host}/role-turn-host.ts +46 -81
- package/src/{grok → acp-host}/session-identity.ts +7 -7
- package/src/diarist-contracts.ts +88 -0
- package/src/diarist-role.ts +60 -0
- package/src/diarist.ts +253 -179
- package/src/host-contracts.ts +11 -4
- package/src/host-descriptions.ts +51 -0
- package/src/host-transition-prior-native.ts +12 -24
- package/src/package-contracts/terminating-tools.ts +22 -2
- package/src/packaged-role-registry.ts +20 -0
- package/src/pi/role-turn-host.ts +10 -1
- package/src/public-cli/cli.ts +39 -9
- package/src/public-cli/config.ts +24 -11
- package/src/public-cli/countersign-run.ts +9 -129
- package/src/public-cli/diarist-run.ts +312 -0
- package/src/public-cli/invocation.ts +23 -2
- package/src/public-cli/load-production-acp-host.ts +52 -0
- package/src/public-cli/option-definitions.ts +18 -0
- package/src/public-cli/post-admission.ts +3 -3
- package/src/public-cli/registry.ts +3 -0
- package/src/public-cli/run-lifecycle.ts +31 -3
- package/src/public-cli/settlement.ts +50 -2
- package/src/public-cli/terminal.ts +2 -1
- package/src/role-runtime.ts +117 -6
- package/src/ticket-provenance-contracts.ts +1 -0
- package/src/ticket-provenance.ts +0 -23
- package/src/diarist-llm-collector.ts +0 -324
- package/src/public-cli/load-production-grok-host.ts +0 -35
|
@@ -3,9 +3,10 @@ import { createInterface } from "node:readline";
|
|
|
3
3
|
|
|
4
4
|
import type { RoleTurnHost, RoleTurnKnownFailure, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
5
5
|
import { renderAgentStartMaterials } from "../agent-start-materials.ts";
|
|
6
|
+
import type { AcpHostDescription } from "./description.ts";
|
|
6
7
|
|
|
7
|
-
/** ACP v1 surface used by the
|
|
8
|
-
export interface
|
|
8
|
+
/** ACP v1 surface used by the generic ACP adapter. Protocol details stay in this module. */
|
|
9
|
+
export interface AcpConnection {
|
|
9
10
|
request(method: string, params: Readonly<Record<string, unknown>>): Promise<Readonly<Record<string, unknown>>>;
|
|
10
11
|
notify(method: string, params: Readonly<Record<string, unknown>>): void;
|
|
11
12
|
/** Subscribe to agent→client notifications (session/update stream, etc.). */
|
|
@@ -15,7 +16,7 @@ export interface GrokAcpConnection {
|
|
|
15
16
|
|
|
16
17
|
/** The shared envelope, prepared before session/new (systemPrompt delivery) and
|
|
17
18
|
* able to observe the host's real builtin tool surface once it arrives post-session. */
|
|
18
|
-
export type
|
|
19
|
+
export type AcpPreparedTurn = Readonly<{
|
|
19
20
|
mcpServers: readonly Readonly<Record<string, unknown>>[];
|
|
20
21
|
/**
|
|
21
22
|
* Structured system-prompt authority. `body` is the provider-facing prompt
|
|
@@ -45,24 +46,26 @@ export type GrokPreparedTurn = Readonly<{
|
|
|
45
46
|
}>;
|
|
46
47
|
|
|
47
48
|
/** Fold structured system-prompt authority into the provider-visible ACP override. */
|
|
48
|
-
export function
|
|
49
|
+
export function renderAcpSystemPromptOverride(authority: {
|
|
49
50
|
readonly body: string;
|
|
50
51
|
readonly materials: readonly unknown[];
|
|
51
52
|
}): string {
|
|
52
53
|
return renderAgentStartMaterials(authority.body, authority.materials);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
export type
|
|
56
|
+
export type AcpSessionIdentityAuthority = Readonly<{
|
|
56
57
|
load(principal: RoleTurnRequest["principal"]): Promise<string | undefined>;
|
|
57
58
|
bind(principal: RoleTurnRequest["principal"], sessionId: string): Promise<void>;
|
|
58
59
|
/** Durable principal session path for layout ownership / isAvailable — not a rebuild source (#617 DK-4). */
|
|
59
60
|
resolveSessionFile(principal: RoleTurnRequest["principal"]): string;
|
|
60
61
|
}>;
|
|
61
62
|
|
|
62
|
-
export type
|
|
63
|
-
sessionIdentity:
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
export type AcpRoleTurnHostConfig = Readonly<{
|
|
64
|
+
sessionIdentity: AcpSessionIdentityAuthority;
|
|
65
|
+
/** Whether a bound resume reuses the native session or mints a fresh one. */
|
|
66
|
+
boundResume: AcpHostDescription["boundResume"];
|
|
67
|
+
connect(request: RoleTurnRequest): Promise<AcpConnection>;
|
|
68
|
+
prepare(request: RoleTurnRequest): Promise<AcpPreparedTurn>;
|
|
66
69
|
}>;
|
|
67
70
|
|
|
68
71
|
function failure(cause: "activation" | "session" | "output", name: string, code: string, details?: Readonly<Record<string, unknown>>): RoleTurnResult {
|
|
@@ -81,7 +84,7 @@ function failure(cause: "activation" | "session" | "output", name: string, code:
|
|
|
81
84
|
type RpcReply = { readonly id?: unknown; readonly method?: unknown; readonly params?: unknown; readonly result?: unknown; readonly error?: unknown };
|
|
82
85
|
|
|
83
86
|
function hostAbortedError(): Error & { readonly code: "host-aborted" } {
|
|
84
|
-
return Object.assign(new Error("
|
|
87
|
+
return Object.assign(new Error("ACP host aborted"), { code: "host-aborted" as const });
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
function acpError(code: string, message: string, cause?: unknown): Error & { readonly code: string } {
|
|
@@ -89,23 +92,14 @@ function acpError(code: string, message: string, cause?: unknown): Error & { rea
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
/** One ACP JSON-RPC stdio process. Natural close/SIGTERM are its only lifecycle exits. */
|
|
92
|
-
export function
|
|
95
|
+
export function connectAcpStdio(options: {
|
|
93
96
|
readonly binary: string;
|
|
97
|
+
readonly args: readonly string[];
|
|
94
98
|
readonly cwd: string;
|
|
95
99
|
readonly env: NodeJS.ProcessEnv;
|
|
96
|
-
readonly model?: string;
|
|
97
|
-
readonly toolset?: string;
|
|
98
100
|
readonly onNotification?: (method: string, params: Readonly<Record<string, unknown>>) => void;
|
|
99
|
-
}): Promise<
|
|
100
|
-
const
|
|
101
|
-
"agent",
|
|
102
|
-
...(options.model === undefined ? [] : ["--model", options.model]),
|
|
103
|
-
"stdio",
|
|
104
|
-
];
|
|
105
|
-
const env = options.toolset === undefined
|
|
106
|
-
? options.env
|
|
107
|
-
: { ...options.env, GROK_CONFIG: JSON.stringify({ toolset: options.toolset }) };
|
|
108
|
-
const child = spawn(options.binary, args, { cwd: options.cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
101
|
+
}): Promise<AcpConnection> {
|
|
102
|
+
const child = spawn(options.binary, [...options.args], { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
109
103
|
const pending = new Map<number, { resolve(value: Readonly<Record<string, unknown>>): void; reject(error: Error): void }>();
|
|
110
104
|
const notificationHandlers: Array<(method: string, params: Readonly<Record<string, unknown>>) => void> = [];
|
|
111
105
|
if (options.onNotification !== undefined) notificationHandlers.push(options.onNotification);
|
|
@@ -128,12 +122,12 @@ export function connectGrokAcpStdio(options: {
|
|
|
128
122
|
child.kill("SIGTERM");
|
|
129
123
|
};
|
|
130
124
|
child.stderr.setEncoding("utf8").on("data", (chunk: string) => { stderr += chunk; });
|
|
131
|
-
child.on("error", (error) => settleClosed(acpError("acp-process-error", `
|
|
125
|
+
child.on("error", (error) => settleClosed(acpError("acp-process-error", `ACP process error: ${error.message}`, error)));
|
|
132
126
|
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
133
127
|
let message: RpcReply;
|
|
134
128
|
try { message = JSON.parse(line) as RpcReply; }
|
|
135
129
|
catch (error) {
|
|
136
|
-
terminate(acpError("acp-invalid-json", `Invalid
|
|
130
|
+
terminate(acpError("acp-invalid-json", `Invalid ACP JSON: ${String(error)}`, error));
|
|
137
131
|
return;
|
|
138
132
|
}
|
|
139
133
|
if (typeof message.method === "string") {
|
|
@@ -142,14 +136,14 @@ export function connectGrokAcpStdio(options: {
|
|
|
142
136
|
for (const handler of notificationHandlers) handler(message.method, params);
|
|
143
137
|
if (typeof message.id === "number") {
|
|
144
138
|
if (message.method !== "session/request_permission") {
|
|
145
|
-
terminate(acpError("acp-unsupported-client-request", `Unsupported
|
|
139
|
+
terminate(acpError("acp-unsupported-client-request", `Unsupported ACP client request: ${message.method}`));
|
|
146
140
|
return;
|
|
147
141
|
}
|
|
148
142
|
const choices = Array.isArray(params.options) ? params.options : [];
|
|
149
143
|
const selected = choices.find((value) =>
|
|
150
144
|
typeof value === "object" && value !== null && (value as { kind?: unknown }).kind === "allow_once") as { optionId?: unknown } | undefined;
|
|
151
145
|
if (typeof selected?.optionId !== "string") {
|
|
152
|
-
terminate(acpError("acp-permission-missing-allow-once", "
|
|
146
|
+
terminate(acpError("acp-permission-missing-allow-once", "ACP permission request omitted allow_once"));
|
|
153
147
|
return;
|
|
154
148
|
}
|
|
155
149
|
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { outcome: { outcome: "selected", optionId: selected.optionId } } })}\n`);
|
|
@@ -160,13 +154,13 @@ export function connectGrokAcpStdio(options: {
|
|
|
160
154
|
const waiter = pending.get(message.id);
|
|
161
155
|
if (waiter === undefined) return;
|
|
162
156
|
pending.delete(message.id);
|
|
163
|
-
if (message.error !== undefined) waiter.reject(acpError("acp-upstream-error", `
|
|
157
|
+
if (message.error !== undefined) waiter.reject(acpError("acp-upstream-error", `ACP error: ${JSON.stringify(message.error)}`));
|
|
164
158
|
else waiter.resolve((message.result ?? {}) as Readonly<Record<string, unknown>>);
|
|
165
159
|
});
|
|
166
|
-
child.on("close", (code) => settleClosed(acpError("acp-closed", `
|
|
160
|
+
child.on("close", (code) => settleClosed(acpError("acp-closed", `ACP closed (${String(code)}): ${stderr}`)));
|
|
167
161
|
return Promise.resolve({
|
|
168
162
|
request(method, params) {
|
|
169
|
-
if (closed) return Promise.reject(terminalError ?? acpError("acp-connection-closed", "
|
|
163
|
+
if (closed) return Promise.reject(terminalError ?? acpError("acp-connection-closed", "ACP connection is closed"));
|
|
170
164
|
const id = ++nextId;
|
|
171
165
|
return new Promise((resolve, reject) => {
|
|
172
166
|
pending.set(id, { resolve, reject });
|
|
@@ -175,12 +169,12 @@ export function connectGrokAcpStdio(options: {
|
|
|
175
169
|
const waiter = pending.get(id);
|
|
176
170
|
if (waiter === undefined) return;
|
|
177
171
|
pending.delete(id);
|
|
178
|
-
waiter.reject(acpError("acp-write-failed", `
|
|
172
|
+
waiter.reject(acpError("acp-write-failed", `ACP write failed: ${error.message}`, error));
|
|
179
173
|
});
|
|
180
174
|
});
|
|
181
175
|
},
|
|
182
176
|
notify(method, params) {
|
|
183
|
-
if (closed) throw terminalError ?? acpError("acp-connection-closed", "
|
|
177
|
+
if (closed) throw terminalError ?? acpError("acp-connection-closed", "ACP connection is closed");
|
|
184
178
|
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
185
179
|
},
|
|
186
180
|
onNotification(handler) {
|
|
@@ -188,7 +182,7 @@ export function connectGrokAcpStdio(options: {
|
|
|
188
182
|
},
|
|
189
183
|
async close() {
|
|
190
184
|
if (closed) return;
|
|
191
|
-
settleClosed(acpError("acp-connection-closed", "
|
|
185
|
+
settleClosed(acpError("acp-connection-closed", "ACP connection is closed"));
|
|
192
186
|
child.stdin.end();
|
|
193
187
|
child.kill("SIGTERM");
|
|
194
188
|
await new Promise<void>((resolve) => child.once("close", () => resolve()));
|
|
@@ -197,23 +191,23 @@ export function connectGrokAcpStdio(options: {
|
|
|
197
191
|
}
|
|
198
192
|
|
|
199
193
|
/**
|
|
200
|
-
* Main-session
|
|
194
|
+
* Main-session ACP adapter. The injected composition callbacks are the shared
|
|
201
195
|
* envelope boundary: this module owns ACP lifecycle, never role policy.
|
|
202
196
|
*/
|
|
203
|
-
export function
|
|
197
|
+
export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHost {
|
|
204
198
|
let serial = Promise.resolve();
|
|
205
199
|
return {
|
|
206
200
|
executeTurn(request) {
|
|
207
201
|
const execution = serial.then(async (): Promise<RoleTurnResult> => {
|
|
208
202
|
const continuation = request.continuation;
|
|
209
203
|
const prepared = await config.prepare(request);
|
|
210
|
-
let connection:
|
|
204
|
+
let connection: AcpConnection | undefined;
|
|
211
205
|
let sessionId: string | undefined;
|
|
212
206
|
let accepted = false;
|
|
213
207
|
try {
|
|
214
208
|
// AK injection proof is prepared MCP composition (envelope).
|
|
215
209
|
if (prepared.mcpServers.length === 0) {
|
|
216
|
-
return failure("activation", "
|
|
210
|
+
return failure("activation", "UncontrolledAcpSession", "ak-config-missing");
|
|
217
211
|
}
|
|
218
212
|
connection = await config.connect(request);
|
|
219
213
|
const initialized = await connection.request("initialize", {
|
|
@@ -227,60 +221,47 @@ export function createGrokRoleTurnHost(config: GrokRoleTurnHostConfig): RoleTurn
|
|
|
227
221
|
const availableModels = Array.isArray(modelState?.availableModels) ? modelState.availableModels : undefined;
|
|
228
222
|
if (request.model !== undefined && availableModels !== undefined && !availableModels.some((entry) =>
|
|
229
223
|
typeof entry === "object" && entry !== null && (entry as { modelId?: unknown }).modelId === request.model?.model)) {
|
|
230
|
-
return failure("activation", "
|
|
224
|
+
return failure("activation", "AcpHostModelMismatch", "host-model-mismatch", {
|
|
231
225
|
provider: request.model.provider,
|
|
232
226
|
model: request.model.model,
|
|
233
227
|
});
|
|
234
228
|
}
|
|
235
229
|
|
|
236
|
-
// #617 DK-7: hand
|
|
230
|
+
// #617 DK-7 / #732: hand the projected prior-native session paths once,
|
|
231
|
+
// whichever record family they came from; the host reads the files itself.
|
|
237
232
|
const priorNativePaths =
|
|
238
233
|
continuation.kind === "resume"
|
|
239
|
-
|
|
240
|
-
? request.hostTransition.priorNativePaths
|
|
234
|
+
? request.hostTransition?.priorNativePaths
|
|
241
235
|
: undefined;
|
|
242
|
-
if (continuation.kind === "resume") {
|
|
236
|
+
if (continuation.kind === "resume" && config.boundResume === "session/load") {
|
|
237
|
+
// Same-host resume reuses the native ACP session via session/load.
|
|
243
238
|
const boundSessionId = await config.sessionIdentity.load(request.principal);
|
|
244
239
|
if (boundSessionId !== undefined && boundSessionId !== "") {
|
|
245
|
-
// Same-host Grok resume reuses native ACP session via session/load.
|
|
246
240
|
const loaded = await connection.request("session/load", {
|
|
247
241
|
sessionId: boundSessionId,
|
|
248
242
|
cwd: request.cwd,
|
|
249
243
|
mcpServers: prepared.mcpServers,
|
|
250
|
-
_meta: { systemPromptOverride:
|
|
244
|
+
_meta: { systemPromptOverride: renderAcpSystemPromptOverride(prepared.systemPrompt), yoloMode: false },
|
|
251
245
|
});
|
|
252
246
|
sessionId = typeof loaded.sessionId === "string" && loaded.sessionId !== ""
|
|
253
247
|
? loaded.sessionId
|
|
254
248
|
: boundSessionId;
|
|
255
|
-
} else {
|
|
256
|
-
// Unbound resume (cross-host or lost binding): session/new + bind.
|
|
257
|
-
const session = await connection.request(
|
|
258
|
-
"session/new",
|
|
259
|
-
{
|
|
260
|
-
cwd: request.cwd,
|
|
261
|
-
mcpServers: prepared.mcpServers,
|
|
262
|
-
_meta: { systemPromptOverride: renderGrokSystemPromptOverride(prepared.systemPrompt), yoloMode: false },
|
|
263
|
-
},
|
|
264
|
-
);
|
|
265
|
-
sessionId = typeof session.sessionId === "string" ? session.sessionId : undefined;
|
|
266
|
-
if (sessionId === undefined || sessionId === "") {
|
|
267
|
-
return failure("session", "GrokAcpSessionFailure", "session-id-missing");
|
|
268
|
-
}
|
|
269
|
-
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
270
249
|
}
|
|
271
|
-
}
|
|
272
|
-
|
|
250
|
+
}
|
|
251
|
+
if (sessionId === undefined) {
|
|
252
|
+
// Initial run, unbound resume (cross-host / lost binding), or a host
|
|
253
|
+
// whose bound resume is session/new: mint the session and bind it.
|
|
273
254
|
const session = await connection.request(
|
|
274
255
|
"session/new",
|
|
275
256
|
{
|
|
276
257
|
cwd: request.cwd,
|
|
277
258
|
mcpServers: prepared.mcpServers,
|
|
278
|
-
_meta: { systemPromptOverride:
|
|
259
|
+
_meta: { systemPromptOverride: renderAcpSystemPromptOverride(prepared.systemPrompt), yoloMode: false },
|
|
279
260
|
},
|
|
280
261
|
);
|
|
281
262
|
sessionId = typeof session.sessionId === "string" ? session.sessionId : undefined;
|
|
282
263
|
if (sessionId === undefined || sessionId === "") {
|
|
283
|
-
return failure("session", "
|
|
264
|
+
return failure("session", "AcpSessionFailure", "session-id-missing");
|
|
284
265
|
}
|
|
285
266
|
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
286
267
|
}
|
|
@@ -353,7 +334,7 @@ export function createGrokRoleTurnHost(config: GrokRoleTurnHostConfig): RoleTurn
|
|
|
353
334
|
throw error;
|
|
354
335
|
}
|
|
355
336
|
if (result.stopReason === "refusal") {
|
|
356
|
-
return failure("output", "
|
|
337
|
+
return failure("output", "AcpRefusal", "refusal", { sessionId });
|
|
357
338
|
}
|
|
358
339
|
// session/prompt resolution is the sole typed round boundary before seal
|
|
359
340
|
// when the turn ends without host abort; abort path closes above.
|
|
@@ -370,7 +351,7 @@ export function createGrokRoleTurnHost(config: GrokRoleTurnHostConfig): RoleTurn
|
|
|
370
351
|
}
|
|
371
352
|
prompt = `The prior terminal submission was rejected (${closure.retry.code}). Resubmit it as the sole terminal tool call. Rejected call ids: ${closure.retry.toolCallIds.join(", ") || "none"}.`;
|
|
372
353
|
}
|
|
373
|
-
return failure("output", "
|
|
354
|
+
return failure("output", "AcpRoundLimit", "round-retry-limit", { sessionId });
|
|
374
355
|
} finally {
|
|
375
356
|
if (connection !== undefined) {
|
|
376
357
|
if (sessionId !== undefined && !accepted) {
|
|
@@ -389,19 +370,3 @@ export function createGrokRoleTurnHost(config: GrokRoleTurnHostConfig): RoleTurn
|
|
|
389
370
|
},
|
|
390
371
|
};
|
|
391
372
|
}
|
|
392
|
-
|
|
393
|
-
const PRIVATE_COMPAT_ENV = Object.fromEntries(
|
|
394
|
-
["CLAUDE", "CURSOR", "CODEX"].flatMap((vendor) =>
|
|
395
|
-
["SKILLS", "RULES", "AGENTS", "MCPS", "HOOKS", "SESSIONS"].map((kind) =>
|
|
396
|
-
[`GROK_${vendor}_${kind}_ENABLED`, "false"] as const)),
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
/** Child environment shared by ACP agent processes. */
|
|
400
|
-
export function controlledGrokChildEnv(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
401
|
-
return {
|
|
402
|
-
...base,
|
|
403
|
-
...PRIVATE_COMPAT_ENV,
|
|
404
|
-
GROK_MEMORY: "0",
|
|
405
|
-
GROK_SUBAGENTS: "0",
|
|
406
|
-
};
|
|
407
|
-
}
|
|
@@ -2,15 +2,15 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { DurablePrincipal, DurablePrincipalAuthority } from "../host-contracts.ts";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
/** ACP binding filename written only by the grok host on initial bind. */
|
|
8
|
-
export const GROK_ACP_SESSION_BINDING = "grok-acp-session.json";
|
|
5
|
+
import type { AcpSessionIdentityAuthority } from "./role-turn-host.ts";
|
|
9
6
|
|
|
10
7
|
/** Durable ACP binding stored beside the host-owned session principal. */
|
|
11
|
-
export function
|
|
8
|
+
export function createAcpSessionIdentityAuthority(
|
|
9
|
+
authority: DurablePrincipalAuthority,
|
|
10
|
+
sessionBindingFile: string,
|
|
11
|
+
): AcpSessionIdentityAuthority {
|
|
12
12
|
const bindingPath = (principal: DurablePrincipal): string =>
|
|
13
|
-
join(authority.decode(principal).sessionDirectory,
|
|
13
|
+
join(authority.decode(principal).sessionDirectory, sessionBindingFile);
|
|
14
14
|
return {
|
|
15
15
|
resolveSessionFile(principal) {
|
|
16
16
|
return authority.decode(principal).sessionFile;
|
|
@@ -19,7 +19,7 @@ export function createGrokSessionIdentityAuthority(authority: DurablePrincipalAu
|
|
|
19
19
|
try {
|
|
20
20
|
const value: unknown = JSON.parse(await readFile(bindingPath(principal), "utf8"));
|
|
21
21
|
if (typeof value !== "object" || value === null || typeof (value as { sessionId?: unknown }).sessionId !== "string") {
|
|
22
|
-
throw new Error("durable
|
|
22
|
+
throw new Error("durable ACP session binding is invalid");
|
|
23
23
|
}
|
|
24
24
|
return (value as { sessionId: string }).sessionId;
|
|
25
25
|
} catch (error) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public 起居郎 (diarist) terminating receipt contracts — ADR 0075 `diarist-is-role`.
|
|
3
|
+
* Lawful explicit release: completed, with empty or nonempty selections.
|
|
4
|
+
* Machine facts about the volume come from the mechanical sitian seam, never
|
|
5
|
+
* from model self-report (锚定宪法); this module owns the receipt shape only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
|
|
9
|
+
export const DIARIST_ACCEPTED_TEXT = "起居郎回执已接受";
|
|
10
|
+
|
|
11
|
+
/** Internal transport: frozen candidate-catalog path for this turn's ticket. */
|
|
12
|
+
export const DIARIST_SOURCES_FLAG = {
|
|
13
|
+
name: "ak-diarist-sources",
|
|
14
|
+
definition: {
|
|
15
|
+
description: "Frozen per-ticket source catalog the diarist selects from",
|
|
16
|
+
type: "string" as const,
|
|
17
|
+
},
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/** One selected block reference into the frozen candidate catalog. */
|
|
21
|
+
export type DiaristSelection = {
|
|
22
|
+
/** Index into the catalog the diarist received. */
|
|
23
|
+
readonly candidateIndex: number;
|
|
24
|
+
/** Quotes claimed verbatim from that block (reverse-verified mechanically). */
|
|
25
|
+
readonly quotes: readonly string[];
|
|
26
|
+
/** Human-facing note (relation to this case). Not a machine gate. */
|
|
27
|
+
readonly note?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DiaristOutput = {
|
|
31
|
+
readonly status: "completed";
|
|
32
|
+
readonly selections: readonly DiaristSelection[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
|
|
36
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
37
|
+
throw new Error("Diarist output has no execution discriminator");
|
|
38
|
+
}
|
|
39
|
+
let status: unknown;
|
|
40
|
+
try {
|
|
41
|
+
status = (value as Record<string, unknown>).status;
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error("Diarist output has no execution discriminator");
|
|
44
|
+
}
|
|
45
|
+
if (status === "completed") {
|
|
46
|
+
return value as DiaristOutput;
|
|
47
|
+
}
|
|
48
|
+
throw new Error("Diarist output has no execution discriminator");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Lenient projection of submitted selections (第 0 条: shape is not an admission
|
|
53
|
+
* gate). Rows that carry no usable candidateIndex are dropped from the commit,
|
|
54
|
+
* never bounced back at the role.
|
|
55
|
+
*/
|
|
56
|
+
export function projectDiaristSelections(value: unknown): DiaristSelection[] {
|
|
57
|
+
const rows = (value as { selections?: unknown } | null)?.selections;
|
|
58
|
+
if (!Array.isArray(rows)) return [];
|
|
59
|
+
const out: DiaristSelection[] = [];
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
|
|
62
|
+
const record = row as Record<string, unknown>;
|
|
63
|
+
const index = record.candidateIndex;
|
|
64
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0) continue;
|
|
65
|
+
const quotes = Array.isArray(record.quotes)
|
|
66
|
+
? record.quotes.filter((q): q is string => typeof q === "string" && q.length > 0)
|
|
67
|
+
: [];
|
|
68
|
+
out.push({
|
|
69
|
+
candidateIndex: index,
|
|
70
|
+
quotes,
|
|
71
|
+
...(typeof record.note === "string" ? { note: record.note } : {}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Machine-facing facts from an accepted receipt. Submitted rows retained as-is. */
|
|
78
|
+
export function diaristDecisiveFacts(
|
|
79
|
+
output: DiaristOutput,
|
|
80
|
+
): Record<string, unknown> {
|
|
81
|
+
const facts: Record<string, unknown> = { status: output.status };
|
|
82
|
+
const selections = (output as { selections?: unknown }).selections;
|
|
83
|
+
if (Array.isArray(selections)) facts.selections = selections;
|
|
84
|
+
// Mechanical sitian facts recorded by the envelope at accept time.
|
|
85
|
+
const sitian = (output as { sitian?: unknown }).sitian;
|
|
86
|
+
if (sitian !== undefined) facts.sitian = sitian;
|
|
87
|
+
return facts;
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Static } from "typebox";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
import { openToolObject } from "./open-tool-schema.ts";
|
|
5
|
+
import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
|
|
6
|
+
import {
|
|
7
|
+
DIARIST_OUTPUT_TOOL_NAME,
|
|
8
|
+
validateRecordedDiaristOutput,
|
|
9
|
+
type DiaristOutput,
|
|
10
|
+
} from "./diarist-contracts.ts";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
DIARIST_ACCEPTED_TEXT,
|
|
14
|
+
DIARIST_OUTPUT_TOOL_NAME,
|
|
15
|
+
} from "./diarist-contracts.ts";
|
|
16
|
+
export type { DiaristOutput };
|
|
17
|
+
export { validateRecordedDiaristOutput };
|
|
18
|
+
|
|
19
|
+
/** 起居郎交卷形状;形状指引,非 schema 闸。 */
|
|
20
|
+
export const diaristOutputSchema = withInfrastructureFailureDeclaration(
|
|
21
|
+
openToolObject(
|
|
22
|
+
Type.Object({
|
|
23
|
+
status: Type.Unknown({
|
|
24
|
+
description: "completed — 形状指引,非 schema 闸",
|
|
25
|
+
}),
|
|
26
|
+
selections: Type.Array(
|
|
27
|
+
Type.Object(
|
|
28
|
+
{
|
|
29
|
+
candidateIndex: Type.Number({ description: "候选目录中的整块序号" }),
|
|
30
|
+
quotes: Type.Array(Type.String(), {
|
|
31
|
+
description: "该块 transcript 内的连续原文;机械反验",
|
|
32
|
+
}),
|
|
33
|
+
note: Type.Optional(
|
|
34
|
+
Type.String({ description: "该材料与本案的关系(人读)" }),
|
|
35
|
+
),
|
|
36
|
+
},
|
|
37
|
+
{ additionalProperties: true, description: "一条入录选择" },
|
|
38
|
+
),
|
|
39
|
+
{ description: "入录选择;空列表合法完局" },
|
|
40
|
+
),
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export type DiaristOutputParameters = Static<typeof diaristOutputSchema>;
|
|
46
|
+
|
|
47
|
+
export type DiaristRuntimeDependencies = {
|
|
48
|
+
loadSoul(): Promise<string>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
53
|
+
*/
|
|
54
|
+
export const DIARIST_TOOL_SPEC = {
|
|
55
|
+
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
56
|
+
label: "起居郎输出",
|
|
57
|
+
description: "起居郎入录选择。",
|
|
58
|
+
promptSnippet: "起居郎入录选择",
|
|
59
|
+
parameters: diaristOutputSchema,
|
|
60
|
+
} as const;
|