@gajae-code/coding-agent 0.12.6 → 0.12.8

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 (150) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/types/async/job-manager.d.ts +36 -1
  3. package/dist/types/commands/notify.d.ts +4 -0
  4. package/dist/types/config/file-lock.d.ts +3 -0
  5. package/dist/types/config/model-discovery-manager.d.ts +6 -1
  6. package/dist/types/config/model-registry.d.ts +2 -0
  7. package/dist/types/config/resolve-config-value.d.ts +2 -1
  8. package/dist/types/config/settings-schema.d.ts +17 -1
  9. package/dist/types/eval/backend.d.ts +2 -2
  10. package/dist/types/exec/bash-executor.d.ts +8 -10
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
  12. package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/components/hook-selector.d.ts +20 -0
  15. package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
  16. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  17. package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
  18. package/dist/types/sdk/broker/broker.d.ts +5 -1
  19. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
  21. package/dist/types/sdk/bus/config.d.ts +75 -113
  22. package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
  23. package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
  24. package/dist/types/sdk/bus/index.d.ts +1 -1
  25. package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
  26. package/dist/types/sdk/bus/notification-service.d.ts +31 -4
  27. package/dist/types/sdk/bus/session-control.d.ts +8 -3
  28. package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
  29. package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  31. package/dist/types/sdk/client/discovery.d.ts +2 -1
  32. package/dist/types/sdk/host/host.d.ts +2 -23
  33. package/dist/types/sdk/host/query/handlers.d.ts +2 -0
  34. package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
  35. package/dist/types/sdk/index.d.ts +1 -0
  36. package/dist/types/sdk/providers.d.ts +22 -0
  37. package/dist/types/sdk/session-directory.d.ts +1 -1
  38. package/dist/types/session/artifacts.d.ts +5 -8
  39. package/dist/types/session/client-bridge.d.ts +0 -14
  40. package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
  41. package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
  42. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
  43. package/dist/types/session/internal/session-open-errors.d.ts +5 -0
  44. package/dist/types/session/messages.d.ts +1 -4
  45. package/dist/types/session/session-manager.d.ts +7 -6
  46. package/dist/types/session/session-storage.d.ts +2 -0
  47. package/dist/types/session/streaming-output.d.ts +0 -32
  48. package/dist/types/task/receipt.d.ts +1 -2
  49. package/dist/types/task/types.d.ts +43 -178
  50. package/dist/types/tools/bash.d.ts +1 -1
  51. package/dist/types/tools/fetch.d.ts +0 -3
  52. package/dist/types/tools/output-meta.d.ts +5 -37
  53. package/dist/types/tools/subagent.d.ts +15 -0
  54. package/dist/types/tools/todo-write.d.ts +3 -0
  55. package/dist/types/tools/tool-result.d.ts +0 -1
  56. package/package.json +10 -8
  57. package/scripts/build-sdk-package-smoke.ts +51 -0
  58. package/scripts/capture-notifications-settings-showcase.ts +1 -1
  59. package/src/async/job-manager.ts +430 -46
  60. package/src/cli/notify-cli.ts +204 -94
  61. package/src/commands/notify.ts +19 -8
  62. package/src/config/file-lock.ts +6 -0
  63. package/src/config/model-discovery-manager.ts +50 -8
  64. package/src/config/model-registry.ts +862 -75
  65. package/src/config/resolve-config-value.ts +10 -8
  66. package/src/config/settings-schema.ts +25 -15
  67. package/src/config/settings.ts +15 -15
  68. package/src/cursor.ts +13 -20
  69. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
  70. package/src/eval/backend.ts +2 -2
  71. package/src/exec/bash-executor.ts +151 -425
  72. package/src/gjc-runtime/session-state-sidecar.ts +12 -2
  73. package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
  74. package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
  75. package/src/gjc-runtime/workflow-manifest.ts +0 -12
  76. package/src/internal-urls/agent-protocol.ts +38 -98
  77. package/src/internal-urls/artifact-protocol.ts +17 -28
  78. package/src/internal-urls/docs-index.generated.ts +7 -7
  79. package/src/internal-urls/registry-helpers.ts +0 -62
  80. package/src/main.ts +43 -7
  81. package/src/modes/DESIGN.md +11 -11
  82. package/src/modes/acp/acp-agent.ts +0 -24
  83. package/src/modes/acp/acp-client-bridge.ts +11 -154
  84. package/src/modes/components/hook-selector.ts +155 -44
  85. package/src/modes/components/model-selector.ts +242 -49
  86. package/src/modes/components/notifications-settings-editor.ts +631 -85
  87. package/src/modes/controllers/extension-ui-controller.ts +114 -15
  88. package/src/modes/controllers/selector-controller.ts +464 -50
  89. package/src/prompts/tools/bash.md +2 -2
  90. package/src/sdk/acp/adapter.ts +9 -200
  91. package/src/sdk/broker/broker.ts +54 -10
  92. package/src/sdk/broker/lifecycle-ledger.ts +34 -1
  93. package/src/sdk/broker/lifecycle.ts +47 -50
  94. package/src/sdk/bus/chat-daemon-cli.ts +12 -5
  95. package/src/sdk/bus/chat-daemon-control.ts +35 -8
  96. package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
  97. package/src/sdk/bus/config.ts +534 -286
  98. package/src/sdk/bus/discord-live-provider.ts +57 -2
  99. package/src/sdk/bus/discord-provider.ts +21 -0
  100. package/src/sdk/bus/index.ts +105 -359
  101. package/src/sdk/bus/notification-orchestration.ts +371 -68
  102. package/src/sdk/bus/notification-service.ts +431 -130
  103. package/src/sdk/bus/session-control.ts +119 -44
  104. package/src/sdk/bus/slack-live-provider.ts +78 -2
  105. package/src/sdk/bus/slack-provider.ts +24 -0
  106. package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
  107. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  108. package/src/sdk/bus/telegram-daemon-control.ts +3 -3
  109. package/src/sdk/bus/telegram-daemon.ts +23 -15
  110. package/src/sdk/client/discovery.ts +10 -4
  111. package/src/sdk/host/host.ts +9 -187
  112. package/src/sdk/host/query/handlers.ts +20 -1
  113. package/src/sdk/host/reverse-leases.ts +42 -249
  114. package/src/sdk/index.ts +1 -0
  115. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  116. package/src/sdk/protocol/operation-registry.ts +5 -2
  117. package/src/sdk/providers.ts +51 -0
  118. package/src/sdk/session-directory.ts +1 -0
  119. package/src/sdk/session.ts +10 -7
  120. package/src/session/agent-session.ts +59 -202
  121. package/src/session/artifacts.ts +74 -211
  122. package/src/session/client-bridge.ts +0 -15
  123. package/src/session/internal/managed-session-scope.ts +981 -450
  124. package/src/session/internal/managed-session-storage.ts +362 -124
  125. package/src/session/internal/native-publish-outcome.ts +3 -1
  126. package/src/session/internal/session-open-errors.ts +11 -0
  127. package/src/session/messages.ts +7 -32
  128. package/src/session/session-manager.ts +48 -15
  129. package/src/session/session-storage.ts +72 -5
  130. package/src/session/streaming-output.ts +40 -198
  131. package/src/slash-commands/builtin-registry.ts +82 -4
  132. package/src/task/index.ts +237 -65
  133. package/src/task/receipt.ts +2 -4
  134. package/src/task/render.ts +0 -30
  135. package/src/task/types.ts +10 -34
  136. package/src/tools/ask.ts +1 -1
  137. package/src/tools/bash.ts +239 -818
  138. package/src/tools/eval.ts +4 -99
  139. package/src/tools/fetch.ts +15 -77
  140. package/src/tools/find.ts +3 -7
  141. package/src/tools/output-meta.ts +114 -696
  142. package/src/tools/search.ts +4 -9
  143. package/src/tools/subagent.ts +97 -75
  144. package/src/tools/telegram-send.ts +4 -2
  145. package/src/tools/todo-write.ts +47 -0
  146. package/src/tools/tool-result.ts +0 -1
  147. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
  148. package/dist/types/utils/artifact-id.d.ts +0 -2
  149. package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
  150. package/src/utils/artifact-id.ts +0 -6
