@akagilnc/pi-workflow-roles 0.1.5614 → 0.1.5650
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 +960 -465
- package/dist/activation-ledger-topology.js +12 -0
- package/dist/external-host-turn-loop.js +281 -0
- package/dist/headless-host/production-host.js +900 -406
- package/dist/migrate-book-topology.js +8 -5
- package/dist/pi/role-turn-host.js +33 -3
- package/dist/public-cli/instruction-seat-run.js +177 -46
- package/dist/public-cli/invocation.js +5 -12
- package/dist/public-cli/main.js +541 -165
- package/dist/public-cli/post-admission.js +171 -18
- package/dist/public-cli/settlement.js +20 -10
- package/dist/report-ticket-tool.js +136 -0
- package/dist/role-run-placement.js +1 -2
- package/dist/role-run-relocation.js +24 -4
- package/package.json +1 -1
- package/resources/engine-dispatch.md +6 -3
- package/resources/engines/claude-code.md +4 -5
- package/resources/missing-ticket-reask.md +1 -0
- package/src/acp-host/role-turn-host.ts +41 -31
- package/src/activation-ledger-topology.ts +16 -0
- package/src/external-host-turn-loop.ts +103 -12
- package/src/headless-host/role-turn-host.ts +41 -25
- package/src/pi/role-turn-host.ts +37 -3
- package/src/public-cli/instruction-seat-run.ts +248 -50
- package/src/public-cli/invocation.ts +15 -27
- package/src/public-cli/post-admission.ts +250 -33
- package/src/public-cli/run-lifecycle.ts +5 -5
- package/src/public-cli/settlement.ts +38 -11
- package/src/public-cli/terminal.ts +6 -0
- package/src/report-ticket-tool.ts +176 -0
- package/src/role-envelope.ts +28 -3
- package/src/role-run-placement.ts +0 -2
- package/src/role-run-relocation.ts +45 -4
- package/src/role-runtime.ts +6 -0
- package/src/worker-submission-gates.ts +26 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mid-turn ticket report (#1171): roles call this as soon as they know the
|
|
3
|
+
* ticket number; the executing process binds and relocates immediately.
|
|
4
|
+
* When to call lives in this tool's description — not Soul.
|
|
5
|
+
*/
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
DurablePrincipalAuthority,
|
|
11
|
+
HostContext,
|
|
12
|
+
HostToolResult,
|
|
13
|
+
RoleHost,
|
|
14
|
+
} from "./host-contracts.ts";
|
|
15
|
+
import { runDirectoryFromHostContext } from "./host-contracts.ts";
|
|
16
|
+
import { piDurablePrincipalAuthority } from "./pi/durable-principal.ts";
|
|
17
|
+
import {
|
|
18
|
+
bindAdmittedTicketNumber,
|
|
19
|
+
relocateAdmittedRunToTicket,
|
|
20
|
+
type AdmittedRoleInvocation,
|
|
21
|
+
} from "./public-cli/invocation.ts";
|
|
22
|
+
import { isUnboundRunDirectory, sessionDirectoryOf, sessionFileOf } from "./role-run-placement.ts";
|
|
23
|
+
import { readPageSync } from "./run-dossier.ts";
|
|
24
|
+
import { readDeclaredTicketNumber } from "./run-ticket-number.ts";
|
|
25
|
+
import { isRecord } from "./unknown-value.ts";
|
|
26
|
+
|
|
27
|
+
export const REPORT_TICKET_TOOL_NAME = "ak_report_ticket" as const;
|
|
28
|
+
|
|
29
|
+
const reportTicketArgsSchema = Type.Object(
|
|
30
|
+
{
|
|
31
|
+
ticketNumber: Type.Unknown({
|
|
32
|
+
description:
|
|
33
|
+
"本票号(正整数、数字串或前导 #N)。已知票号时开工即报;尚无票号时不要调用。",
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
{ additionalProperties: true },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const REPORT_TICKET_TOOL_DESCRIPTION =
|
|
40
|
+
"向包申报本票号。已知票号时开工第一件事调用;包当场把本腿归到票号目录。"
|
|
41
|
+
+ "尚无票号(例如中书省建票前)不要调用,取得后再报。"
|
|
42
|
+
+ "交卷时若仍未报过且交卷亦未带票号,包会经既有重交通道再问一次。";
|
|
43
|
+
|
|
44
|
+
function pageString(page: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
45
|
+
const value = page?.[key];
|
|
46
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build the relocate seam's admitted face from durable pages + live principal.
|
|
51
|
+
* The executing process owns this snapshot for the bind/relocate call only.
|
|
52
|
+
*/
|
|
53
|
+
export function admittedForLiveTicketReport(
|
|
54
|
+
runDirectory: string,
|
|
55
|
+
authority: DurablePrincipalAuthority,
|
|
56
|
+
): AdmittedRoleInvocation {
|
|
57
|
+
const admittedPage = readPageSync(runDirectory, "admitted") ?? {};
|
|
58
|
+
const invocationPage = readPageSync(runDirectory, "invocation") ?? {};
|
|
59
|
+
const role = pageString(admittedPage, "role") ?? pageString(invocationPage, "role");
|
|
60
|
+
const runId = pageString(admittedPage, "runId") ?? pageString(invocationPage, "runId");
|
|
61
|
+
const bookKey = pageString(admittedPage, "bookKey") ?? pageString(invocationPage, "bookKey");
|
|
62
|
+
const projectRoot = pageString(admittedPage, "projectRoot")
|
|
63
|
+
?? pageString(invocationPage, "projectRoot");
|
|
64
|
+
if (role === undefined || runId === undefined || bookKey === undefined || projectRoot === undefined) {
|
|
65
|
+
throw new Error("ak_report_ticket requires durable role/runId/bookKey/projectRoot on the run");
|
|
66
|
+
}
|
|
67
|
+
const attachments = Array.isArray(admittedPage.attachments)
|
|
68
|
+
? admittedPage.attachments as AdmittedRoleInvocation["attachments"]
|
|
69
|
+
: [];
|
|
70
|
+
const sessionDirectory = sessionDirectoryOf(runDirectory);
|
|
71
|
+
const sessionFile = sessionFileOf(runDirectory);
|
|
72
|
+
const principal = authority.seal({ sessionDirectory, sessionFile });
|
|
73
|
+
const ticketNumber = readDeclaredTicketNumber(admittedPage.ticketNumber)
|
|
74
|
+
?? readDeclaredTicketNumber(invocationPage.ticketNumber);
|
|
75
|
+
return {
|
|
76
|
+
role,
|
|
77
|
+
runId,
|
|
78
|
+
bookKey,
|
|
79
|
+
projectRoot,
|
|
80
|
+
instruction: typeof admittedPage.instruction === "string" ? admittedPage.instruction : "",
|
|
81
|
+
instructionEmpty: admittedPage.instructionEmpty === true
|
|
82
|
+
|| (typeof admittedPage.instruction === "string" && admittedPage.instruction.trim() === ""),
|
|
83
|
+
attachments,
|
|
84
|
+
runDirectory,
|
|
85
|
+
principal,
|
|
86
|
+
...(ticketNumber === undefined ? {} : { ticketNumber }),
|
|
87
|
+
} as AdmittedRoleInvocation;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Role-facing tool details only (#1171 / ADR 0087): ticket identity and whether
|
|
92
|
+
* placement moved. Never hand a package-computed runDirectory back to the seat.
|
|
93
|
+
*/
|
|
94
|
+
export type ReportTicketResult = {
|
|
95
|
+
readonly ticketNumber: number;
|
|
96
|
+
readonly relocated: boolean;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Bind + relocate under the existing post-admission seams, then update the
|
|
101
|
+
* executing process's live path handles (HostContext + optional env + setSessionFile).
|
|
102
|
+
* Pi: native SessionManager.setSessionFile so later appends stay on the moved file.
|
|
103
|
+
*/
|
|
104
|
+
export async function reportTicketFromHostContext(
|
|
105
|
+
context: HostContext,
|
|
106
|
+
rawTicketNumber: unknown,
|
|
107
|
+
authority: DurablePrincipalAuthority = piDurablePrincipalAuthority,
|
|
108
|
+
): Promise<ReportTicketResult> {
|
|
109
|
+
const ticketNumber = readDeclaredTicketNumber(rawTicketNumber);
|
|
110
|
+
if (ticketNumber === undefined) {
|
|
111
|
+
throw new Error("ak_report_ticket requires an identifiable ticketNumber");
|
|
112
|
+
}
|
|
113
|
+
const runDirectory = runDirectoryFromHostContext(context);
|
|
114
|
+
if (runDirectory === undefined) {
|
|
115
|
+
throw new Error("ak_report_ticket requires a per-turn run directory");
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync(runDirectory)) {
|
|
118
|
+
throw new Error(`ak_report_ticket run directory missing: ${runDirectory}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const admitted = admittedForLiveTicketReport(runDirectory, authority);
|
|
122
|
+
const alreadyPlaced = !isUnboundRunDirectory(runDirectory);
|
|
123
|
+
// #1171 B2 / #1025: already under a ticket leaf — do not rewrite durable identity.
|
|
124
|
+
// Placement stays put; no heterogenous-ticket refuse policy (#1171 judge).
|
|
125
|
+
if (!alreadyPlaced) {
|
|
126
|
+
await bindAdmittedTicketNumber(admitted, ticketNumber);
|
|
127
|
+
}
|
|
128
|
+
// Live handles belong with rename ownership — before derived renderCurrentSync
|
|
129
|
+
// (#1171 F4-R2). Reuse relocate's heldLease.relocate seam; keep raw I/O throw.
|
|
130
|
+
const applyLiveHandles = (nextDirectory: string): void => {
|
|
131
|
+
(context as { runDirectory?: string }).runDirectory = nextDirectory;
|
|
132
|
+
// Same-leg spawn env only (#1171 F1): never overwrite another leg's ambient
|
|
133
|
+
// AK_ROLE_RUN_DIR in a shared process. HostContext is the per-turn authority
|
|
134
|
+
// (host-contracts); Pi dedicated children still refresh when env === old path.
|
|
135
|
+
if (process.env.AK_ROLE_RUN_DIR === runDirectory) {
|
|
136
|
+
process.env.AK_ROLE_RUN_DIR = nextDirectory;
|
|
137
|
+
}
|
|
138
|
+
// Native host rebind only — SessionManager has no setSessionDir; appends follow sessionFile.
|
|
139
|
+
context.sessionManager.setSessionFile?.(sessionFileOf(nextDirectory));
|
|
140
|
+
};
|
|
141
|
+
const relocation = alreadyPlaced
|
|
142
|
+
? undefined
|
|
143
|
+
: await relocateAdmittedRunToTicket(admitted, authority, { relocate: applyLiveHandles });
|
|
144
|
+
const nextDirectory = relocation?.newRunDirectory ?? admitted.runDirectory;
|
|
145
|
+
if (relocation === undefined) applyLiveHandles(nextDirectory);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
ticketNumber: alreadyPlaced
|
|
149
|
+
? (admitted.ticketNumber ?? ticketNumber)
|
|
150
|
+
: ticketNumber,
|
|
151
|
+
relocated: relocation !== undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Register the report-ticket tool on the shared host tool mouth (same as submission). */
|
|
156
|
+
export function registerReportTicketTool(roleHost: RoleHost): void {
|
|
157
|
+
if (roleHost.getAllTools().some((tool) => tool.name === REPORT_TICKET_TOOL_NAME)) return;
|
|
158
|
+
roleHost.registerTool({
|
|
159
|
+
name: REPORT_TICKET_TOOL_NAME,
|
|
160
|
+
label: "报票号",
|
|
161
|
+
description: REPORT_TICKET_TOOL_DESCRIPTION,
|
|
162
|
+
promptSnippet: "已知票号时立即申报,包当场归位",
|
|
163
|
+
parameters: reportTicketArgsSchema,
|
|
164
|
+
async execute(_toolCallId, parameters, _signal, _onUpdate, ctx): Promise<HostToolResult<ReportTicketResult>> {
|
|
165
|
+
const raw = isRecord(parameters) ? parameters.ticketNumber : undefined;
|
|
166
|
+
const details = await reportTicketFromHostContext(ctx, raw);
|
|
167
|
+
return {
|
|
168
|
+
content: [],
|
|
169
|
+
details,
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Soft re-ask when a sealed submission still leaves the leg unbound (#1171 / ADR 0073). */
|
|
176
|
+
export const MISSING_TICKET_REASK_MATERIAL = "resources/missing-ticket-reask.md" as const;
|
package/src/role-envelope.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { createServer, type Server, type Socket } from "node:net";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
|
|
6
|
-
import { sessionFileOf } from "./role-run-placement.ts";
|
|
6
|
+
import { sessionDirectoryOf, sessionFileOf } from "./role-run-placement.ts";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
import {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "./role-runtime.ts";
|
|
29
29
|
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
30
30
|
import { projectActivationFlags } from "./role-activation-flags.ts";
|
|
31
|
+
import { projectTurnRequestLiveRunDirectory } from "./role-run-relocation.ts";
|
|
31
32
|
import {
|
|
32
33
|
isCorrectableExecuteError,
|
|
33
34
|
projectCorrectableExecuteRejection,
|
|
@@ -170,11 +171,14 @@ export async function prepareRoleEnvelope(options: {
|
|
|
170
171
|
}
|
|
171
172
|
});
|
|
172
173
|
};
|
|
174
|
+
const mutableRequest = request as {
|
|
175
|
+
runDirectory: string;
|
|
176
|
+
principal: { sessionDirectory?: string; sessionFile?: string };
|
|
177
|
+
};
|
|
173
178
|
const context: HostContext = {
|
|
174
179
|
cwd: request.cwd,
|
|
175
180
|
mode: "print",
|
|
176
181
|
model: request.model === undefined ? undefined : { provider: request.model.provider },
|
|
177
|
-
runDirectory: request.runDirectory,
|
|
178
182
|
...(request.courtAttemptId === undefined ? {} : { courtAttemptId: request.courtAttemptId }),
|
|
179
183
|
...(request.invocationScopeId === undefined ? {} : { invocationScopeId: request.invocationScopeId }),
|
|
180
184
|
...(request.host === undefined || request.host.trim() === "" ? {} : { host: request.host.trim() }),
|
|
@@ -202,6 +206,27 @@ export async function prepareRoleEnvelope(options: {
|
|
|
202
206
|
// and non-correctable MCP catch). Submission does not abort the turn.
|
|
203
207
|
},
|
|
204
208
|
};
|
|
209
|
+
// #1171: report-ticket updates HostContext.runDirectory; keep the shared turn
|
|
210
|
+
// request (and principal session paths) on the same leaf for adapter exit-copy.
|
|
211
|
+
Object.defineProperty(context, "runDirectory", {
|
|
212
|
+
configurable: true,
|
|
213
|
+
enumerable: true,
|
|
214
|
+
get() {
|
|
215
|
+
return mutableRequest.runDirectory;
|
|
216
|
+
},
|
|
217
|
+
set(next: string) {
|
|
218
|
+
if (typeof next !== "string" || next.trim() === "") return;
|
|
219
|
+
if (mutableRequest.runDirectory === next) return;
|
|
220
|
+
projectTurnRequestLiveRunDirectory(mutableRequest, next);
|
|
221
|
+
sessionFile = sessionFileOf(next);
|
|
222
|
+
if (typeof mutableRequest.principal.sessionDirectory !== "string") {
|
|
223
|
+
mutableRequest.principal.sessionDirectory = sessionDirectoryOf(next);
|
|
224
|
+
}
|
|
225
|
+
if (typeof mutableRequest.principal.sessionFile !== "string") {
|
|
226
|
+
mutableRequest.principal.sessionFile = sessionFile;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
});
|
|
205
230
|
const bookCustomMessage = (customType: string, message: { content?: string; details?: unknown }): void => {
|
|
206
231
|
const payload = {
|
|
207
232
|
...(message.content === undefined ? {} : { content: message.content }),
|
|
@@ -599,7 +624,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
599
624
|
} catch (error) {
|
|
600
625
|
// Closing the relay is ordinary cleanup, not required envelope work.
|
|
601
626
|
await retainPackageFault({
|
|
602
|
-
runDirectory:
|
|
627
|
+
runDirectory: mutableRequest.runDirectory,
|
|
603
628
|
diagnostic: `relay close failed beside envelope shutdown: ${describeErrorIdentity(error)}`,
|
|
604
629
|
error,
|
|
605
630
|
});
|
|
@@ -17,7 +17,6 @@ export type RoleRunPlacement = {
|
|
|
17
17
|
readonly runDirectory: string;
|
|
18
18
|
readonly sessionDirectory: string;
|
|
19
19
|
readonly sessionFile: string;
|
|
20
|
-
readonly attachmentsDirectory: string;
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
/** Writer leaf `<runId>@<role>`. Empty sides stay empty; this is not a validator. */
|
|
@@ -158,7 +157,6 @@ export function roleRunPlacement(
|
|
|
158
157
|
runDirectory,
|
|
159
158
|
sessionDirectory,
|
|
160
159
|
sessionFile: sessionFileIn(sessionDirectory),
|
|
161
|
-
attachmentsDirectory: join(runDirectory, "attachments"),
|
|
162
160
|
};
|
|
163
161
|
}
|
|
164
162
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Live callers project typed fields; only historical migrators walk durable pages.
|
|
4
4
|
*
|
|
5
5
|
* Scope is typed machine-consumed path fields only — never free text, never
|
|
6
|
-
*
|
|
6
|
+
* caller file-flag path strings. Nested walk is confined to package-owned
|
|
7
7
|
* `session/` seams.
|
|
8
8
|
*/
|
|
9
9
|
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -43,6 +43,12 @@ const RUN_STATE_PAGE_FIELDS = [
|
|
|
43
43
|
"sessionFile",
|
|
44
44
|
] as const;
|
|
45
45
|
|
|
46
|
+
/** Typed principal session coordinates rewritten when the owning run leaf moves. */
|
|
47
|
+
export const PRINCIPAL_SESSION_PATH_FIELDS = [
|
|
48
|
+
"sessionDirectory",
|
|
49
|
+
"sessionFile",
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
46
52
|
const SOURCE_RUN_LOCATOR_FIELDS = ["runDirectory"] as const;
|
|
47
53
|
const SUMMONS_PATH_FIELDS = ["sourceRunPath"] as const;
|
|
48
54
|
const OFFICER_POINTER_FIELDS = ["sessionFile", "runDirectory"] as const;
|
|
@@ -146,6 +152,41 @@ export function rewriteRunDirectoryPathFields(
|
|
|
146
152
|
]);
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
/** Principal session paths only — shared live + durable projection after a run move. */
|
|
156
|
+
export function rewritePrincipalSessionPaths(
|
|
157
|
+
principal: Record<string, unknown>,
|
|
158
|
+
oldRunDirectory: string,
|
|
159
|
+
newRunDirectory: string,
|
|
160
|
+
): void {
|
|
161
|
+
rewriteRunDirectoryPathFields(
|
|
162
|
+
principal,
|
|
163
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
164
|
+
oldRunDirectory,
|
|
165
|
+
newRunDirectory,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Live turn request: move runDirectory and rewrite principal session paths in place.
|
|
171
|
+
* Callers that lack authority.seal use this; admitted refresh seals after the same rewrite.
|
|
172
|
+
*/
|
|
173
|
+
export function projectTurnRequestLiveRunDirectory(
|
|
174
|
+
request: {
|
|
175
|
+
runDirectory: string;
|
|
176
|
+
principal: { sessionDirectory?: string; sessionFile?: string };
|
|
177
|
+
},
|
|
178
|
+
newRunDirectory: string,
|
|
179
|
+
): void {
|
|
180
|
+
const old = request.runDirectory;
|
|
181
|
+
if (old === newRunDirectory) return;
|
|
182
|
+
request.runDirectory = newRunDirectory;
|
|
183
|
+
rewritePrincipalSessionPaths(
|
|
184
|
+
request.principal as Record<string, unknown>,
|
|
185
|
+
old,
|
|
186
|
+
newRunDirectory,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
149
190
|
/** In-place rewrite of selected fields against a rewrite set. */
|
|
150
191
|
export function rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
151
192
|
record: Record<string, unknown>,
|
|
@@ -227,7 +268,7 @@ export function rewriteAdmittedRoleRunPage(
|
|
|
227
268
|
if (isRecord(page.principal)) {
|
|
228
269
|
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
229
270
|
page.principal,
|
|
230
|
-
|
|
271
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
231
272
|
rewrites,
|
|
232
273
|
);
|
|
233
274
|
}
|
|
@@ -385,7 +426,7 @@ async function rewriteSessionTranscriptBindings(
|
|
|
385
426
|
/**
|
|
386
427
|
* Package-owned nested seams under session/ only:
|
|
387
428
|
* sitian records.jsonl,
|
|
388
|
-
* session transcript typed parent bindings. Never walks
|
|
429
|
+
* session transcript typed parent bindings. Never walks caller file paths.
|
|
389
430
|
*/
|
|
390
431
|
async function rewriteNestedMachinePathPages(
|
|
391
432
|
pagesDirectory: string,
|
|
@@ -447,7 +488,7 @@ export async function rewriteRoleRunDurablePages(input: {
|
|
|
447
488
|
if (isRecord(page.principal)) {
|
|
448
489
|
rewriteRunDirectoryPathFieldsAgainstRewrites(
|
|
449
490
|
page.principal,
|
|
450
|
-
|
|
491
|
+
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
451
492
|
rewrites,
|
|
452
493
|
);
|
|
453
494
|
}
|
package/src/role-runtime.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { sitianReport } from "./sitian-facade.ts";
|
|
|
16
16
|
import { sitianReportSafe } from "./host-session-record.ts";
|
|
17
17
|
import { createSubmissionLedgerHost, sealAcceptedSubmission } from "./submission-ledger.ts";
|
|
18
18
|
import { registerFiledSubmissionTool, type FiledSubmissionBeforeAccept } from "./filed-submission.ts";
|
|
19
|
+
import { registerReportTicketTool, REPORT_TICKET_TOOL_NAME } from "./report-ticket-tool.ts";
|
|
19
20
|
import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
20
21
|
|
|
21
22
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
@@ -1790,6 +1791,11 @@ export function createRoleRuntimeExtension(
|
|
|
1790
1791
|
}
|
|
1791
1792
|
|
|
1792
1793
|
await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
|
|
1794
|
+
// #1171: same registration mouth as submission tools; when-to-call is on the tool itself.
|
|
1795
|
+
registerReportTicketTool(roleHost);
|
|
1796
|
+
roleHost.setActiveTools([
|
|
1797
|
+
...new Set([...roleHost.getActiveTools(), REPORT_TICKET_TOOL_NAME]),
|
|
1798
|
+
]);
|
|
1793
1799
|
roleReferenceMaterials = await dependencies.loadRoleReferenceMaterials?.(entry.role) ?? "";
|
|
1794
1800
|
// #357 T2 / #378 / #380 / #391 / #818: any role+engine activation registers the package detour tool once.
|
|
1795
1801
|
// Gate is resolveEngineName (RoleHost flag → env fallback) — no per-engine execute branch; no role-module spawn.
|
|
@@ -264,6 +264,8 @@ export function createWorkerSubmissionGate(
|
|
|
264
264
|
let unfinishedReasonBounces = 0;
|
|
265
265
|
let invocationScopeId: string | undefined;
|
|
266
266
|
let record: HostRecordSession | undefined;
|
|
267
|
+
/** Live parent — mid-turn run relocate (#1171) may move its session file. */
|
|
268
|
+
let parentRef: WorkerSubmissionGateParent | undefined;
|
|
267
269
|
/** Parent session file retained so every gate sitian write path-derives the same ledger home. */
|
|
268
270
|
let sessionParent: string | undefined;
|
|
269
271
|
const explicitHome =
|
|
@@ -278,6 +280,25 @@ export function createWorkerSubmissionGate(
|
|
|
278
280
|
return null;
|
|
279
281
|
}
|
|
280
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* #1171: report-ticket renames the run leaf; the gate nest moves with it, but the
|
|
285
|
+
* in-memory HostRecordSession still names the old path. Reopen under the live
|
|
286
|
+
* parent (continueRecent resumes the moved nest) before any append.
|
|
287
|
+
*/
|
|
288
|
+
const liveRecord = (): HostRecordSession | undefined => {
|
|
289
|
+
if (record === undefined || root === undefined || parentRef === undefined) return record;
|
|
290
|
+
const file = record.getSessionFile();
|
|
291
|
+
if (file !== undefined && existsSync(file)) return record;
|
|
292
|
+
const parentFile = parentRef.getSessionFile();
|
|
293
|
+
if (parentFile === undefined || parentFile.length === 0) return record;
|
|
294
|
+
sessionParent = parentFile;
|
|
295
|
+
record = createRecordSession({
|
|
296
|
+
cwd: root,
|
|
297
|
+
kind: WORKER_SUBMISSION_GATE_RECORD_KIND,
|
|
298
|
+
parent: parentRef,
|
|
299
|
+
});
|
|
300
|
+
return record;
|
|
301
|
+
};
|
|
281
302
|
const gateSitian = (cwd: string, payload: Record<string, unknown>) => {
|
|
282
303
|
sitianReport({
|
|
283
304
|
level: "event",
|
|
@@ -294,6 +315,7 @@ export function createWorkerSubmissionGate(
|
|
|
294
315
|
invocationScopeId = scope;
|
|
295
316
|
uninstallPackageWorkerHooks(cwd);
|
|
296
317
|
root = cwd;
|
|
318
|
+
parentRef = parent;
|
|
297
319
|
sessionParent = parent.getSessionFile();
|
|
298
320
|
record = createRecordSession({
|
|
299
321
|
cwd,
|
|
@@ -322,9 +344,10 @@ export function createWorkerSubmissionGate(
|
|
|
322
344
|
});
|
|
323
345
|
},
|
|
324
346
|
assertAcceptable(status, details) {
|
|
347
|
+
const gateRecord = liveRecord();
|
|
325
348
|
if (status === "unfinished" && !unfinishedReasonPresent(details)) {
|
|
326
349
|
if (unfinishedReasonBounces < unfinishedReasonBounceLimit) {
|
|
327
|
-
|
|
350
|
+
gateRecord?.appendCustomEntry(WORKER_UNFINISHED_REASON_BOUNCE_ENTRY_TYPE, {
|
|
328
351
|
version: 1, invocationScopeId,
|
|
329
352
|
});
|
|
330
353
|
unfinishedReasonBounces += 1;
|
|
@@ -338,7 +361,7 @@ export function createWorkerSubmissionGate(
|
|
|
338
361
|
// Gate ① — forgetfulness reminder (ADR 0066; behavior unchanged).
|
|
339
362
|
if (!headMoved && !reminded) {
|
|
340
363
|
reminded = true;
|
|
341
|
-
|
|
364
|
+
gateRecord?.appendCustomEntry(WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
|
|
342
365
|
gateSitian(root, {
|
|
343
366
|
type: WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE,
|
|
344
367
|
version: 1,
|
|
@@ -358,7 +381,7 @@ export function createWorkerSubmissionGate(
|
|
|
358
381
|
return;
|
|
359
382
|
}
|
|
360
383
|
prefixReminded = true;
|
|
361
|
-
|
|
384
|
+
gateRecord?.appendCustomEntry(WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
|
|
362
385
|
gateSitian(root, {
|
|
363
386
|
type: WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE,
|
|
364
387
|
version: 1,
|