@akagilnc/pi-workflow-roles 0.1.4021 → 0.1.4062
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/dist/acp-host/production-host.js +682 -355
- package/dist/doctor-auditor.js +1 -1
- package/dist/dossier-resolution.js +21 -8
- package/dist/gatekeeper-role.js +34 -27
- package/dist/headless-host/description.js +7 -8
- package/dist/headless-host/production-host.js +709 -369
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/navigator-attendance.js +2 -0
- package/dist/navigator-public-session.js +18 -34
- package/dist/navigator-session-contracts.js +19 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/known-failure.js +3 -4
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/auto-resume.js +20 -7
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/diarist-run.js +9 -23
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +12 -7
- package/dist/public-cli/judge-run.js +3 -1
- package/dist/public-cli/main.js +440 -222
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/post-admission.js +66 -59
- package/dist/public-cli/run-lifecycle.js +3 -0
- package/dist/public-cli/settlement.js +78 -58
- package/dist/public-cli/terminal.js +1 -11
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +139 -19
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/src/doctor-auditor.ts +1 -1
- package/src/dossier-resolution.ts +26 -8
- package/src/external-host-turn-loop.ts +9 -0
- package/src/gatekeeper-pass-envelope.ts +6 -0
- package/src/gatekeeper-role.ts +47 -28
- package/src/headless-host/description.ts +8 -11
- package/src/headless-host/role-turn-host.ts +20 -5
- package/src/host-contracts.ts +30 -7
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-attendance.ts +2 -0
- package/src/navigator-public-session.ts +19 -55
- package/src/navigator-session-contracts.ts +39 -0
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/known-failure.ts +3 -4
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/auto-resume.ts +57 -20
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/collector-run.ts +3 -1
- package/src/public-cli/countersign-run.ts +34 -27
- package/src/public-cli/diarist-run.ts +10 -27
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +20 -10
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/judge-run.ts +3 -1
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/reviewer-run.ts +3 -1
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +105 -80
- package/src/public-cli/terminal.ts +7 -16
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +95 -19
- package/src/submission-ledger.ts +195 -26
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
|
@@ -159,6 +159,8 @@ async function summonPublicRole(options) {
|
|
|
159
159
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
160
160
|
// #753: reask rides the existing notary same-ticket resume summons.instruction.
|
|
161
161
|
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
162
|
+
// #879: verbatim submission body on officer dialogue content channel.
|
|
163
|
+
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
162
164
|
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
163
165
|
// createRunId is the only remaining env overlay — host is seat-selected above.
|
|
164
166
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
@@ -266,6 +268,11 @@ async function summonGateOfficer(options) {
|
|
|
266
268
|
const { homeFromRunDirectory } = await import("./activation-ledger-topology.js");
|
|
267
269
|
home = homeFromRunDirectory(options.sourceRunDirectory);
|
|
268
270
|
}
|
|
271
|
+
let gateReviewInstruction;
|
|
272
|
+
if (options.reask === void 0 && options.submission !== void 0) {
|
|
273
|
+
const { readableGateItem } = await import("./readable-gate-item.js");
|
|
274
|
+
gateReviewInstruction = readableGateItem(options.submission);
|
|
275
|
+
}
|
|
269
276
|
const common = {
|
|
270
277
|
cwd: options.cwd,
|
|
271
278
|
...home === void 0 ? {} : { home },
|
|
@@ -273,6 +280,7 @@ async function summonGateOfficer(options) {
|
|
|
273
280
|
...options.io === void 0 ? {} : { io: options.io },
|
|
274
281
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
275
282
|
...options.reask === void 0 ? {} : { reviewReask: options.reask },
|
|
283
|
+
...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
|
|
276
284
|
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
277
285
|
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
278
286
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
|
|
2
|
+
import { courtAttemptIdFromHostContext, runDirectoryFromHostContext, } from "./host-contracts.js";
|
|
2
3
|
import { isAuditEscalationProjection } from "./audit-escalation.js";
|
|
3
4
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
|
|
4
5
|
import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport } from "./sitian-facade.js";
|
|
@@ -10,8 +11,8 @@ import { failOnInfrastructureFailureDeclaration } from "./package-contracts/term
|
|
|
10
11
|
* otherwise session header id. Never a shared "unbound" bucket.
|
|
11
12
|
*/
|
|
12
13
|
function runIdentity(context) {
|
|
13
|
-
const directory =
|
|
14
|
-
if (
|
|
14
|
+
const directory = runDirectoryFromHostContext(context);
|
|
15
|
+
if (directory !== undefined) {
|
|
15
16
|
const fromDir = runIdFromRunDirectory(directory);
|
|
16
17
|
if (fromDir !== undefined)
|
|
17
18
|
return fromDir;
|
|
@@ -27,8 +28,8 @@ function runIdentity(context) {
|
|
|
27
28
|
*/
|
|
28
29
|
export const COURT_ATTEMPT_ENV = "AK_ROLE_COURT_ATTEMPT";
|
|
29
30
|
function attemptIdentity(context, runId) {
|
|
30
|
-
const courtAttempt =
|
|
31
|
-
if (
|
|
31
|
+
const courtAttempt = courtAttemptIdFromHostContext(context);
|
|
32
|
+
if (courtAttempt !== undefined)
|
|
32
33
|
return courtAttempt;
|
|
33
34
|
return context.sessionManager.getHeader?.()?.id ?? context.sessionManager.getLeafId?.() ?? `${runId}:initial`;
|
|
34
35
|
}
|
|
@@ -110,39 +111,155 @@ function recordedRole(payload) {
|
|
|
110
111
|
return payload.projection.role;
|
|
111
112
|
return undefined;
|
|
112
113
|
}
|
|
114
|
+
/** Prefer a more complete row for the same tool call without inventing a sole winner across calls. */
|
|
115
|
+
function rowRank(kind) {
|
|
116
|
+
switch (kind) {
|
|
117
|
+
case "accepted":
|
|
118
|
+
return 4;
|
|
119
|
+
case "audit-escalation":
|
|
120
|
+
return 3;
|
|
121
|
+
case "correctable-rejection":
|
|
122
|
+
case "infrastructure":
|
|
123
|
+
return 2;
|
|
124
|
+
case "candidate":
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
113
128
|
/**
|
|
114
|
-
*
|
|
129
|
+
* Same-call identity for reader pairing only (#881 / #836).
|
|
130
|
+
* attemptId is already on the record (subject/payload); bare toolCallId alone
|
|
131
|
+
* collapses distinct court attempts that reused a host call id.
|
|
132
|
+
*/
|
|
133
|
+
function submissionCallKey(attemptId, toolCallId) {
|
|
134
|
+
return `${attemptId ?? ""}\0${toolCallId}`;
|
|
135
|
+
}
|
|
136
|
+
function rowFromPayload(kind, payload, accepted, roleFallback) {
|
|
137
|
+
const role = recordedRole(payload) ?? roleFallback;
|
|
138
|
+
return {
|
|
139
|
+
...(role === undefined ? {} : { role }),
|
|
140
|
+
kind,
|
|
141
|
+
accepted,
|
|
142
|
+
...(typeof payload.toolCallId === "string" && payload.toolCallId.length > 0
|
|
143
|
+
? { toolCallId: payload.toolCallId }
|
|
144
|
+
: {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* All recorded role submissions in ledger order (#836 multi-submit / #881).
|
|
149
|
+
* Projects every original payload — sealed, audit-escalation, correctable-rejection,
|
|
150
|
+
* infrastructure, and bare candidate — without outcome-class filtering.
|
|
151
|
+
* Same call (candidate + outcome both carrying params) appears once — keyed by
|
|
152
|
+
* recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
|
|
115
153
|
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
116
154
|
*/
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
120
|
-
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
155
|
+
function mapOwnedToSubmissionRows(owned) {
|
|
156
|
+
const scoped = owned;
|
|
121
157
|
const out = [];
|
|
158
|
+
const indexByCall = new Map();
|
|
159
|
+
// Recover seat identity for historical non-sealed rows that omitted role (#881).
|
|
160
|
+
const roleByCall = new Map();
|
|
161
|
+
for (const record of scoped) {
|
|
162
|
+
const payload = record.payload;
|
|
163
|
+
if (typeof payload?.toolCallId !== "string" || payload.toolCallId.length === 0)
|
|
164
|
+
continue;
|
|
165
|
+
const role = recordedRole(payload);
|
|
166
|
+
if (role !== undefined) {
|
|
167
|
+
roleByCall.set(submissionCallKey(recordAttemptId(record), payload.toolCallId), role);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const take = (row, callKey) => {
|
|
171
|
+
const toolCallId = row.toolCallId;
|
|
172
|
+
if (toolCallId !== undefined && callKey !== undefined) {
|
|
173
|
+
const existingIndex = indexByCall.get(callKey);
|
|
174
|
+
if (existingIndex !== undefined) {
|
|
175
|
+
const existing = out[existingIndex];
|
|
176
|
+
if (rowRank(row.kind) >= rowRank(existing.kind)) {
|
|
177
|
+
out[existingIndex] = {
|
|
178
|
+
...row,
|
|
179
|
+
toolCallId,
|
|
180
|
+
// Keep a previously recovered role when the upgraded row still omits it.
|
|
181
|
+
...(row.role === undefined && existing.role !== undefined ? { role: existing.role } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
else if (existing.role === undefined && row.role !== undefined) {
|
|
185
|
+
out[existingIndex] = { ...existing, role: row.role };
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
indexByCall.set(callKey, out.length);
|
|
190
|
+
}
|
|
191
|
+
out.push(row);
|
|
192
|
+
};
|
|
122
193
|
for (const record of scoped) {
|
|
194
|
+
const attemptId = recordAttemptId(record);
|
|
195
|
+
if (record.kind === "candidate") {
|
|
196
|
+
const payload = record.payload;
|
|
197
|
+
if (payload?.type !== "candidate" || payload.params === undefined)
|
|
198
|
+
continue;
|
|
199
|
+
const callKey = typeof payload.toolCallId === "string"
|
|
200
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
201
|
+
: undefined;
|
|
202
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
203
|
+
take(rowFromPayload("candidate", payload, payload.params, fallback), callKey);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
123
206
|
if (record.kind === "sealed") {
|
|
124
207
|
const payload = record.payload;
|
|
125
208
|
if (payload?.type !== "sealed" || payload.accepted === undefined)
|
|
126
209
|
continue;
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
210
|
+
const callKey = typeof payload.toolCallId === "string"
|
|
211
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
212
|
+
: undefined;
|
|
213
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
214
|
+
take(rowFromPayload("accepted", payload, payload.accepted, fallback), callKey);
|
|
131
215
|
continue;
|
|
132
216
|
}
|
|
133
217
|
if (record.kind !== "outcome")
|
|
134
218
|
continue;
|
|
135
219
|
const payload = record.payload;
|
|
136
|
-
if (payload?.type !== "outcome" || payload.
|
|
220
|
+
if (payload?.type !== "outcome" || payload.accepted === undefined)
|
|
137
221
|
continue;
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
222
|
+
const outcome = payload.outcome;
|
|
223
|
+
const kind = outcome === "audit-escalation"
|
|
224
|
+
? "audit-escalation"
|
|
225
|
+
: outcome === "correctable-rejection"
|
|
226
|
+
? "correctable-rejection"
|
|
227
|
+
: outcome === "infrastructure"
|
|
228
|
+
? "infrastructure"
|
|
229
|
+
: undefined;
|
|
230
|
+
if (kind === undefined)
|
|
141
231
|
continue;
|
|
142
|
-
|
|
232
|
+
const callKey = typeof payload.toolCallId === "string"
|
|
233
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
234
|
+
: undefined;
|
|
235
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
236
|
+
take(rowFromPayload(kind, payload, payload.accepted, fallback), callKey);
|
|
143
237
|
}
|
|
144
238
|
return out;
|
|
145
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* All recorded role submissions in ledger order (#836 multi-submit).
|
|
242
|
+
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
243
|
+
* Presentation stays run-scoped: attemptId on the read scope is a recording tag,
|
|
244
|
+
* not a visibility gate (#836).
|
|
245
|
+
*/
|
|
246
|
+
export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
|
|
247
|
+
const scope = resolveReadScope(homeOrScope);
|
|
248
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
249
|
+
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
250
|
+
return mapOwnedToSubmissionRows(scoped);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Settlement-only this-court rows (#879 return path).
|
|
254
|
+
* Filters by attemptId for court-scoped roleOutcome; does not replace the
|
|
255
|
+
* run-scoped presentation API above (#836 visibility gate stays pass-through).
|
|
256
|
+
*/
|
|
257
|
+
export async function readAttemptScopedSubmissionRows(cwd, runId, attemptId, home) {
|
|
258
|
+
if (attemptId.length === 0)
|
|
259
|
+
return [];
|
|
260
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, home);
|
|
261
|
+
return mapOwnedToSubmissionRows(owned.filter((record) => recordAttemptId(record) === attemptId));
|
|
262
|
+
}
|
|
146
263
|
/**
|
|
147
264
|
* All raw role payloads in ledger order (#836 multi-submit).
|
|
148
265
|
* Returns `accepted` bytes as stored — unknown, never re-wrapped.
|
|
@@ -150,7 +267,7 @@ export async function readRecordedSubmissionRows(cwd, runId, homeOrScope) {
|
|
|
150
267
|
export async function readRecordedSubmissions(cwd, runId, homeOrScope) {
|
|
151
268
|
return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).map((row) => row.accepted);
|
|
152
269
|
}
|
|
153
|
-
/** True when the run has at least one recorded
|
|
270
|
+
/** True when the run has at least one recorded original payload (any outcome class). */
|
|
154
271
|
export async function hasRecordedSubmission(cwd, runId, homeOrScope) {
|
|
155
272
|
return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).length > 0;
|
|
156
273
|
}
|
|
@@ -254,6 +371,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
254
371
|
toolCallId,
|
|
255
372
|
toolName: tool.name,
|
|
256
373
|
sequence: ++state.sequence,
|
|
374
|
+
role,
|
|
257
375
|
params,
|
|
258
376
|
});
|
|
259
377
|
let result;
|
|
@@ -274,6 +392,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
274
392
|
outcome: "correctable-rejection",
|
|
275
393
|
code: "typed-bounce",
|
|
276
394
|
diagnostic: error instanceof Error ? error.message : String(error),
|
|
395
|
+
role,
|
|
277
396
|
accepted: params,
|
|
278
397
|
});
|
|
279
398
|
throw error;
|
|
@@ -285,6 +404,7 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
285
404
|
toolCallId,
|
|
286
405
|
outcome: "infrastructure",
|
|
287
406
|
diagnostic: error instanceof Error ? error.message : String(error),
|
|
407
|
+
role,
|
|
288
408
|
accepted: params,
|
|
289
409
|
});
|
|
290
410
|
throw error;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed user-dialogue stdin transport (#879 / #471).
|
|
3
|
+
* Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
|
|
4
|
+
* No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
|
|
5
|
+
*/
|
|
6
|
+
export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue";
|
|
7
|
+
export function encodeUserDialogueStdin(body) {
|
|
8
|
+
return JSON.stringify({ kind: USER_DIALOGUE_STDIN_KIND, body });
|
|
9
|
+
}
|
|
10
|
+
function isUserDialogueEnvelope(value) {
|
|
11
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
12
|
+
return false;
|
|
13
|
+
const record = value;
|
|
14
|
+
return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Host read: recover original dialogue after a trim-capable pipe.
|
|
18
|
+
* Non-envelope text (interactive Pi, officer JSON, headless raw body) is unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export function readUserDialogueStdin(raw) {
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (!trimmed.startsWith("{"))
|
|
23
|
+
return raw;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(trimmed);
|
|
26
|
+
if (isUserDialogueEnvelope(parsed))
|
|
27
|
+
return parsed.body;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Not our envelope — leave caller bytes intact.
|
|
31
|
+
}
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
@@ -111,6 +111,7 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
111
111
|
registerNavigatorModelCommand(pi);
|
|
112
112
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
113
113
|
createPiRoleRuntimeExtension({
|
|
114
|
+
packageRoot,
|
|
114
115
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
115
116
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
116
117
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
package/package.json
CHANGED
package/src/doctor-auditor.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type DoctorAuditOptions = {
|
|
|
26
26
|
*/
|
|
27
27
|
export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promise<ComplianceDecision> {
|
|
28
28
|
return async (options) => {
|
|
29
|
-
const dossier = resolveAuditDossier();
|
|
29
|
+
const dossier = resolveAuditDossier(options.context);
|
|
30
30
|
requireAuditMaterials(dossier);
|
|
31
31
|
const subjects = readDoctorAuditSubjects(options.context);
|
|
32
32
|
requireAuditMaterials(subjects);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unique dossier-resolution seam for 审刑院 (#233).
|
|
3
|
-
* Machine pointers only: cwd +
|
|
3
|
+
* Machine pointers only: cwd + per-turn HostContext (Pi child env fallback).
|
|
4
|
+
* No latest-run / mtime / global scan.
|
|
4
5
|
*/
|
|
5
6
|
import { existsSync, statSync } from "node:fs";
|
|
6
7
|
import { resolve } from "node:path";
|
|
7
8
|
|
|
9
|
+
import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
|
|
10
|
+
|
|
8
11
|
export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR" as const;
|
|
9
12
|
export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate" as const;
|
|
10
13
|
|
|
@@ -33,18 +36,33 @@ export type SubjectIncomplete = {
|
|
|
33
36
|
};
|
|
34
37
|
export type SubjectResolution = SubjectOk | SubjectIncomplete;
|
|
35
38
|
|
|
39
|
+
function isHostContext(value: object): value is HostContext {
|
|
40
|
+
return "sessionManager" in value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function dossierPointerFrom(source?: HostContext | NodeJS.ProcessEnv): string | undefined {
|
|
44
|
+
if (source !== undefined && isHostContext(source)) {
|
|
45
|
+
return runDirectoryFromHostContext(source);
|
|
46
|
+
}
|
|
47
|
+
const env = source ?? process.env;
|
|
48
|
+
const raw = env[AUDIT_RUN_DIR_ENV];
|
|
49
|
+
return typeof raw === "string" && raw.trim() !== "" ? raw : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
/**
|
|
37
|
-
* Resolve the per-run dossier pointer
|
|
53
|
+
* Resolve the per-run dossier pointer.
|
|
38
54
|
*
|
|
55
|
+
* ACP/headless: typed HostContext.runDirectory.
|
|
56
|
+
* Pi child: HostContext projected from env, or env fallback when no context.
|
|
39
57
|
* Absent pointer = bare Pi internal seam (ADR 0052): audit proceeds; the model
|
|
40
|
-
* self-locates the dossier from its own fall-volume position per soul.
|
|
41
|
-
* always injects the pointer — only then does the machine validate the path.
|
|
42
|
-
* Concurrent runs stay isolated because a present pointer is per-process.
|
|
58
|
+
* self-locates the dossier from its own fall-volume position per soul.
|
|
43
59
|
*/
|
|
44
|
-
export function resolveAuditDossier(
|
|
45
|
-
|
|
60
|
+
export function resolveAuditDossier(
|
|
61
|
+
source?: HostContext | NodeJS.ProcessEnv,
|
|
62
|
+
): DossierResolution {
|
|
63
|
+
const raw = dossierPointerFrom(source);
|
|
46
64
|
// Bare Pi activation seam: no machine gate when the pointer was never injected.
|
|
47
|
-
if (
|
|
65
|
+
if (raw === undefined) {
|
|
48
66
|
return { status: "ok" };
|
|
49
67
|
}
|
|
50
68
|
const runDirectory = resolve(raw);
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RoleTurnRequest,
|
|
10
10
|
RoleTurnResult,
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
|
+
import { isOfficerReviewSeat } from "./host-contracts.ts";
|
|
12
13
|
|
|
13
14
|
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
14
15
|
|
|
@@ -54,8 +55,16 @@ export function mergeRoleTurnAbortSignals(
|
|
|
54
55
|
return AbortSignal.any([prepared, request]);
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Host-transition prior-native paths are machine handoff material (#617 DK-4).
|
|
60
|
+
* #879: station-child officer dialogue must not receive those paths spliced into
|
|
61
|
+
* the review prompt — officers already hold --source-run / binding pointers.
|
|
62
|
+
*/
|
|
57
63
|
export function promptWithPriorNativePaths(basePrompt: string, request: RoleTurnRequest): string {
|
|
58
64
|
if (request.continuation.kind !== "resume") return basePrompt;
|
|
65
|
+
if (request.stationChild === true && isOfficerReviewSeat(request.activation.role)) {
|
|
66
|
+
return basePrompt;
|
|
67
|
+
}
|
|
59
68
|
const paths = request.hostTransition?.priorNativePaths;
|
|
60
69
|
if (paths === undefined || paths.length === 0) return basePrompt;
|
|
61
70
|
return `${basePrompt}\n${paths.join("\n")}`;
|
|
@@ -74,6 +74,11 @@ export async function requireGatekeeperPass(options: {
|
|
|
74
74
|
readonly signal?: AbortSignal;
|
|
75
75
|
readonly hostActions: GatekeeperPassHostActions;
|
|
76
76
|
readonly toolCallId: string;
|
|
77
|
+
/**
|
|
78
|
+
* In-flight parent typed payload for this gate turn (#879). Relayed verbatim
|
|
79
|
+
* as officer dialogue content; binding pointer stays the parent run directory.
|
|
80
|
+
*/
|
|
81
|
+
readonly submission?: unknown;
|
|
77
82
|
/** Lowest seam: same as runGatekeeper options.summonOfficer — offline tracers only. */
|
|
78
83
|
readonly summonOfficer?: GateOfficerSummon;
|
|
79
84
|
}): Promise<void> {
|
|
@@ -85,6 +90,7 @@ export async function requireGatekeeperPass(options: {
|
|
|
85
90
|
subject: options.subject,
|
|
86
91
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
87
92
|
...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
|
|
93
|
+
...(options.submission === undefined ? {} : { submission: options.submission }),
|
|
88
94
|
...(reask === undefined ? {} : { reask }),
|
|
89
95
|
});
|
|
90
96
|
const gatekeeper = projected.result;
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -86,18 +86,30 @@ export type GateOfficerSummon = (
|
|
|
86
86
|
* conclusion (#753 / #756). Hosted as same-ticket resume instruction.
|
|
87
87
|
*/
|
|
88
88
|
reask?: string,
|
|
89
|
+
/**
|
|
90
|
+
* In-flight parent 交卷 body (tool-call arguments). Production default relays
|
|
91
|
+
* it verbatim on the officer dialogue content channel (#786 / #879).
|
|
92
|
+
* Identity-bound at the submit site — never recovered as latest toolCall.
|
|
93
|
+
*/
|
|
94
|
+
submission?: unknown,
|
|
89
95
|
) => Promise<PublicSummonResult>;
|
|
90
96
|
|
|
91
97
|
export type RunGatekeeperOptions = {
|
|
92
98
|
readonly context: ExtensionContext | HostContext;
|
|
93
99
|
readonly subject: GatekeeperSubject;
|
|
94
100
|
readonly signal?: AbortSignal;
|
|
95
|
-
/** Run directory of the parent role (pointer
|
|
101
|
+
/** Run directory of the parent role (binding pointer, ADR 0079 / #879). */
|
|
96
102
|
readonly runDirectory?: string;
|
|
97
103
|
/**
|
|
98
104
|
* Plain-language re-ask for this summon (resume speaker after non-three-state).
|
|
99
105
|
*/
|
|
100
106
|
readonly reask?: string;
|
|
107
|
+
/**
|
|
108
|
+
* In-flight parent typed payload for this gate turn. Relayed verbatim as
|
|
109
|
+
* officer dialogue content (#879). Call site passes the current tool args —
|
|
110
|
+
* code must not scan session latest/mtime to recover it.
|
|
111
|
+
*/
|
|
112
|
+
readonly submission?: unknown;
|
|
101
113
|
/**
|
|
102
114
|
* Test seam for public-role summons. Production calls the shared public
|
|
103
115
|
* activation path (#675); inject only in offline tracers.
|
|
@@ -198,15 +210,26 @@ function projectOfficerDecision(
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
213
|
+
* This-court officer payloads for the parent return path (#879).
|
|
214
|
+
* Only settlement-scoped roleOutcome.payloads (courtAttempt seal) carry this-court
|
|
215
|
+
* identity. Never guess from undivided submissions — sole row included — history
|
|
216
|
+
* stays on terminal.submissions (#836 presentation).
|
|
204
217
|
*/
|
|
205
|
-
function
|
|
218
|
+
function thisCourtOfficerPayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
206
219
|
const outcome = terminal?.roleOutcome;
|
|
207
220
|
if (outcome !== undefined && (outcome.kind === "accepted" || outcome.kind === "audit_escalation")) {
|
|
208
|
-
|
|
221
|
+
if (outcome.payloads !== undefined && outcome.payloads.length > 0) return outcome.payloads;
|
|
222
|
+
}
|
|
223
|
+
if (outcome?.kind === "failure" && outcome.payloads !== undefined && outcome.payloads.length > 0) {
|
|
224
|
+
return outcome.payloads;
|
|
209
225
|
}
|
|
226
|
+
// No sole-row identity guess: undivided submissions are not this-court (#879).
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Failure/transport channel may still surface every recorded payload beside the failure (#836 A.3). */
|
|
231
|
+
function officerFailurePayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
232
|
+
const outcome = terminal?.roleOutcome;
|
|
210
233
|
if (outcome?.kind === "failure") return outcome.payloads ?? terminal?.submissions ?? [];
|
|
211
234
|
return terminal?.submissions ?? [];
|
|
212
235
|
}
|
|
@@ -219,19 +242,9 @@ function projectOfficerPayloads(
|
|
|
219
242
|
if (payloads.length === 0) {
|
|
220
243
|
return projectOfficerDecision(officer, undefined, fallbackStatus);
|
|
221
244
|
}
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
if (payloads.length === 1) return queued;
|
|
226
|
-
if (
|
|
227
|
-
queued.status === "pass"
|
|
228
|
-
|| queued.status === "bounce"
|
|
229
|
-
|| queued.status === "escalate"
|
|
230
|
-
|| queued.status === "needs_reask"
|
|
231
|
-
) {
|
|
232
|
-
return { ...queued, receipt: payloads };
|
|
233
|
-
}
|
|
234
|
-
return queued;
|
|
245
|
+
// This-court multi-submit: queue the latest seal of THIS court only (#836 呈现≠排队).
|
|
246
|
+
// Single this-court seal is the common path. Never fold history into an array receipt.
|
|
247
|
+
return projectOfficerDecision(officer, payloads[payloads.length - 1], fallbackStatus);
|
|
235
248
|
}
|
|
236
249
|
|
|
237
250
|
function projectOfficerTerminal(
|
|
@@ -240,16 +253,17 @@ function projectOfficerTerminal(
|
|
|
240
253
|
): GatekeeperResult {
|
|
241
254
|
const terminal: TerminalResult | undefined = summoned.terminal;
|
|
242
255
|
const outcome = terminal?.roleOutcome;
|
|
243
|
-
const
|
|
256
|
+
const thisCourt = thisCourtOfficerPayloads(terminal);
|
|
244
257
|
if (outcome === undefined) {
|
|
245
258
|
const detail = summoned.stderr ?? "";
|
|
259
|
+
const failurePayloads = officerFailurePayloads(terminal);
|
|
246
260
|
return {
|
|
247
261
|
status: "transport_failure",
|
|
248
262
|
stage: officer,
|
|
249
263
|
reason: detail.length > 0
|
|
250
264
|
? `${gateSeatLabel(officer)} public summon exit ${summoned.exitCode}: ${detail}`
|
|
251
265
|
: `${gateSeatLabel(officer)} public summon produced no terminal (exit ${summoned.exitCode})`,
|
|
252
|
-
submission:
|
|
266
|
+
submission: failurePayloads.length > 0 ? failurePayloads : summoned,
|
|
253
267
|
};
|
|
254
268
|
}
|
|
255
269
|
if (outcome.kind === "no_receipt") {
|
|
@@ -263,30 +277,33 @@ function projectOfficerTerminal(
|
|
|
263
277
|
};
|
|
264
278
|
}
|
|
265
279
|
if (outcome.kind === "failure") {
|
|
280
|
+
const failurePayloads = officerFailurePayloads(terminal);
|
|
266
281
|
return {
|
|
267
282
|
status: "transport_failure",
|
|
268
283
|
stage: officer,
|
|
269
284
|
reason: outcome.diagnostic,
|
|
270
|
-
submission:
|
|
285
|
+
submission: failurePayloads.length > 0 ? failurePayloads : outcome.decisiveFacts,
|
|
271
286
|
};
|
|
272
287
|
}
|
|
273
288
|
if (outcome.kind === "audit_escalation") {
|
|
274
289
|
return {
|
|
275
290
|
status: "escalate",
|
|
276
291
|
officer,
|
|
277
|
-
receipt
|
|
292
|
+
// This-court receipt only (#879) — historical rows remain on terminal.submissions.
|
|
293
|
+
receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
|
|
278
294
|
};
|
|
279
295
|
}
|
|
280
296
|
if (outcome.kind === "accepted") {
|
|
281
297
|
// outcome.status is the fixture/compat leaf: production settlement leaves
|
|
282
298
|
// it undefined once payloads are recorded, so this only matters when a
|
|
283
299
|
// caller still supplies status without any recorded payload (#836 hang).
|
|
284
|
-
return projectOfficerPayloads(officer,
|
|
300
|
+
return projectOfficerPayloads(officer, thisCourt, outcome.status);
|
|
285
301
|
}
|
|
286
302
|
return {
|
|
287
303
|
status: "needs_reask",
|
|
288
304
|
officer,
|
|
289
|
-
|
|
305
|
+
// This-court receipt only (#879).
|
|
306
|
+
receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
|
|
290
307
|
};
|
|
291
308
|
}
|
|
292
309
|
|
|
@@ -324,13 +341,13 @@ export async function projectGatekeeperRun(
|
|
|
324
341
|
},
|
|
325
342
|
};
|
|
326
343
|
}
|
|
327
|
-
// #
|
|
328
|
-
//
|
|
344
|
+
// #879: binding pointer = parent run directory; dialogue content = this-turn
|
|
345
|
+
// typed payload passed explicitly (never latest-toolCall recovery, A7.1–A7.3).
|
|
329
346
|
let summoned: PublicSummonResult;
|
|
330
347
|
try {
|
|
331
348
|
const summon =
|
|
332
349
|
options.summonOfficer
|
|
333
|
-
?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
|
|
350
|
+
?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask, nextSubmission) => {
|
|
334
351
|
const { summonGateOfficer } = await import("./public-role-summons.ts");
|
|
335
352
|
return summonGateOfficer({
|
|
336
353
|
officer: nextOfficer,
|
|
@@ -338,6 +355,7 @@ export async function projectGatekeeperRun(
|
|
|
338
355
|
cwd: options.context.cwd ?? process.cwd(),
|
|
339
356
|
...(officerSignal === undefined ? {} : { signal: officerSignal }),
|
|
340
357
|
...(reask === undefined ? {} : { reask }),
|
|
358
|
+
...(nextSubmission === undefined ? {} : { submission: nextSubmission }),
|
|
341
359
|
...(options.home === undefined ? {} : { home: options.home }),
|
|
342
360
|
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
343
361
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
@@ -349,6 +367,7 @@ export async function projectGatekeeperRun(
|
|
|
349
367
|
runDirectory,
|
|
350
368
|
options.signal,
|
|
351
369
|
options.reask,
|
|
370
|
+
options.submission,
|
|
352
371
|
);
|
|
353
372
|
} catch (error) {
|
|
354
373
|
return {
|