@@ -2,10 +2,7 @@
2
2
  * Shared helpers for internal-url protocol handlers that resolve session-scoped
3
3
  * artifact IDs.
4
4
  */
5
-
6
- import * as fs from "node:fs";
7
5
  import * as path from "node:path";
8
- import { openRecoveryFsRoot, type RecoveryFsRoot } from "@gajae-code/natives";
9
6
  import type { ResolveContext } from "./types";
10
7
 
11
8
  function addDir(dirs: string[], dir: string | null | undefined): void {
@@ -29,62 +26,3 @@ export function authorizedArtifactsDirsFromContext(context?: ResolveContext): st
29
26
  for (const dir of context?.getAuthorizedArtifactsDirs?.() ?? []) addDir(dirs, dir);
30
27
  return dirs;
31
28
  }
32
-
33
- export interface AuthorizedArtifactsRoot {
34
- readonly directory: string;
35
- readonly dev: bigint;
36
- readonly ino: bigint;
37
- readonly authority: RecoveryFsRoot;
38
- }
39
-
40
- export interface AuthorizedArtifactSnapshot {
41
- bytes: Buffer;
42
- size: number;
43
- }
44
-
45
- export function openAuthorizedArtifactsRoot(directory: string): AuthorizedArtifactsRoot {
46
- if (process.platform !== "linux") throw new Error("artifact_authority_unavailable");
47
- const named = fs.lstatSync(directory, { bigint: true });
48
- if (!named.isDirectory() || named.isSymbolicLink()) throw new Error("unsafe_artifacts_root");
49
- const authority = openRecoveryFsRoot(directory);
50
- const retained = authority.identity();
51
- if (
52
- !retained.ok ||
53
- !retained.identity ||
54
- retained.identity.dev !== named.dev.toString() ||
55
- retained.identity.ino !== named.ino.toString()
56
- ) {
57
- authority.close();
58
- throw new Error("unsafe_artifacts_root");
59
- }
60
- return { directory, dev: named.dev, ino: named.ino, authority };
61
- }
62
-
63
- export function closeAuthorizedArtifactsRoot(root: AuthorizedArtifactsRoot): void {
64
- root.authority.close();
65
- }
66
-
67
- export function readAuthorizedArtifactFile(
68
- root: AuthorizedArtifactsRoot,
69
- filename: string,
70
- maxBytes: number,
71
- ): AuthorizedArtifactSnapshot | null {
72
- if (
73
- path.basename(filename) !== filename ||
74
- !Number.isSafeInteger(maxBytes) ||
75
- maxBytes < 0 ||
76
- maxBytes > 16 * 1024 * 1024
77
- ) {
78
- throw new Error("unsafe_artifact_leaf");
79
- }
80
- if (!root.authority) throw new Error("artifact_authority_unavailable");
81
- const captured = root.authority.read(filename, maxBytes);
82
- if (!captured.ok) {
83
- if (captured.code === "not_found") return null;
84
- throw new Error(`unsafe_artifact_leaf:${captured.code ?? "capture_failed"}`);
85
- }
86
- if (!captured.data || !captured.identity) throw new Error("unsafe_artifact_leaf:missing_evidence");
87
- const size = Number(captured.identity.size);
88
- if (!Number.isSafeInteger(size) || size < 0) throw new Error("unsafe_artifact_leaf:invalid_size");
89
- return { bytes: Buffer.from(captured.data), size };
90
- }
package/src/main.ts CHANGED
@@ -57,10 +57,11 @@ import {
57
57
  discoverAuthStorage,
58
58
  } from "./sdk";
