@gajae-code/coding-agent 0.16.3 → 0.16.4

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 (133) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/types/cli/args.d.ts +2 -2
  3. package/dist/types/cli/root-flags.d.ts +4 -2
  4. package/dist/types/cli.d.ts +1 -1
  5. package/dist/types/commands/launch.d.ts +1 -1
  6. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/model-resolver.d.ts +9 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  11. package/dist/types/discovery/helpers.d.ts +2 -0
  12. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  13. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  14. package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
  15. package/dist/types/extensibility/shared-events.d.ts +1 -1
  16. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  17. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  18. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  19. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  20. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  21. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  22. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  23. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  24. package/dist/types/modes/interactive-mode.d.ts +1 -0
  25. package/dist/types/modes/types.d.ts +1 -0
  26. package/dist/types/runtime-mcp/client.d.ts +9 -1
  27. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  28. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  29. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  30. package/dist/types/runtime-mcp/types.d.ts +22 -0
  31. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  32. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  33. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  34. package/dist/types/sdk/host/session-runtime.d.ts +23 -0
  35. package/dist/types/sdk/session-api-key.d.ts +26 -0
  36. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  37. package/dist/types/session/agent-session.d.ts +9 -0
  38. package/dist/types/setup/paseo/announce.d.ts +111 -0
  39. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  40. package/dist/types/system-prompt.d.ts +2 -0
  41. package/dist/types/task/executor.d.ts +2 -1
  42. package/dist/types/tools/fetch.d.ts +7 -0
  43. package/dist/types/tools/index.d.ts +8 -0
  44. package/dist/types/tools/path-utils.d.ts +2 -0
  45. package/dist/types/tools/search.d.ts +6 -0
  46. package/package.json +7 -7
  47. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  48. package/scripts/generate-sdk-operation-inventory.ts +9 -0
  49. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  50. package/src/cli/args.ts +20 -9
  51. package/src/cli/root-flags.ts +5 -3
  52. package/src/config/autorouting-tier-map.ts +1 -0
  53. package/src/config/file-lock.ts +181 -17
  54. package/src/config/model-preset-registry.ts +5 -3
  55. package/src/config/model-profiles.ts +40 -0
  56. package/src/config/model-registry.ts +57 -11
  57. package/src/config/model-resolver.ts +48 -0
  58. package/src/config/settings-schema.ts +33 -1
  59. package/src/config/settings.ts +1 -0
  60. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  61. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  62. package/src/discovery/builtin.ts +13 -4
  63. package/src/discovery/claude-plugins.ts +12 -4
  64. package/src/discovery/helpers.ts +10 -2
  65. package/src/extensibility/gjc-plugins/compiler.ts +208 -58
  66. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  67. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  68. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  69. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  70. package/src/extensibility/runtime-skill-discovery.ts +77 -151
  71. package/src/extensibility/shared-events.ts +1 -1
  72. package/src/extensibility/skills.ts +44 -16
  73. package/src/gjc-runtime/repository-binding.ts +1 -1
  74. package/src/gjc-runtime/session-state-lock.ts +92 -2
  75. package/src/internal-urls/docs-index.generated.ts +7 -7
  76. package/src/main.ts +72 -34
  77. package/src/modes/DESIGN.md +54 -0
  78. package/src/modes/components/assistant-message.ts +22 -9
  79. package/src/modes/components/model-selector.ts +166 -33
  80. package/src/modes/components/settings-selector.ts +1075 -235
  81. package/src/modes/components/status-line/command.ts +190 -0
  82. package/src/modes/components/status-line/index.ts +1 -0
  83. package/src/modes/components/status-line/segments.ts +32 -5
  84. package/src/modes/components/status-line/types.ts +5 -0
  85. package/src/modes/components/tool-status-header.ts +393 -21
  86. package/src/modes/controllers/command-controller.ts +2 -0
  87. package/src/modes/controllers/event-controller.ts +148 -5
  88. package/src/modes/controllers/selector-controller.ts +21 -0
  89. package/src/modes/interactive-mode.ts +47 -1
  90. package/src/modes/types.ts +1 -0
  91. package/src/modes/utils/ui-helpers.ts +1 -0
  92. package/src/prompts/system/system-prompt.md +8 -0
  93. package/src/prompts/tools/search.md +2 -0
  94. package/src/runtime-mcp/client.ts +60 -17
  95. package/src/runtime-mcp/manager.ts +171 -14
  96. package/src/runtime-mcp/pool.test.ts +209 -0
  97. package/src/runtime-mcp/pool.ts +224 -23
  98. package/src/runtime-mcp/transports/stdio.ts +158 -24
  99. package/src/runtime-mcp/types.ts +24 -0
  100. package/src/sdk/broker/broker.ts +5 -4
  101. package/src/sdk/bus/chat-daemon-control.ts +1 -1
  102. package/src/sdk/bus/chat-daemon-runtime.ts +37 -1
  103. package/src/sdk/bus/slack-daemon.ts +92 -2
  104. package/src/sdk/bus/slack-live-provider.ts +12 -0
  105. package/src/sdk/bus/slack-provider.ts +5 -0
  106. package/src/sdk/cli/session-cli.ts +9 -6
  107. package/src/sdk/host/session-runtime.test.ts +2 -2
  108. package/src/sdk/host/session-runtime.ts +275 -43
  109. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  110. package/src/sdk/session-api-key.ts +94 -0
  111. package/src/sdk/session.ts +73 -29
  112. package/src/sdk/transport/serve-cli.ts +3 -3
  113. package/src/session/agent-session.ts +123 -38
  114. package/src/session/contribution-prep.ts +214 -21
  115. package/src/setup/paseo/announce.ts +450 -0
  116. package/src/setup/paseo/setup-deps.ts +15 -3
  117. package/src/slash-commands/builtin-registry.ts +1 -0
  118. package/src/system-prompt.ts +4 -0
  119. package/src/task/executor.ts +2 -1
  120. package/src/task/index.ts +17 -4
  121. package/src/tools/bash.ts +35 -8
  122. package/src/tools/cron.ts +1 -1
  123. package/src/tools/fetch.ts +29 -1
  124. package/src/tools/index.ts +8 -0
  125. package/src/tools/job.ts +8 -2
  126. package/src/tools/monitor.ts +8 -2
  127. package/src/tools/path-utils.ts +7 -0
  128. package/src/tools/search.ts +116 -58
  129. package/src/tools/skill.ts +22 -2
  130. package/src/tools/subagent.ts +3 -1
  131. package/src/tools/tool-catalog.generated.ts +8 -1
  132. package/src/web/insane/bridge.ts +113 -23
  133. package/src/web/search/providers/perplexity.ts +102 -41
