@akagilnc/pi-workflow-roles 0.1.5775 → 0.1.5797
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 +556 -209
- package/dist/external-host-turn-loop.js +3 -0
- package/dist/headless-host/production-host.js +529 -182
- package/dist/migrate-book-topology.js +63 -23
- package/dist/pi/role-turn-host.js +6 -1
- package/dist/public-cli/instruction-seat-run.js +10 -7
- package/dist/public-cli/invocation.js +95 -39
- package/dist/public-cli/main.js +357 -147
- package/dist/public-cli/post-admission.js +2 -0
- package/dist/public-cli/run-lifecycle.js +10 -4
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/report-ticket-tool.js +3 -3
- package/dist/role-run-placement.js +14 -2
- package/dist/role-run-relocation.js +2 -2
- package/dist/run-dossier.js +5 -4
- package/dist/session-identity.js +3 -2
- package/dist/sitian-appender.js +12 -3
- package/dist/submission-ledger.js +24 -0
- package/dist/ticket-progress.js +306 -0
- package/dist/user-dialogue-stdin.js +23 -17
- package/package.json +1 -1
- package/src/external-host-turn-loop.ts +7 -1
- package/src/host-contracts.ts +1 -1
- package/src/pi/adapter.ts +57 -45
- package/src/pi/role-turn-host.ts +6 -1
- package/src/prepared-role-turn.ts +6 -0
- package/src/public-cli/instruction-seat-run.ts +10 -7
- package/src/public-cli/invocation.ts +116 -38
- package/src/public-cli/post-admission.ts +3 -1
- package/src/public-cli/run-lifecycle.ts +10 -8
- package/src/public-cli/turn-request.ts +3 -0
- package/src/report-ticket-tool.ts +7 -3
- package/src/role-envelope.ts +5 -0
- package/src/role-run-placement.ts +19 -2
- package/src/role-run-relocation.ts +2 -2
- package/src/role-runtime.ts +14 -3
- package/src/run-dossier.ts +5 -3
- package/src/run-terminal-artifacts.ts +0 -1
- package/src/session-identity.ts +3 -2
- package/src/sitian-appender.ts +14 -3
- package/src/submission-ledger.ts +27 -0
- package/src/ticket-progress.ts +383 -0
- package/src/user-dialogue-stdin.ts +21 -14
|
@@ -129,7 +129,10 @@ export async function recordTurnDelivery(
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
export type ExternalPreparedTurn = Pick<
|
|
132
|
+
export type ExternalPreparedTurn = Pick<
|
|
133
|
+
PreparedRoleTurn,
|
|
134
|
+
"prompt" | "abortSignal" | "closeRound" | "recordSentInstruction"
|
|
135
|
+
>;
|
|
133
136
|
|
|
134
137
|
export type ExternalHostRoundOutcome =
|
|
135
138
|
| {
|
|
@@ -318,6 +321,9 @@ export async function driveExternalRoleTurnRounds(
|
|
|
318
321
|
}
|
|
319
322
|
}
|
|
320
323
|
prompt = closure.retry.message;
|
|
324
|
+
// #1199: same-envelope 催交 / correctable retry — progress instruction is
|
|
325
|
+
// the bytes actually sent this round, not the first-mint summons.
|
|
326
|
+
prepared.recordSentInstruction?.(prompt);
|
|
321
327
|
driver.afterRetry?.();
|
|
322
328
|
}
|
|
323
329
|
|
package/src/host-contracts.ts
CHANGED
|
@@ -264,7 +264,7 @@ export interface DurablePrincipalAuthority {
|
|
|
264
264
|
type HostSessionManager = { getLeafEntry(): HostSessionEntry | undefined; getLeafId(): string | null | undefined; getEntries(): Iterable<HostSessionEntry>; getSessionDir(): string; getSessionFile(): string | undefined; getHeader?(): { readonly type: string; readonly id?: string } | null; setSessionFile?(path: string): void; appendCustomEntry?(customType: string, data?: unknown): unknown; };
|
|
265
265
|
|
|
266
266
|
/** Context supplied by a host for one activation and its interceptable events. */
|
|
267
|
-
export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; /** Public-invocation scope (#537); never process-global env. */ invocationScopeId?: string; /** Selected host axis (#537 / ADR 0082); never process-global env invent. */ host?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
|
|
267
|
+
export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; /** Public-invocation scope (#537); never process-global env. */ invocationScopeId?: string; /** Selected host axis (#537 / ADR 0082); never process-global env invent. */ host?: string; /** #1199 bytes actually sent to the seat this turn (progress instruction); input-seam / in-process only — never env/argv body. */ summonsInstruction?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
|
|
268
268
|
|
|
269
269
|
/** Per-turn run directory; adapters must project any child-process identity. */
|
|
270
270
|
export function runDirectoryFromHostContext(context: HostContext): string | undefined {
|
package/src/pi/adapter.ts
CHANGED
|
@@ -45,44 +45,6 @@ export function createPiRoleRuntimeExtension(
|
|
|
45
45
|
return (pi) => createRoleRuntimeExtension(dependencies)(createPiRoleHostAdapter(pi, options));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Project Pi's activation context onto the package-owned host contract. */
|
|
49
|
-
function projectPiContext(context: ExtensionContext, transcriptFromContext?: (context: ExtensionContext) => string): HostContext {
|
|
50
|
-
const sessionManager = context.sessionManager as SessionManager;
|
|
51
|
-
const host: HostContext = {
|
|
52
|
-
cwd: context.cwd,
|
|
53
|
-
mode: context.mode,
|
|
54
|
-
model: context.model === undefined ? undefined : { provider: context.model.provider },
|
|
55
|
-
...(typeof process.env.AK_ROLE_RUN_DIR === "string" && process.env.AK_ROLE_RUN_DIR.trim() !== ""
|
|
56
|
-
? { runDirectory: process.env.AK_ROLE_RUN_DIR }
|
|
57
|
-
: {}),
|
|
58
|
-
...(typeof process.env.AK_ROLE_COURT_ATTEMPT === "string" && process.env.AK_ROLE_COURT_ATTEMPT.trim() !== ""
|
|
59
|
-
? { courtAttemptId: process.env.AK_ROLE_COURT_ATTEMPT }
|
|
60
|
-
: {}),
|
|
61
|
-
...(typeof process.env.AK_ROLE_INVOCATION_SCOPE === "string" && process.env.AK_ROLE_INVOCATION_SCOPE.trim() !== ""
|
|
62
|
-
? { invocationScopeId: process.env.AK_ROLE_INVOCATION_SCOPE }
|
|
63
|
-
: {}),
|
|
64
|
-
...(typeof process.env.AK_ROLE_HOST === "string" && process.env.AK_ROLE_HOST.trim() !== ""
|
|
65
|
-
? { host: process.env.AK_ROLE_HOST.trim() }
|
|
66
|
-
: {}),
|
|
67
|
-
sessionManager: {
|
|
68
|
-
getLeafEntry: () => context.sessionManager.getLeafEntry(),
|
|
69
|
-
getLeafId: () => context.sessionManager.getLeafId(),
|
|
70
|
-
getEntries: () => context.sessionManager.getEntries(),
|
|
71
|
-
getSessionDir: () => context.sessionManager.getSessionDir(),
|
|
72
|
-
getSessionFile: () => context.sessionManager.getSessionFile(),
|
|
73
|
-
getHeader: () => context.sessionManager.getHeader(),
|
|
74
|
-
setSessionFile: (path) => sessionManager.setSessionFile(path),
|
|
75
|
-
appendCustomEntry: (customType, data) => sessionManager.appendCustomEntry(customType, data),
|
|
76
|
-
},
|
|
77
|
-
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
78
|
-
...(context.ui === undefined ? {} : { ui: { notify: (message, type) => context.ui.notify(message, type) } }),
|
|
79
|
-
...(transcriptFromContext === undefined ? {} : { transcript: () => transcriptFromContext(context) }),
|
|
80
|
-
abort: () => context.abort(),
|
|
81
|
-
};
|
|
82
|
-
piContexts.set(host, context);
|
|
83
|
-
return host;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
48
|
/** Recover the Pi context only at the Pi composition boundary. */
|
|
87
49
|
export function toPiContext(context: HostContext): ExtensionContext {
|
|
88
50
|
const piContext = piContexts.get(context);
|
|
@@ -158,10 +120,63 @@ export function createPiRoleHostAdapter(
|
|
|
158
120
|
let beforeAgentStartHandlers = 0;
|
|
159
121
|
// Parameters of registered terminating tools — the schema Pi was handed.
|
|
160
122
|
const registeredToolParameters = new Map<string, unknown>();
|
|
161
|
-
//
|
|
162
|
-
//
|
|
123
|
+
// Pi createContext() is fresh per emit/tool (runner.js). Do not cache HostContext by
|
|
124
|
+
// ExtensionContext identity. Share only this-turn 传召词 / 催交 on the adapter instance;
|
|
125
|
+
// model and identity pointers stay live on each fresh projection (#1199).
|
|
126
|
+
let turnSummonsInstruction: string | undefined;
|
|
127
|
+
const transcriptFromContext = options.transcriptFromContext;
|
|
128
|
+
const projectPiContext = (context: ExtensionContext): HostContext => {
|
|
129
|
+
const sessionManager = context.sessionManager as SessionManager;
|
|
130
|
+
const host: HostContext = {
|
|
131
|
+
cwd: context.cwd,
|
|
132
|
+
mode: context.mode,
|
|
133
|
+
model: context.model === undefined ? undefined : { provider: context.model.provider },
|
|
134
|
+
...(typeof process.env.AK_ROLE_RUN_DIR === "string" && process.env.AK_ROLE_RUN_DIR.trim() !== ""
|
|
135
|
+
? { runDirectory: process.env.AK_ROLE_RUN_DIR }
|
|
136
|
+
: {}),
|
|
137
|
+
...(typeof process.env.AK_ROLE_COURT_ATTEMPT === "string" && process.env.AK_ROLE_COURT_ATTEMPT.trim() !== ""
|
|
138
|
+
? { courtAttemptId: process.env.AK_ROLE_COURT_ATTEMPT }
|
|
139
|
+
: {}),
|
|
140
|
+
...(typeof process.env.AK_ROLE_INVOCATION_SCOPE === "string" && process.env.AK_ROLE_INVOCATION_SCOPE.trim() !== ""
|
|
141
|
+
? { invocationScopeId: process.env.AK_ROLE_INVOCATION_SCOPE }
|
|
142
|
+
: {}),
|
|
143
|
+
...(typeof process.env.AK_ROLE_HOST === "string" && process.env.AK_ROLE_HOST.trim() !== ""
|
|
144
|
+
? { host: process.env.AK_ROLE_HOST.trim() }
|
|
145
|
+
: {}),
|
|
146
|
+
sessionManager: {
|
|
147
|
+
getLeafEntry: () => context.sessionManager.getLeafEntry(),
|
|
148
|
+
getLeafId: () => context.sessionManager.getLeafId(),
|
|
149
|
+
getEntries: () => context.sessionManager.getEntries(),
|
|
150
|
+
getSessionDir: () => context.sessionManager.getSessionDir(),
|
|
151
|
+
getSessionFile: () => context.sessionManager.getSessionFile(),
|
|
152
|
+
getHeader: () => context.sessionManager.getHeader(),
|
|
153
|
+
setSessionFile: (path) => sessionManager.setSessionFile(path),
|
|
154
|
+
appendCustomEntry: (customType, data) => sessionManager.appendCustomEntry(customType, data),
|
|
155
|
+
},
|
|
156
|
+
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
157
|
+
...(context.ui === undefined ? {} : { ui: { notify: (message, type) => context.ui.notify(message, type) } }),
|
|
158
|
+
...(transcriptFromContext === undefined ? {} : { transcript: () => transcriptFromContext(context) }),
|
|
159
|
+
abort: () => context.abort(),
|
|
160
|
+
};
|
|
161
|
+
// exactOptionalPropertyTypes: wire via accessors so undefined stays "absent value"
|
|
162
|
+
// while every fresh projection shares the adapter-instance turn field.
|
|
163
|
+
Object.defineProperty(host, "summonsInstruction", {
|
|
164
|
+
enumerable: true,
|
|
165
|
+
configurable: true,
|
|
166
|
+
get: () => turnSummonsInstruction,
|
|
167
|
+
set: (value: string | undefined) => {
|
|
168
|
+
turnSummonsInstruction = value;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
piContexts.set(host, context);
|
|
172
|
+
return host;
|
|
173
|
+
};
|
|
174
|
+
// Decode transport exactly once before Pi's native handler chain. Progress
|
|
175
|
+
// instruction is the model-facing body (what the seat actually receives) —
|
|
176
|
+
// no parallel 传召词 channel. Every input refreshes the turn field.
|
|
163
177
|
pi.on("input", (value) => {
|
|
164
178
|
const text = readUserDialogueStdin(value.text);
|
|
179
|
+
turnSummonsInstruction = text;
|
|
165
180
|
return text === value.text ? { action: "continue" } : { action: "transform", text };
|
|
166
181
|
});
|
|
167
182
|
const host: RoleHost = {
|
|
@@ -172,10 +187,7 @@ export function createPiRoleHostAdapter(
|
|
|
172
187
|
getFlag: (name) => pi.getFlag(name),
|
|
173
188
|
registerTool: (tool) => {
|
|
174
189
|
registeredToolParameters.set(tool.name, tool.parameters);
|
|
175
|
-
return pi.registerTool(toPiToolDefinition(
|
|
176
|
-
tool,
|
|
177
|
-
(context) => projectPiContext(context, options.transcriptFromContext),
|
|
178
|
-
));
|
|
190
|
+
return pi.registerTool(toPiToolDefinition(tool, projectPiContext));
|
|
179
191
|
},
|
|
180
192
|
getAllTools: () => pi.getAllTools().map(({ name, sourceInfo }) => ({
|
|
181
193
|
name,
|
|
@@ -184,7 +196,7 @@ export function createPiRoleHostAdapter(
|
|
|
184
196
|
setActiveTools: (names) => pi.setActiveTools(names),
|
|
185
197
|
getActiveTools: () => pi.getActiveTools(),
|
|
186
198
|
on(...registration: HostEventRegistration) {
|
|
187
|
-
const context =
|
|
199
|
+
const context = projectPiContext;
|
|
188
200
|
if (registration[0] === "before_agent_start") {
|
|
189
201
|
const [, handler] = registration;
|
|
190
202
|
const position = beforeAgentStartHandlers;
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -438,7 +438,9 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
438
438
|
config.extraPiArgs ?? [],
|
|
439
439
|
);
|
|
440
440
|
const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
|
|
441
|
-
const
|
|
441
|
+
const transportBody = piUserDialogueBody(turnRequest);
|
|
442
|
+
// #1199: progress instruction is these same body bytes (actual seat input).
|
|
443
|
+
const stdin = encodeUserDialogueStdin(transportBody);
|
|
442
444
|
// Shared envelope isolates this call's court identity: omitting courtAttemptId
|
|
443
445
|
// must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
|
|
444
446
|
const env: NodeJS.ProcessEnv = {
|
|
@@ -458,6 +460,9 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
458
460
|
// Selected host axis (#537 / ADR 0082): omit must not inherit a parent env value.
|
|
459
461
|
if (request.host === undefined || request.host.trim() === "") delete env.AK_ROLE_HOST;
|
|
460
462
|
else env.AK_ROLE_HOST = request.host.trim();
|
|
463
|
+
// #1199: 传召词 is unknown-length body — never argv/env (execve ARG_MAX / E2BIG).
|
|
464
|
+
// Typed stdin envelope carries dialogue + optional progress 传召词.
|
|
465
|
+
delete env.AK_ROLE_SUMMONS_INSTRUCTION;
|
|
461
466
|
// #1132: the one effective delivery-request ceiling travels to the child
|
|
462
467
|
// on its own env so the in-child role runtime and the worker submission
|
|
463
468
|
// gate count with the same configured number the AK seam resolved. Omit
|
|
@@ -15,6 +15,12 @@ export type PreparedRoleTurn = Readonly<{
|
|
|
15
15
|
systemPrompt: { readonly body: string; readonly materials: readonly unknown[] };
|
|
16
16
|
/** Effective user prompt after host-side input transform (canonical Skill invocation). */
|
|
17
17
|
prompt: string;
|
|
18
|
+
/**
|
|
19
|
+
* #1199: book the bytes actually sent this round as progress instruction
|
|
20
|
+
* (same-envelope 催交 / correctable retry). Production prepareRoleEnvelope
|
|
21
|
+
* always provides this; test stubs may omit.
|
|
22
|
+
*/
|
|
23
|
+
recordSentInstruction?(text: string): void;
|
|
18
24
|
/**
|
|
19
25
|
* Host abort signal armed only by typed infrastructure failure (envelope
|
|
20
26
|
* rememberInfrastructureFailure / non-correctable MCP catch). Lawful
|
|
@@ -585,7 +585,10 @@ export async function resolveReviewSeatDialogueBody(input: {
|
|
|
585
585
|
return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
-
|
|
588
|
+
/**
|
|
589
|
+
* Initial host prompt for this turn (#1199: progress instruction = these bytes).
|
|
590
|
+
*/
|
|
591
|
+
async function resolveInitialTurnPrompt(
|
|
589
592
|
admitted: AdmittedRoleInvocation,
|
|
590
593
|
env: InstructionSeatRunEnv,
|
|
591
594
|
): Promise<string> {
|
|
@@ -726,7 +729,7 @@ async function dispatchAdmitted(
|
|
|
726
729
|
if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
|
|
727
730
|
await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
|
|
728
731
|
}
|
|
729
|
-
const
|
|
732
|
+
const initialPrompt = await resolveInitialTurnPrompt(admitted, activeEnv);
|
|
730
733
|
const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
|
|
731
734
|
if (auto) {
|
|
732
735
|
// #1208: auto-resume resends this turn's actual content, never a filler phrase.
|
|
@@ -738,14 +741,14 @@ async function dispatchAdmitted(
|
|
|
738
741
|
admitted,
|
|
739
742
|
roleTurnOptions(activeEnv, admitted, {
|
|
740
743
|
kind: "initial",
|
|
741
|
-
prompt:
|
|
744
|
+
prompt: initialPrompt,
|
|
742
745
|
}),
|
|
743
746
|
),
|
|
744
747
|
buildResumeRequest: () => buildInstructionSeatTurnRequest(
|
|
745
748
|
admitted,
|
|
746
749
|
roleTurnOptions(activeEnv, admitted, {
|
|
747
750
|
kind: "resume",
|
|
748
|
-
prompt:
|
|
751
|
+
prompt: initialPrompt,
|
|
749
752
|
}),
|
|
750
753
|
),
|
|
751
754
|
adapters,
|
|
@@ -760,7 +763,7 @@ async function dispatchAdmitted(
|
|
|
760
763
|
admitted,
|
|
761
764
|
roleTurnOptions(activeEnv, admitted, {
|
|
762
765
|
kind: "initial",
|
|
763
|
-
prompt:
|
|
766
|
+
prompt: initialPrompt,
|
|
764
767
|
}),
|
|
765
768
|
),
|
|
766
769
|
adapters,
|
|
@@ -899,7 +902,7 @@ function resumeSameParentInstructionSeat(input: {
|
|
|
899
902
|
}
|
|
900
903
|
|
|
901
904
|
/**
|
|
902
|
-
* Same delivery rule as
|
|
905
|
+
* Same delivery rule as resolveInitialTurnPrompt for same-parent resume summons.
|
|
903
906
|
* Shared resolver owns the input choice — callers only pass env fields.
|
|
904
907
|
*/
|
|
905
908
|
async function sameParentDialogue(
|
|
@@ -1005,7 +1008,7 @@ async function runCountersignBody(
|
|
|
1005
1008
|
// #1208 C1 / #1166 J11: one shared initial-prompt seam (incl. file flags).
|
|
1006
1009
|
const turnProjection = roleTurnOptions(env, admitted, {
|
|
1007
1010
|
kind: "initial",
|
|
1008
|
-
prompt: await
|
|
1011
|
+
prompt: await resolveInitialTurnPrompt(admitted, env),
|
|
1009
1012
|
});
|
|
1010
1013
|
const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
|
|
1011
1014
|
const result = await runPostAdmissionOneShot({
|
|
@@ -23,12 +23,14 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
|
|
|
23
23
|
import {
|
|
24
24
|
ensureRoleRunDirectory,
|
|
25
25
|
ensureRoleRunPlacement,
|
|
26
|
-
formatRunLeaf,
|
|
27
26
|
isUnboundRunDirectory,
|
|
28
27
|
listBookRunDirectories,
|
|
29
28
|
roleRunPlacement,
|
|
29
|
+
sessionDirectoryOf,
|
|
30
|
+
sessionFileIn,
|
|
30
31
|
type RoleRunSubject,
|
|
31
32
|
} from "../role-run-placement.ts";
|
|
33
|
+
import { relocateTicketProgressForLeg } from "../ticket-progress.ts";
|
|
32
34
|
import type {
|
|
33
35
|
DurablePrincipal,
|
|
34
36
|
DurablePrincipalAuthority,
|
|
@@ -580,15 +582,101 @@ export async function recordAdmittedCorrelation(
|
|
|
580
582
|
mutable.correlationIds = correlationIds;
|
|
581
583
|
}
|
|
582
584
|
|
|
585
|
+
/**
|
|
586
|
+
* #1199 R2: registered 起居郎 legs that already belong on this ticket.
|
|
587
|
+
* Unbound source → whole-leg rename + publish; already under ticket → publish
|
|
588
|
+
* leftover staging only. No scan of unregistered siblings.
|
|
589
|
+
*/
|
|
590
|
+
async function relocateRegisteredChildDiarists(input: {
|
|
591
|
+
readonly parentRunDirectory: string;
|
|
592
|
+
readonly bookKey: string;
|
|
593
|
+
readonly ticketNumber: number;
|
|
594
|
+
readonly projectRoot: string;
|
|
595
|
+
readonly ledgerHome: string;
|
|
596
|
+
readonly authority: DurablePrincipalAuthority;
|
|
597
|
+
}): Promise<void> {
|
|
598
|
+
const childRunIds = readPageSync(input.parentRunDirectory, "admitted")?.childDiaristRunIds;
|
|
599
|
+
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
600
|
+
if (typeof childRunId !== "string") continue;
|
|
601
|
+
const childTarget = roleRunPlacement(input.ledgerHome, {
|
|
602
|
+
bookKey: input.bookKey,
|
|
603
|
+
subject: { ticketNumber: input.ticketNumber },
|
|
604
|
+
runId: childRunId,
|
|
605
|
+
role: "diarist",
|
|
606
|
+
});
|
|
607
|
+
const unboundChildDirectory = roleRunPlacement(input.ledgerHome, {
|
|
608
|
+
bookKey: input.bookKey,
|
|
609
|
+
subject: { unbound: true },
|
|
610
|
+
runId: childRunId,
|
|
611
|
+
role: "diarist",
|
|
612
|
+
}).runDirectory;
|
|
613
|
+
|
|
614
|
+
if (existsSync(unboundChildDirectory)) {
|
|
615
|
+
input.authority.seal(childTarget);
|
|
616
|
+
await bindTicketNumberOnRunDirectory(unboundChildDirectory, input.ticketNumber);
|
|
617
|
+
await rehomeUnboundTicketProvenance(
|
|
618
|
+
unboundChildDirectory,
|
|
619
|
+
input.ticketNumber,
|
|
620
|
+
input.projectRoot,
|
|
621
|
+
homeFromRunDirectory(input.parentRunDirectory),
|
|
622
|
+
);
|
|
623
|
+
ensureRoleRunDirectory(input.ledgerHome, dirname(childTarget.runDirectory));
|
|
624
|
+
// #1199: whole-leg rename first; originals travel with the directory.
|
|
625
|
+
await rename(unboundChildDirectory, childTarget.runDirectory);
|
|
626
|
+
relocateTicketProgressForLeg({
|
|
627
|
+
runDirectory: childTarget.runDirectory,
|
|
628
|
+
});
|
|
629
|
+
// Finished child legs will not settle again; refresh derived host.original
|
|
630
|
+
// after the rename commit. A derived render fault must not undo or block
|
|
631
|
+
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
632
|
+
try {
|
|
633
|
+
renderCurrentSync(childTarget.runDirectory);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
process.stderr.write(
|
|
636
|
+
`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Prior partial relocate left the child under the ticket with staging still held.
|
|
643
|
+
if (existsSync(childTarget.runDirectory)) {
|
|
644
|
+
relocateTicketProgressForLeg({
|
|
645
|
+
runDirectory: childTarget.runDirectory,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
583
651
|
/** File a role run under its latest typed ticket identity. */
|
|
584
652
|
export async function relocateAdmittedRunToTicket(
|
|
585
653
|
admitted: AdmittedRoleInvocation,
|
|
586
654
|
authority: DurablePrincipalAuthority,
|
|
587
655
|
heldLease?: { relocate(runDirectory: string): void },
|
|
588
656
|
): Promise<RunDirectoryRelocation | undefined> {
|
|
589
|
-
if (admitted.ticketNumber === undefined
|
|
657
|
+
if (admitted.ticketNumber === undefined) return undefined;
|
|
658
|
+
|
|
659
|
+
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(admitted.runDirectory));
|
|
660
|
+
|
|
661
|
+
// Already under the ticket: publish leftover progress.jsonl (self + registered children).
|
|
662
|
+
if (!isUnboundRunDirectory(admitted.runDirectory)) {
|
|
663
|
+
relocateTicketProgressForLeg({
|
|
664
|
+
runDirectory: admitted.runDirectory,
|
|
665
|
+
});
|
|
666
|
+
if (admitted.role !== "diarist") {
|
|
667
|
+
await relocateRegisteredChildDiarists({
|
|
668
|
+
parentRunDirectory: admitted.runDirectory,
|
|
669
|
+
bookKey: admitted.bookKey,
|
|
670
|
+
ticketNumber: admitted.ticketNumber,
|
|
671
|
+
projectRoot: admitted.projectRoot,
|
|
672
|
+
ledgerHome,
|
|
673
|
+
authority,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
|
|
590
679
|
const oldRunDirectory = admitted.runDirectory;
|
|
591
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(oldRunDirectory));
|
|
592
680
|
const target = roleRunPlacement(ledgerHome, {
|
|
593
681
|
bookKey: admitted.bookKey,
|
|
594
682
|
subject: { ticketNumber: admitted.ticketNumber },
|
|
@@ -596,39 +684,18 @@ export async function relocateAdmittedRunToTicket(
|
|
|
596
684
|
role: admitted.role,
|
|
597
685
|
});
|
|
598
686
|
ensureRoleRunDirectory(ledgerHome, dirname(target.runDirectory));
|
|
599
|
-
//
|
|
600
|
-
|
|
601
|
-
const principal = authority.seal(target);
|
|
687
|
+
// Pre-flight seal; final seal after the filesystem commit.
|
|
688
|
+
authority.seal(target);
|
|
602
689
|
|
|
603
690
|
if (admitted.role !== "diarist") {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
subject: { ticketNumber: admitted.ticketNumber },
|
|
613
|
-
runId: childRunId,
|
|
614
|
-
role: "diarist",
|
|
615
|
-
});
|
|
616
|
-
authority.seal(childTarget);
|
|
617
|
-
await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
|
|
618
|
-
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
619
|
-
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
620
|
-
await rename(childDirectory, childTarget.runDirectory);
|
|
621
|
-
// Finished child legs will not settle again; refresh derived host.original
|
|
622
|
-
// after the rename commit. A derived render fault must not undo or block
|
|
623
|
-
// the committed placement (#1161 L1) — note and keep the true cause.
|
|
624
|
-
try {
|
|
625
|
-
renderCurrentSync(childTarget.runDirectory);
|
|
626
|
-
} catch (error) {
|
|
627
|
-
process.stderr.write(
|
|
628
|
-
`[invocation] current.json render refused after child relocate; placement kept: ${childTarget.runDirectory}: ${errorText(error)}\n`,
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
691
|
+
await relocateRegisteredChildDiarists({
|
|
692
|
+
parentRunDirectory: oldRunDirectory,
|
|
693
|
+
bookKey: admitted.bookKey,
|
|
694
|
+
ticketNumber: admitted.ticketNumber,
|
|
695
|
+
projectRoot: admitted.projectRoot,
|
|
696
|
+
ledgerHome,
|
|
697
|
+
authority,
|
|
698
|
+
});
|
|
632
699
|
}
|
|
633
700
|
|
|
634
701
|
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
@@ -637,21 +704,32 @@ export async function relocateAdmittedRunToTicket(
|
|
|
637
704
|
}
|
|
638
705
|
|
|
639
706
|
// Rename commits the run placement. Diary assignment above is a Sitian append
|
|
640
|
-
// outside the atomic directory move.
|
|
641
|
-
// Persisted paths are resolved from typed run identity on read.
|
|
707
|
+
// outside the atomic directory move. Session/receipts/progress travel with the leg.
|
|
642
708
|
await rename(oldRunDirectory, target.runDirectory);
|
|
643
709
|
|
|
644
710
|
// rename moved the open lock inode with the directory. Transfer cleanup and
|
|
645
|
-
//
|
|
646
|
-
//
|
|
711
|
+
// live path ownership immediately after the commit — before final seal/render
|
|
712
|
+
// (#1161 L1 / BASE order). Live handles first so mid-turn setSessionFile sees
|
|
713
|
+
// the ticket leaf; final seal uses the post-move existence landing.
|
|
647
714
|
heldLease?.relocate(target.runDirectory);
|
|
648
715
|
|
|
716
|
+
const actualSessionDirectory = sessionDirectoryOf(target.runDirectory);
|
|
717
|
+
const principal = authority.seal({
|
|
718
|
+
sessionDirectory: actualSessionDirectory,
|
|
719
|
+
sessionFile: sessionFileIn(actualSessionDirectory),
|
|
720
|
+
});
|
|
721
|
+
|
|
649
722
|
rewriteAdmittedRoleRunPage(
|
|
650
723
|
admitted as unknown as Record<string, unknown>,
|
|
651
724
|
[{ oldRunDirectory, newRunDirectory: target.runDirectory }],
|
|
652
725
|
);
|
|
653
726
|
(admitted as { principal: DurablePrincipal }).principal = principal;
|
|
654
727
|
|
|
728
|
+
// After whole-leg rename + live-handle handoff: fill rounds and append ticket current.
|
|
729
|
+
relocateTicketProgressForLeg({
|
|
730
|
+
runDirectory: target.runDirectory,
|
|
731
|
+
});
|
|
732
|
+
|
|
655
733
|
// current.json projects host.original from this run directory. Ownership is
|
|
656
734
|
// already transferred; a refuse still carries the true cause to the caller
|
|
657
735
|
// (#1161 L1) without leaving the leg under the unbound path.
|
|
@@ -835,6 +835,7 @@ function buildReceiptDeliveryRequest(input: {
|
|
|
835
835
|
receiptDelivery: ReturnType<typeof createReceiptDeliveryPolicy>;
|
|
836
836
|
}): RoleTurnRequest {
|
|
837
837
|
const { request, receiptDelivery } = input;
|
|
838
|
+
// #1199: progress instruction is this turn's prompt (envelope books continuation.prompt).
|
|
838
839
|
return {
|
|
839
840
|
...request,
|
|
840
841
|
continuation: {
|
|
@@ -1696,6 +1697,7 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1696
1697
|
const officerDialogue = isStationChildOfficerDialogue(admitted.role, env);
|
|
1697
1698
|
const fileFlags = summonsPrepared?.attachments ?? [];
|
|
1698
1699
|
let prompt: string;
|
|
1700
|
+
// #1199: progress instruction = this prompt (actual seat input, file flags included).
|
|
1699
1701
|
if (request.message !== undefined) {
|
|
1700
1702
|
if (summonsPrepared !== undefined) {
|
|
1701
1703
|
// #755: same-ticket review / open-court — caller words + caller file flags.
|
|
@@ -1743,7 +1745,7 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1743
1745
|
/** Shared new-turn and in-call auto-resume projection. Seat code supplies prompt and activation. */
|
|
1744
1746
|
export function roleTurnOptions(
|
|
1745
1747
|
env: PostAdmissionEnv,
|
|
1746
|
-
admitted: { readonly correlationId?: string },
|
|
1748
|
+
admitted: { readonly correlationId?: string; readonly instruction?: string },
|
|
1747
1749
|
continuation: RoleTurnRequest["continuation"],
|
|
1748
1750
|
): RoleTurnRequestProjectionOptions {
|
|
1749
1751
|
const correlationId = env.correlationId ?? admitted.correlationId;
|
|
@@ -28,7 +28,7 @@ import type { NotarySourceRunLocator } from "../notary-contracts.ts";
|
|
|
28
28
|
import {
|
|
29
29
|
retainedRunPathsMatch,
|
|
30
30
|
rewriteAdmittedRoleRunPage,
|
|
31
|
-
|
|
31
|
+
rewritePrincipalSessionPaths,
|
|
32
32
|
} from "../role-run-relocation.ts";
|
|
33
33
|
import type { PublicThinkingLevel } from "./registry.ts";
|
|
34
34
|
import {
|
|
@@ -290,14 +290,16 @@ async function readRoleRunStateDisk(
|
|
|
290
290
|
: runDirectory;
|
|
291
291
|
const runDir = runDirectory;
|
|
292
292
|
// Principal wire stays uninterpreted — authority owns legacy sessionFile fallback.
|
|
293
|
+
// #1199: leg-local session/ moves with the run-prefix rewrite.
|
|
294
|
+
const principalCoords = {
|
|
295
|
+
sessionDirectory: record.sessionDirectory,
|
|
296
|
+
...(typeof record.sessionFile === "string" ? { sessionFile: record.sessionFile } : {}),
|
|
297
|
+
};
|
|
298
|
+
rewritePrincipalSessionPaths(principalCoords, storedRunDirectory, runDirectory);
|
|
293
299
|
const principalWire: RoleRunPrincipalWire = {
|
|
294
|
-
sessionDirectory:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
runDirectory,
|
|
298
|
-
) as string,
|
|
299
|
-
...(typeof record.sessionFile === "string"
|
|
300
|
-
? { sessionFile: rewriteRunDirectoryPathValue(record.sessionFile, storedRunDirectory, runDirectory) as string }
|
|
300
|
+
sessionDirectory: principalCoords.sessionDirectory as string,
|
|
301
|
+
...(typeof principalCoords.sessionFile === "string"
|
|
302
|
+
? { sessionFile: principalCoords.sessionFile }
|
|
301
303
|
: {}),
|
|
302
304
|
};
|
|
303
305
|
const phase =
|
|
@@ -131,6 +131,9 @@ export function projectRoleTurnRequest(
|
|
|
131
131
|
...(options.deliveryRequestLimit === undefined
|
|
132
132
|
? {}
|
|
133
133
|
: { deliveryRequestLimit: options.deliveryRequestLimit }),
|
|
134
|
+
...(options.host === undefined || options.host.trim() === ""
|
|
135
|
+
? {}
|
|
136
|
+
: { host: options.host.trim() }),
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -141,6 +141,7 @@ export async function reportTicketFromHostContext(
|
|
|
141
141
|
if (process.env.AK_ROLE_RUN_DIR === runDirectory) {
|
|
142
142
|
process.env.AK_ROLE_RUN_DIR = nextDirectory;
|
|
143
143
|
}
|
|
144
|
+
// Leg-local session/: rewrite the open handle under the renamed run prefix.
|
|
144
145
|
const projectedSessionFile = typeof priorSessionFile === "string" && priorSessionFile.trim() !== ""
|
|
145
146
|
? rewriteRunDirectoryPathValue(priorSessionFile, runDirectory, nextDirectory)
|
|
146
147
|
: sessionFileOf(nextDirectory);
|
|
@@ -148,9 +149,12 @@ export async function reportTicketFromHostContext(
|
|
|
148
149
|
context.sessionManager.setSessionFile?.(projectedSessionFile);
|
|
149
150
|
}
|
|
150
151
|
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Always enter relocate: unbound→rename+publish; already-on-ticket→publish leftover staging.
|
|
153
|
+
const relocation = await relocateAdmittedRunToTicket(
|
|
154
|
+
admitted,
|
|
155
|
+
authority,
|
|
156
|
+
alreadyPlaced ? undefined : { relocate: applyLiveHandles },
|
|
157
|
+
);
|
|
154
158
|
const nextDirectory = relocation?.newRunDirectory ?? admitted.runDirectory;
|
|
155
159
|
if (relocation === undefined) applyLiveHandles(nextDirectory);
|
|
156
160
|
|
package/src/role-envelope.ts
CHANGED
|
@@ -704,6 +704,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
704
704
|
const record = value as Record<string, unknown>;
|
|
705
705
|
if (record.action === "transform" && typeof record.text === "string") prompt = record.text;
|
|
706
706
|
}
|
|
707
|
+
// #1199: progress instruction = bytes actually handed to the seat this turn.
|
|
708
|
+
context.summonsInstruction = prompt;
|
|
707
709
|
// Book the user assignment so judge audit subjects recover it from parent books.
|
|
708
710
|
if (typeof prompt === "string" && prompt.trim() !== "") {
|
|
709
711
|
sessionEntries.push({
|
|
@@ -751,6 +753,9 @@ export async function prepareRoleEnvelope(options: {
|
|
|
751
753
|
}],
|
|
752
754
|
systemPrompt: { body: systemPromptBody, materials: readingMaterials },
|
|
753
755
|
prompt,
|
|
756
|
+
recordSentInstruction(text: string) {
|
|
757
|
+
context.summonsInstruction = text;
|
|
758
|
+
},
|
|
754
759
|
abortSignal: hostAbort.signal,
|
|
755
760
|
closeRound,
|
|
756
761
|
dispose,
|
|
@@ -34,12 +34,28 @@ export function parseRunLeaf(name: string): { readonly runId: string; readonly r
|
|
|
34
34
|
return { runId: name.slice(0, at), role: name.slice(at + 1) };
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export const ROLE_RUN_SESSION_FILENAME = "session.jsonl";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ticket or unbound subject directory owning a run under `<subject>/runs/<leaf>`.
|
|
41
|
+
* Used by ticket-progress relative paths — not a second session landing.
|
|
42
|
+
*/
|
|
43
|
+
export function subjectDirectoryOfRun(runDirectory: string): string | undefined {
|
|
44
|
+
const runsParent = dirname(runDirectory);
|
|
45
|
+
if (basename(runsParent) !== "runs") return undefined;
|
|
46
|
+
const subjectDirectory = dirname(runsParent);
|
|
47
|
+
const subjectName = basename(subjectDirectory);
|
|
48
|
+
if (subjectName === "unbound" || /^[1-9]\d*$/.test(subjectName)) {
|
|
49
|
+
return subjectDirectory;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** #1199 / main: session originals stay under the leg — `<run>/session`. */
|
|
37
55
|
export function sessionDirectoryOf(runDirectory: string): string {
|
|
38
56
|
return join(runDirectory, "session");
|
|
39
57
|
}
|
|
40
58
|
|
|
41
|
-
export const ROLE_RUN_SESSION_FILENAME = "session.jsonl";
|
|
42
|
-
|
|
43
59
|
export function sessionFileIn(sessionDirectory: string): string {
|
|
44
60
|
return join(sessionDirectory, ROLE_RUN_SESSION_FILENAME);
|
|
45
61
|
}
|
|
@@ -177,5 +193,6 @@ export function ensureRoleRunPlacement(
|
|
|
177
193
|
ledgerHome: string,
|
|
178
194
|
placement: RoleRunPlacement,
|
|
179
195
|
): void {
|
|
196
|
+
ensureRoleRunDirectory(ledgerHome, placement.runDirectory);
|
|
180
197
|
ensureRoleRunDirectory(ledgerHome, placement.sessionDirectory);
|
|
181
198
|
}
|
|
@@ -13,7 +13,7 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { activationBookDirectory, physicalPathIdentity } from "./activation-ledger-topology.ts";
|
|
14
14
|
import { AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE } from "./compliance-transport.ts";
|
|
15
15
|
import { readRoleRunIdentity } from "./public-cli/run-lifecycle.ts";
|
|
16
|
-
import { roleRunPlacement } from "./role-run-placement.ts";
|
|
16
|
+
import { roleRunPlacement, sessionDirectoryOf } from "./role-run-placement.ts";
|
|
17
17
|
import {
|
|
18
18
|
INVOCATION_PAGE_PATH_FIELDS,
|
|
19
19
|
PRINCIPAL_SESSION_PATH_FIELDS,
|
|
@@ -267,7 +267,7 @@ async function rewriteNestedMachinePathPages(
|
|
|
267
267
|
pagesDirectory: string,
|
|
268
268
|
rewrites: readonly RunDirectoryPathRewrite[],
|
|
269
269
|
): Promise<void> {
|
|
270
|
-
const sessionRoot =
|
|
270
|
+
const sessionRoot = sessionDirectoryOf(pagesDirectory);
|
|
271
271
|
async function walk(directory: string): Promise<void> {
|
|
272
272
|
let entries;
|
|
273
273
|
try {
|