59
59
  import type { AgentSession } from "./session/agent-session";
60
-
60
+ import { SessionMigrationBusyError } from "./session/internal/session-open-errors";
61
61
  import {
62
62
  type ResumeSessionIdentity,
63
63
  resolveResumableSession,
64
+ SessionArtifactCapacityError,
64
65
  type SessionDestination,
65
66
  type SessionDirectoryMigrationPolicy,
66
67
  type SessionInfo,
@@ -562,6 +563,22 @@ export function resolveManagedAgentDirForScope(_cwd: string): string {
562
563
  return getAgentDir();
563
564
  }
564
565
  export const BARE_RESUME_OPEN_ERROR = "Could not open the selected session. Use --resume <id>.";
566
+ const SESSION_ARTIFACT_CAPACITY_RECOVERY_MESSAGE =
567
+ "The selected legacy session's artifacts exceed the supported migration capacity. Archive or remove only that legacy session's artifacts after confirming they are no longer needed, then retry.";
568
+
569
+ function operatorFacingSessionOpenMessage(value: unknown): string | undefined {
570
+ const code =
571
+ value instanceof SessionArtifactCapacityError
572
+ ? value.code
573
+ : value instanceof SessionMigrationBusyError
574
+ ? value.code
575
+ : typeof value === "string"
576
+ ? value
577
+ : undefined;
578
+ if (code === "artifact_capacity_exceeded") return SESSION_ARTIFACT_CAPACITY_RECOVERY_MESSAGE;
579
+ if (code === "migration_busy") return new SessionMigrationBusyError().message;
580
+ return undefined;
581
+ }
565
582
 
566
583
  function isBareResume(parsed: Args): boolean {
567
584
  return (
@@ -1072,6 +1089,7 @@ type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promis
1072
1089
 
1073
1090
  export interface RunRootCommandDependencies {
1074
1091
  createAgentSession?: typeof createAgentSession;
1092
+ createSessionManager?: typeof createSessionManager;
1075
1093
  discoverAuthStorage?: typeof discoverAuthStorage;
1076
1094
  runAcpMode?: (options?: { agentDir?: string }) => Promise<void>;
1077
1095
  settings?: Settings;
@@ -1208,13 +1226,13 @@ export async function runRootCommand(
1208
1226
  undefined,
1209
1227
  resumeMigrationPolicy,
1210
1228
  );
1211
- } catch {
1212
- process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
1229
+ } catch (error) {
1230
+ process.stderr.write(`${operatorFacingSessionOpenMessage(error) ?? BARE_RESUME_OPEN_ERROR}\n`);
1213
1231
  if (!deps.suppressProcessExit) process.exitCode = 1;
1214
1232
  return;
1215
1233
  }
1216
1234
  if (opened.kind === "error") {
1217
- process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
1235
+ process.stderr.write(`${operatorFacingSessionOpenMessage(opened.reason) ?? BARE_RESUME_OPEN_ERROR}\n`);
1218
1236
  if (!deps.suppressProcessExit) process.exitCode = 1;
1219
1237
  return;
1220
1238
  }
@@ -1381,9 +1399,27 @@ export async function runRootCommand(
1381
1399
 
1382
1400
  // Create session manager based on CLI flags. A bare resume was strictly opened
1383
1401
  // before startup discovery, so it never reaches create-or-open behavior here.
1384
- const sessionManager =
1385
- bareResumeSessionManager ??
1386
- (await logger.time("createSessionManager", createSessionManager, parsedArgs, cwd, settingsInstance));
1402
+ let sessionManager: SessionManager | undefined = bareResumeSessionManager;
1403
+ if (!sessionManager) {
1404
+ try {
1405
+ sessionManager = await logger.time(
1406
+ "createSessionManager",
1407
+ deps.createSessionManager ?? createSessionManager,
1408
+ parsedArgs,
1409
+ cwd,
1410
+ settingsInstance,
1411
+ );
1412
+ } catch (error) {
1413
+ const message = operatorFacingSessionOpenMessage(error);
1414
+ if (!message) throw error;
1415
+ process.stderr.write(`${message}\n`);
1416
+ if (!deps.suppressProcessExit) process.exitCode = 1;
1417
+ authStorage.close();
1418
+ stopThemeWatcher();
1419
+ await postmortem.cleanup();
1420
+ return;
1421
+ }
1422
+ }
1387
1423
 
1388
1424
  // Restore the resumed session's working directory so the HUD branch, the
1389
1425
  // project path, and the agent's tools all match where the session was
@@ -187,21 +187,21 @@ combine, or substitute them.
187
187
 
188
188
  | State ID | Required condition represented |
189
189
  | --- | --- |
190
- | `home-unconfigured` | No configured notification destination. |
191
- | `home-configured-inactive` | Credentials/configuration exist; current session is inactive. |
190
+ | `home-unconfigured` | No complete notification provider. |
191
+ | `home-configured-inactive` | Provider configuration and desired intent exist; current session is inactive. |
192
192
  | `home-runtime-active` | Current session endpoint is active. |
193
193
  | `home-local-off` | Current session is explicitly locally disabled. |
194
- | `home-env-off` | Environment hard-off suppresses the surface/runtime. |
195
- | `home-env-on` | Explicit environment opt-in enables the current session. |
196
- | `home-discord-only` | Global Discord configuration without Telegram setup. |
197
- | `home-slack-only` | Global Slack configuration without Telegram setup. |
198
- | `setup-provider` | Provider choice is focused. |
194
+ | `home-env-off` | Process-level environment hard-off suppresses the surface/runtime. |
195
+ | `home-env-on` | Explicit environment opt-in enables automatic current-session admission. |
196
+ | `home-discord-only` | Discord is complete, desired on, and effective without Telegram. |
197
+ | `home-slack-only` | Slack is complete, desired on, and effective without Telegram. |
198
+ | `setup-provider` | First-class Telegram, Discord, and Slack provider choice is focused. |
199
199
  | `setup-chat-entry` | Telegram chat ID field is focused. |
200
200
  | `setup-token-entry` | Masked Telegram token field is focused. |
201
201
  | `setup-validating` | Token/destination validation is pending. |
202
202
  | `setup-threaded-warning` | Threaded mode compatibility warning is visible. |
203
203
  | `setup-pairing` | Cancellable private-chat pairing/discovery is pending. |
204
- | `setup-review` | Sanitized setup choices await explicit save. |
204
+ | `setup-review` | Sanitized provider, secret action, desired intent, and destination await explicit save. |
205
205
  | `saving` | Durable atomic save is in progress and guarded. |
206
206
  | `health-probing` | Non-cancellable health probe is in progress and guarded. |
207
207
  | `health-ok` | Health report is successful. |
@@ -211,12 +211,12 @@ combine, or substitute them.
211
211
  | `recovering` | Recovery action is in progress and guarded. |
212
212
  | `reconnecting` | Reconnect action is in progress and guarded. |
213
213
  | `navigation-locked` | A guarded operation explains why Tab/Escape cannot leave. |
214
- | `confirmation-remove` | Adapter-local Telegram removal awaits confirmation. |
215
- | `confirmation-disable` | Global disable awaits confirmation. |
214
+ | `confirmation-remove` | Adapter-local Telegram removal awaits confirmation; sibling/global state is preserved. |
215
+ | `confirmation-disable` | Global disable awaits confirmation and preserves provider credentials/intent. |
216
216
  | `success` | A completed operation has concise success copy. |
217
217
  | `preferences` | Notification preferences are visible and editable. |
218
218
  | `error` | A sanitized operation failure has recovery guidance. |
219
- | `foreign-blocked` | A foreign/unknown daemon identity blocks activation safely. |
219
+ | `foreign-blocked` | Telegram is excluded from the shared endpoint while effective chat siblings use isolated discovery. |
220
220
  | `blocked-restore-retain` | A blocked post-save identity race requires Restore or Retain before navigation. |
221
221
  | `cancellation` | A cancellable setup/pairing action was cancelled and restored. |
222
222
  | `narrow-cjk` | Narrow localized CJK content exercises semantic line wrapping. |
@@ -660,10 +660,6 @@ export function createAcpReverseConnection(connection: AgentSideConnection, sess
660
660
  "fs.readTextFile": "fs/read_text_file",
661
661
  "fs.writeTextFile": "fs/write_text_file",
662
662
  "terminal.create": "terminal/create",
663
- "terminal.output": "terminal/output",
664
- "terminal.waitForExit": "terminal/wait_for_exit",
665
- "terminal.kill": "terminal/kill",
666
- "terminal.release": "terminal/release",
667
663
  "ui.elicit": "elicitation/create",
668
664
  };
669
665
  return {
@@ -672,26 +668,6 @@ export function createAcpReverseConnection(connection: AgentSideConnection, sess
672
668
  params: JsonObject,
673
669
  options?: { cancellationSignal?: AbortSignal },
674
670
  ): Promise<unknown> => {
675
- if (method === "terminal.publish") {
676
- const toolCallId = typeof params.toolCallId === "string" ? params.toolCallId : undefined;
677
- const terminalId = typeof params.terminalId === "string" ? params.terminalId : undefined;
678
- if (!toolCallId || !terminalId) {
679
- throw new AcpSdkAdapterError(
680
- "invalid_params",
681
- "ACP terminal publication requires toolCallId and terminalId.",
682
- );
683
- }
684
- await connection.sessionUpdate({
685
- sessionId,
686
- update: {
687
- sessionUpdate: "tool_call_update",
688
- toolCallId,
689
- status: "in_progress",
690
- content: [{ type: "terminal", terminalId }],
691
- },
692
- });
693
- return {};
694
- }
695
671
  const name = methods[method];
696
672
  if (!name)
697
673
  throw new AcpSdkAdapterError("acp_reverse_unavailable", `ACP reverse method is unavailable: ${method}`);
@@ -4,8 +4,6 @@
4
4
  * `AgentSession`) can route through the client when it advertises the
5
5
  * relevant capabilities at `initialize` time.
6
6
  */
7
-
8
- import { randomUUID } from "node:crypto";
9
7
  import type {
10
8
  PermissionOption as AcpPermissionOption,
11
9
  TerminalHandle as AcpTerminalHandle,
@@ -21,25 +19,10 @@ import type {
21
19
  ClientBridgePermissionOption,
22
20
  ClientBridgePermissionOutcome,
23
21
  ClientBridgePermissionToolCall,
24
- ClientBridgeProviderLeaseIdentity,
25
22
  ClientBridgeTerminalHandle,
26
23
  } from "../../session/client-bridge";
27
24
  import { resolveAcpPermissionMode } from "./permission-mode";
28
25
 
29
- const ACP_TERMINAL_PUBLICATION_WAIT_MS = 500;
30
-
31
- async function settleTerminalCleanup(operation: () => Promise<unknown>): Promise<boolean> {
32
- return await Promise.race([
33
- Promise.resolve()
34
- .then(operation)
35
- .then(
36
- () => true,
37
- () => false,
38
- ),
39
- Bun.sleep(ACP_TERMINAL_PUBLICATION_WAIT_MS).then(() => false),
40
- ]);
41
- }
42
-
43
26
  export { type AcpPermissionMode, resolveAcpPermissionMode } from "./permission-mode";
44
27
 
45
28
  export function createAcpClientBridge(
@@ -56,15 +39,6 @@ export function createAcpClientBridge(
56
39
  };
57
40
 
58
41
  const bridge: ClientBridge = { capabilities, deferAgentInitiatedTurns: true };
59
- const connectionId = `acp:${sessionId}`;
60
- const connectionGeneration = 1;
61
- const terminalProviderLeaseId = `${connectionId}:${randomUUID()}`;
62
- const terminalProviderIdentity: ClientBridgeProviderLeaseIdentity = {
63
- leaseId: terminalProviderLeaseId,
64
- connectionId,
65
- connectionGeneration,
66
- fence: randomUUID(),
67
- };
68
42
 
69
43
  if (capabilities.readTextFile) {
70
44
  bridge.readTextFile = async params => {
@@ -89,49 +63,8 @@ export function createAcpClientBridge(
89
63
  }
90
64
 
91
65
  if (capabilities.terminal) {
92
- let terminalPublicationFailed = false;
93
- bridge.quarantineProviderLease = async (providerLease, _reason) => {
94
- if (
95
- typeof providerLease === "string" ||
96
- providerLease.leaseId !== terminalProviderIdentity.leaseId ||
97
- providerLease.connectionId !== terminalProviderIdentity.connectionId ||
98
- providerLease.connectionGeneration !== terminalProviderIdentity.connectionGeneration ||
99
- providerLease.fence !== terminalProviderIdentity.fence
100
- )
101
- throw new Error("terminal provider lease mismatch");
102
- terminalPublicationFailed = true;
103
- };
104
- let terminalCreationTail = Promise.resolve();
105
- const withTerminalCreationAdmission = async <T>(operation: () => Promise<T>): Promise<T> => {
106
- const previous = terminalCreationTail;
107
- const release = Promise.withResolvers<void>();
108
- terminalCreationTail = previous.then(() => release.promise);
109
- await previous;
110
- try {
111
- return await operation();
112
- } finally {
113
- release.resolve();
114
- }
115
- };
116
66
  bridge.createTerminal = (params: ClientBridgeCreateTerminalParams) =>
117
- withTerminalCreationAdmission(async () => {
118
- if (terminalPublicationFailed) throw new Error("terminal provider quarantined after publication failure");
119
- const creationTimeout = AbortSignal.timeout(ACP_TERMINAL_PUBLICATION_WAIT_MS);
120
- const creationSignal = params.signal ? AbortSignal.any([params.signal, creationTimeout]) : creationTimeout;
121
- return await createTerminalHandle(
122
- connection,
123
- sessionId,
124
- params,
125
- creationSignal,
126
- terminalProviderIdentity,
127
- () => {
128
- terminalPublicationFailed = true;
129
- },
130
- () => {
131
- terminalPublicationFailed = true;
132
- },
133
- );
134
- });
67
+ createTerminalHandle(connection, sessionId, params);
135
68
  }
136
69
 
137
70
  if (promptPermission) {
@@ -146,97 +79,21 @@ async function createTerminalHandle(
146
79
  connection: AgentSideConnection,
147
80
  sessionId: string,
148
81
  params: ClientBridgeCreateTerminalParams,
149
- creationSignal: AbortSignal,
150
- providerIdentity: ClientBridgeProviderLeaseIdentity,
151
- onCreationUncertain: () => void,
152
- onPublicationFailure: () => void,
153
82
  ): Promise<ClientBridgeTerminalHandle> {
154
- if (creationSignal.aborted) throw new Error("terminal creation aborted");
155
- const creation = Promise.resolve().then(() =>
156
- connection.createTerminal({
157
- sessionId,
158
- command: params.command,
159
- ...(params.args ? { args: params.args } : {}),
160
- ...(params.env ? { env: params.env } : {}),
161
- ...(params.cwd ? { cwd: params.cwd } : {}),
162
- ...(typeof params.outputByteLimit === "number" ? { outputByteLimit: params.outputByteLimit } : {}),
163
- }),
164
- );
165
- const creationAborted = Promise.withResolvers<"aborted">();
166
- const onCreationAbort = () => creationAborted.resolve("aborted");
167
- creationSignal.addEventListener("abort", onCreationAbort, { once: true });
168
- if (creationSignal.aborted) creationAborted.resolve("aborted");
169
- const creationOutcome = await Promise.race([
170
- creation.then(
171
- handle => ({ status: "created" as const, handle }),
172
- error => ({ status: "failed" as const, error }),
173
- ),
174
- creationAborted.promise.then(() => ({ status: "aborted" as const })),
175
- ]);
176
- creationSignal.removeEventListener("abort", onCreationAbort);
177
- if (creationOutcome.status === "aborted") {
178
- onCreationUncertain();
179
- void creation.then(
180
- async handle => {
181
- await settleTerminalCleanup(() => handle.kill());
182
- await settleTerminalCleanup(() => handle.release());
183
- },
184
- () => undefined,
185
- );
186
- throw new Error("terminal creation aborted");
187
- }
188
- if (creationOutcome.status === "failed") {
189
- onCreationUncertain();
190
- throw creationOutcome.error;
191
- }
192
- const handle = creationOutcome.handle;
193
- const publication = Promise.resolve().then(() =>
194
- connection.sessionUpdate({
195
- sessionId,
196
- update: {
197
- sessionUpdate: "tool_call_update",
198
- toolCallId: params.toolCallId,
199
- status: "in_progress",
200
- content: [{ type: "terminal", terminalId: handle.id }],
201
- },
202
- }),
203
- );
204
- const aborted = Promise.withResolvers<"aborted">();
205
- const onAbort = () => aborted.resolve("aborted");
206
- params.signal?.addEventListener("abort", onAbort, { once: true });
207
- if (params.signal?.aborted) aborted.resolve("aborted");
208
- const outcome = await Promise.race([
209
- publication.then(
210
- () => ({ status: "published" as const }),
211
- error => ({ status: "failed" as const, error }),
212
- ),
213
- Bun.sleep(ACP_TERMINAL_PUBLICATION_WAIT_MS).then(() => ({ status: "timeout" as const })),
214
- aborted.promise.then(() => ({ status: "aborted" as const })),
215
- ]);
216
- params.signal?.removeEventListener("abort", onAbort);
217
- if (outcome.status !== "published") {
218
- const killed = await settleTerminalCleanup(() => handle.kill());
219
- const released = await settleTerminalCleanup(() => handle.release());
220
- if (!params.signal?.aborted || !killed || !released) onPublicationFailure();
221
- if (outcome.status === "failed") throw outcome.error;
222
- throw new Error(
223
- outcome.status === "aborted"
224
- ? "terminal publication aborted"
225
- : `terminal publication did not settle within ${ACP_TERMINAL_PUBLICATION_WAIT_MS}ms`,
226
- );
227
- }
228
- return wrapTerminalHandle(handle, providerIdentity.leaseId, providerIdentity);
83
+ const handle = await connection.createTerminal({
84
+ sessionId,
85
+ command: params.command,
86
+ ...(params.args ? { args: params.args } : {}),
87
+ ...(params.env ? { env: params.env } : {}),
88
+ ...(params.cwd ? { cwd: params.cwd } : {}),
89
+ ...(typeof params.outputByteLimit === "number" ? { outputByteLimit: params.outputByteLimit } : {}),
90
+ });
91
+ return wrapTerminalHandle(handle);
229
92
  }
230
93
 
231
- function wrapTerminalHandle(
232
- handle: AcpTerminalHandle,
233
- providerLeaseId: string,
234
- providerLeaseIdentity: ClientBridgeProviderLeaseIdentity,
235
- ): ClientBridgeTerminalHandle {
94
+ function wrapTerminalHandle(handle: AcpTerminalHandle): ClientBridgeTerminalHandle {
236
95
  return {
237
96
  terminalId: handle.id,
238
- providerLeaseId,
239
- providerLeaseIdentity,
240
97
  async currentOutput() {
241
98
  const out = await handle.currentOutput();
242
99
  return {