@@ -92,6 +92,36 @@ function isReservedIdentity(name: string | undefined): boolean {
92
92
  return isLifecycleEvent(name) || isControlPlaneFrameType(name);
93
93
  }
94
94
 
95
+ /**
96
+ * Chat notifications in lean mode are an answer surface, not a mirror of the
97
+ * session protocol. Lifecycle, identity, and activity frames remain available
98
+ * to verbose consumers, while finalized answers and action-needed events are
99
+ * delivered in lean mode.
100
+ */
101
+ export function shouldPublishChatFrame(
102
+ frame: Record<string, unknown>,
103
+ verbosity: "lean" | "verbose" | undefined,
104
+ ): boolean {
105
+ if (verbosity !== "lean") return frame.type !== "turn_stream" || frame.phase !== "live";
106
+ return frame.type === "turn_stream" && frame.phase === "finalized" && frame.finalAnswer === true;
107
+ }
108
+
109
+ /**
110
+ * A final answer's message ref identifies its one rendered message even when
111
+ * the SDK transport delivers an unpositioned duplicate frame.
112
+ */
113
+ export function publicationIdForFinalChatAnswer(sessionId: string, frame: Record<string, unknown>): string | undefined {
114
+ if (
115
+ frame.type !== "turn_stream" ||
116
+ frame.phase !== "finalized" ||
117
+ frame.finalAnswer !== true ||
118
+ typeof frame.messageRef !== "string" ||
119
+ frame.messageRef.length === 0
120
+ )
121
+ return undefined;
122
+ return `turn:${sessionId}:${frame.messageRef}`;
123
+ }
124
+
95
125
  /** One delivered frame reduced to a single event identity. */
