@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca

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.
Files changed (109) hide show
  1. package/README.md +129 -39
  2. package/README.zh-CN.md +128 -39
  3. package/dist/activation-ledger-topology.js +1 -1
  4. package/dist/archivist-record-entry.js +47 -26
  5. package/dist/auditor-dossier-tool.js +3 -1
  6. package/dist/compliance-transport.js +17 -26
  7. package/dist/doctor-contracts.js +1 -2
  8. package/dist/dossier-resolution.js +4 -8
  9. package/dist/engine-detour-tool.js +33 -8
  10. package/dist/engine-detour.js +1 -0
  11. package/dist/evidence-child-executor.js +277 -424
  12. package/dist/host-contracts.js +22 -0
  13. package/dist/institutional-resolution.js +88 -0
  14. package/dist/merger-contracts.js +1 -2
  15. package/dist/navigator-attendance.js +16 -0
  16. package/dist/notary-contracts.js +2 -3
  17. package/dist/package-contracts/fixer-output.js +1 -2
  18. package/dist/packaged-role-registry.js +102 -9
  19. package/dist/pi/in-process-session.js +671 -0
  20. package/dist/public-cli/config.js +7 -5
  21. package/dist/public-cli/main.js +3767 -4113
  22. package/dist/reviewer-child-executor.js +6 -1
  23. package/dist/session-opening-materials.js +10 -38
  24. package/dist/sitian-appender.js +146 -0
  25. package/dist/sitian-contracts.js +21 -0
  26. package/dist/sitian-facade.js +15 -0
  27. package/dist/sitian-reader.js +52 -0
  28. package/extensions/role-runtime.ts +9 -7
  29. package/package.json +5 -5
  30. package/resources/engines/cursor.md +12 -26
  31. package/resources/engines/hermes.md +2 -23
  32. package/resources/engines/opus.md +1 -16
  33. package/resources/navigator-route-playbook.md +2 -2
  34. package/scripts/build-package.mjs +0 -1
  35. package/souls/quality-law.md +1 -1
  36. package/src/activation-ledger-topology.ts +1 -1
  37. package/src/archivist-record-entry.ts +57 -45
  38. package/src/auditor-dossier-tool.ts +4 -2
  39. package/src/canonical-skill-binding.ts +37 -27
  40. package/src/collector-role.ts +11 -20
  41. package/src/collector-tool-schemas.ts +1 -4
  42. package/src/compliance-transport.ts +19 -44
  43. package/src/doctor-auditor.ts +1 -5
  44. package/src/doctor-contracts.ts +1 -4
  45. package/src/doctor-role.ts +31 -8
  46. package/src/dossier-resolution.ts +8 -8
  47. package/src/engine-detour-tool.ts +48 -19
  48. package/src/engine-detour.ts +3 -0
  49. package/src/evidence-child-executor.ts +277 -485
  50. package/src/factory-board.ts +3 -3
  51. package/src/gatekeeper-role.ts +21 -10
  52. package/src/host-contracts.ts +386 -0
  53. package/src/in-process-session.ts +1 -115
  54. package/src/institutional-resolution.ts +135 -0
  55. package/src/judge-auditor.ts +1 -5
  56. package/src/judge-role.ts +37 -51
  57. package/src/merger-contracts.ts +1 -4
  58. package/src/merger-role.ts +7 -11
  59. package/src/navigator-attendance.ts +20 -1
  60. package/src/notary-contracts.ts +8 -11
  61. package/src/notary-role.ts +7 -23
  62. package/src/notary-source-run.ts +4 -3
  63. package/src/package-contracts/fixer-output.ts +1 -4
  64. package/src/package-resources/method-skill-binding.ts +10 -34
  65. package/src/packaged-role-registry.ts +122 -12
  66. package/src/pi/adapter.ts +192 -0
  67. package/src/pi/durable-principal.ts +98 -0
  68. package/src/pi/in-process-session.ts +819 -0
  69. package/src/pi/known-failure.ts +77 -0
  70. package/src/pi/pi-normalization.ts +284 -0
  71. package/src/pi/role-turn-host.ts +459 -0
  72. package/src/public-cli/auto-resume.ts +42 -44
  73. package/src/public-cli/cli.ts +171 -257
  74. package/src/public-cli/coder-run.ts +115 -433
  75. package/src/public-cli/collector-run.ts +62 -314
  76. package/src/public-cli/config.ts +7 -5
  77. package/src/public-cli/doctor-run.ts +41 -52
  78. package/src/public-cli/fixer-run.ts +122 -427
  79. package/src/public-cli/invocation.ts +287 -96
  80. package/src/public-cli/judge-run.ts +92 -422
  81. package/src/public-cli/merger-run.ts +167 -429
  82. package/src/public-cli/notary-run.ts +41 -47
  83. package/src/public-cli/option-definitions.ts +136 -3
  84. package/src/public-cli/post-admission.ts +497 -0
  85. package/src/public-cli/public-run-credentials.ts +4 -4
  86. package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
  87. package/src/public-cli/reviewer-run.ts +132 -449
  88. package/src/public-cli/run-lifecycle.ts +339 -327
  89. package/src/public-cli/settlement.ts +251 -134
  90. package/src/public-cli/terminal.ts +3 -9
  91. package/src/public-cli/turn-request.ts +87 -0
  92. package/src/reviewer-child-executor.ts +9 -1
  93. package/src/reviewer-role.ts +11 -20
  94. package/src/role-runtime.ts +129 -78
  95. package/src/session-opening-materials.ts +25 -39
  96. package/src/sitian-appender.ts +171 -0
  97. package/src/sitian-contracts.ts +106 -0
  98. package/src/sitian-facade.ts +18 -0
  99. package/src/sitian-reader.ts +62 -0
  100. package/src/worker-role.ts +42 -40
  101. package/src/worker-submission-gates.ts +33 -0
  102. package/dist/archivist-role-run-coordinates.js +0 -28
  103. package/dist/package-contracts/terminating-infrastructure.js +0 -90
  104. package/resources/engines/sonnet.md +0 -27
  105. package/resources/engines/zcode.md +0 -63
  106. package/src/archivist-role-run-coordinates.ts +0 -45
  107. package/src/package-contracts/terminating-infrastructure.ts +0 -132
  108. package/src/public-cli/explicit-internal.ts +0 -427
  109. package/src/public-cli/one-shot-dispatch.ts +0 -300
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Pi Institutional Sub-Session Open Seam (#518 / #233).
3
+ * Adapts Pi AgentSession / ModelRuntime / ModelRegistry into the host-neutral
4
+ * HostInstitutionalSessionHandle contract.
5
+ * Zero Pi type leakage out of this module.
6
+ */
7
+ import { mkdtemp, rm } from "node:fs/promises";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { createAgentSession, DefaultResourceLoader, ModelRegistry, ModelRuntime, SettingsManager, } from "@earendil-works/pi-coding-agent";
11
+ import { createAssistantMessageEventStream, InMemoryCredentialStore, } from "@earendil-works/pi-ai";
12
+ import { createRecordSession } from "../archivist-record-entry.js";
13
+ import { createStreamIdleGuard, isStreamIdleTimeoutError, StreamIdleTimeoutError, } from "../stream-idle-guard.js";
14
+ import { hasUpstreamErrorTestimony, isNonSuccessHttpStatus, projectConfirmedRemotePayload, } from "../upstream-error-testimony.js";
15
+ export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
16
+ /** Recover a typed idle timeout from a thrown error or an error-stop message. */
17
+ function streamIdleTimeoutFromUnknown(value) {
18
+ if (isStreamIdleTimeoutError(value))
19
+ return value;
20
+ const message = value instanceof Error ? value.message : typeof value === "string" ? value : undefined;
21
+ if (message === undefined)
22
+ return undefined;
23
+ const match = /stream idle timeout after (\d+)ms/i.exec(message);
24
+ return match !== null && match[1] !== undefined
25
+ ? new StreamIdleTimeoutError(Number(match[1]))
26
+ : undefined;
27
+ }
28
+ function resolveSessionManager(options) {
29
+ if (options.sessionManager !== undefined)
30
+ return options.sessionManager;
31
+ return createRecordSession({
32
+ cwd: options.cwd,
33
+ kind: options.kind,
34
+ ...(options.subject === undefined ? {} : { subject: options.subject }),
35
+ ...(options.parent === undefined ? {} : { parent: options.parent }),
36
+ });
37
+ }
38
+ export async function openInProcessAgentSession(options) {
39
+ const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
40
+ const sessionManager = resolveSessionManager(options);
41
+ const createArgs = {
42
+ cwd: options.cwd,
43
+ model: options.model,
44
+ thinkingLevel: options.thinkingLevel ?? "off",
45
+ modelRuntime: options.modelRuntime,
46
+ sessionManager,
47
+ settingsManager: settings,
48
+ ...(options.noTools === undefined ? {} : { noTools: options.noTools }),
49
+ ...(options.tools === undefined ? {} : { tools: options.tools }),
50
+ ...(options.customTools === undefined ? {} : { customTools: options.customTools }),
51
+ };
52
+ if (options.agentDir !== undefined) {
53
+ const loader = new DefaultResourceLoader({
54
+ cwd: options.cwd,
55
+ agentDir: options.agentDir,
56
+ settingsManager: settings,
57
+ noExtensions: true,
58
+ noSkills: true,
59
+ noPromptTemplates: true,
60
+ noThemes: true,
61
+ noContextFiles: true,
62
+ ...(options.systemPrompt === undefined ? {} : { systemPrompt: options.systemPrompt }),
63
+ });
64
+ await loader.reload();
65
+ createArgs.agentDir = options.agentDir;
66
+ createArgs.resourceLoader = loader;
67
+ }
68
+ else if (options.systemPrompt !== undefined) {
69
+ throw new Error("openInProcessAgentSession requires agentDir when systemPrompt is set");
70
+ }
71
+ const { session } = await createAgentSession(createArgs);
72
+ return {
73
+ session,
74
+ dispose() {
75
+ session.dispose();
76
+ },
77
+ };
78
+ }
79
+ // ── Stream / remote error projection helpers ───────────────────────────────
80
+ function emptyUsage() {
81
+ return {
82
+ input: 0,
83
+ output: 0,
84
+ cacheRead: 0,
85
+ cacheWrite: 0,
86
+ totalTokens: 0,
87
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
88
+ };
89
+ }
90
+ function addUsage(total, next) {
91
+ if (!next)
92
+ return;
93
+ total.input += next.input;
94
+ total.output += next.output;
95
+ total.cacheRead += next.cacheRead;
96
+ total.cacheWrite += next.cacheWrite;
97
+ total.totalTokens += next.totalTokens;
98
+ total.cost.input += next.cost?.input ?? 0;
99
+ total.cost.output += next.cost?.output ?? 0;
100
+ total.cost.cacheRead += next.cost?.cacheRead ?? 0;
101
+ total.cost.cacheWrite += next.cost?.cacheWrite ?? 0;
102
+ total.cost.total += next.cost?.total ?? 0;
103
+ }
104
+ function numericHttpStatus(value) {
105
+ return isNonSuccessHttpStatus(value) ? value : undefined;
106
+ }
107
+ function projectStructuredRemote(error) {
108
+ let httpStatus;
109
+ let diagnostics;
110
+ let body;
111
+ let code;
112
+ let errno;
113
+ let cursor = error;
114
+ const seen = new Set();
115
+ while (typeof cursor === "object" && cursor !== null && !seen.has(cursor)) {
116
+ seen.add(cursor);
117
+ const record = cursor;
118
+ const nodeStatus = numericHttpStatus(record.statusCode)
119
+ ?? numericHttpStatus(record.status)
120
+ ?? numericHttpStatus(record.httpStatus);
121
+ const nodeDiagnostics = Array.isArray(record.diagnostics) && record.diagnostics.length > 0
122
+ ? record.diagnostics
123
+ : undefined;
124
+ const nodeHasTestimony = hasUpstreamErrorTestimony({
125
+ ...(nodeStatus === undefined ? {} : { httpStatus: nodeStatus }),
126
+ ...(nodeDiagnostics === undefined ? {} : { diagnostics: nodeDiagnostics }),
127
+ });
128
+ if (httpStatus === undefined && nodeStatus !== undefined)
129
+ httpStatus = nodeStatus;
130
+ if (diagnostics === undefined && nodeDiagnostics !== undefined)
131
+ diagnostics = nodeDiagnostics;
132
+ if (nodeHasTestimony) {
133
+ const payload = projectConfirmedRemotePayload(record);
134
+ if (body === undefined && payload.body !== undefined)
135
+ body = payload.body;
136
+ if (code === undefined && payload.code !== undefined)
137
+ code = payload.code;
138
+ if (errno === undefined && payload.errno !== undefined)
139
+ errno = payload.errno;
140
+ }
141
+ cursor = record.cause;
142
+ }
143
+ return {
144
+ hasTestimony: hasUpstreamErrorTestimony({
145
+ ...(httpStatus === undefined ? {} : { httpStatus }),
146
+ ...(diagnostics === undefined ? {} : { diagnostics }),
147
+ }),
148
+ ...(httpStatus === undefined ? {} : { httpStatus }),
149
+ ...(diagnostics === undefined ? {} : { diagnostics }),
150
+ ...(body === undefined ? {} : { body }),
151
+ ...(code === undefined ? {} : { code }),
152
+ ...(errno === undefined ? {} : { errno }),
153
+ };
154
+ }
155
+ function attachObservedHttpStatus(message, observedHttpStatus) {
156
+ if (observedHttpStatus === undefined)
157
+ return message;
158
+ if (message.stopReason !== "error" && message.stopReason !== "aborted")
159
+ return message;
160
+ if (numericHttpStatus(observedHttpStatus) === undefined)
161
+ return message;
162
+ if (projectStructuredRemote(message).httpStatus !== undefined)
163
+ return message;
164
+ return Object.assign(message, {
165
+ status: observedHttpStatus,
166
+ statusCode: observedHttpStatus,
167
+ });
168
+ }
169
+ function enrichStreamEvent(event, observedHttpStatus) {
170
+ if (observedHttpStatus === undefined || event === null || typeof event !== "object")
171
+ return event;
172
+ const record = event;
173
+ if (record.type === "error" && record.error !== null && typeof record.error === "object") {
174
+ return {
175
+ ...record,
176
+ error: attachObservedHttpStatus(record.error, observedHttpStatus),
177
+ };
178
+ }
179
+ if (record.type === "done" && record.message !== null && typeof record.message === "object") {
180
+ return {
181
+ ...record,
182
+ message: attachObservedHttpStatus(record.message, observedHttpStatus),
183
+ };
184
+ }
185
+ if (record.partial !== null && typeof record.partial === "object") {
186
+ return {
187
+ ...record,
188
+ partial: attachObservedHttpStatus(record.partial, observedHttpStatus),
189
+ };
190
+ }
191
+ return event;
192
+ }
193
+ export async function openPiInstitutionalSession(options) {
194
+ const label = options.label ?? "Institutional sub-session";
195
+ const selection = options.selection;
196
+ // 1. Scratch management
197
+ let scratchDir;
198
+ let resolvedAgentDir = options.agentDir;
199
+ if (resolvedAgentDir === undefined) {
200
+ scratchDir = await mkdtemp(join(options.credentialScratchParent ?? tmpdir(), "ak-institutional-"));
201
+ resolvedAgentDir = scratchDir;
202
+ }
203
+ try {
204
+ // 2. Auth resolution in Pi layer via explicit selection (Hop 3)
205
+ // spec-2: adapter creates its OWN child-local ModelRuntime and ModelRegistry.
206
+ // Auth is resolved strictly by explicit selection on the child registry — never
207
+ // ambiently inherited from the parent ExtensionContext/modelRegistry.
208
+ const childRuntime = await ModelRuntime.create();
209
+ const childRegistry = new ModelRegistry(childRuntime);
210
+ const childProvider = typeof childRegistry.getProvider === "function"
211
+ ? childRegistry.getProvider(selection.provider)
212
+ : undefined;
213
+ const foundModel = typeof childRegistry.find === "function"
214
+ ? childRegistry.find(selection.provider, selection.model)
215
+ : undefined;
216
+ const providerDefaultModel = childProvider?.getModels?.()[0];
217
+ const fallbackApi = providerDefaultModel?.api
218
+ ?? childProvider?.api
219
+ ?? (selection.provider === "openai-codex" ? "openai-codex-responses" : "openai-completions");
220
+ const modelToUse = foundModel ?? {
221
+ id: selection.model,
222
+ name: selection.model,
223
+ api: fallbackApi,
224
+ provider: selection.provider,
225
+ baseUrl: providerDefaultModel?.baseUrl ?? "",
226
+ reasoning: selection.thinking !== undefined && selection.thinking !== "off",
227
+ input: ["text"],
228
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
229
+ contextWindow: 128000,
230
+ maxTokens: 16384,
231
+ };
232
+ let resolution;
233
+ if (typeof childRegistry.getProviderAuth === "function") {
234
+ resolution = await childRegistry.getProviderAuth(selection.provider).catch((error) => {
235
+ throw new Error(`${label} authentication failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
236
+ });
237
+ if (resolution === undefined) {
238
+ throw new Error(`${label} authentication failed: provider is not configured: ${selection.provider}`);
239
+ }
240
+ }
241
+ let authResult;
242
+ if (typeof childRegistry.getApiKeyAndHeaders === "function") {
243
+ authResult = await childRegistry.getApiKeyAndHeaders(modelToUse);
244
+ if (authResult && !authResult.ok) {
245
+ throw new Error(`${label} authentication failed: ${authResult.error}`);
246
+ }
247
+ }
248
+ const resolvedApiKey = authResult?.apiKey ?? resolution?.auth?.apiKey;
249
+ const resolvedHeaders = authResult?.headers ?? resolution?.auth?.headers;
250
+ const resolvedEnv = authResult?.env ?? resolution?.env;
251
+ const effectiveBaseUrl = resolution?.auth?.baseUrl ?? modelToUse.baseUrl;
252
+ const effectiveModel = {
253
+ ...modelToUse,
254
+ baseUrl: effectiveBaseUrl,
255
+ };
256
+ // 3. Standalone ModelRuntime + Provider with idle-only retry.
257
+ // spec-2: the adapter constructs its OWN runtime/provider registration.
258
+ // Auth is resolved only by explicit selection above and seeded into the
259
+ // child runtime's own credential store — never inherited ambiently from
260
+ // the parent ExtensionContext/Provider.
261
+ const credentials = new InMemoryCredentialStore();
262
+ const runtime = await ModelRuntime.create({
263
+ credentials,
264
+ modelsPath: null,
265
+ });
266
+ // Seed the child runtime's own credential store with the explicitly
267
+ // resolved auth so stream auth resolution is self-contained. No ambient
268
+ // fallback: absence of a resolved apiKey is not silently supplied.
269
+ if (resolvedApiKey !== undefined) {
270
+ await credentials.modify(selection.provider, async () => ({
271
+ type: "api_key",
272
+ key: resolvedApiKey,
273
+ ...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
274
+ }));
275
+ }
276
+ const abortReason = (signal) => signal.reason ?? new Error(`${label} provider stream aborted`);
277
+ let streamFailureValue;
278
+ async function waitForStream(promise, signal) {
279
+ if (signal.aborted)
280
+ throw abortReason(signal);
281
+ let onAbort;
282
+ try {
283
+ return await Promise.race([
284
+ promise,
285
+ new Promise((_resolve, reject) => {
286
+ onAbort = () => reject(abortReason(signal));
287
+ signal.addEventListener("abort", onAbort, { once: true });
288
+ }),
289
+ ]);
290
+ }
291
+ finally {
292
+ if (onAbort !== undefined)
293
+ signal.removeEventListener("abort", onAbort);
294
+ }
295
+ }
296
+ const createRetriedStream = (simple, model, context, request) => {
297
+ const wrapped = createAssistantMessageEventStream();
298
+ void (async () => {
299
+ for (let attempt = 0;; attempt += 1) {
300
+ const idle = createStreamIdleGuard(options.signal === undefined ? {} : { parentSignal: options.signal });
301
+ let observedHttpStatus;
302
+ try {
303
+ const requestSignal = request?.signal;
304
+ const streamSignal = requestSignal === undefined
305
+ ? idle.signal
306
+ : AbortSignal.any([idle.signal, requestSignal]);
307
+ const priorOnResponse = request?.onResponse;
308
+ const retriedRequest = {
309
+ ...(request ?? {}),
310
+ ...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
311
+ signal: streamSignal,
312
+ maxRetries: 0,
313
+ onResponse: async (response, resModel) => {
314
+ if (typeof response?.status === "number")
315
+ observedHttpStatus = response.status;
316
+ await priorOnResponse?.(response, resModel);
317
+ },
318
+ };
319
+ if (childProvider === undefined) {
320
+ throw new Error(`${label} provider not found: ${model.provider}`);
321
+ }
322
+ const source = simple
323
+ ? childProvider.streamSimple(model, context, retriedRequest)
324
+ : childProvider.stream(model, context, retriedRequest);
325
+ let sawEvent = false;
326
+ const attemptEvents = [];
327
+ const iterator = source[Symbol.asyncIterator]();
328
+ while (true) {
329
+ const next = await waitForStream(iterator.next(), idle.signal);
330
+ if (next.done)
331
+ break;
332
+ sawEvent = true;
333
+ idle.poke();
334
+ attemptEvents.push(enrichStreamEvent(next.value, observedHttpStatus));
335
+ }
336
+ const response = attachObservedHttpStatus(await waitForStream(source.result(), idle.signal), observedHttpStatus);
337
+ // Some stream APIs (e.g. OpenAI-completions over an HTTP error)
338
+ // surface the failure as an error-stop message rather than throwing.
339
+ // Hold that primary provider failure at the adapter boundary so
340
+ // consumers surface a real transport failure, never a projected
341
+ // step-machine "error" response (ADR 0018 / #518 §3).
342
+ if (response.stopReason === "error") {
343
+ const errorMessage = response.errorMessage ?? `${label} provider stream failed`;
344
+ const idleFailure = streamIdleTimeoutFromUnknown(errorMessage);
345
+ if (options.idleRetry !== false
346
+ && idleFailure !== undefined
347
+ && attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
348
+ && options.signal?.aborted !== true) {
349
+ continue;
350
+ }
351
+ streamFailureValue = idleFailure ?? new Error(errorMessage, { cause: response });
352
+ }
353
+ for (const ev of attemptEvents) {
354
+ wrapped.push(ev);
355
+ }
356
+ wrapped.end(response);
357
+ return;
358
+ }
359
+ catch (error) {
360
+ if (request?.signal?.aborted) {
361
+ const response = {
362
+ role: "assistant",
363
+ content: [],
364
+ api: model.api,
365
+ provider: model.provider,
366
+ model: model.id,
367
+ usage: emptyUsage(),
368
+ stopReason: "aborted",
369
+ errorMessage: `${label} session aborted`,
370
+ timestamp: Date.now(),
371
+ };
372
+ wrapped.push({ type: "error", reason: "aborted", error: response });
373
+ wrapped.end(response);
374
+ return;
375
+ }
376
+ const failure = isStreamIdleTimeoutError(idle.signal.reason) ? idle.signal.reason : error;
377
+ const idleFailure = streamIdleTimeoutFromUnknown(failure);
378
+ if (options.idleRetry !== false
379
+ && idleFailure !== undefined
380
+ && attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
381
+ && options.signal?.aborted !== true) {
382
+ continue;
383
+ }
384
+ const typedFailure = idleFailure ?? failure;
385
+ // Hold the primary provider failure at the adapter boundary (ADR
386
+ // 0018 / 失败诚实宪法). Consumers that surface transport failures
387
+ // read this so the real cause is never masked by a projected
388
+ // step-machine "error" response.
389
+ streamFailureValue = typedFailure;
390
+ const projected = projectStructuredRemote(failure);
391
+ const httpStatus = projected.httpStatus ?? numericHttpStatus(observedHttpStatus);
392
+ const response = {
393
+ role: "assistant",
394
+ content: [],
395
+ api: model.api,
396
+ provider: model.provider,
397
+ model: model.id,
398
+ usage: emptyUsage(),
399
+ stopReason: "error",
400
+ errorMessage: failure instanceof Error ? failure.message : String(failure),
401
+ timestamp: Date.now(),
402
+ ...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
403
+ ...(httpStatus === undefined ? {} : { status: httpStatus, statusCode: httpStatus }),
404
+ ...(projected.body === undefined ? {} : { body: projected.body }),
405
+ ...(projected.code === undefined ? {} : { code: projected.code }),
406
+ ...(projected.errno === undefined ? {} : { errno: projected.errno }),
407
+ };
408
+ wrapped.push({ type: "error", reason: "error", error: response });
409
+ wrapped.end(response);
410
+ return;
411
+ }
412
+ finally {
413
+ idle.dispose();
414
+ }
415
+ }
416
+ })();
417
+ return wrapped;
418
+ };
419
+ const provider = {
420
+ id: selection.provider,
421
+ name: childProvider?.name ?? label,
422
+ ...(effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}),
423
+ ...(resolvedHeaders ? { headers: resolvedHeaders } : {}),
424
+ auth: {
425
+ apiKey: {
426
+ name: `${label} authentication`,
427
+ async resolve() {
428
+ return {
429
+ auth: {
430
+ ...(resolvedApiKey === undefined ? {} : { apiKey: resolvedApiKey }),
431
+ ...(resolvedHeaders === undefined ? {} : { headers: resolvedHeaders }),
432
+ ...(effectiveBaseUrl === undefined ? {} : { baseUrl: effectiveBaseUrl }),
433
+ },
434
+ ...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
435
+ };
436
+ },
437
+ },
438
+ },
439
+ getModels() { return [effectiveModel]; },
440
+ stream(model, childContext, request) {
441
+ return createRetriedStream(false, model, childContext, request);
442
+ },
443
+ streamSimple(model, childContext, request) {
444
+ return createRetriedStream(true, model, childContext, request);
445
+ },
446
+ };
447
+ runtime.registerNativeProvider(provider);
448
+ await runtime.refresh({ allowNetwork: false });
449
+ // 4. Settings and ResourceLoader (fixed adapter policies: no extensions/skills/themes/templates/context-files)
450
+ const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
451
+ const loader = new DefaultResourceLoader({
452
+ cwd: options.cwd,
453
+ agentDir: resolvedAgentDir,
454
+ settingsManager: settings,
455
+ noExtensions: true,
456
+ noSkills: true,
457
+ noPromptTemplates: true,
458
+ noThemes: true,
459
+ noContextFiles: true,
460
+ systemPrompt: options.systemPrompt,
461
+ });
462
+ await loader.reload();
463
+ // 5. SessionManager resolution
464
+ let sessionManager;
465
+ if (options.sessionManager !== undefined) {
466
+ sessionManager = options.sessionManager;
467
+ }
468
+ else if (options.sessionIdentity !== undefined) {
469
+ sessionManager = createRecordSession({
470
+ cwd: options.cwd,
471
+ kind: options.sessionIdentity.kind,
472
+ ...(options.sessionIdentity.subject === undefined ? {} : { subject: options.sessionIdentity.subject }),
473
+ ...(options.sessionIdentity.parent === undefined ? {} : { parent: options.sessionIdentity.parent }),
474
+ });
475
+ }
476
+ else {
477
+ sessionManager = createRecordSession({
478
+ cwd: options.cwd,
479
+ kind: "institutional",
480
+ });
481
+ }
482
+ // 6. Tools mapping
483
+ const customTools = [];
484
+ if (options.customTools !== undefined) {
485
+ customTools.push(...options.customTools);
486
+ }
487
+ if (options.tools !== undefined) {
488
+ for (const hostTool of options.tools) {
489
+ customTools.push({
490
+ name: hostTool.name,
491
+ label: hostTool.label,
492
+ description: hostTool.description,
493
+ parameters: hostTool.parameters,
494
+ execute: async (toolCallId, params, signal, update, _ctx) => {
495
+ const res = await hostTool.execute(toolCallId, params, signal, update === undefined ? undefined : (u) => update(u), undefined);
496
+ return res;
497
+ },
498
+ });
499
+ }
500
+ }
501
+ // 7. Create AgentSession
502
+ const { session } = await createAgentSession({
503
+ cwd: options.cwd,
504
+ model: effectiveModel,
505
+ thinkingLevel: (options.selection.thinking ?? "off"),
506
+ modelRuntime: runtime,
507
+ sessionManager,
508
+ settingsManager: settings,
509
+ agentDir: resolvedAgentDir,
510
+ resourceLoader: loader,
511
+ ...(options.noTools === undefined ? {} : { noTools: options.noTools }),
512
+ ...(options.toolsAllowlist === undefined ? {} : { tools: options.toolsAllowlist }),
513
+ ...(customTools.length === 0 ? {} : { customTools }),
514
+ });
515
+ // 8. Event subscriptions
516
+ const listeners = new Set();
517
+ const accumulatedUsage = emptyUsage();
518
+ let lastEmittedAssistant;
519
+ const unsubscribeSession = session.subscribe((event) => {
520
+ if (event.type === "message_end") {
521
+ const msg = event.message;
522
+ if (msg.role === "assistant") {
523
+ lastEmittedAssistant = msg;
524
+ if (msg.usage) {
525
+ addUsage(accumulatedUsage, msg.usage);
526
+ }
527
+ }
528
+ for (const listener of listeners) {
529
+ listener({
530
+ type: "message_end",
531
+ role: msg.role,
532
+ message: msg,
533
+ ...(msg.usage === undefined ? {} : { usage: msg.usage }),
534
+ });
535
+ }
536
+ }
537
+ else if (event.type === "turn_end") {
538
+ const msg = event.message;
539
+ for (const listener of listeners) {
540
+ listener({
541
+ type: "turn_end",
542
+ ...(msg.stopReason === undefined ? {} : { stopReason: msg.stopReason }),
543
+ });
544
+ }
545
+ }
546
+ else if (event.type === "tool_execution_start") {
547
+ for (const listener of listeners) {
548
+ listener({
549
+ type: "tool_call",
550
+ toolCallId: event.toolCallId,
551
+ toolName: event.toolName,
552
+ args: event.args ?? event.input,
553
+ });
554
+ }
555
+ }
556
+ else if (event.type === "tool_execution_end") {
557
+ for (const listener of listeners) {
558
+ listener({
559
+ type: "tool_result",
560
+ toolCallId: event.toolCallId,
561
+ toolName: event.toolName,
562
+ isError: event.isError,
563
+ details: event.result ?? event.details,
564
+ });
565
+ }
566
+ }
567
+ });
568
+ let closed = false;
569
+ const sessionFile = sessionManager.getSessionFile();
570
+ const sessionId = sessionManager.getHeader?.()?.id;
571
+ const handle = {
572
+ ...(sessionFile === undefined ? {} : { sessionFile }),
573
+ ...(sessionId === undefined ? {} : { sessionId }),
574
+ async prompt(text) {
575
+ const abortSession = () => { void session.abort(); };
576
+ if (options.signal?.aborted)
577
+ abortSession();
578
+ else
579
+ options.signal?.addEventListener("abort", abortSession, { once: true });
580
+ let promptError;
581
+ try {
582
+ await session.prompt(text);
583
+ }
584
+ catch (error) {
585
+ promptError = error;
586
+ }
587
+ finally {
588
+ options.signal?.removeEventListener("abort", abortSession);
589
+ }
590
+ const lastAssistant = [...session.messages]
591
+ .reverse()
592
+ .find((message) => message.role === "assistant");
593
+ if (lastAssistant !== undefined && lastAssistant !== lastEmittedAssistant) {
594
+ lastEmittedAssistant = lastAssistant;
595
+ for (const listener of listeners) {
596
+ listener({
597
+ type: "message_end",
598
+ role: lastAssistant.role,
599
+ message: lastAssistant,
600
+ ...(lastAssistant.usage === undefined ? {} : { usage: lastAssistant.usage }),
601
+ });
602
+ }
603
+ }
604
+ if (promptError !== undefined && lastAssistant === undefined) {
605
+ throw promptError;
606
+ }
607
+ return {
608
+ text: session.getLastAssistantText() ?? "",
609
+ ...(lastAssistant?.stopReason === undefined ? {} : { stopReason: lastAssistant.stopReason }),
610
+ ...(lastAssistant?.errorMessage === undefined ? {} : { errorMessage: lastAssistant.errorMessage }),
611
+ usage: accumulatedUsage,
612
+ messages: session.messages,
613
+ };
614
+ },
615
+ subscribe(listener) {
616
+ listeners.add(listener);
617
+ return () => {
618
+ listeners.delete(listener);
619
+ };
620
+ },
621
+ abort() {
622
+ void session.abort();
623
+ },
624
+ async close() {
625
+ if (closed)
626
+ return;
627
+ closed = true;
628
+ listeners.clear();
629
+ try {
630
+ unsubscribeSession();
631
+ }
632
+ finally {
633
+ // spec-3: handle.close must always dispose the child session even when
634
+ // unsubscribing throws (the throwing unsubscribe is a cleanup failure the
635
+ // caller aggregates, never a reason to skip session teardown).
636
+ session.dispose();
637
+ }
638
+ if (scratchDir !== undefined) {
639
+ try {
640
+ await rm(scratchDir, { recursive: true, force: true });
641
+ }
642
+ catch (error) {
643
+ throw error;
644
+ }
645
+ }
646
+ },
647
+ };
648
+ return {
649
+ handle,
650
+ // Read lazily: the provider stream runs during session.prompt, so the
651
+ // primary failure is only known after that turn completes. A getter keeps
652
+ // this reflecting the latest value instead of freezing it at open time.
653
+ get streamFailure() {
654
+ return streamFailureValue;
655
+ },
656
+ };
657
+ }
658
+ catch (openError) {
659
+ if (scratchDir !== undefined) {
660
+ try {
661
+ await rm(scratchDir, { recursive: true, force: true });
662
+ }
663
+ catch (cleanupFailure) {
664
+ // ADR 0018 / #518 §1④: primary failure and cleanup failure stay
665
+ // separated — cleanup must not mask the original open cause.
666
+ throw new AggregateError([openError, cleanupFailure], `${label} open failed and its scratch cleanup also failed`, { cause: openError });
667
+ }
668
+ }
669
+ throw openError;
670
+ }
671
+ }