@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.
- package/CHANGELOG.md +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- 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
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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
|
package/src/modes/DESIGN.md
CHANGED
|
@@ -187,21 +187,21 @@ combine, or substitute them.
|
|
|
187
187
|
|
|
188
188
|
| State ID | Required condition represented |
|
|
189
189
|
| --- | --- |
|
|
190
|
-
| `home-unconfigured` | No
|
|
191
|
-
| `home-configured-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` |
|
|
195
|
-
| `home-env-on` | Explicit environment opt-in enables
|
|
196
|
-
| `home-discord-only` |
|
|
197
|
-
| `home-slack-only` |
|
|
198
|
-
| `setup-provider` |
|
|
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
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 {
|