@stigmer/server 3.36.0 → 3.37.1

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 (112) hide show
  1. package/dist/domain/agentexecution/artifacts.d.ts.map +1 -1
  2. package/dist/domain/agentexecution/artifacts.js +15 -6
  3. package/dist/domain/agentexecution/artifacts.js.map +1 -1
  4. package/dist/domain/agentexecution/constants.d.ts +0 -10
  5. package/dist/domain/agentexecution/constants.d.ts.map +1 -1
  6. package/dist/domain/agentexecution/constants.js +0 -12
  7. package/dist/domain/agentexecution/constants.js.map +1 -1
  8. package/dist/domain/agentexecution/lifecycle.d.ts.map +1 -1
  9. package/dist/domain/agentexecution/lifecycle.js +5 -3
  10. package/dist/domain/agentexecution/lifecycle.js.map +1 -1
  11. package/dist/domain/agentexecution/platform-rows.d.ts +63 -0
  12. package/dist/domain/agentexecution/platform-rows.d.ts.map +1 -0
  13. package/dist/domain/agentexecution/platform-rows.js +106 -0
  14. package/dist/domain/agentexecution/platform-rows.js.map +1 -0
  15. package/dist/domain/agentexecution/subscribe.d.ts.map +1 -1
  16. package/dist/domain/agentexecution/subscribe.js +7 -4
  17. package/dist/domain/agentexecution/subscribe.js.map +1 -1
  18. package/dist/domain/agentexecution/update-status.d.ts.map +1 -1
  19. package/dist/domain/agentexecution/update-status.js +48 -17
  20. package/dist/domain/agentexecution/update-status.js.map +1 -1
  21. package/dist/domain/agentexecution/usage.d.ts +0 -2
  22. package/dist/domain/agentexecution/usage.d.ts.map +1 -1
  23. package/dist/domain/agentexecution/usage.js +12 -10
  24. package/dist/domain/agentexecution/usage.js.map +1 -1
  25. package/dist/domain/organization/controller.d.ts +4 -1
  26. package/dist/domain/organization/controller.d.ts.map +1 -1
  27. package/dist/domain/organization/controller.js +30 -8
  28. package/dist/domain/organization/controller.js.map +1 -1
  29. package/dist/domain/workflow/controller.js +2 -1
  30. package/dist/domain/workflow/controller.js.map +1 -1
  31. package/dist/domain/workflow/steps.d.ts +7 -4
  32. package/dist/domain/workflow/steps.d.ts.map +1 -1
  33. package/dist/domain/workflow/steps.js +29 -17
  34. package/dist/domain/workflow/steps.js.map +1 -1
  35. package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
  36. package/dist/domain/workflowexecution/create-steps.js +6 -2
  37. package/dist/domain/workflowexecution/create-steps.js.map +1 -1
  38. package/dist/domain/workflowexecution/lifecycle.d.ts.map +1 -1
  39. package/dist/domain/workflowexecution/lifecycle.js +5 -2
  40. package/dist/domain/workflowexecution/lifecycle.js.map +1 -1
  41. package/dist/domain/workflowexecution/send-signal.d.ts.map +1 -1
  42. package/dist/domain/workflowexecution/send-signal.js +6 -3
  43. package/dist/domain/workflowexecution/send-signal.js.map +1 -1
  44. package/dist/domain/workflowexecution/submit-approval.d.ts.map +1 -1
  45. package/dist/domain/workflowexecution/submit-approval.js +6 -2
  46. package/dist/domain/workflowexecution/submit-approval.js.map +1 -1
  47. package/dist/domain/workflowexecution/submit-file-decision.d.ts.map +1 -1
  48. package/dist/domain/workflowexecution/submit-file-decision.js +6 -2
  49. package/dist/domain/workflowexecution/submit-file-decision.js.map +1 -1
  50. package/dist/domain/workflowexecution/submit-workflow-task-approval.d.ts.map +1 -1
  51. package/dist/domain/workflowexecution/submit-workflow-task-approval.js +6 -2
  52. package/dist/domain/workflowexecution/submit-workflow-task-approval.js.map +1 -1
  53. package/dist/domain/workflowexecution/subscribe-events.d.ts.map +1 -1
  54. package/dist/domain/workflowexecution/subscribe-events.js +6 -2
  55. package/dist/domain/workflowexecution/subscribe-events.js.map +1 -1
  56. package/dist/domain/workflowexecution/subscribe.d.ts.map +1 -1
  57. package/dist/domain/workflowexecution/subscribe.js +7 -4
  58. package/dist/domain/workflowexecution/subscribe.js.map +1 -1
  59. package/dist/extensions/organization-directory.d.ts +29 -6
  60. package/dist/extensions/organization-directory.d.ts.map +1 -1
  61. package/dist/extensions/organization-directory.js.map +1 -1
  62. package/dist/index.d.ts +1 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js.map +1 -1
  65. package/dist/pipeline/steps/guard-memory-capture.d.ts +9 -4
  66. package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
  67. package/dist/pipeline/steps/guard-memory-capture.js +10 -12
  68. package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
  69. package/dist/temporal/agentexecution/runner-result.d.ts +12 -0
  70. package/dist/temporal/agentexecution/runner-result.d.ts.map +1 -1
  71. package/dist/temporal/agentexecution/runner-result.js +15 -0
  72. package/dist/temporal/agentexecution/runner-result.js.map +1 -1
  73. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.d.ts.map +1 -1
  74. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js +44 -22
  75. package/dist/temporal/agentexecution/workflows/invoke-agent-execution.js.map +1 -1
  76. package/package.json +6 -6
  77. package/src/domain/agentexecution/__tests__/agentexecution.test.ts +2 -8
  78. package/src/domain/agentexecution/__tests__/store-faults.test.ts +315 -0
  79. package/src/domain/agentexecution/__tests__/update-status.test.ts +217 -0
  80. package/src/domain/agentexecution/artifacts.ts +21 -12
  81. package/src/domain/agentexecution/constants.ts +0 -15
  82. package/src/domain/agentexecution/lifecycle.ts +5 -3
  83. package/src/domain/agentexecution/platform-rows.ts +133 -0
  84. package/src/domain/agentexecution/subscribe.ts +7 -3
  85. package/src/domain/agentexecution/update-status.ts +70 -19
  86. package/src/domain/agentexecution/usage.ts +16 -13
  87. package/src/domain/organization/controller.ts +47 -10
  88. package/src/domain/workflow/__tests__/version-audit-degradation.test.ts +192 -0
  89. package/src/domain/workflow/controller.ts +2 -1
  90. package/src/domain/workflow/steps.ts +30 -18
  91. package/src/domain/workflowexecution/__tests__/store-faults.test.ts +418 -0
  92. package/src/domain/workflowexecution/create-steps.ts +6 -2
  93. package/src/domain/workflowexecution/lifecycle.ts +5 -2
  94. package/src/domain/workflowexecution/send-signal.ts +11 -4
  95. package/src/domain/workflowexecution/submit-approval.ts +6 -2
  96. package/src/domain/workflowexecution/submit-file-decision.ts +6 -2
  97. package/src/domain/workflowexecution/submit-workflow-task-approval.ts +6 -2
  98. package/src/domain/workflowexecution/subscribe-events.ts +6 -2
  99. package/src/domain/workflowexecution/subscribe.ts +11 -4
  100. package/src/extensions/__tests__/extension-composition.test.ts +90 -18
  101. package/src/extensions/organization-directory.ts +33 -7
  102. package/src/index.ts +5 -1
  103. package/src/pipeline/__tests__/blind-not-found.test.ts +297 -0
  104. package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +67 -5
  105. package/src/pipeline/steps/guard-memory-capture.ts +24 -19
  106. package/src/temporal/agentexecution/__tests__/invoke-workflow.test.ts +51 -1
  107. package/src/temporal/agentexecution/__tests__/own-behalf-status-writes.test.ts +149 -9
  108. package/src/temporal/agentexecution/__tests__/replay-histories/runner-failed.json +757 -0
  109. package/src/temporal/agentexecution/__tests__/replay-histories/user-cancel.json +755 -0
  110. package/src/temporal/agentexecution/__tests__/replay.test.ts +4 -2
  111. package/src/temporal/agentexecution/runner-result.ts +16 -0
  112. package/src/temporal/agentexecution/workflows/invoke-agent-execution.ts +49 -22
