@akagilnc/pi-workflow-roles 0.1.4243 → 0.1.4274

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.
@@ -189,27 +189,55 @@ function hostSelectionFailureFromUnknown(error: unknown): HostSelectionFailure |
189
189
  return failure;
190
190
  }
191
191
 
192
- async function createSummonEnv(options: {
193
- readonly role: PublicCallableRole;
192
+ type SummonEnvOk = {
194
193
  readonly home: string;
194
+ readonly principalAuthority: import("./host-contracts.ts").DurablePrincipalAuthority;
195
195
  readonly agentDir: string;
196
- readonly cwd: string;
196
+ readonly sessionAppender: typeof import("./pi/role-turn-host.ts").appendPiSessionCustomEntry;
197
197
  readonly packageRoot: string;
198
+ readonly roleTurnHost: RoleTurnHost;
199
+ readonly cwd: string;
198
200
  readonly credentials: CredentialProviders;
199
- readonly seat: EffectiveSeat;
200
- readonly extraPiArgs?: readonly string[];
201
- readonly roleTurnHost?: RoleTurnHost;
202
- readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
203
- }) {
204
- const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry }, { resolveRoleTurnHost }] =
205
- await Promise.all([
206
- import("./pi/durable-principal.ts"),
207
- import("./pi/role-turn-host.ts"),
208
- import("./public-cli/role-turn-host-resolution.ts"),
209
- ]);
201
+ readonly model?: import("./host-contracts.ts").HostInstitutionalModelSelection;
202
+ readonly engine?: string;
203
+ readonly engineModel?: string;
204
+ readonly host?: string;
205
+ };
206
+
207
+ /** #178: missing model is ok:false (typed fact), not a thrown message. */
208
+ type SummonEnvBuild =
209
+ | { readonly ok: true; readonly env: SummonEnvOk }
210
+ | { readonly ok: false; readonly missingModelMessage: string };
211
+
212
+ async function createSummonEnv(
213
+ options: {
214
+ readonly role: PublicCallableRole;
215
+ readonly home: string;
216
+ readonly agentDir: string;
217
+ readonly cwd: string;
218
+ readonly packageRoot: string;
219
+ readonly credentials: CredentialProviders;
220
+ readonly seat: EffectiveSeat;
221
+ readonly extraPiArgs?: readonly string[];
222
+ readonly roleTurnHost?: RoleTurnHost;
223
+ readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
224
+ },
225
+ /** #178: after host selection, before missing-model — structural argv parse once. */
226
+ afterHost?: () => void,
227
+ ): Promise<SummonEnvBuild> {
228
+ const [
229
+ { piDurablePrincipalAuthority },
230
+ { appendPiSessionCustomEntry },
231
+ { resolveRoleTurnHost },
232
+ { missingResolvedSeatModelMessage, resolvedSeatWithModel },
233
+ ] = await Promise.all([
234
+ import("./pi/durable-principal.ts"),
235
+ import("./pi/role-turn-host.ts"),
236
+ import("./public-cli/role-turn-host-resolution.ts"),
237
+ import("./public-cli/config.ts"),
238
+ ]);
210
239
  const principalAuthority = piDurablePrincipalAuthority;
211
- // Same host axis table as public CLI (#617 DK-3 / #675 / #840): child seat
212
- // selects; injected roleTurnHost is the pi adapter only.
240
+ // Host first → argv (afterHost) → missing-model → provider projection (#617/#178/#840).
213
241
  const roleTurnHost = resolveRoleTurnHost(
214
242
  {
215
243
  packageRoot: options.packageRoot,
@@ -221,32 +249,39 @@ async function createSummonEnv(options: {
221
249
  },
222
250
  { role: options.role, seat: options.seat, principalAuthority },
223
251
  );
224
- const hostName = options.seat.host ?? "pi";
225
- // #788: host is registered above; only then project host-facing provider.
252
+ afterHost?.();
253
+ const seatWithModel = resolvedSeatWithModel(options.seat);
254
+ if (seatWithModel === undefined) {
255
+ return {
256
+ ok: false,
257
+ missingModelMessage: missingResolvedSeatModelMessage(options.role),
258
+ };
259
+ }
260
+ const hostName = seatWithModel.host ?? "pi";
226
261
  const { loadHostProvidersTable, projectHostFacingProvider } = await import(
227
262
  "./public-cli/host-providers.ts"
228
263
  );
229
- const hostFacingSelection =
230
- options.seat.selection === undefined
231
- ? undefined
232
- : projectHostFacingProvider(
233
- options.seat.selection,
234
- hostName,
235
- loadHostProvidersTable(options.home),
236
- options.home,
237
- );
264
+ const hostFacingSelection = projectHostFacingProvider(
265
+ seatWithModel.selection,
266
+ hostName,
267
+ loadHostProvidersTable(options.home),
268
+ options.home,
269
+ );
238
270
  return {
239
- home: options.home,
240
- principalAuthority,
241
- agentDir: options.agentDir,
242
- sessionAppender: appendPiSessionCustomEntry,
243
- packageRoot: options.packageRoot,
244
- roleTurnHost,
245
- cwd: options.cwd,
246
- credentials: options.credentials,
247
- ...(hostFacingSelection === undefined ? {} : { model: hostFacingSelection }),
248
- ...projectSeatEngine(options.seat),
249
- ...projectSeatHost(options.seat),
271
+ ok: true,
272
+ env: {
273
+ home: options.home,
274
+ principalAuthority,
275
+ agentDir: options.agentDir,
276
+ sessionAppender: appendPiSessionCustomEntry,
277
+ packageRoot: options.packageRoot,
278
+ roleTurnHost,
279
+ cwd: options.cwd,
280
+ credentials: options.credentials,
281
+ ...(hostFacingSelection === undefined ? {} : { model: hostFacingSelection }),
282
+ ...projectSeatEngine(seatWithModel),
283
+ ...projectSeatHost(seatWithModel),
284
+ },
250
285
  };
251
286
  }
252
287
 
@@ -271,56 +306,100 @@ export async function summonPublicRole(
271
306
  const credentials =
272
307
  options.credentials ?? (await loadCredentialProviders(agentDir));
273
308
  const config = await loadPublicCliConfig(home);
274
- // Nested summons resolve host on the officer seat only (flag>seat>default pi).
275
- // Parent run host is not an override channel (#821 / ADR 0082: --host 旗标>席位配置>缺省 pi).
309
+ // Nested summons: officer seat only (flag>seat>default pi). #178 order below.
276
310
  const seat = resolveEffectiveSeat(config, options.role, credentials);
277
- let summonEnv;
278
- try {
279
- summonEnv = await createSummonEnv({
280
- role: options.role,
281
- home,
282
- agentDir,
283
- cwd: options.cwd,
284
- packageRoot,
285
- credentials,
286
- seat,
287
- ...(options.extraPiArgs === undefined ? {} : { extraPiArgs: options.extraPiArgs }),
288
- ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
289
- ...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
290
- });
291
- } catch (error) {
292
- const failure = hostSelectionFailureFromUnknown(error);
293
- if (failure !== undefined) {
294
- const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.ts");
295
- return { exitCode: 1, stderr: formatHostSelectionFailure(failure) };
296
- }
297
- throw error;
298
- }
299
- const env = {
300
- ...summonEnv,
301
- // Station child role run (#840): omit automatic navigator attendance.
302
- stationChild: true,
303
- // Host config passthrough only — same face as public CLI (#422 / #675).
304
- ...(config.autoResumeLimit === undefined
305
- ? {}
306
- : { autoResumeLimit: config.autoResumeLimit }),
307
- // Parent cancellation reaches the nested activation's own turn dispatch.
308
- ...(options.signal === undefined ? {} : { signal: options.signal }),
309
- // #753: reask rides the existing notary same-ticket resume summons.instruction.
310
- ...(options.reviewReask === undefined ? {} : { reviewReask: options.reviewReask }),
311
- // #879: verbatim submission body on officer dialogue content channel.
312
- ...(options.gateReviewInstruction === undefined
313
- ? {}
314
- : { gateReviewInstruction: options.gateReviewInstruction }),
315
- ...(options.boundTicketNumber === undefined
316
- ? {}
317
- : { boundTicketNumber: options.boundTicketNumber }),
318
- // createRunId is the only remaining env overlay — host is seat-selected above.
319
- ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
320
- };
321
311
  const captured = options.io === undefined ? createCapturingIo() : undefined;
322
312
  const io = options.io ?? captured!.io;
323
313
 
314
+ const summonEnvInput = {
315
+ role: options.role,
316
+ home,
317
+ agentDir,
318
+ cwd: options.cwd,
319
+ packageRoot,
320
+ credentials,
321
+ seat,
322
+ ...(options.extraPiArgs === undefined ? {} : { extraPiArgs: options.extraPiArgs }),
323
+ ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
324
+ ...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
325
+ } as const;
326
+
327
+ type Prepared =
328
+ | { readonly ok: true; readonly env: Record<string, unknown> }
329
+ | { readonly ok: false; readonly exitCode: number; readonly stderr?: string };
330
+
331
+ async function prepareSummonEnv(afterHost: () => void): Promise<Prepared> {
332
+ try {
333
+ const built = await createSummonEnv(summonEnvInput, afterHost);
334
+ if (!built.ok) {
335
+ return { ok: false, exitCode: 1, stderr: built.missingModelMessage };
336
+ }
337
+ return {
338
+ ok: true,
339
+ env: {
340
+ ...built.env,
341
+ stationChild: true,
342
+ ...(config.autoResumeLimit === undefined
343
+ ? {}
344
+ : { autoResumeLimit: config.autoResumeLimit }),
345
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
346
+ ...(options.reviewReask === undefined ? {} : { reviewReask: options.reviewReask }),
347
+ ...(options.gateReviewInstruction === undefined
348
+ ? {}
349
+ : { gateReviewInstruction: options.gateReviewInstruction }),
350
+ ...(options.boundTicketNumber === undefined
351
+ ? {}
352
+ : { boundTicketNumber: options.boundTicketNumber }),
353
+ ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
354
+ },
355
+ };
356
+ } catch (error) {
357
+ const failure = hostSelectionFailureFromUnknown(error);
358
+ if (failure !== undefined) {
359
+ const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.ts");
360
+ return { ok: false, exitCode: 1, stderr: formatHostSelectionFailure(failure) };
361
+ }
362
+ const { CliUsageError } = await import("./public-cli/cli-errors.ts");
363
+ if (error instanceof CliUsageError) {
364
+ const { presentStructuralRejection } = await import("./public-cli/settlement.ts");
365
+ presentStructuralRejection(error, io);
366
+ return { ok: false, exitCode: 2 };
367
+ }
368
+ throw error;
369
+ }
370
+ }
371
+
372
+ function projectPrepareFailure(prepared: Extract<Prepared, { ok: false }>): PublicSummonResult {
373
+ const parts = [prepared.stderr, captured?.stderrText()].filter(
374
+ (s): s is string => typeof s === "string" && s !== "",
375
+ );
376
+ const stderr = parts.length === 0 ? undefined : parts.join("");
377
+ return {
378
+ exitCode: prepared.exitCode,
379
+ ...(stderr === undefined ? {} : { stderr }),
380
+ };
381
+ }
382
+
383
+ /** Host → argv once → missing-model → runner. */
384
+ async function runPrepared<TParsed>(
385
+ parse: (argv: readonly string[]) => TParsed,
386
+ run: (
387
+ env: Record<string, unknown>,
388
+ parseOnce: () => TParsed,
389
+ ) => Promise<{
390
+ exitCode: number;
391
+ terminal?: TerminalResult;
392
+ admitted?: { readonly runDirectory?: string };
393
+ }>,
394
+ ) {
395
+ let parsedArgv!: TParsed;
396
+ const prepared = await prepareSummonEnv(() => {
397
+ parsedArgv = parse(options.argv);
398
+ });
399
+ if (!prepared.ok) return { fail: projectPrepareFailure(prepared) as PublicSummonResult };
400
+ return { ok: await run(prepared.env, () => parsedArgv) };
401
+ }
402
+
324
403
  let result: {
325
404
  exitCode: number;
326
405
  terminal?: TerminalResult;
@@ -332,7 +411,10 @@ export async function summonPublicRole(
332
411
  import("./public-cli/notary-run.ts"),
333
412
  import("./public-cli/invocation.ts"),
334
413
  ]);
335
- result = await runPublicNotary(options.argv, env, io, parseNotaryArgv);
414
+ const stepped = await runPrepared(parseNotaryArgv, (env, once) =>
415
+ runPublicNotary(options.argv, env as never, io, once));
416
+ if ("fail" in stepped) return stepped.fail;
417
+ result = stepped.ok;
336
418
  break;
337
419
  }
338
420
  case "inspector": {
@@ -340,49 +422,30 @@ export async function summonPublicRole(
340
422
  import("./public-cli/inspector-run.ts"),
341
423
  import("./public-cli/invocation.ts"),
342
424
  ]);
343
- result = await runPublicInspector(options.argv, env, io, parseInspectorArgv);
344
- break;
345
- }
346
- case "auditor": {
347
- const [{ runPublicInstructionSeat }, { parseAuditorArgv }] = await Promise.all([
348
- import("./public-cli/instruction-seat-run.ts"),
349
- import("./public-cli/invocation.ts"),
350
- ]);
351
- result = await runPublicInstructionSeat(
352
- options.argv,
353
- env,
354
- io,
355
- "auditor",
356
- parseAuditorArgv,
357
- );
358
- break;
359
- }
360
- case "navigator": {
361
- const [{ runPublicInstructionSeat }, { parseNavigatorArgv }] = await Promise.all([
362
- import("./public-cli/instruction-seat-run.ts"),
363
- import("./public-cli/invocation.ts"),
364
- ]);
365
- result = await runPublicInstructionSeat(
366
- options.argv,
367
- env,
368
- io,
369
- "navigator",
370
- parseNavigatorArgv,
371
- );
425
+ const stepped = await runPrepared(parseInspectorArgv, (env, once) =>
426
+ runPublicInspector(options.argv, env as never, io, once));
427
+ if ("fail" in stepped) return stepped.fail;
428
+ result = stepped.ok;
372
429
  break;
373
430
  }
431
+ case "auditor":
432
+ case "navigator":
374
433
  case "gatekeeper": {
375
- const [{ runPublicInstructionSeat }, { parseGatekeeperArgv }] = await Promise.all([
434
+ const seat = options.role;
435
+ const [{ runPublicInstructionSeat }, invocation] = await Promise.all([
376
436
  import("./public-cli/instruction-seat-run.ts"),
377
437
  import("./public-cli/invocation.ts"),
378
438
  ]);
379
- result = await runPublicInstructionSeat(
380
- options.argv,
381
- env,
382
- io,
383
- "gatekeeper",
384
- parseGatekeeperArgv,
385
- );
439
+ const parse =
440
+ seat === "auditor"
441
+ ? invocation.parseAuditorArgv
442
+ : seat === "navigator"
443
+ ? invocation.parseNavigatorArgv
444
+ : invocation.parseGatekeeperArgv;
445
+ const stepped = await runPrepared(parse, (env, once) =>
446
+ runPublicInstructionSeat(options.argv, env as never, io, seat, once));
447
+ if ("fail" in stepped) return stepped.fail;
448
+ result = stepped.ok;
386
449
  break;
387
450
  }
388
451
  case "judge": {
@@ -390,7 +453,10 @@ export async function summonPublicRole(
390
453
  import("./public-cli/judge-run.ts"),
391
454
  import("./public-cli/invocation.ts"),
392
455
  ]);
393
- result = await runPublicJudge(options.argv, env, io, parseJudgeArgv);
456
+ const stepped = await runPrepared(parseJudgeArgv, (env, once) =>
457
+ runPublicJudge(options.argv, env as never, io, once));
458
+ if ("fail" in stepped) return stepped.fail;
459
+ result = stepped.ok;
394
460
  break;
395
461
  }
396
462
  case "doctor": {
@@ -398,7 +464,10 @@ export async function summonPublicRole(
398
464
  import("./public-cli/doctor-run.ts"),
399
465
  import("./public-cli/invocation.ts"),
400
466
  ]);
401
- result = await runPublicDoctor(options.argv, env, io, parseDoctorArgv);
467
+ const stepped = await runPrepared(parseDoctorArgv, (env, once) =>
468
+ runPublicDoctor(options.argv, env as never, io, once));
469
+ if ("fail" in stepped) return stepped.fail;
470
+ result = stepped.ok;
402
471
  break;
403
472
  }
404
473
  case "diarist": {
@@ -406,7 +475,10 @@ export async function summonPublicRole(
406
475
  import("./public-cli/diarist-run.ts"),
407
476
  import("./public-cli/invocation.ts"),
408
477
  ]);
409
- result = await runPublicDiarist(options.argv, env, io, parseDiaristArgv);
478
+ const stepped = await runPrepared(parseDiaristArgv, (env, once) =>
479
+ runPublicDiarist(options.argv, env as never, io, once));
480
+ if ("fail" in stepped) return stepped.fail;
481
+ result = stepped.ok;
410
482
  break;
411
483
  }
412
484
  }
@@ -175,6 +175,8 @@ export async function prepareRoleEnvelope(options: {
175
175
  model: request.model === undefined ? undefined : { provider: request.model.provider },
176
176
  runDirectory: request.runDirectory,
177
177
  ...(request.courtAttemptId === undefined ? {} : { courtAttemptId: request.courtAttemptId }),
178
+ ...(request.invocationScopeId === undefined ? {} : { invocationScopeId: request.invocationScopeId }),
179
+ ...(request.host === undefined || request.host.trim() === "" ? {} : { host: request.host.trim() }),
178
180
  sessionManager: {
179
181
  getLeafEntry: () => sessionEntries.at(-1) as ReturnType<HostContext["sessionManager"]["getLeafEntry"]>,
180
182
  getLeafId: () => runId,