@akagilnc/pi-workflow-roles 0.1.5663 → 0.1.5671
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 +180 -105
- package/dist/headless-host/production-host.js +201 -126
- package/dist/migrate-book-topology.js +11 -10
- package/dist/navigator-attendance.js +61 -78
- package/dist/navigator-public-session.js +17 -12
- package/dist/package-contracts/navigator-output.js +101 -15
- package/dist/public-cli/main.js +18 -10
- package/dist/public-cli/post-admission.js +9 -0
- package/dist/public-cli/turn-request.js +3 -0
- package/dist/public-role-summons.js +1 -0
- package/package.json +1 -1
- package/resources/navigator-route-playbook.md +3 -3
- package/src/headless-host/description.ts +2 -1
- package/src/headless-host/role-turn-host.ts +18 -15
- package/src/host-contracts.ts +6 -0
- package/src/navigator-attendance.ts +149 -171
- package/src/navigator-public-session.ts +23 -16
- package/src/navigator-session-contracts.ts +0 -1
- package/src/package-contracts/navigator-output.ts +146 -20
- package/src/public-cli/post-admission.ts +11 -0
- package/src/public-cli/turn-request.ts +5 -0
- package/src/public-role-summons.ts +8 -0
- package/src/role-runtime.ts +3 -2
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* no books/<book>/navigator/<hash>/ side-branch nest, work-context file,
|
|
7
7
|
* current-session pointer, or attendance parallel ledger (#1178).
|
|
8
8
|
* Host conversation continuity uses CLI resume (`resumeRunId`) within this
|
|
9
|
-
* attendance lifetime (in-memory pointer)
|
|
9
|
+
* attendance lifetime (in-memory pointer). Parallel byStatus prepare is #1160.
|
|
10
10
|
*/
|
|
11
11
|
import { basename } from "node:path";
|
|
12
12
|
|
|
@@ -81,6 +81,8 @@ export type NavigatorPublicSummon = (options: {
|
|
|
81
81
|
readonly resumeRunId?: string;
|
|
82
82
|
/** Shared-lifecycle cancel forwarded from HostContext.signal (#675 / #959). */
|
|
83
83
|
readonly signal?: AbortSignal;
|
|
84
|
+
/** #1160 attendance auto byStatus prepare — host mounts structured schema. */
|
|
85
|
+
readonly navigatorByStatusPrepare?: boolean;
|
|
84
86
|
}) => Promise<PublicSummonResult>;
|
|
85
87
|
|
|
86
88
|
type MemoryEntry = {
|
|
@@ -98,7 +100,6 @@ export function createNativeNavigatorSessionFactory(deps?: {
|
|
|
98
100
|
return async ({ context, tool }) => {
|
|
99
101
|
// Model is enforced at prepare (attendance seat resolve) and at prompt (summon).
|
|
100
102
|
// Factory open only holds in-memory attendance bookkeeping — no side-branch nest.
|
|
101
|
-
let thinkingLevel: string | undefined;
|
|
102
103
|
const entries: MemoryEntry[] = [];
|
|
103
104
|
let providerFailure: NavigatorProviderFailureFact | undefined;
|
|
104
105
|
let noReceipt: Partial<NoReceiptLifecycleFacts> | undefined;
|
|
@@ -137,6 +138,9 @@ export function createNativeNavigatorSessionFactory(deps?: {
|
|
|
137
138
|
role: "navigator" as const,
|
|
138
139
|
argv: [text] as const,
|
|
139
140
|
cwd: context.cwd,
|
|
141
|
+
// Attendance prepare always asks for structured byStatus (#1160).
|
|
142
|
+
// Direct `ak-role navigator` never sets this flag.
|
|
143
|
+
navigatorByStatusPrepare: true as const,
|
|
140
144
|
...(summonHome === undefined ? {} : { home: summonHome }),
|
|
141
145
|
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
142
146
|
};
|
|
@@ -222,18 +226,24 @@ export function createNativeNavigatorSessionFactory(deps?: {
|
|
|
222
226
|
// Not a shape-unusable judgment on the navigator reply (#757).
|
|
223
227
|
return;
|
|
224
228
|
}
|
|
225
|
-
// #959:
|
|
226
|
-
const {
|
|
227
|
-
|
|
229
|
+
// #959 / #1160: pass through prose and byStatus. No route parsing.
|
|
230
|
+
const {
|
|
231
|
+
byStatusToPlainObject,
|
|
232
|
+
mergePreparedAdvice,
|
|
233
|
+
preparedAdviceFromUnknown,
|
|
234
|
+
} = await import("./package-contracts/navigator-output.ts");
|
|
235
|
+
let prepared = undefined as ReturnType<typeof preparedAdviceFromUnknown>;
|
|
228
236
|
for (const payload of outcome.payloads ?? []) {
|
|
229
|
-
|
|
230
|
-
if (prose !== undefined) proseParts.push(prose);
|
|
237
|
+
prepared = mergePreparedAdvice(prepared, preparedAdviceFromUnknown(payload));
|
|
231
238
|
}
|
|
232
|
-
if (
|
|
239
|
+
if (prepared === undefined) return;
|
|
233
240
|
if (disposed) return;
|
|
234
241
|
await tool.execute(
|
|
235
242
|
"navigator-public-prepare",
|
|
236
|
-
{
|
|
243
|
+
{
|
|
244
|
+
...(prepared.prose === undefined ? {} : { prose: prepared.prose }),
|
|
245
|
+
...(prepared.byStatus.size === 0 ? {} : { byStatus: byStatusToPlainObject(prepared.byStatus) }),
|
|
246
|
+
},
|
|
237
247
|
undefined,
|
|
238
248
|
undefined,
|
|
239
249
|
context as never,
|
|
@@ -264,18 +274,15 @@ export function createNativeNavigatorSessionFactory(deps?: {
|
|
|
264
274
|
});
|
|
265
275
|
},
|
|
266
276
|
entries: () => entries,
|
|
267
|
-
setModel: async (next
|
|
268
|
-
|
|
277
|
+
setModel: async (next) => {
|
|
278
|
+
// Validate only — resume/fresh mint both read the live seat table
|
|
279
|
+
// (#675 / #617 DK-3). No test-only thinking observation state (#1160 F1).
|
|
269
280
|
try {
|
|
270
|
-
|
|
281
|
+
parseNavigatorModelSetting(next);
|
|
271
282
|
} catch (error) {
|
|
272
283
|
throw navigatorUnavailableError("model", error);
|
|
273
284
|
}
|
|
274
|
-
// Resume and fresh mint both read the live seat table (#675 / #617 DK-3).
|
|
275
|
-
// A seat edit between prepares applies on the next host turn.
|
|
276
|
-
thinkingLevel = nextThinking ?? nextParsed.thinkingLevel;
|
|
277
285
|
},
|
|
278
|
-
getThinkingLevel: () => thinkingLevel,
|
|
279
286
|
dispose: async () => {
|
|
280
287
|
// Marker only — nested cancel and non-blocking teardown are owned by
|
|
281
288
|
// navigator-attendance / role-runtime (ADR 0018 / #959).
|
|
@@ -203,7 +203,6 @@ export type NavigatorPreparationSession = {
|
|
|
203
203
|
/** Native message from the nested playbook read, when that read failed. */
|
|
204
204
|
routePlaybookReadFailure?(): string | undefined;
|
|
205
205
|
setModel?(model: string, thinkingLevel?: string): Promise<void>;
|
|
206
|
-
getThinkingLevel?(): string | undefined;
|
|
207
206
|
dispose(): void | Promise<void>;
|
|
208
207
|
};
|
|
209
208
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public Navigator (游奕使) terminating receipt contracts (#639 / #959).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Public Navigator (游奕使) terminating receipt contracts (#639 / #959 / #1160).
|
|
3
|
+
* Advice body is free-form prose. Status association for parallel prepare lives on
|
|
4
|
+
* `byStatus` — code picks by the settlement status field only (owner 3188b502).
|
|
5
|
+
* Code does not parse, rank, or judge advice semantics.
|
|
6
6
|
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
7
7
|
*/
|
|
8
8
|
import { Type } from "typebox";
|
|
@@ -13,20 +13,75 @@ import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.
|
|
|
13
13
|
|
|
14
14
|
export const NAVIGATOR_OUTPUT_TOOL_NAME = "ak_navigator_output";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
),
|
|
24
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Single owner of prose / byStatus field declarations (#1160).
|
|
18
|
+
* Output tool and attendance prepare tool both compose from this.
|
|
19
|
+
*/
|
|
20
|
+
export const navigatorAdviceFields = {
|
|
21
|
+
prose: Type.Unknown({
|
|
22
|
+
description: "单条散文建议。无状态分叉时使用;原样呈现。",
|
|
23
|
+
}),
|
|
24
|
+
byStatus: Type.Unknown({
|
|
25
|
+
description:
|
|
26
|
+
"按主衙门结局 status 预写的散文建议。键为 status 字面量,值为建议正文。"
|
|
27
|
+
+ "结算时代码只按实际 status 取对应原文,不解析建议语义。",
|
|
28
|
+
}),
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/** Open tool object for prose/byStatus (attendance prepare + public output body). */
|
|
32
|
+
export const navigatorAdviceBodySchema = openToolObject(Type.Object(navigatorAdviceFields));
|
|
33
|
+
|
|
34
|
+
export const navigatorOutputSchema = withTerminatingOutputDeclarations(navigatorAdviceBodySchema);
|
|
25
35
|
|
|
26
36
|
export type NavigatorAdvice = { readonly prose: string };
|
|
27
37
|
|
|
38
|
+
/** Parallel-prepare payload: status-keyed prose plus optional single prose. */
|
|
39
|
+
export type PreparedNavigatorAdvice = {
|
|
40
|
+
/** Map keeps opaque status keys (including "__proto__") as own entries. */
|
|
41
|
+
readonly byStatus: ReadonlyMap<string, string>;
|
|
42
|
+
readonly prose?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Serialize structured bodies with JSON; never String(object) → [object Object]. */
|
|
46
|
+
function nonEmptyProse(value: unknown): string | undefined {
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
return value.trim() === "" ? undefined : value;
|
|
49
|
+
}
|
|
50
|
+
if (value === undefined || value === null) return undefined;
|
|
51
|
+
if (typeof value === "object") {
|
|
52
|
+
// No catch: serialization failure must surface (failure honesty).
|
|
53
|
+
const text = JSON.stringify(value);
|
|
54
|
+
if (text === undefined || text.trim() === "" || text === "{}" || text === "[]") return undefined;
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
const text = String(value);
|
|
58
|
+
return text.trim() === "" ? undefined : text;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setStatusProse(target: Map<string, string>, key: string, prose: string): void {
|
|
62
|
+
const prior = target.get(key);
|
|
63
|
+
target.set(key, prior === undefined ? prose : `${prior}\n\n${prose}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function byStatusFromUnknown(value: unknown): Map<string, string> {
|
|
67
|
+
const out = new Map<string, string>();
|
|
68
|
+
if (!isRecord(value)) return out;
|
|
69
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
70
|
+
const prose = nonEmptyProse(raw);
|
|
71
|
+
if (prose !== undefined) out.set(key, prose);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Plain object for tool.execute / host payloads; Object.fromEntries keeps opaque keys. */
|
|
77
|
+
export function byStatusToPlainObject(
|
|
78
|
+
byStatus: ReadonlyMap<string, string>,
|
|
79
|
+
): Record<string, string> {
|
|
80
|
+
return Object.fromEntries(byStatus);
|
|
81
|
+
}
|
|
82
|
+
|
|
28
83
|
/**
|
|
29
|
-
* Project one Navigator advice receipt as prose.
|
|
84
|
+
* Project one Navigator advice receipt as prose (display / single-advice path).
|
|
30
85
|
* Accepts { prose }, a bare string, or any object — objects stringify as the
|
|
31
86
|
* prose body so historical / free-form submissions still present. Shape is not
|
|
32
87
|
* an admission gate (ADR 0055 / 仓内 CLAUDE.md 开篇 / #959).
|
|
@@ -37,16 +92,13 @@ export function projectLawfulNavigatorOutput(value: unknown): NavigatorAdvice |
|
|
|
37
92
|
if (isRecord(value)) {
|
|
38
93
|
if (typeof value.prose === "string") return { prose: value.prose };
|
|
39
94
|
if (value.prose !== undefined && value.prose !== null) {
|
|
40
|
-
|
|
95
|
+
const serialized = nonEmptyProse(value.prose);
|
|
96
|
+
if (serialized !== undefined) return { prose: serialized };
|
|
41
97
|
}
|
|
42
98
|
// Free-form object submission: present the whole body as prose when it has content.
|
|
43
99
|
const keys = Object.keys(value);
|
|
44
100
|
if (keys.length === 0) return { prose: "" };
|
|
45
|
-
|
|
46
|
-
return { prose: JSON.stringify(value) };
|
|
47
|
-
} catch {
|
|
48
|
-
return { prose: String(value) };
|
|
49
|
-
}
|
|
101
|
+
return { prose: JSON.stringify(value) };
|
|
50
102
|
}
|
|
51
103
|
return { prose: String(value) };
|
|
52
104
|
}
|
|
@@ -69,3 +121,77 @@ export function navigatorProseFromUnknown(value: unknown): string | undefined {
|
|
|
69
121
|
const trimmed = projected.prose.trim();
|
|
70
122
|
return trimmed === "" ? undefined : projected.prose;
|
|
71
123
|
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract parallel-prepare advice. Empty / missing → undefined.
|
|
127
|
+
* Never a rejection — shape is not an admission gate.
|
|
128
|
+
*/
|
|
129
|
+
export function preparedAdviceFromUnknown(value: unknown): PreparedNavigatorAdvice | undefined {
|
|
130
|
+
if (value === undefined || value === null) return undefined;
|
|
131
|
+
if (typeof value === "string") {
|
|
132
|
+
const prose = nonEmptyProse(value);
|
|
133
|
+
return prose === undefined ? undefined : { byStatus: new Map(), prose };
|
|
134
|
+
}
|
|
135
|
+
if (!isRecord(value)) {
|
|
136
|
+
const prose = nonEmptyProse(value);
|
|
137
|
+
return prose === undefined ? undefined : { byStatus: new Map(), prose };
|
|
138
|
+
}
|
|
139
|
+
const byStatus = byStatusFromUnknown(value.byStatus);
|
|
140
|
+
const hasProseField = Object.prototype.hasOwnProperty.call(value, "prose");
|
|
141
|
+
const hasByStatusField = Object.prototype.hasOwnProperty.call(value, "byStatus");
|
|
142
|
+
const prose = nonEmptyProse(value.prose);
|
|
143
|
+
if (byStatus.size === 0 && prose === undefined) {
|
|
144
|
+
// Explicit empty prose / byStatus → no advice (do not re-stringify the shell).
|
|
145
|
+
if (hasProseField || hasByStatusField) return undefined;
|
|
146
|
+
// Free-form object without byStatus/prose: single-advice compat via stringify.
|
|
147
|
+
const keys = Object.keys(value);
|
|
148
|
+
if (keys.length === 0) return undefined;
|
|
149
|
+
const text = JSON.stringify(value);
|
|
150
|
+
return text.trim() === "" || text === "{}" ? undefined : { byStatus: new Map(), prose: text };
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
byStatus,
|
|
154
|
+
...(prose === undefined ? {} : { prose }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Merge status keys; same-status prose concatenates (no silent drop). */
|
|
159
|
+
export function mergePreparedAdvice(
|
|
160
|
+
prior: PreparedNavigatorAdvice | undefined,
|
|
161
|
+
next: PreparedNavigatorAdvice | undefined,
|
|
162
|
+
): PreparedNavigatorAdvice | undefined {
|
|
163
|
+
if (prior === undefined) return next;
|
|
164
|
+
if (next === undefined) return prior;
|
|
165
|
+
const byStatus = new Map(prior.byStatus);
|
|
166
|
+
for (const [key, prose] of next.byStatus) {
|
|
167
|
+
setStatusProse(byStatus, key, prose);
|
|
168
|
+
}
|
|
169
|
+
const proseParts = [prior.prose, next.prose]
|
|
170
|
+
.filter((part): part is string => typeof part === "string" && part.trim() !== "");
|
|
171
|
+
const prose = proseParts.length === 0 ? undefined : proseParts.join("\n\n");
|
|
172
|
+
if (byStatus.size === 0 && prose === undefined) return undefined;
|
|
173
|
+
return {
|
|
174
|
+
byStatus,
|
|
175
|
+
...(prose === undefined ? {} : { prose }),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Pick prepared prose for a settlement status (#1160 / owner 3188b502).
|
|
181
|
+
* - byStatus present → only the matching status key (no prose fallback).
|
|
182
|
+
* - byStatus empty → single prose used as the only advice.
|
|
183
|
+
* Missing match → undefined (honest no-advice).
|
|
184
|
+
*/
|
|
185
|
+
export function pickPreparedProse(
|
|
186
|
+
prepared: PreparedNavigatorAdvice | undefined,
|
|
187
|
+
status: string | undefined,
|
|
188
|
+
): string | undefined {
|
|
189
|
+
if (prepared === undefined) return undefined;
|
|
190
|
+
if (prepared.byStatus.size > 0) {
|
|
191
|
+
if (status === undefined) return undefined;
|
|
192
|
+
const hit = prepared.byStatus.get(status);
|
|
193
|
+
return typeof hit === "string" && hit.trim() !== "" ? hit : undefined;
|
|
194
|
+
}
|
|
195
|
+
const prose = prepared.prose;
|
|
196
|
+
return typeof prose === "string" && prose.trim() !== "" ? prose : undefined;
|
|
197
|
+
}
|
|
@@ -487,6 +487,8 @@ export type PostAdmissionEnv = {
|
|
|
487
487
|
boundTicketNumber?: number;
|
|
488
488
|
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
489
489
|
stationChild?: boolean;
|
|
490
|
+
/** #1160 attendance auto byStatus prepare (nested navigator only). */
|
|
491
|
+
navigatorByStatusPrepare?: boolean;
|
|
490
492
|
};
|
|
491
493
|
|
|
492
494
|
/**
|
|
@@ -1379,6 +1381,9 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1379
1381
|
if (env.stationChild !== undefined) {
|
|
1380
1382
|
turnRequest = { ...turnRequest, stationChild: env.stationChild };
|
|
1381
1383
|
}
|
|
1384
|
+
if (env.navigatorByStatusPrepare === true) {
|
|
1385
|
+
turnRequest = { ...turnRequest, navigatorByStatusPrepare: true };
|
|
1386
|
+
}
|
|
1382
1387
|
// Selected host axis rides the shared Host envelope for in-turn tools
|
|
1383
1388
|
// (detour usage ledger) — never a pre-spawn current.json reread.
|
|
1384
1389
|
if (typeof env.host === "string" && env.host.trim() !== "") {
|
|
@@ -1735,6 +1740,9 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1735
1740
|
prompt,
|
|
1736
1741
|
},
|
|
1737
1742
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
1743
|
+
...(env.navigatorByStatusPrepare === true
|
|
1744
|
+
? { navigatorByStatusPrepare: true as const }
|
|
1745
|
+
: {}),
|
|
1738
1746
|
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
1739
1747
|
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
1740
1748
|
};
|
|
@@ -1755,6 +1763,9 @@ export function roleTurnOptions(
|
|
|
1755
1763
|
: { correlationId }),
|
|
1756
1764
|
continuation,
|
|
1757
1765
|
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
1766
|
+
...(env.navigatorByStatusPrepare === true
|
|
1767
|
+
? { navigatorByStatusPrepare: true as const }
|
|
1768
|
+
: {}),
|
|
1758
1769
|
// #1132: one configured ceiling, already resolved by the caller (#422).
|
|
1759
1770
|
deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
|
|
1760
1771
|
};
|
|
@@ -70,6 +70,8 @@ export type RoleTurnRequestProjectionOptions = {
|
|
|
70
70
|
invocationScopeId?: string;
|
|
71
71
|
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
72
72
|
readonly stationChild?: boolean;
|
|
73
|
+
/** #1160 attendance auto byStatus prepare (not direct navigator prose). */
|
|
74
|
+
readonly navigatorByStatusPrepare?: boolean;
|
|
73
75
|
/**
|
|
74
76
|
* #1132: the one effective delivery-request ceiling (#422 single resolve by
|
|
75
77
|
* the caller). Projected so the host adapter's own re-ask loop and the AK
|
|
@@ -123,6 +125,9 @@ export function projectRoleTurnRequest(
|
|
|
123
125
|
? {}
|
|
124
126
|
: { invocationScopeId: options.invocationScopeId }),
|
|
125
127
|
...(options.stationChild === undefined ? {} : { stationChild: options.stationChild }),
|
|
128
|
+
...(options.navigatorByStatusPrepare === undefined
|
|
129
|
+
? {}
|
|
130
|
+
: { navigatorByStatusPrepare: options.navigatorByStatusPrepare }),
|
|
126
131
|
...(options.deliveryRequestLimit === undefined
|
|
127
132
|
? {}
|
|
128
133
|
: { deliveryRequestLimit: options.deliveryRequestLimit }),
|
|
@@ -121,6 +121,11 @@ export type PublicSummonRequest = {
|
|
|
121
121
|
* (#946 / ADR 0082).
|
|
122
122
|
*/
|
|
123
123
|
readonly stationChild?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* #1160 attendance auto byStatus prepare. Nested navigator only — never set
|
|
126
|
+
* on direct `ak-role navigator` (keeps free-form prose exit).
|
|
127
|
+
*/
|
|
128
|
+
readonly navigatorByStatusPrepare?: boolean;
|
|
124
129
|
};
|
|
125
130
|
|
|
126
131
|
const execFileAsync = promisify(execFile);
|
|
@@ -398,6 +403,9 @@ export async function summonPublicRole(
|
|
|
398
403
|
// Nested court stations keep station-child semantics; dual-lens
|
|
399
404
|
// ordinary Reviewer axes opt out (#946 / ADR 0082).
|
|
400
405
|
...(options.stationChild === false ? {} : { stationChild: true }),
|
|
406
|
+
...(options.navigatorByStatusPrepare === true
|
|
407
|
+
? { navigatorByStatusPrepare: true as const }
|
|
408
|
+
: {}),
|
|
401
409
|
// Forward composition-root adapters so nested stations (e.g.
|
|
402
410
|
// secretariat → diarist) select the same faux/production table (#924).
|
|
403
411
|
...(options.hostAdapters === undefined
|
package/src/role-runtime.ts
CHANGED
|
@@ -1107,9 +1107,10 @@ export function createRoleRuntimeExtension(
|
|
|
1107
1107
|
if (settlement === undefined || attendance === undefined) return;
|
|
1108
1108
|
const workContext = navigatorWorkContext;
|
|
1109
1109
|
const pending = (async () => {
|
|
1110
|
-
// ADR 0052 / #959: every settlement that
|
|
1110
|
+
// ADR 0052 / #959 / #1160: every settlement that waits on parallel prepare
|
|
1111
1111
|
// (accepted, human_decision, role_infrastructure_failure) shares one grace
|
|
1112
1112
|
// and the same honest unavailable projection — no unbounded parallel branch.
|
|
1113
|
+
// Settle picks byStatus; grace is only the tail wait, not a cold model start.
|
|
1113
1114
|
const settlePromise = attendance.settle(settlement);
|
|
1114
1115
|
// Attach catch immediately so a late rejection after grace timeout cannot
|
|
1115
1116
|
// surface as unhandledRejection / stale-ctx after session dispose (#675).
|
|
@@ -1775,7 +1776,7 @@ export function createRoleRuntimeExtension(
|
|
|
1775
1776
|
}
|
|
1776
1777
|
},
|
|
1777
1778
|
});
|
|
1778
|
-
// Concrete work context starts
|
|
1779
|
+
// Concrete work context starts parallel prepare (model runs now; #1160).
|
|
1779
1780
|
// Placeholder subjects wait for before_agent_start (user prompt may replace the subject key).
|
|
1780
1781
|
if (
|
|
1781
1782
|
navigatorWorkContext.contextError === undefined &&
|