@@ -14,7 +14,12 @@
14
14
  * every Stigmer-minted non-first-party credential carries the
15
15
  * `platform_client_id` claim (StigmerJwtIssuer stamps it on user, guest,
16
16
  * channel, and schedule tokens alike), so its presence IS the exclusion
17
- * the Java RequestCallerIdentity.isFirstPartyHumanOperator computes.
17
+ * the Java RequestCallerIdentity.isFirstPartyHumanOperator computes. The
18
+ * claim counts only on a platform token (`iss: "stigmer"`, read through
19
+ * decodeVerifiedPlatformTokenPayload as the PlatformClient origin guard
20
+ * reads it): a bearer another verifier claimed is some other issuer's,
21
+ * whatever claims it carries, and is never taken for a Stigmer-minted
22
+ * one (stigmer#1312).
18
23
  *
19
24
  * RUNNER credentials carry neither the machine class nor that claim —
20
25
  * their eligibility is EDITION POLICY, so the gate consults the composed
@@ -30,16 +35,21 @@
30
35
  * this gate's own logic applies). The capability throws its own
31
36
  * byte-pinned refusal for the org-mismatch arm. With no capability
32
37
  * composed the gate's logic is byte-identical to before the seam: OSS
33
- * trusted-local callers carry no token and OIDC console logins carry no
34
- * platform_client_id claim — the step admits both, so the single-user
35
- * posture is unchanged (proven by the rosters).
38
+ * trusted-local callers carry no token and OIDC console logins are not
39
+ * platform tokens — the step admits both, so the single-user posture is
40
+ * unchanged (proven by the rosters).
36
41
  */
37
42
  import type { DescMessage } from "@bufbuild/protobuf";
38
43
  import { Code, ConnectError } from "@connectrpc/connect";
39
44
 
45
+ import { USER_TOKEN_CLAIMS } from "../../domain/platformclient/constants.js";
40
46
  import { isServerComposedRequest } from "../../extensions/identity.js";
41
47
  import type { PipelineStep } from "../pipeline.js";
42
48
  import type { RequestContext } from "../request-context.js";
49
+ import {
50
+ decodeVerifiedPlatformTokenPayload,
51
+ stringClaim,
52
+ } from "../../platformtoken/envelope.js";
43
53
  import type { RunnerCredentialProvider } from "../../runnerauth/runner-credential-provider.js";
44
54
  import { metadataOf } from "./shapes.js";
45
55
 
@@ -138,20 +148,15 @@ export function memoryCaptureCredentialOf<Desc extends DescMessage>(
138
148
  return undefined;
139
149
  }
140
150
 
151
+ /**
152
+ * Whether the bearer is a platform token naming a PlatformClient. Anything
153
+ * that is not a platform token (no token, an API key, another issuer's
154
+ * JWT) is not one, whatever claims it carries.
155
+ */
141
156
  function carriesPlatformClientClaim(rawToken: string): boolean {
142
- const segments = rawToken.split(".");
143
- if (segments.length !== 3) {
144
- return false; // not a JWT — the trusted-local no-token posture
145
- }
146
- try {
147
- const payload = JSON.parse(
148
- Buffer.from(segments[1], "base64url").toString("utf8"),
149
- ) as { platform_client_id?: unknown };
150
- return (
151
- typeof payload.platform_client_id === "string" &&
152
- payload.platform_client_id !== ""
153
- );
154
- } catch {
155
- return false;
156
- }
157
+ const payload = decodeVerifiedPlatformTokenPayload(rawToken);
158
+ return (
159
+ payload !== undefined &&
160
+ stringClaim(payload, USER_TOKEN_CLAIMS.platformClientId) !== undefined
161
+ );
157
162
  }
@@ -16,6 +16,10 @@
16
16
  * - bounded auto-recovery on worker-shutdown shapes (#776: IN_PROGRESS
17
17
  * persisted, honest status copy on exhaustion);
18
18
  * - EXECUTION_FAILED propagation with the fallback persist;
19
+ * - the stop and failure copy each has one writer (stigmer#980): the
20
+ * pause persist carries the pause row, a runner-reported failure's
21
+ * write carries no lines (the runner explained it), and only a broken
22
+ * flow gets the platform's two lines;
19
23
  * - external cancellation cleanup (CANCELLED persisted quietly —
20
24
  * stigmer#282 — and the EC deleted);
21
25
  * - callback-token completion on success AND failure (the DD-001 lane,
@@ -50,9 +54,15 @@ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/ag
50
54
  import {
51
55
  ExecutionPhase,
52
56
  FileChangeSetStatus,
57
+ MessageType,
53
58
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
54
59
  import { Harness } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
55
60
 
61
+ import {
62
+ CANCEL_STOP_ROW,
63
+ PAUSE_STOP_ROW,
64
+ } from "../../../domain/agentexecution/platform-rows.js";
65
+
56
66
  import {
57
67
  INVOKE_AGENT_EXECUTION_WORKFLOW_NAME,
58
68
  MEMO_ACTIVITY_TASK_QUEUE,
@@ -565,6 +575,12 @@ describe("invoke-agent-execution workflow (TestWorkflowEnvironment)", () => {
565
575
  .includes(ExecutionPhase.EXECUTION_IN_PROGRESS),
566
576
  "resume re-asserts IN_PROGRESS after the PAUSED persist",
567
577
  ).toBe(true);
578
+ // The workflow is the pause row's one writer: the runner cannot tell
579
+ // this stop from a cancel, so it settles without a row.
580
+ expect(
581
+ script.persistedStatuses[pausedAt]!.status.messages.map((m) => [m.type, m.content]),
582
+ "the PAUSED persist carries the pause row",
583
+ ).toEqual([[MessageType.MESSAGE_SYSTEM, PAUSE_STOP_ROW]]);
568
584
  }, 60_000);
569
585
 
570
586
  it("auto-recovers a worker-shutdown interruption and persists IN_PROGRESS (#776)", async (testCtx) => { if (!envReady) return testCtx.skip();
@@ -611,6 +627,40 @@ describe("invoke-agent-execution workflow (TestWorkflowEnvironment)", () => {
611
627
  );
612
628
  expect(failed.length).toBeGreaterThan(0);
613
629
  expect(failed[0]!.status.error).toBe("agent blew up");
630
+ // The runner already explained its own failure in the transcript, so
631
+ // no FAILED write of the workflow adds a second explanation.
632
+ expect(
633
+ failed.flatMap((entry) => entry.status.messages),
634
+ "a runner-reported failure adds no platform lines",
635
+ ).toEqual([]);
636
+ expectExecutionContextDeleted();
637
+ }, 30_000);
638
+
639
+ it("explains a flow that broke without a runner verdict with the platform's two lines", async (testCtx) => { if (!envReady) return testCtx.skip();
640
+ const { ApplicationFailure } = await import("@temporalio/common");
641
+ script.executeBehaviors = [
642
+ async () => {
643
+ // Not recoverable (not a cancellation, heartbeat timeout or worker
644
+ // shutdown), so the flow breaks with no FAILED result to read.
645
+ throw ApplicationFailure.create({ message: "runner crashed", nonRetryable: true });
646
+ },
647
+ ];
648
+
649
+ const handle = await startWorkflow(workflowInput());
650
+ await expect(handle.result()).rejects.toThrow(/Workflow execution failed/);
651
+
652
+ const failed = script.persistedStatuses.filter(
653
+ (entry) => entry.status.phase === ExecutionPhase.EXECUTION_FAILED,
654
+ );
655
+ expect(failed.length).toBeGreaterThan(0);
656
+ const lines = failed.at(-1)!.status.messages;
657
+ expect(lines.map((m) => m.type)).toEqual([
658
+ MessageType.MESSAGE_SYSTEM,
659
+ MessageType.MESSAGE_SYSTEM,
660
+ ]);
661
+ // The details carry the wrapped activity error (wrapActivityError's
662
+ // operator-actionable prefix), not a runner-authored sentence.
663
+ expect(lines[1]!.content).toMatch(/^Error details: activity 'ExecuteDeepAgent'/);
614
664
  expectExecutionContextDeleted();
615
665
  }, 30_000);
616
666
 
@@ -638,7 +688,7 @@ describe("invoke-agent-execution workflow (TestWorkflowEnvironment)", () => {
638
688
  // system message is the durable marker.
639
689
  expect(cancelled.status.error).toBe("");
640
690
  expect(cancelled.status.messages.map((message) => message.content)).toEqual([
641
- "Execution was cancelled.",
691
+ CANCEL_STOP_ROW,
642
692
  ]);
643
693
  expectExecutionContextDeleted();
644
694
  }, 60_000);
@@ -20,19 +20,24 @@
20
20
  * execution status` from authorize.ts — the dark production composition's
21
21
  * exact log line.
22
22
  *
23
- * The three payload shapes are the workflow's real ones
23
+ * The payload shapes are the workflow's real ones
24
24
  * (invoke-agent-execution.ts): updateStatusOnFailure's FAILED with two
25
- * MESSAGE_SYSTEM lines, updateStatusOnCancellation's CANCELLED with one,
25
+ * MESSAGE_SYSTEM lines (a flow that broke) or none (a runner-reported
26
+ * failure), updateStatusOnCancellation's CANCELLED with its stop row,
26
27
  * and the phase-only persistFinalStatus / persistInterruptedStatus /
27
- * persistResumedStatus shape. Transcript shape after a late fallback is
28
- * deliberately NOT asserted here — stigmer#980 owns that cross-edition
29
- * behavior.
28
+ * persistResumedStatus shape.
29
+ *
30
+ * The last arms pin the transcript a stopped execution keeps over this
31
+ * production lane (stigmer#980): the platform's lines are appended to the
32
+ * runner's transcript, never substituted for it, and the cancel row stays
33
+ * last when the runner's settle write lands after it. The merge's full
34
+ * case matrix is domain/agentexecution/__tests__/update-status.test.ts.
30
35
  */
31
36
  import { mkdtempSync, rmSync } from "node:fs";
32
37
  import { tmpdir } from "node:os";
33
38
  import path from "node:path";
34
39
  import { create, toJson } from "@bufbuild/protobuf";
35
- import type { JsonValue } from "@bufbuild/protobuf";
40
+ import type { JsonValue, MessageInitShape } from "@bufbuild/protobuf";
36
41
  import { createClient } from "@connectrpc/connect";
37
42
  import type { Client } from "@temporalio/client";
38
43
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -46,9 +51,12 @@ import {
46
51
  ExecutionPhase,
47
52
  MessageType,
48
53
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
54
+ import { AgentExecutionUpdateStatusInputSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
55
+ import type { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
49
56
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
50
57
 
51
58
  import { loadConfig } from "../../../boot/config.js";
59
+ import { CANCEL_STOP_ROW } from "../../../domain/agentexecution/platform-rows.js";
52
60
  import { composeServer } from "../../../boot/compose.js";
53
61
  import type { ComposedServer } from "../../../boot/compose.js";
54
62
  import { createLogger } from "../../../boot/logger.js";
@@ -147,7 +155,11 @@ describe("own-behalf status writes under an enforcing Authorizer", () => {
147
155
  transitions.length = 0;
148
156
  });
149
157
 
150
- async function seedExecution(id: string): Promise<void> {
158
+ async function seedExecution(
159
+ id: string,
160
+ phase: ExecutionPhase = ExecutionPhase.EXECUTION_IN_PROGRESS,
161
+ messages: MessageInitShape<typeof AgentMessageSchema>[] = [],
162
+ ): Promise<void> {
151
163
  // Seeded through the store: the create path is not under test, and
152
164
  // the denying Authorizer would refuse it over the wire.
153
165
  await server.store.saveResource(
@@ -159,7 +171,7 @@ describe("own-behalf status writes under an enforcing Authorizer", () => {
159
171
  kind: "AgentExecution",
160
172
  metadata: { id, name: "own-behalf", org: "acme" },
161
173
  spec: { agentId: "agt_1", sessionId: "ses_1" },
162
- status: { phase: ExecutionPhase.EXECUTION_IN_PROGRESS },
174
+ status: { phase, messages },
163
175
  }),
164
176
  );
165
177
  }
@@ -253,7 +265,7 @@ describe("own-behalf status writes under an enforcing Authorizer", () => {
253
265
  messages: [
254
266
  {
255
267
  type: MessageType.MESSAGE_SYSTEM,
256
- content: "Execution was cancelled.",
268
+ content: CANCEL_STOP_ROW,
257
269
  },
258
270
  ],
259
271
  }),
@@ -296,6 +308,134 @@ describe("own-behalf status writes under an enforcing Authorizer", () => {
296
308
  expect(authorizerCalls).toHaveLength(0);
297
309
  });
298
310
 
311
+ describe("the transcript a stopped execution keeps (stigmer#980)", () => {
312
+ const runnerTurns = [
313
+ { type: MessageType.MESSAGE_AI, content: "a1" },
314
+ { type: MessageType.MESSAGE_AI, content: "a2" },
315
+ ];
316
+
317
+ async function transcriptOf(id: string): Promise<string[]> {
318
+ return ((await persisted(id)).status?.messages ?? []).map(
319
+ (m) => m.content,
320
+ );
321
+ }
322
+
323
+ it("appends the cancel row to a transcript the Cancel RPC already stamped CANCELLED, and keeps it last across the runner's late settle write", async () => {
324
+ await seedExecution(
325
+ "aex_ownbehalf_cancel_race",
326
+ ExecutionPhase.EXECUTION_CANCELLED,
327
+ runnerTurns,
328
+ );
329
+
330
+ await updateStatus(
331
+ "aex_ownbehalf_cancel_race",
332
+ toJson(
333
+ AgentExecutionStatusSchema,
334
+ create(AgentExecutionStatusSchema, {
335
+ phase: ExecutionPhase.EXECUTION_CANCELLED,
336
+ messages: [
337
+ { type: MessageType.MESSAGE_SYSTEM, content: CANCEL_STOP_ROW },
338
+ ],
339
+ }),
340
+ ),
341
+ );
342
+ expect(await transcriptOf("aex_ownbehalf_cancel_race")).toEqual([
343
+ "a1",
344
+ "a2",
345
+ CANCEL_STOP_ROW,
346
+ ]);
347
+
348
+ // The runner learns of the cancel on its next heartbeat and settles
349
+ // its transcript over the same handler, with no row and no phase.
350
+ await statusWriter.updateStatus(
351
+ create(AgentExecutionUpdateStatusInputSchema, {
352
+ executionId: "aex_ownbehalf_cancel_race",
353
+ status: {
354
+ messages: [
355
+ ...runnerTurns,
356
+ { type: MessageType.MESSAGE_AI, content: "a3" },
357
+ ],
358
+ },
359
+ }),
360
+ );
361
+ expect(await transcriptOf("aex_ownbehalf_cancel_race")).toEqual([
362
+ "a1",
363
+ "a2",
364
+ "a3",
365
+ CANCEL_STOP_ROW,
366
+ ]);
367
+ });
368
+
369
+ it("appends a broken flow's two lines to the runner's transcript", async () => {
370
+ await seedExecution(
371
+ "aex_ownbehalf_broken_flow",
372
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
373
+ runnerTurns,
374
+ );
375
+
376
+ await updateStatus(
377
+ "aex_ownbehalf_broken_flow",
378
+ toJson(
379
+ AgentExecutionStatusSchema,
380
+ create(AgentExecutionStatusSchema, {
381
+ phase: ExecutionPhase.EXECUTION_FAILED,
382
+ error: "activity crashed",
383
+ messages: [
384
+ {
385
+ type: MessageType.MESSAGE_SYSTEM,
386
+ content: "Internal system error occurred during execution.",
387
+ },
388
+ {
389
+ type: MessageType.MESSAGE_SYSTEM,
390
+ content: "Error details: activity crashed",
391
+ },
392
+ ],
393
+ }),
394
+ ),
395
+ );
396
+
397
+ const execution = await persisted("aex_ownbehalf_broken_flow");
398
+ expect(execution.status?.phase).toBe(ExecutionPhase.EXECUTION_FAILED);
399
+ expect(await transcriptOf("aex_ownbehalf_broken_flow")).toEqual([
400
+ "a1",
401
+ "a2",
402
+ "Internal system error occurred during execution.",
403
+ "Error details: activity crashed",
404
+ ]);
405
+ });
406
+
407
+ it("leaves a runner-reported failure's transcript as the runner wrote it", async () => {
408
+ await seedExecution(
409
+ "aex_ownbehalf_runner_failed",
410
+ ExecutionPhase.EXECUTION_FAILED,
411
+ [
412
+ ...runnerTurns,
413
+ {
414
+ type: MessageType.MESSAGE_SYSTEM,
415
+ content: "Execution failed: the tool refused",
416
+ },
417
+ ],
418
+ );
419
+
420
+ await updateStatus(
421
+ "aex_ownbehalf_runner_failed",
422
+ toJson(
423
+ AgentExecutionStatusSchema,
424
+ create(AgentExecutionStatusSchema, {
425
+ phase: ExecutionPhase.EXECUTION_FAILED,
426
+ error: "agent execution failed: the tool refused",
427
+ }),
428
+ ),
429
+ );
430
+
431
+ expect(await transcriptOf("aex_ownbehalf_runner_failed")).toEqual([
432
+ "a1",
433
+ "a2",
434
+ "Execution failed: the tool refused",
435
+ ]);
436
+ });
437
+ });
438
+
299
439
  it("surfaces the lane's NotFound as the activity's failure for a deleted execution", async () => {
300
440
  await expect(
301
441
  updateStatus("aex_ownbehalf_missing", { phase: "EXECUTION_FAILED" }),