96
126
  type CorrelatedFrame = SessionRouterFrame;
97
127
 
@@ -571,8 +601,9 @@ export class ChatDaemonRuntime {
571
601
  if (this.#attachments.get(attachment.sessionId) !== attachment) return;
572
602
  await this.#attachmentBarriers.get(attachment.sessionId);
573
603
  if (this.#attachments.get(attachment.sessionId) !== attachment) return;
574
- const publicationId = correlated.publicationId;
575
604
  const normalizedFrame = correlated.body;
605
+ const publicationId =
606
+ publicationIdForFinalChatAnswer(attachment.sessionId, normalizedFrame) ?? correlated.publicationId;
576
607
  const bodyType = typeof normalizedFrame.type === "string" ? normalizedFrame.type : undefined;
577
608
  if (isControlPlaneFrameType(correlated.name) || isControlPlaneFrameType(bodyType)) return;
578
609
  if (normalizedFrame.type === "turn_stream" && normalizedFrame.phase === "live") return;
@@ -607,6 +638,11 @@ export class ChatDaemonRuntime {
607
638
  return;
608
639
  }
609
640
  if (!notification) return;
641
+ if (
642
+ notification.type === "frame" &&
643
+ !shouldPublishChatFrame(normalizedFrame, this.input.config.presentation?.verbosity)
644
+ )
645
+ return;
610
646
  const payload = this.#presentation?.fanout(notification)[0];
611
647
  const body = payload?.body;
612
648
  const content =
@@ -58,7 +58,9 @@ function slackPublicationClientMsgId(publicationId: string): string {
58
58
 
59
59
  // Durable filesystem publication leases must outlast one event-loop and persistence turn.
60
60
  const MIN_PUBLICATION_LEASE_MS = 100;
61
+ const INBOUND_REACTION_DEADLINE_MS = 5_000;
61
62
  const RETIREMENT_DRAIN_MS = 5_000;
63
+ const INBOUND_REACTION = "eyes";
62
64
 
63
65
  type SlackProviderWorkScope = {
64
66
  sessionId: string;
@@ -70,6 +72,8 @@ type SlackProviderWork = {
70
72
  scopeKey: string;
71
73
  invalidate(): Promise<void>;
72
74
  abort(): void;
75
+ /** Best-effort work is aborted before the graceful shutdown drain. */
76
+ abortOnStop?: boolean;
73
77
  terminalize(): Promise<void>;
74
78
  settled: Promise<void>;
75
79
  };
@@ -362,6 +366,7 @@ export class SlackNotificationDaemon {
362
366
  this.#startedGeneration = undefined;
363
367
  if (this.#providerRunningGeneration === lifecycleGeneration) this.#providerRunningGeneration = undefined;
364
368
  this.#clearLeaseRecoveryTimer();
369
+ for (const work of this.#workInvalidators) if (work.abortOnStop) work.abort();
365
370
  if (stopProvider) this.#providerStopRequestedGenerations.add(lifecycleGeneration);
366
371
  const providerStop = stopProvider ? Promise.resolve().then(() => this.options.provider.stop()) : undefined;
367
372
  const providerStopResult = providerStop?.then(
@@ -449,6 +454,7 @@ export class SlackNotificationDaemon {
449
454
 
450
455
  /** Accepted inbound effects are durably claimed before their Socket Mode ACK. */
451
456
  async handleEnvelope(envelope: SlackSocketEnvelope): Promise<boolean> {
457
+ const lifecycleGeneration = this.#lifecycleGeneration;
452
458
  if (!text(envelope.envelope_id)) return false;
453
459
  const inbound = messageFromEnvelope(envelope.payload);
454
460
  if (!inbound || inbound.teamId !== this.options.teamId || inbound.channelId !== this.options.channelId) {
@@ -479,6 +485,15 @@ export class SlackNotificationDaemon {
479
485
  this.#inflightInbound.add(inflightKey);
480
486
  try {
481
487
  await this.options.provider.ack(envelope.envelope_id);
488
+ const messageTs = text(inbound.event.ts);
489
+ if (messageTs)
490
+ this.#scheduleInboundReaction(
491
+ claim.sessionId,
492
+ claim.endpoint,
493
+ inbound.channelId,
494
+ messageTs,
495
+ lifecycleGeneration,
496
+ );
482
497
  return await this.#dispatchInbound(claim);
483
498
  } finally {
484
499
  this.#inflightInbound.delete(inflightKey);
@@ -491,6 +506,76 @@ export class SlackNotificationDaemon {
491
506
  return work.finally(() => this.#activeWork.delete(work));
492
507
  }
493
508
 
509
+ /** Track the best-effort acknowledgement in the owning attachment's provider-work scope. */
510
+ #scheduleInboundReaction(
511
+ sessionId: string,
512
+ endpoint: SessionAttachment,
513
+ channel: string,
514
+ timestamp: string,
515
+ lifecycleGeneration: number,
516
+ ): void {
517
+ const scope: SlackProviderWorkScope = {
518
+ sessionId,
519
+ endpointGeneration: endpoint.generation,
520
+ ...(endpoint.authorityId === undefined ? {} : { attachmentAuthorityId: endpoint.authorityId }),
521
+ };
522
+ const scopeKey = slackProviderWorkScopeKey(scope);
523
+ const workGeneration = this.#workGeneration;
524
+ const controller = new AbortController();
525
+ const settled = Promise.withResolvers<void>();
526
+ let abortKind: "deadline" | "lifecycle" | undefined;
527
+ const abort = (kind: "deadline" | "lifecycle"): void => {
528
+ abortKind ??= kind;
529
+ controller.abort();
530
+ };
531
+ const work: SlackProviderWork = {
532
+ scopeKey,
533
+ invalidate: async () => abort("lifecycle"),
534
+ abort: () => abort("lifecycle"),
535
+ abortOnStop: true,
536
+ terminalize: async () => abort("lifecycle"),
537
+ settled: settled.promise,
538
+ };
539
+ this.#workInvalidators.add(work);
540
+ const reaction = (async () => {
541
+ const deadline = setTimeout(() => abort("deadline"), INBOUND_REACTION_DEADLINE_MS);
542
+ try {
543
+ if (
544
+ workGeneration !== this.#workGeneration ||
545
+ lifecycleGeneration !== this.#lifecycleGeneration ||
546
+ this.#retiredProviderWorkScopes.has(scopeKey) ||
547
+ !endpoint.isCurrent()
548
+ ) {
549
+ abort("lifecycle");
550
+ return;
551
+ }
552
+ await this.options.provider.addReaction({
553
+ channel,
554
+ timestamp,
555
+ name: INBOUND_REACTION,
556
+ signal: controller.signal,
557
+ });
558
+ if (abortKind === "deadline")
559
+ logger.warn(
560
+ `Slack inbound acknowledgement reaction exceeded the ${INBOUND_REACTION_DEADLINE_MS}ms deadline and was aborted.`,
561
+ );
562
+ } catch {
563
+ if (abortKind !== "lifecycle")
564
+ logger.warn(
565
+ abortKind === "deadline"
566
+ ? `Slack inbound acknowledgement reaction exceeded the ${INBOUND_REACTION_DEADLINE_MS}ms deadline and was aborted.`
567
+ : "Slack inbound acknowledgement reaction failed.",
568
+ );
569
+ } finally {
570
+ clearTimeout(deadline);
571
+ this.#workInvalidators.delete(work);
572
+ settled.resolve();
573
+ this.#tryReleaseRetiredProviderWorkScope(scopeKey);
574
+ }
575
+ })();
576
+ void this.#track(reaction);
577
+ }
578
+
494
579
  async #invalidateActiveWork(): Promise<void> {
495
580
  this.#workGeneration += 1;
496
581
  await Promise.allSettled([...this.#workInvalidators].map(work => work.invalidate()));
@@ -1496,7 +1581,11 @@ export class SlackNotificationDaemon {
1496
1581
  updatedAt: this.#now(),
1497
1582
  });
1498
1583
  });
1499
- return sessionId && receipt ? { key, endpoint, sessionId, receipt } : undefined;
1584
+ if (!sessionId || !receipt) {
1585
+ await this.#journal.terminalize(effectId, { status: "rejected" });
1586
+ return undefined;
1587
+ }
1588
+ return { key, endpoint, sessionId, receipt };
1500
1589
  }
1501
1590
 
1502
1591
  async #dispatchInbound(claim: {
@@ -2020,7 +2109,8 @@ export class SlackNotificationDaemon {
2020
2109
  current.seenEventIds.includes(routing.eventId) ||
2021
2110
  current.seenInteractionIds.includes(routing.interactionId) ||
2022
2111
  current.seenRetryKeys.includes(routing.retryKey) ||
2023
- (routing.kind === "action" && replyableActionId(current.pendingActionId) !== routing.actionId)
2112
+ (routing.kind === "action" && replyableActionId(current.pendingActionId) !== routing.actionId) ||
2113
+ (routing.kind === "message" && replyableActionId(current.pendingActionId) !== undefined)
2024
2114
  )
2025
2115
  return current;
2026
2116
  receipt = {
@@ -294,6 +294,18 @@ export class SlackLiveProvider implements SlackProviderClient, SlackDiagnosticPr
294
294
  return message;
295
295
  }
296
296
 
297
+ async addReaction(input: { channel: string; timestamp: string; name: string; signal?: AbortSignal }): Promise<void> {
298
+ await this.#api(
299
+ "reactions.add",
300
+ {
301
+ channel: input.channel,
302
+ timestamp: input.timestamp,
303
+ name: input.name,
304
+ },
305
+ input.signal,
306
+ );
307
+ }
308
+
297
309
  async findMessageByClientMsgId(input: {
298
310
  channel: string;
299
311
  clientMsgId: string;
@@ -57,6 +57,7 @@ export interface SlackProviderClient {
57
57
  threadTs?: string;
58
58
  signal?: AbortSignal;
59
59
  }): Promise<SlackMessageSearchResult | null>;
60
+ addReaction?(input: { channel: string; timestamp: string; name: string; signal?: AbortSignal }): Promise<void>;
60
61
  /**
61
62
  * Prove that an operator-supplied timestamp addresses a real message in the
62
63
  * requested channel. Adoption of an existing root is refused when a client
@@ -108,6 +109,10 @@ export class SlackProvider {
108
109
  return (await this.client.findMessageByClientMsgId?.(input)) ?? null;
109
110
  }
110
111
 
112
+ async addReaction(input: { channel: string; timestamp: string; name: string; signal?: AbortSignal }): Promise<void> {
113
+ await this.client.addReaction?.(input);
114
+ }
115
+
111
116
  /** Fail-closed root verification: a client without this capability can never confirm a root. */
112
117
  async findMessageByTimestamp(input: { channel: string; ts: string }): Promise<SlackMessageSearchResult | null> {
113
118
  return (await this.client.findMessageByTimestamp?.(input)) ?? null;
@@ -334,13 +334,14 @@ function throwResponseFailure(response: unknown): void {
334
334
  async function paginatedSessionList(
335
335
  router: SessionRouter,
336
336
  input: JsonRecord = {},
337
- requestKey = `${SDK_SESSION_CLI_LIFECYCLE_ACTOR.namespace}:session.list`,
337
+ requestKey = `${SDK_SESSION_CLI_LIFECYCLE_ACTOR.namespace}:session.list:${randomBytes(12).toString("hex")}`,
338
338
  ): Promise<unknown> {
339
339
  try {
340
340
  const pages = await traverseSessionList(
341
341
  input,
342
342
  async pageInput => {
343
- const response = object(await router.listBrokerSessions(pageInput, requestKey));
343
+ const cursor = typeof pageInput.cursor === "string" ? pageInput.cursor : "initial";
344
+ const response = object(await router.listBrokerSessions(pageInput, `${requestKey}:${cursor}`));
344
345
  if (response?.ok === false) {
345
346
  const failure = object(response.error);
346
347
  throw new SdkClientError(
@@ -375,10 +376,10 @@ type SessionRows = {
375
376
  sessions: SdkSessionRowV1[];
376
377
  };
377
378
 
378
- async function sessionRows(agentDir: string): Promise<SessionRows> {
379
+ async function sessionRows(agentDir: string, input: JsonRecord = {}): Promise<SessionRows> {
379
380
  await ensureBroker({ agentDir });
380
381
  return await withRouter(agentDir, async router => {
381
- const response = await paginatedSessionList(router);
382
+ const response = await paginatedSessionList(router, input);
382
383
  const result = resultObject(response) ?? {};
383
384
  let sessions: SdkSessionRowV1[];
384
385
  try {
@@ -689,7 +690,7 @@ async function runList(agentDir: string, args: SdkSessionCliArgs): Promise<unkno
689
690
  }
690
691
 
691
692
  async function runInspect(agentDir: string, sessionId: string): Promise<unknown> {
692
- const listing = await sessionRows(agentDir);
693
+ const listing = await sessionRows(agentDir, { resolveSessionId: sessionId });
693
694
  const session = listing.sessions.find(candidate => candidate.sessionId === sessionId);
694
695
  if (!session)
695
696
  throw new SdkSessionCliError("session_unavailable", `Session ${sessionId} is not indexed by the broker.`, 1);
@@ -1671,7 +1672,9 @@ export async function runTail(
1671
1672
  sessionId: string,
1672
1673
  args: SdkSessionCliArgs,
1673
1674
  ): Promise<unknown> {
1674
- const row = (await sessionRows(agentDir)).sessions.find(candidate => candidate.sessionId === sessionId);
1675
+ const row = (await sessionRows(agentDir, { resolveSessionId: sessionId })).sessions.find(
1676
+ candidate => candidate.sessionId === sessionId,
1677
+ );
1675
1678
  if (!row)
1676
1679
  throw new SdkSessionCliError("session_unavailable", `Session ${sessionId} is not indexed by the broker.`, 1);
1677
1680
  if (row.deleted)
@@ -2125,7 +2125,7 @@ describe("SessionSdkSessionRuntime", () => {
2125
2125
  while (!transport.sent.some(frame => frame.id === "predecessor-prompt")) await Bun.sleep(10);
2126
2126
  await handlers.get("agent_start")?.({}, ctx); // lifecycle epoch 1
2127
2127
  await handlers.get("agent_end")?.(
2128
- { type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "completed" },
2128
+ { type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "compacted" },
2129
2129
  ctx,
2130
2130
  );
2131
2131
  transport.feed("client", {
@@ -2148,7 +2148,7 @@ describe("SessionSdkSessionRuntime", () => {
2148
2148
  while (abortCalls < 1) await Bun.sleep(10);
2149
2149
  await handlers.get("agent_end")?.({ type: "agent_end" }, ctx); // delayed predecessor
2150
2150
  await handlers.get("agent_end")?.(
2151
- { type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "completed" },
2151
+ { type: "agent_end", stopReason: "maintenance", maintenanceOutcome: "compacted" },
2152
2152
  ctx,
2153
2153
  );
2154
2154
  abortRelease.resolve();