@akagilnc/pi-workflow-roles 0.1.1883 → 0.1.1893

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.
@@ -11,6 +11,7 @@ import { AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, AUDITOR_PARENT_ATTEMPT_BINDING_E
11
11
  import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
12
12
  import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
13
13
  import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
14
+ import { hasUpstreamErrorTestimony, isNonSuccessHttpStatus, projectConfirmedRemotePayload, } from "./upstream-error-testimony.js";
14
15
  // ── shared constants / types ──────────────────────────────────────────────
15
16
  export const AUDITOR_TURN_LIMIT = 32;
16
17
  export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
@@ -116,15 +117,23 @@ export async function createInheritedRuntime(options) {
116
117
  void (async () => {
117
118
  for (let attempt = 0;; attempt += 1) {
118
119
  const idle = createStreamIdleGuard(options.signal === undefined ? {} : { parentSignal: options.signal });
120
+ // Typed HTTP status observed via onResponse — never inferred from prose.
121
+ let observedHttpStatus;
119
122
  try {
120
123
  const requestSignal = request?.signal;
121
124
  const streamSignal = requestSignal === undefined
122
125
  ? idle.signal
123
126
  : AbortSignal.any([idle.signal, requestSignal]);
127
+ const priorOnResponse = request?.onResponse;
124
128
  const inheritedRequest = {
125
129
  ...(request ?? {}),
126
130
  ...(dispatch.auth.env === undefined ? {} : { env: dispatch.auth.env }),
127
131
  signal: streamSignal,
132
+ onResponse: async (response, model) => {
133
+ if (typeof response?.status === "number")
134
+ observedHttpStatus = response.status;
135
+ await priorOnResponse?.(response, model);
136
+ },
128
137
  };
129
138
  if (options.runCompletion !== undefined) {
130
139
  await new Promise((resolve) => setImmediate(resolve));
@@ -133,12 +142,12 @@ export async function createInheritedRuntime(options) {
133
142
  const completed = await waitForStream(options.runCompletion(model, options.injectedSystemPrompt === undefined
134
143
  ? context
135
144
  : { ...context, systemPrompt: options.injectedSystemPrompt }, inheritedRequest), streamSignal);
136
- const response = {
145
+ const response = attachObservedHttpStatus({
137
146
  ...completed,
138
147
  api: model.api,
139
148
  provider: model.provider,
140
149
  model: model.id,
141
- };
150
+ }, observedHttpStatus);
142
151
  if (response.stopReason === "error" || response.stopReason === "aborted") {
143
152
  wrapped.push({ type: "error", reason: response.stopReason, error: response });
144
153
  }
@@ -158,9 +167,9 @@ export async function createInheritedRuntime(options) {
158
167
  break;
159
168
  sawEvent = true;
160
169
  idle.poke();
161
- wrapped.push(next.value);
170
+ wrapped.push(enrichStreamEvent(next.value, observedHttpStatus));
162
171
  }
163
- const response = await waitForStream(source.result(), idle.signal);
172
+ const response = attachObservedHttpStatus(await waitForStream(source.result(), idle.signal), observedHttpStatus);
164
173
  if (!sawEvent)
165
174
  wrapped.end(response);
166
175
  return;
@@ -190,6 +199,9 @@ export async function createInheritedRuntime(options) {
190
199
  continue;
191
200
  }
192
201
  state.streamFailure = failure;
202
+ const projected = projectStructuredRemote(failure);
203
+ // Prefer structured fields on the thrown failure; fall back to onResponse observation.
204
+ const httpStatus = projected.httpStatus ?? numericHttpStatus(observedHttpStatus);
193
205
  const response = {
194
206
  role: "assistant",
195
207
  content: [],
@@ -198,8 +210,14 @@ export async function createInheritedRuntime(options) {
198
210
  model: model.id,
199
211
  usage: emptyUsage(),
200
212
  stopReason: "error",
213
+ // Preserve the held failure message bytes — do not rewrite.
201
214
  errorMessage: failure instanceof Error ? failure.message : String(failure),
202
215
  timestamp: Date.now(),
216
+ ...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
217
+ ...(httpStatus === undefined ? {} : { status: httpStatus, statusCode: httpStatus }),
218
+ ...(projected.body === undefined ? {} : { body: projected.body }),
219
+ ...(projected.code === undefined ? {} : { code: projected.code }),
220
+ ...(projected.errno === undefined ? {} : { errno: projected.errno }),
203
221
  };
204
222
  wrapped.push({ type: "error", reason: "error", error: response });
205
223
  wrapped.end(response);
@@ -270,6 +288,104 @@ export async function createInheritedRuntime(options) {
270
288
  runtime.registerNativeProvider(provider);
271
289
  return state;
272
290
  }
291
+ function numericHttpStatus(value) {
292
+ return isNonSuccessHttpStatus(value) ? value : undefined;
293
+ }
294
+ /**
295
+ * Cause-chain reader over the shared upstream-testimony authority.
296
+ * Shape walking stays here; testimony + confirmed-remote payload rules are shared.
297
+ */
298
+ function projectStructuredRemote(error) {
299
+ let httpStatus;
300
+ let diagnostics;
301
+ let body;
302
+ let code;
303
+ let errno;
304
+ let cursor = error;
305
+ const seen = new Set();
306
+ while (typeof cursor === "object" && cursor !== null && !seen.has(cursor)) {
307
+ seen.add(cursor);
308
+ const record = cursor;
309
+ const nodeStatus = numericHttpStatus(record.statusCode)
310
+ ?? numericHttpStatus(record.status)
311
+ ?? numericHttpStatus(record.httpStatus);
312
+ const nodeDiagnostics = Array.isArray(record.diagnostics) && record.diagnostics.length > 0
313
+ ? record.diagnostics
314
+ : undefined;
315
+ const nodeHasTestimony = hasUpstreamErrorTestimony({
316
+ ...(nodeStatus === undefined ? {} : { httpStatus: nodeStatus }),
317
+ ...(nodeDiagnostics === undefined ? {} : { diagnostics: nodeDiagnostics }),
318
+ });
319
+ if (httpStatus === undefined && nodeStatus !== undefined)
320
+ httpStatus = nodeStatus;
321
+ if (diagnostics === undefined && nodeDiagnostics !== undefined)
322
+ diagnostics = nodeDiagnostics;
323
+ // Payload only from confirmed-remote nodes — never arbitrary local Error.code.
324
+ if (nodeHasTestimony) {
325
+ const payload = projectConfirmedRemotePayload(record);
326
+ if (body === undefined && payload.body !== undefined)
327
+ body = payload.body;
328
+ if (code === undefined && payload.code !== undefined)
329
+ code = payload.code;
330
+ if (errno === undefined && payload.errno !== undefined)
331
+ errno = payload.errno;
332
+ }
333
+ cursor = record.cause;
334
+ }
335
+ return {
336
+ hasTestimony: hasUpstreamErrorTestimony({
337
+ ...(httpStatus === undefined ? {} : { httpStatus }),
338
+ ...(diagnostics === undefined ? {} : { diagnostics }),
339
+ }),
340
+ ...(httpStatus === undefined ? {} : { httpStatus }),
341
+ ...(diagnostics === undefined ? {} : { diagnostics }),
342
+ ...(body === undefined ? {} : { body }),
343
+ ...(code === undefined ? {} : { code }),
344
+ ...(errno === undefined ? {} : { errno }),
345
+ };
346
+ }
347
+ /**
348
+ * Attach a directly observed HTTP status onto an error/aborted assistant message.
349
+ * Does not invent status from errorMessage prose; skips when already held.
350
+ */
351
+ function attachObservedHttpStatus(message, observedHttpStatus) {
352
+ if (observedHttpStatus === undefined)
353
+ return message;
354
+ if (message.stopReason !== "error" && message.stopReason !== "aborted")
355
+ return message;
356
+ if (numericHttpStatus(observedHttpStatus) === undefined)
357
+ return message;
358
+ if (projectStructuredRemote(message).httpStatus !== undefined)
359
+ return message;
360
+ return Object.assign(message, {
361
+ status: observedHttpStatus,
362
+ statusCode: observedHttpStatus,
363
+ });
364
+ }
365
+ function enrichStreamEvent(event, observedHttpStatus) {
366
+ if (observedHttpStatus === undefined || event === null || typeof event !== "object")
367
+ return event;
368
+ const record = event;
369
+ if (record.type === "error" && record.error !== null && typeof record.error === "object") {
370
+ return {
371
+ ...record,
372
+ error: attachObservedHttpStatus(record.error, observedHttpStatus),
373
+ };
374
+ }
375
+ if (record.type === "done" && record.message !== null && typeof record.message === "object") {
376
+ return {
377
+ ...record,
378
+ message: attachObservedHttpStatus(record.message, observedHttpStatus),
379
+ };
380
+ }
381
+ if (record.partial !== null && typeof record.partial === "object") {
382
+ return {
383
+ ...record,
384
+ partial: attachObservedHttpStatus(record.partial, observedHttpStatus),
385
+ };
386
+ }
387
+ return event;
388
+ }
273
389
  function classifiedError(error, evidenceChildFailure) {
274
390
  const diagnostic = typeof error === "object" && error !== null && typeof error.errorMessage === "string"
275
391
  ? error.errorMessage
@@ -279,7 +395,9 @@ function classifiedError(error, evidenceChildFailure) {
279
395
  : Object.assign(new Error(diagnostic, { cause: error }), { evidenceChildOriginal: error });
280
396
  const classification = "evidenceChildFailure" in wrapped
281
397
  ? wrapped.evidenceChildFailure
282
- : evidenceChildFailure;
398
+ : evidenceChildFailure === "provider" && !projectStructuredRemote(error).hasTestimony
399
+ ? "unknown"
400
+ : evidenceChildFailure;
283
401
  return Object.assign(wrapped, { evidenceChildFailure: classification });
284
402
  }
285
403
  function emptyUsage() {
@@ -368,10 +486,14 @@ export async function executeEvidenceChild(workspace, prompt, context, options =
368
486
  const lastAssistant = [...session.messages]
369
487
  .reverse()
370
488
  .find((message) => message.role === "assistant");
371
- if (lastAssistant?.role === "assistant" && lastAssistant.stopReason === "error") {
372
- throw classifiedError(new Error(lastAssistant.errorMessage ?? "", { cause: lastAssistant }), "provider");
489
+ // error|aborted assistant stops share the upstream-testimony rule: provider only
490
+ // with direct HTTP/SDK testimony, otherwise existing unknown. child is reserved
491
+ // for real local child/report failures (no assistant / blank report / cleanup).
492
+ if (lastAssistant?.role === "assistant"
493
+ && (lastAssistant.stopReason === "error" || lastAssistant.stopReason === "aborted")) {
494
+ throw classifiedError(new Error(lastAssistant.errorMessage ?? "", { cause: lastAssistant }), projectStructuredRemote(lastAssistant).hasTestimony ? "provider" : "unknown");
373
495
  }
374
- if (lastAssistant?.role !== "assistant" || lastAssistant.stopReason === "aborted") {
496
+ if (lastAssistant?.role !== "assistant") {
375
497
  throw classifiedError(new Error("Evidence child child terminated without a report", {
376
498
  cause: lastAssistant ?? session.messages,
377
499
  }), "child");
@@ -721,15 +843,30 @@ export async function executeAuditorChild(options) {
721
843
  catch (retentionFailure) {
722
844
  if (response.stopReason !== "error")
723
845
  throw retentionFailure;
724
- const failure = new Error(response.errorMessage?.trim() || "provider failure", { cause: retentionFailure });
725
- failure.name = response.model || response.provider || "Error";
726
- failure.knownCause = "provider";
727
- failure.failureCode = response.provider || response.model;
846
+ // Do not trim/rewrite the held errorMessage bytes.
847
+ const diagnostic = typeof response.errorMessage === "string" && response.errorMessage.trim() !== ""
848
+ ? response.errorMessage
849
+ : undefined;
850
+ const projected = projectStructuredRemote(response);
851
+ const failure = new Error(diagnostic ?? "", { cause: retentionFailure });
852
+ if (projected.hasTestimony && (response.model || response.provider)) {
853
+ failure.name = response.model || response.provider || "Error";
854
+ failure.failureCode = response.provider || response.model;
855
+ }
856
+ failure.knownCause = projected.hasTestimony ? "provider" : "unrecognized";
728
857
  const retentionError = retentionFailure instanceof Error ? retentionFailure : undefined;
729
858
  const retentionCause = retentionError?.cause;
730
859
  failure.details = {
731
- ...(response.provider ? { provider: response.provider } : {}),
732
- ...(response.model ? { model: response.model } : {}),
860
+ ...(diagnostic === undefined ? {} : { errorMessage: diagnostic }),
861
+ ...(projected.hasTestimony && response.provider ? { provider: response.provider } : {}),
862
+ ...(projected.hasTestimony && response.model ? { model: response.model } : {}),
863
+ ...(response.api ? { api: response.api } : {}),
864
+ ...(response.rawStopReason ? { rawStopReason: response.rawStopReason } : {}),
865
+ ...(projected.httpStatus === undefined ? {} : { httpStatus: projected.httpStatus }),
866
+ ...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
867
+ ...(projected.body === undefined ? {} : { body: projected.body }),
868
+ ...(projected.code === undefined ? {} : { code: projected.code }),
869
+ ...(projected.errno === undefined ? {} : { errno: projected.errno }),
733
870
  retentionFailure: {
734
871
  name: retentionError?.name ?? typeof retentionFailure,
735
872
  message: retentionError?.message ?? String(retentionFailure),
@@ -756,8 +893,8 @@ export async function executeAuditorChild(options) {
756
893
  parent: binding.parent,
757
894
  failure: {
758
895
  cause: failure.knownCause,
759
- identity: { name: failure.name, code: failure.failureCode },
760
- diagnostic: failure.message,
896
+ ...(failure.failureCode === undefined ? {} : { identity: { name: failure.name, code: failure.failureCode } }),
897
+ ...(failure.message === "" ? {} : { diagnostic: failure.message }),
761
898
  details: failure.details,
762
899
  },
763
900
  });
@@ -16,6 +16,12 @@ import { renderPublicAkRoleCommand } from "./public-command-renderer.js";
16
16
  import { issueRoot, subjectPath } from "./work-subject-identity.js";
17
17
  import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
18
18
  import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
19
+ import { recordTypedProviderHttpStatus } from "./typed-provider-http.js";
20
+ import {
21
+ hasUpstreamErrorTestimony,
22
+ isNonSuccessHttpStatus,
23
+ projectConfirmedRemotePayload
24
+ } from "./upstream-error-testimony.js";
19
25
  const NAVIGATOR_EVENT_TYPE = "ak-navigator-attendance";
20
26
  const NAVIGATOR_PREPARE_TOOL_NAME = "ak_navigator_prepare";
21
27
  const NAVIGATOR_DEFAULT_MODEL = "openai-codex/gpt-5.6-luna:max";
@@ -810,19 +816,26 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
810
816
  }
811
817
  assignProviderFailure(navigatorProviderFailureFromStatus(status));
812
818
  };
813
- const humanProviderError = (error) => {
814
- const human = { ...error };
815
- delete human.statusCode;
816
- delete human.code;
817
- delete human.navigatorFailure;
818
- return human;
819
+ const projectHeldUpstream = (error) => {
820
+ if (!exactRecord(error)) return {};
821
+ const status = typeof error.statusCode === "number" ? error.statusCode : typeof error.status === "number" ? error.status : typeof error.httpStatus === "number" ? error.httpStatus : void 0;
822
+ const httpStatus = isNonSuccessHttpStatus(status) ? status : void 0;
823
+ const diagnostics = Array.isArray(error.diagnostics) && error.diagnostics.length > 0 ? error.diagnostics : void 0;
824
+ const testimony = hasUpstreamErrorTestimony({
825
+ ...httpStatus === void 0 ? {} : { httpStatus },
826
+ ...diagnostics === void 0 ? {} : { diagnostics }
827
+ });
828
+ return {
829
+ ...httpStatus === void 0 ? {} : { statusCode: httpStatus, status: httpStatus },
830
+ ...diagnostics === void 0 ? {} : { diagnostics },
831
+ ...testimony ? projectConfirmedRemotePayload(error) : {}
832
+ };
819
833
  };
820
- let providerFailureEvidenceNumber = 0;
821
- let providerFailureEvidence;
822
- const retainProviderFailure = (error) => {
823
- const id = `navigator-provider-failure-${++providerFailureEvidenceNumber}`;
824
- providerFailureEvidence = { id, error };
825
- return id;
834
+ const retainUpstreamMessage = (error) => {
835
+ if (!("navigatorFailure" in error)) return error;
836
+ const copy = { ...error };
837
+ delete copy.navigatorFailure;
838
+ return copy;
826
839
  };
827
840
  const setupFailureMessage = (error) => ({
828
841
  role: "assistant",
@@ -833,22 +846,38 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
833
846
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
834
847
  stopReason: "error",
835
848
  errorMessage: error instanceof Error ? error.message : String(error),
836
- navigatorFailureEvidenceId: retainProviderFailure(error),
837
- timestamp: Date.now()
849
+ timestamp: Date.now(),
850
+ ...projectHeldUpstream(error)
838
851
  });
852
+ const persistNavigatorHttpObservation = async (status, model2) => {
853
+ const runDir = process.env.AK_ROLE_RUN_DIR;
854
+ if (typeof runDir !== "string" || runDir.trim() === "") return;
855
+ const provider2 = exactRecord(model2) && typeof model2.provider === "string" && model2.provider.trim() !== "" ? model2.provider : void 0;
856
+ if (provider2 === void 0) return;
857
+ await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider: provider2 });
858
+ };
839
859
  const instrumentProvider = (sourceProvider) => {
840
- const instrumentStreamOptions = (options) => {
860
+ const instrumentStreamOptions = (options, observedStatus) => {
841
861
  const record = exactRecord(options) ? options : {};
842
862
  const previous = typeof record.onResponse === "function" ? record.onResponse : void 0;
843
863
  return {
844
864
  ...record,
845
865
  onResponse: async (response, model2) => {
866
+ observedStatus.value = response.status;
846
867
  classifyProviderResponseStatus(response.status);
868
+ await persistNavigatorHttpObservation(response.status, model2);
847
869
  await previous?.(response, model2);
848
870
  }
849
871
  };
850
872
  };
851
- const wrapProviderStream = (source) => {
873
+ const withObservedStatus = (message, observedStatus) => {
874
+ if (observedStatus === void 0 || observedStatus >= 200 && observedStatus < 300) return message;
875
+ if (typeof message.statusCode === "number" || typeof message.status === "number" || typeof message.httpStatus === "number") {
876
+ return message;
877
+ }
878
+ return { ...message, statusCode: observedStatus, status: observedStatus };
879
+ };
880
+ const wrapProviderStream = (source, observedStatus) => {
852
881
  const wrapped = createAssistantMessageEventStream();
853
882
  void (async () => {
854
883
  let result;
@@ -859,13 +888,13 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
859
888
  sawTerminal = true;
860
889
  if (event.type === "done" && exactRecord(event.message)) {
861
890
  assignProviderFailure(navigatorProviderFailureFromDiagnostics(event.message.diagnostics));
862
- result = humanProviderError(event.message);
891
+ result = withObservedStatus(retainUpstreamMessage(event.message), observedStatus.value);
863
892
  wrapped.push({ ...event, message: result });
864
893
  continue;
865
894
  }
866
895
  if (event.type === "error" && exactRecord(event.error)) {
867
896
  classifyProviderStreamError(event.error);
868
- result = humanProviderError(event.error);
897
+ result = withObservedStatus(retainUpstreamMessage(event.error), observedStatus.value);
869
898
  wrapped.push({ ...event, error: result });
870
899
  continue;
871
900
  }
@@ -874,21 +903,22 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
874
903
  }
875
904
  if (sawTerminal) {
876
905
  const terminal = await source.result();
877
- if (result === void 0 && exactRecord(terminal)) result = humanProviderError(terminal);
878
- else if (result === void 0) result = terminal;
906
+ if (result === void 0 && exactRecord(terminal)) {
907
+ result = withObservedStatus(retainUpstreamMessage(terminal), observedStatus.value);
908
+ } else if (result === void 0) result = terminal;
879
909
  }
880
910
  } catch (error) {
881
911
  classifyProviderStreamError(error);
882
- if (providerFailure === void 0) providerFailure = { source: "transport", cause: "transport" };
912
+ if (providerFailure === void 0) providerFailure = { source: "unknown", cause: "unknown" };
883
913
  if (!sawTerminal) {
884
- const message = setupFailureMessage(error);
914
+ const message = withObservedStatus(setupFailureMessage(error), observedStatus.value);
885
915
  wrapped.push({ type: "error", reason: "error", error: message });
886
916
  result = message;
887
917
  sawTerminal = true;
888
918
  }
889
919
  } finally {
890
920
  if (!sawTerminal) {
891
- if (providerFailure === void 0) providerFailure = { source: "transport", cause: "transport" };
921
+ if (providerFailure === void 0) providerFailure = { source: "unknown", cause: "unknown" };
892
922
  const message = setupFailureMessage(new Error("Navigator provider produced no response"));
893
923
  wrapped.push({ type: "error", reason: "error", error: message });
894
924
  result = message;
@@ -901,11 +931,12 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
901
931
  };
902
932
  const invokeInstrumentedStream = (invoke) => {
903
933
  providerFailure = void 0;
934
+ const observedStatus = {};
904
935
  try {
905
- return wrapProviderStream(invoke());
936
+ return wrapProviderStream(invoke(observedStatus), observedStatus);
906
937
  } catch (error) {
907
938
  classifyProviderStreamError(error);
908
- if (providerFailure === void 0) providerFailure = { source: "transport", cause: "transport" };
939
+ if (providerFailure === void 0) providerFailure = { source: "unknown", cause: "unknown" };
909
940
  const wrapped = createAssistantMessageEventStream();
910
941
  const message = setupFailureMessage(error);
911
942
  queueMicrotask(() => {
@@ -918,12 +949,16 @@ function createNativeNavigatorSessionFactory(defaultModelSettingPath = navigator
918
949
  return {
919
950
  ...sourceProvider,
920
951
  stream(model2, streamContext, options) {
921
- const instrumented = instrumentStreamOptions(options);
922
- return invokeInstrumentedStream(() => sourceProvider.stream(model2, streamContext, instrumented));
952
+ return invokeInstrumentedStream((observedStatus) => {
953
+ const instrumented = instrumentStreamOptions(options, observedStatus);
954
+ return sourceProvider.stream(model2, streamContext, instrumented);
955
+ });
923
956
  },
924
957
  streamSimple(model2, streamContext, options) {
925
- const instrumented = instrumentStreamOptions(options);
926
- return invokeInstrumentedStream(() => sourceProvider.streamSimple(model2, streamContext, instrumented));
958
+ return invokeInstrumentedStream((observedStatus) => {
959
+ const instrumented = instrumentStreamOptions(options, observedStatus);
960
+ return sourceProvider.streamSimple(model2, streamContext, instrumented);
961
+ });
927
962
  }
928
963
  };
929
964
  };