@gajae-code/coding-agent 0.11.10 → 0.12.0
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 +86 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +86 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +18 -2
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
- package/dist/types/sdk/host/control/operations.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +51 -19
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +59 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +49 -2
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +91 -7
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +24 -0
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +84 -3
- package/src/config/settings.ts +254 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -921
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +979 -71
- package/src/gjc-runtime/state-runtime.ts +50 -12
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +10 -11
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +597 -67
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +76 -5
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +62 -6
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +127 -29
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/lifecycle.ts +107 -7
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +503 -60
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
- package/src/sdk/bus/telegram-daemon.ts +531 -53
- package/src/sdk/host/control/dispatch.ts +7 -1
- package/src/sdk/host/control/operations.ts +2 -1
- package/src/sdk/host/host.ts +25 -3
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +76 -0
- package/src/sdk/protocol/operation-registry.ts +8 -2
- package/src/sdk/session.ts +25 -2
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +720 -190
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +40 -10
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +1372 -42
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +2 -0
- package/src/task/types.ts +22 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type AuthenticateRequest,
|
|
7
7
|
type AuthenticateResponse,
|
|
8
8
|
type AuthMethod,
|
|
9
|
+
type AvailableCommand,
|
|
9
10
|
type ClientCapabilities,
|
|
10
11
|
type CloseSessionRequest,
|
|
11
12
|
type CloseSessionResponse,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
PROTOCOL_VERSION,
|
|
25
26
|
type PromptRequest,
|
|
26
27
|
type PromptResponse,
|
|
28
|
+
RequestError,
|
|
27
29
|
type ResumeSessionRequest,
|
|
28
30
|
type ResumeSessionResponse,
|
|
29
31
|
type SessionInfo,
|
|
@@ -33,7 +35,7 @@ import {
|
|
|
33
35
|
type SetSessionModeRequest,
|
|
34
36
|
type SetSessionModeResponse,
|
|
35
37
|
} from "@agentclientprotocol/sdk";
|
|
36
|
-
import { getAgentDir } from "@gajae-code/utils";
|
|
38
|
+
import { getAgentDir, logger } from "@gajae-code/utils";
|
|
37
39
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
38
40
|
import {
|
|
39
41
|
type AcpProviderRegistration,
|
|
@@ -41,9 +43,15 @@ import {
|
|
|
41
43
|
AcpSdkAdapter,
|
|
42
44
|
AcpSdkAdapterError,
|
|
43
45
|
} from "../../sdk/acp";
|
|
46
|
+
import { resolveAcpFinalText } from "../../sdk/acp/final-text";
|
|
47
|
+
import { ACP_MCP_LIFECYCLE_TIMEOUT_MS, type SessionLifecycleMcpServer } from "../../sdk/acp/mcp";
|
|
44
48
|
import { ensureBroker } from "../../sdk/broker/ensure";
|
|
45
49
|
import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../../sdk/client";
|
|
46
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
buildToolCallStartUpdate,
|
|
52
|
+
mapAgentSessionEventToAcpSessionUpdates,
|
|
53
|
+
mapAgentWireEventPayloadToAcpSessionUpdates,
|
|
54
|
+
} from "./acp-event-mapper";
|
|
47
55
|
import { resolveAcpPermissionMode } from "./permission-mode";
|
|
48
56
|
import type { AcpStartupOptions } from "./startup-options";
|
|
49
57
|
import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
|
|
@@ -52,6 +60,8 @@ const ACP_DEFAULT_MODE_ID = "default";
|
|
|
52
60
|
const ACP_PLAN_MODE_ID = "plan";
|
|
53
61
|
const MODE_CONFIG_ID = "mode";
|
|
54
62
|
const MODEL_CONFIG_ID = "model";
|
|
63
|
+
const MODEL_PRESET_CONFIG_KEY = "modelPreset";
|
|
64
|
+
const ACP_CUSTOM_MODEL_PRESET = "__custom__";
|
|
55
65
|
const THINKING_CONFIG_ID = "thinking";
|
|
56
66
|
const SESSION_PAGE_SIZE = 50;
|
|
57
67
|
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
@@ -59,11 +69,10 @@ const MAX_ACP_REPLAY_PAGES = 10_000;
|
|
|
59
69
|
|
|
60
70
|
type JsonObject = Record<string, unknown>;
|
|
61
71
|
/**
|
|
62
|
-
* ACP prompt completion is tied to
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* can never settle the waiter that acknowledgement creates.
|
|
72
|
+
* ACP prompt completion is tied to lifecycle activity observed after dispatch.
|
|
73
|
+
* A fast host may stream and finish a turn before acknowledging `turn.prompt`,
|
|
74
|
+
* so pre-ack activity is retained but cannot resolve the waiter until the
|
|
75
|
+
* acknowledgement supplies its command/turn correlation.
|
|
67
76
|
*/
|
|
68
77
|
interface PromptWaiter {
|
|
69
78
|
cancelRequested: boolean;
|
|
@@ -75,6 +84,7 @@ interface PromptWaiter {
|
|
|
75
84
|
boundary: number;
|
|
76
85
|
correlation: PromptCorrelation;
|
|
77
86
|
messageProgress?: { textEmitted: boolean; thoughtEmitted: boolean };
|
|
87
|
+
emittedAssistantText: string;
|
|
78
88
|
pendingTerminal?: PromptCorrelation;
|
|
79
89
|
resolve: (response: PromptResponse) => void;
|
|
80
90
|
reject: (error: Error) => void;
|
|
@@ -97,6 +107,10 @@ type SessionRecord = {
|
|
|
97
107
|
inboundSequence: number;
|
|
98
108
|
/** Updated at ingress so a prompt acknowledgement can distinguish a steer from a fresh turn. */
|
|
99
109
|
busy: boolean;
|
|
110
|
+
/** Start/update args retained because tool_execution_end does not carry them. */
|
|
111
|
+
toolArgs: Map<string, unknown>;
|
|
112
|
+
/** Actionable model-profile authentication failure detected before prompt dispatch. */
|
|
113
|
+
authFailure?: string;
|
|
100
114
|
activePrompt?: PromptWaiter;
|
|
101
115
|
};
|
|
102
116
|
type Endpoint = { url: string; token: string };
|
|
@@ -106,6 +120,9 @@ type BrokerSession = {
|
|
|
106
120
|
locator?: { repo?: string };
|
|
107
121
|
live?: boolean;
|
|
108
122
|
endpointGeneration?: number;
|
|
123
|
+
endpointMtimeMs?: number;
|
|
124
|
+
title?: string;
|
|
125
|
+
updatedAt?: string;
|
|
109
126
|
};
|
|
110
127
|
|
|
111
128
|
function parseAcpStartupOptions(value: unknown): AcpStartupOptions | undefined {
|
|
@@ -136,7 +153,12 @@ function aggregateAcpFailure(code: string, message: string, failures: unknown[])
|
|
|
136
153
|
}
|
|
137
154
|
|
|
138
155
|
/** Applies ACP's offset cursor after narrowing the broker listing to the requested cwd. */
|
|
139
|
-
export function paginateAcpSessions(
|
|
156
|
+
export function paginateAcpSessions(
|
|
157
|
+
listed: unknown[],
|
|
158
|
+
cwd: string | undefined,
|
|
159
|
+
offset: number,
|
|
160
|
+
sessionMetadata: ReadonlyMap<string, { title?: string; updatedAt?: string }> = new Map(),
|
|
161
|
+
): ListSessionsResponse {
|
|
140
162
|
const filtered = listed
|
|
141
163
|
.map(value => object(value) as BrokerSession | undefined)
|
|
142
164
|
.filter(
|
|
@@ -144,12 +166,28 @@ export function paginateAcpSessions(listed: unknown[], cwd: string | undefined,
|
|
|
144
166
|
typeof value?.sessionId === "string" && typeof value.locator?.repo === "string",
|
|
145
167
|
)
|
|
146
168
|
.filter(value => !cwd || value.locator.repo === cwd);
|
|
147
|
-
const sessions = filtered
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
const sessions = filtered.slice(offset, offset + SESSION_PAGE_SIZE).map(value => {
|
|
170
|
+
const metadata = sessionMetadata.get(value.sessionId);
|
|
171
|
+
const updatedAt =
|
|
172
|
+
typeof metadata?.updatedAt === "string"
|
|
173
|
+
? metadata.updatedAt
|
|
174
|
+
: typeof value.updatedAt === "string"
|
|
175
|
+
? value.updatedAt
|
|
176
|
+
: typeof value.endpointMtimeMs === "number" && Number.isFinite(value.endpointMtimeMs)
|
|
177
|
+
? new Date(value.endpointMtimeMs).toISOString()
|
|
178
|
+
: undefined;
|
|
179
|
+
return {
|
|
180
|
+
sessionId: value.sessionId,
|
|
181
|
+
cwd: value.locator.repo,
|
|
182
|
+
title:
|
|
183
|
+
typeof metadata?.title === "string" && metadata.title
|
|
184
|
+
? metadata.title
|
|
185
|
+
: typeof value.title === "string" && value.title
|
|
186
|
+
? value.title
|
|
187
|
+
: value.sessionId,
|
|
188
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
189
|
+
} satisfies SessionInfo;
|
|
190
|
+
});
|
|
153
191
|
return {
|
|
154
192
|
sessions,
|
|
155
193
|
nextCursor: offset + sessions.length < filtered.length ? String(offset + sessions.length) : undefined,
|
|
@@ -180,6 +218,26 @@ function pageItems(value: unknown): unknown[] {
|
|
|
180
218
|
return Array.isArray(page?.items) ? page.items : [];
|
|
181
219
|
}
|
|
182
220
|
|
|
221
|
+
/** Build the ACP command palette from the shared builtins and live SDK skill state. */
|
|
222
|
+
export function acpAvailableCommandsFromSkills(query: unknown): AvailableCommand[] {
|
|
223
|
+
const commands = new Map<string, AvailableCommand>();
|
|
224
|
+
for (const item of pageItems(query)) {
|
|
225
|
+
const skill = object(item);
|
|
226
|
+
if (typeof skill?.name !== "string" || !skill.name) continue;
|
|
227
|
+
const name = `skill:${skill.name}`;
|
|
228
|
+
if (commands.has(name)) continue;
|
|
229
|
+
commands.set(name, {
|
|
230
|
+
name,
|
|
231
|
+
description:
|
|
232
|
+
typeof skill.description === "string" && skill.description
|
|
233
|
+
? skill.description
|
|
234
|
+
: `Run the ${skill.name} skill`,
|
|
235
|
+
input: { hint: "[request]" },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return [...commands.values()];
|
|
239
|
+
}
|
|
240
|
+
|
|
183
241
|
function correlationFrom(...values: unknown[]): PromptCorrelation {
|
|
184
242
|
const correlation: PromptCorrelation = {};
|
|
185
243
|
for (const value of values) {
|
|
@@ -268,6 +326,7 @@ function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
|
|
|
268
326
|
const type = frame.state === "busy" ? "agent_start" : frame.state === "idle" ? "agent_end" : undefined;
|
|
269
327
|
return type ? { event: { type, ...correlationFrom(frame) } } : undefined;
|
|
270
328
|
}
|
|
329
|
+
if (frame.type === "agent_start" || frame.type === "agent_end") return { event: frame };
|
|
271
330
|
if (frame.type !== "event") return undefined;
|
|
272
331
|
const payload = object(frame.payload);
|
|
273
332
|
if (!payload) return undefined;
|
|
@@ -331,6 +390,19 @@ function configValues(query: unknown): Map<string, string> {
|
|
|
331
390
|
return values;
|
|
332
391
|
}
|
|
333
392
|
|
|
393
|
+
function modelPresetConfigOptions(query: unknown, current: string): { value: string; name: string }[] {
|
|
394
|
+
const options = new Map<string, string>();
|
|
395
|
+
for (const item of pageItems(query)) {
|
|
396
|
+
const profile = object(item);
|
|
397
|
+
if (!profile || typeof profile.id !== "string") continue;
|
|
398
|
+
if (profile.available === false && profile.id !== current) continue;
|
|
399
|
+
options.set(profile.id, typeof profile.displayName === "string" ? profile.displayName : profile.id);
|
|
400
|
+
}
|
|
401
|
+
if (current === ACP_CUSTOM_MODEL_PRESET) options.set(ACP_CUSTOM_MODEL_PRESET, "Custom (current model)");
|
|
402
|
+
else if (!options.has(current)) options.set(current, current);
|
|
403
|
+
return [...options].map(([value, name]) => ({ value, name }));
|
|
404
|
+
}
|
|
405
|
+
|
|
334
406
|
function modelConfigOptions(query: unknown, current: string | undefined): { value: string; name: string }[] {
|
|
335
407
|
const options = new Map<string, string>();
|
|
336
408
|
for (const item of pageItems(query)) {
|
|
@@ -348,9 +420,10 @@ const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhi
|
|
|
348
420
|
name: value,
|
|
349
421
|
}));
|
|
350
422
|
|
|
351
|
-
/** Maps live canonical SDK config and model
|
|
352
|
-
export function acpSessionStateFromConfig(query: unknown,
|
|
423
|
+
/** Maps live canonical SDK config and the selected model catalog into the ACP 1.2.1 session state surface. */
|
|
424
|
+
export function acpSessionStateFromConfig(query: unknown, modelCatalogQuery?: unknown, modelPreset?: string) {
|
|
353
425
|
const values = configValues(query);
|
|
426
|
+
const useModelPresets = modelPreset !== undefined;
|
|
354
427
|
const currentModeId = values.get(MODE_CONFIG_ID) === ACP_PLAN_MODE_ID ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
|
|
355
428
|
return {
|
|
356
429
|
configOptions: [
|
|
@@ -365,15 +438,28 @@ export function acpSessionStateFromConfig(query: unknown, modelsQuery?: unknown)
|
|
|
365
438
|
],
|
|
366
439
|
},
|
|
367
440
|
...ACP_CONFIG_OPTIONS.flatMap(option => {
|
|
368
|
-
const value =
|
|
441
|
+
const value =
|
|
442
|
+
option.id === MODEL_CONFIG_ID && useModelPresets
|
|
443
|
+
? (values.get(MODEL_PRESET_CONFIG_KEY) ?? ACP_CUSTOM_MODEL_PRESET)
|
|
444
|
+
: values.get(option.id);
|
|
369
445
|
if (value === undefined) return [];
|
|
370
446
|
const options =
|
|
371
447
|
option.id === MODEL_CONFIG_ID
|
|
372
|
-
?
|
|
448
|
+
? useModelPresets
|
|
449
|
+
? modelPresetConfigOptions(modelCatalogQuery, value)
|
|
450
|
+
: modelConfigOptions(modelCatalogQuery, value)
|
|
373
451
|
: option.id === THINKING_CONFIG_ID
|
|
374
452
|
? THINKING_CONFIG_OPTIONS
|
|
375
453
|
: [...option.options];
|
|
376
|
-
return [
|
|
454
|
+
return [
|
|
455
|
+
{
|
|
456
|
+
...option,
|
|
457
|
+
...(option.id === MODEL_CONFIG_ID && useModelPresets ? { name: "Preset" } : {}),
|
|
458
|
+
type: "select" as const,
|
|
459
|
+
currentValue: value,
|
|
460
|
+
options,
|
|
461
|
+
},
|
|
462
|
+
];
|
|
377
463
|
}),
|
|
378
464
|
],
|
|
379
465
|
modes: {
|
|
@@ -447,6 +533,34 @@ export function acpPromptPayload(blocks: PromptRequest["prompt"]): {
|
|
|
447
533
|
return { text: text.join("\n"), images };
|
|
448
534
|
}
|
|
449
535
|
|
|
536
|
+
/**
|
|
537
|
+
* `AcpSdkAdapterError.code` is an internal string, but the SDK only derives a
|
|
538
|
+
* JSON-RPC code from a `RequestError`. Everything else collapses to an opaque
|
|
539
|
+
* `-32603 Internal error`, which hides the reason and defeats client-side
|
|
540
|
+
* recovery (an ACP client cannot see that it must authenticate). Map the codes
|
|
541
|
+
* that have a defined ACP/JSON-RPC counterpart onto a real `RequestError`.
|
|
542
|
+
*/
|
|
543
|
+
export function acpRequestFailure(error: unknown): unknown {
|
|
544
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
545
|
+
if (typeof code !== "string") return error;
|
|
546
|
+
const message = error instanceof Error ? error.message : code;
|
|
547
|
+
switch (code) {
|
|
548
|
+
case "authentication_failed":
|
|
549
|
+
return RequestError.authRequired({ code, details: message }, message);
|
|
550
|
+
case "not_found":
|
|
551
|
+
return RequestError.resourceNotFound(message);
|
|
552
|
+
case "invalid_input":
|
|
553
|
+
case "unsupported":
|
|
554
|
+
case "unsupported_content":
|
|
555
|
+
return RequestError.invalidParams({ code, details: message }, message);
|
|
556
|
+
default:
|
|
557
|
+
// The remaining internal codes (conflict, unavailable, busy, …) have no ACP
|
|
558
|
+
// counterpart and stay -32603. Keep the discriminator in `data` so a client can
|
|
559
|
+
// branch on retry/reconnect instead of parsing an English message.
|
|
560
|
+
return RequestError.internalError({ code, details: message }, message);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
450
564
|
/** Registers a permission provider only when the ACP client requires prompts. */
|
|
451
565
|
export function acpProviderRegistrations(
|
|
452
566
|
capabilities: ClientCapabilities | undefined,
|
|
@@ -572,6 +686,7 @@ export class AcpAgent implements Agent {
|
|
|
572
686
|
readonly #attaching = new Map<string, PendingAttachment>();
|
|
573
687
|
readonly #resolvingExisting = new Map<string, PendingAttachment>();
|
|
574
688
|
readonly #knownSessionCwds = new Map<string, string>();
|
|
689
|
+
readonly #knownSessionMetadata = new Map<string, { title?: string; updatedAt?: string }>();
|
|
575
690
|
readonly #pendingCloseIdempotencyKeys = new Map<string, string>();
|
|
576
691
|
readonly #sessionEpochs = new Map<string, number>();
|
|
577
692
|
readonly #tearingDown = new Map<string, number>();
|
|
@@ -624,7 +739,14 @@ export class AcpAgent implements Agent {
|
|
|
624
739
|
agentCapabilities: {
|
|
625
740
|
loadSession: true,
|
|
626
741
|
promptCapabilities: { embeddedContext: true, image: true },
|
|
627
|
-
|
|
742
|
+
mcpCapabilities: { http: true, sse: true },
|
|
743
|
+
sessionCapabilities: {
|
|
744
|
+
list: {},
|
|
745
|
+
fork: {},
|
|
746
|
+
resume: {},
|
|
747
|
+
close: {},
|
|
748
|
+
delete: {},
|
|
749
|
+
},
|
|
628
750
|
},
|
|
629
751
|
};
|
|
630
752
|
}
|
|
@@ -636,16 +758,19 @@ export class AcpAgent implements Agent {
|
|
|
636
758
|
}
|
|
637
759
|
|
|
638
760
|
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
639
|
-
this.#
|
|
761
|
+
const mcpServers = this.#mcpServers(params);
|
|
640
762
|
this.#assertAbsoluteCwd(params.cwd);
|
|
641
|
-
|
|
763
|
+
this.#assertNoAdditionalDirectories(params.additionalDirectories);
|
|
764
|
+
const result = await this.#launchSessionWithMcp(
|
|
642
765
|
"session.create",
|
|
643
766
|
{
|
|
644
767
|
cwd: params.cwd,
|
|
645
768
|
target: { path: params.cwd },
|
|
646
769
|
...(this.#startupOptions?.modelPreset ? { modelPreset: this.#startupOptions.modelPreset } : {}),
|
|
770
|
+
...(mcpServers.length > 0 ? { mcpServers, readinessTimeoutMs: ACP_MCP_LIFECYCLE_TIMEOUT_MS } : {}),
|
|
647
771
|
},
|
|
648
772
|
randomUUID(),
|
|
773
|
+
mcpServers,
|
|
649
774
|
);
|
|
650
775
|
const id = sessionId(result);
|
|
651
776
|
this.#knownSessionCwds.set(id, params.cwd);
|
|
@@ -653,7 +778,7 @@ export class AcpAgent implements Agent {
|
|
|
653
778
|
await this.#attach(id, params.cwd, endpoint(result));
|
|
654
779
|
await applyAcpStartupOptions(this.#adapter(id), this.#startupOptions);
|
|
655
780
|
this.#scheduleBootstrap(id);
|
|
656
|
-
return { sessionId: id, ...(await this.#sessionState(id)) };
|
|
781
|
+
return { sessionId: id, ...(await this.#sessionState(id, true)) };
|
|
657
782
|
} catch (error) {
|
|
658
783
|
await this.#discardNewSession(id);
|
|
659
784
|
throw error;
|
|
@@ -661,35 +786,40 @@ export class AcpAgent implements Agent {
|
|
|
661
786
|
}
|
|
662
787
|
|
|
663
788
|
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
664
|
-
this.#
|
|
789
|
+
const mcpServers = this.#mcpServers(params);
|
|
665
790
|
this.#assertAbsoluteCwd(params.cwd);
|
|
666
|
-
|
|
791
|
+
this.#assertNoAdditionalDirectories(params.additionalDirectories);
|
|
792
|
+
await this.#attachExisting(params.sessionId, params.cwd, mcpServers);
|
|
667
793
|
await this.#replaySession(params.sessionId);
|
|
668
794
|
this.#scheduleBootstrap(params.sessionId);
|
|
669
795
|
return await this.#sessionState(params.sessionId);
|
|
670
796
|
}
|
|
671
797
|
|
|
672
798
|
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
|
673
|
-
this.#
|
|
799
|
+
const mcpServers = this.#mcpServers(params);
|
|
674
800
|
this.#assertAbsoluteCwd(params.cwd);
|
|
675
|
-
|
|
801
|
+
this.#assertNoAdditionalDirectories(params.additionalDirectories);
|
|
802
|
+
await this.#attachExisting(params.sessionId, params.cwd, mcpServers);
|
|
676
803
|
this.#scheduleBootstrap(params.sessionId);
|
|
677
804
|
return await this.#sessionState(params.sessionId);
|
|
678
805
|
}
|
|
679
806
|
|
|
680
807
|
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
|
681
|
-
this.#
|
|
808
|
+
const mcpServers = this.#mcpServers(params);
|
|
682
809
|
this.#assertAbsoluteCwd(params.cwd);
|
|
810
|
+
this.#assertNoAdditionalDirectories(params.additionalDirectories);
|
|
683
811
|
const source = await this.#resolveSavedSession(params.sessionId, params.cwd);
|
|
684
|
-
const result = await
|
|
812
|
+
const result = await this.#launchSessionWithMcp(
|
|
685
813
|
"session.fork",
|
|
686
814
|
{
|
|
687
815
|
cwd: params.cwd,
|
|
688
816
|
sourceSessionId: params.sessionId,
|
|
689
817
|
sourceSessionPath: source,
|
|
690
818
|
target: { path: params.cwd },
|
|
819
|
+
...(mcpServers.length > 0 ? { mcpServers, readinessTimeoutMs: ACP_MCP_LIFECYCLE_TIMEOUT_MS } : {}),
|
|
691
820
|
},
|
|
692
821
|
randomUUID(),
|
|
822
|
+
mcpServers,
|
|
693
823
|
);
|
|
694
824
|
const id = sessionId(result);
|
|
695
825
|
this.#knownSessionCwds.set(id, params.cwd);
|
|
@@ -730,7 +860,12 @@ export class AcpAgent implements Agent {
|
|
|
730
860
|
this.#knownSessionCwds.set(candidate.sessionId, params.cwd);
|
|
731
861
|
}
|
|
732
862
|
}
|
|
733
|
-
return paginateAcpSessions(
|
|
863
|
+
return paginateAcpSessions(
|
|
864
|
+
listed,
|
|
865
|
+
params.cwd ?? undefined,
|
|
866
|
+
this.#cursor(params.cursor),
|
|
867
|
+
this.#knownSessionMetadata,
|
|
868
|
+
);
|
|
734
869
|
}
|
|
735
870
|
|
|
736
871
|
closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
|
@@ -765,6 +900,7 @@ export class AcpAgent implements Agent {
|
|
|
765
900
|
} catch (error) {
|
|
766
901
|
if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
|
|
767
902
|
this.#knownSessionCwds.delete(params.sessionId);
|
|
903
|
+
this.#knownSessionMetadata.delete(params.sessionId);
|
|
768
904
|
return {};
|
|
769
905
|
}
|
|
770
906
|
throw error;
|
|
@@ -775,6 +911,7 @@ export class AcpAgent implements Agent {
|
|
|
775
911
|
this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
|
|
776
912
|
);
|
|
777
913
|
this.#knownSessionCwds.delete(params.sessionId);
|
|
914
|
+
this.#knownSessionMetadata.delete(params.sessionId);
|
|
778
915
|
return {};
|
|
779
916
|
} finally {
|
|
780
917
|
this.#finishTeardown(params.sessionId);
|
|
@@ -800,7 +937,11 @@ export class AcpAgent implements Agent {
|
|
|
800
937
|
await this.setSessionMode({ sessionId: params.sessionId, modeId: params.value });
|
|
801
938
|
break;
|
|
802
939
|
case MODEL_CONFIG_ID:
|
|
803
|
-
|
|
940
|
+
if (this.#startupOptions?.modelPreset === undefined) {
|
|
941
|
+
await this.#adapter(params.sessionId).setModel(params.value);
|
|
942
|
+
} else if (params.value !== ACP_CUSTOM_MODEL_PRESET) {
|
|
943
|
+
await this.#adapter(params.sessionId).control("model.profile.set", { id: params.value });
|
|
944
|
+
}
|
|
804
945
|
break;
|
|
805
946
|
case THINKING_CONFIG_ID:
|
|
806
947
|
await this.#adapter(params.sessionId).control("thinking.set", { level: params.value });
|
|
@@ -823,6 +964,7 @@ export class AcpAgent implements Agent {
|
|
|
823
964
|
const record = this.#sessions.get(params.sessionId);
|
|
824
965
|
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${params.sessionId}`);
|
|
825
966
|
if (record.activePrompt) throw new AcpSdkAdapterError("conflict", "ACP session already has an active prompt.");
|
|
967
|
+
if (record.authFailure) throw new AcpSdkAdapterError("authentication_failed", record.authFailure);
|
|
826
968
|
const payload = acpPromptPayload(params.prompt);
|
|
827
969
|
let waiter!: PromptWaiter;
|
|
828
970
|
const response = new Promise<PromptResponse>((resolve, reject) => {
|
|
@@ -833,6 +975,7 @@ export class AcpAgent implements Agent {
|
|
|
833
975
|
steeringAtAcknowledgement: record.busy,
|
|
834
976
|
boundary: record.inboundSequence,
|
|
835
977
|
correlation: {},
|
|
978
|
+
emittedAssistantText: "",
|
|
836
979
|
resolve,
|
|
837
980
|
reject,
|
|
838
981
|
};
|
|
@@ -874,18 +1017,33 @@ export class AcpAgent implements Agent {
|
|
|
874
1017
|
}
|
|
875
1018
|
|
|
876
1019
|
async extMethod(method: string, params: JsonObject): Promise<JsonObject> {
|
|
1020
|
+
// An unrecognized extension method is a protocol failure, not an application
|
|
1021
|
+
// result: it must reach the client as JSON-RPC -32601 rather than a resolved
|
|
1022
|
+
// payload. Recognized `_gjc/*` methods keep their `{ok:false}` result contract.
|
|
1023
|
+
if (
|
|
1024
|
+
method !== "session/set_model" &&
|
|
1025
|
+
method !== "_gjc/sdk/global" &&
|
|
1026
|
+
method !== "_gjc/sdk/control" &&
|
|
1027
|
+
method !== "_gjc/sdk/query"
|
|
1028
|
+
)
|
|
1029
|
+
throw RequestError.methodNotFound(method);
|
|
877
1030
|
try {
|
|
1031
|
+
if (method === "session/set_model") {
|
|
1032
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
1033
|
+
const modelId = typeof params.modelId === "string" ? params.modelId : undefined;
|
|
1034
|
+
if (!sessionId) throw new AcpSdkAdapterError("invalid_input", "sessionId is required.");
|
|
1035
|
+
if (!modelId) throw new AcpSdkAdapterError("invalid_input", "modelId is required.");
|
|
1036
|
+
await this.setSessionConfigOption({ sessionId, configId: MODEL_CONFIG_ID, value: modelId });
|
|
1037
|
+
return {};
|
|
1038
|
+
}
|
|
878
1039
|
if (method === "_gjc/sdk/global") {
|
|
879
1040
|
const result = await (await this.#brokerAdapter()).handle(method, params);
|
|
880
1041
|
return object(result) ?? {};
|
|
881
1042
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return object(result) ?? {};
|
|
887
|
-
}
|
|
888
|
-
throw new AcpSdkAdapterError("method_not_found", `Unknown ACP ext method: ${method}`);
|
|
1043
|
+
const id = typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
1044
|
+
if (!id) throw new AcpSdkAdapterError("invalid_input", "sessionId is required.");
|
|
1045
|
+
const result = await this.#adapter(id).handle(method, params);
|
|
1046
|
+
return object(result) ?? {};
|
|
889
1047
|
} catch (error) {
|
|
890
1048
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "internal";
|
|
891
1049
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -946,12 +1104,14 @@ export class AcpAgent implements Agent {
|
|
|
946
1104
|
return !["terminal_uncertain", "cleanup_pending", "broker_restarting", "unavailable"].includes(error.code);
|
|
947
1105
|
}
|
|
948
1106
|
|
|
949
|
-
async #attachExisting(id: string, cwd: string): Promise<void> {
|
|
1107
|
+
async #attachExisting(id: string, cwd: string, mcpServers: SessionLifecycleMcpServer[] = []): Promise<void> {
|
|
950
1108
|
const epoch = this.#sessionEpoch(id);
|
|
951
1109
|
const attached = this.#sessions.get(id);
|
|
952
1110
|
if (attached) {
|
|
953
1111
|
if (path.resolve(attached.cwd) !== path.resolve(cwd))
|
|
954
1112
|
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
1113
|
+
if (mcpServers.length > 0)
|
|
1114
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} already has immutable MCP configuration.`);
|
|
955
1115
|
return;
|
|
956
1116
|
}
|
|
957
1117
|
const knownCwd = this.#knownSessionCwds.get(id);
|
|
@@ -968,7 +1128,7 @@ export class AcpAgent implements Agent {
|
|
|
968
1128
|
return;
|
|
969
1129
|
}
|
|
970
1130
|
|
|
971
|
-
const task = this.#resolveExistingAttachment(id, cwd, epoch);
|
|
1131
|
+
const task = this.#resolveExistingAttachment(id, cwd, epoch, mcpServers);
|
|
972
1132
|
const pending = { epoch, task };
|
|
973
1133
|
this.#resolvingExisting.set(id, pending);
|
|
974
1134
|
try {
|
|
@@ -979,11 +1139,18 @@ export class AcpAgent implements Agent {
|
|
|
979
1139
|
}
|
|
980
1140
|
}
|
|
981
1141
|
|
|
982
|
-
async #resolveExistingAttachment(
|
|
1142
|
+
async #resolveExistingAttachment(
|
|
1143
|
+
id: string,
|
|
1144
|
+
cwd: string,
|
|
1145
|
+
epoch: number,
|
|
1146
|
+
mcpServers: SessionLifecycleMcpServer[],
|
|
1147
|
+
): Promise<void> {
|
|
983
1148
|
this.#assertSessionEpoch(id, epoch);
|
|
984
1149
|
const indexed = await this.#scopedBrokerSession(id, cwd);
|
|
985
1150
|
this.#assertSessionEpoch(id, epoch);
|
|
986
1151
|
if (indexed?.live) {
|
|
1152
|
+
if (mcpServers.length > 0)
|
|
1153
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} already has immutable MCP configuration.`);
|
|
987
1154
|
const result = await this.#brokerEndpoint(id, indexed.endpointGeneration);
|
|
988
1155
|
this.#assertSessionEpoch(id, epoch);
|
|
989
1156
|
await this.#attach(id, cwd, endpoint(result), epoch);
|
|
@@ -992,10 +1159,17 @@ export class AcpAgent implements Agent {
|
|
|
992
1159
|
|
|
993
1160
|
const saved = await this.#resolveSavedSession(id, cwd);
|
|
994
1161
|
this.#assertSessionEpoch(id, epoch);
|
|
995
|
-
const result = await
|
|
1162
|
+
const result = await this.#launchSessionWithMcp(
|
|
996
1163
|
"session.resume",
|
|
997
|
-
{
|
|
1164
|
+
{
|
|
1165
|
+
cwd,
|
|
1166
|
+
sessionId: id,
|
|
1167
|
+
sessionPath: saved,
|
|
1168
|
+
target: { path: cwd },
|
|
1169
|
+
...(mcpServers.length > 0 ? { mcpServers, readinessTimeoutMs: ACP_MCP_LIFECYCLE_TIMEOUT_MS } : {}),
|
|
1170
|
+
},
|
|
998
1171
|
randomUUID(),
|
|
1172
|
+
mcpServers,
|
|
999
1173
|
);
|
|
1000
1174
|
this.#assertSessionEpoch(id, epoch);
|
|
1001
1175
|
await this.#attach(id, cwd, endpoint(result), epoch);
|
|
@@ -1065,6 +1239,7 @@ export class AcpAgent implements Agent {
|
|
|
1065
1239
|
frameTail: Promise.resolve(),
|
|
1066
1240
|
inboundSequence: 0,
|
|
1067
1241
|
busy: false,
|
|
1242
|
+
toolArgs: new Map(),
|
|
1068
1243
|
};
|
|
1069
1244
|
record.unsubscribe = adapter.onFrame(frame => this.#enqueueSdkFrame(id, adapter!, frame));
|
|
1070
1245
|
record.reconnectUnsubscribe = adapter.onReconnectFailed(error =>
|
|
@@ -1117,6 +1292,7 @@ export class AcpAgent implements Agent {
|
|
|
1117
1292
|
async #discardNewSession(id: string): Promise<void> {
|
|
1118
1293
|
await this.#teardownSession(id, "discarded", true);
|
|
1119
1294
|
this.#knownSessionCwds.delete(id);
|
|
1295
|
+
this.#knownSessionMetadata.delete(id);
|
|
1120
1296
|
}
|
|
1121
1297
|
|
|
1122
1298
|
async #closeOwnedSession(id: string): Promise<CloseSessionResponse> {
|
|
@@ -1128,6 +1304,7 @@ export class AcpAgent implements Agent {
|
|
|
1128
1304
|
if (attaching) await Promise.allSettled([attaching.task]);
|
|
1129
1305
|
await this.#teardownSession(id, "closed", true);
|
|
1130
1306
|
this.#knownSessionCwds.delete(id);
|
|
1307
|
+
this.#knownSessionMetadata.delete(id);
|
|
1131
1308
|
return {};
|
|
1132
1309
|
} finally {
|
|
1133
1310
|
this.#finishTeardown(id);
|
|
@@ -1307,13 +1484,23 @@ export class AcpAgent implements Agent {
|
|
|
1307
1484
|
const received = receivedSdkEvent(frame);
|
|
1308
1485
|
if (!received) return;
|
|
1309
1486
|
const { event, wirePayload } = received;
|
|
1487
|
+
const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
|
|
1488
|
+
if (
|
|
1489
|
+
toolCallId &&
|
|
1490
|
+
(event.type === "tool_execution_start" || event.type === "tool_execution_update") &&
|
|
1491
|
+
"args" in event
|
|
1492
|
+
) {
|
|
1493
|
+
record.toolArgs.set(toolCallId, event.args);
|
|
1494
|
+
}
|
|
1310
1495
|
const correlation = correlationFrom(frame, event);
|
|
1311
1496
|
const activePrompt = record.activePrompt;
|
|
1312
1497
|
// Prompt ownership is updated before publishing client notifications, but a
|
|
1313
|
-
// terminal waiter is resolved only after
|
|
1314
|
-
//
|
|
1498
|
+
// terminal waiter is resolved only after acknowledgement and publication
|
|
1499
|
+
// succeed. Fast hosts may emit the whole turn before acknowledging the
|
|
1500
|
+
// control request; retaining that activity prevents an idle ACP prompt from
|
|
1501
|
+
// remaining open forever.
|
|
1315
1502
|
if (
|
|
1316
|
-
activePrompt
|
|
1503
|
+
activePrompt &&
|
|
1317
1504
|
sequence > activePrompt.boundary &&
|
|
1318
1505
|
!correlationsConflict(activePrompt.correlation, correlation)
|
|
1319
1506
|
) {
|
|
@@ -1322,6 +1509,8 @@ export class AcpAgent implements Agent {
|
|
|
1322
1509
|
} else if (event.type === "agent_end") {
|
|
1323
1510
|
if (
|
|
1324
1511
|
correlationsMatch(activePrompt.correlation, correlation) ||
|
|
1512
|
+
correlation.commandId !== undefined ||
|
|
1513
|
+
correlation.turnId !== undefined ||
|
|
1325
1514
|
activePrompt.activityObserved ||
|
|
1326
1515
|
activePrompt.steeringAtAcknowledgement
|
|
1327
1516
|
)
|
|
@@ -1331,17 +1520,69 @@ export class AcpAgent implements Agent {
|
|
|
1331
1520
|
if (wirePayload) {
|
|
1332
1521
|
for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(wirePayload as never, id, {
|
|
1333
1522
|
cwd: record.cwd,
|
|
1523
|
+
getToolArgs: id => record.toolArgs.get(id),
|
|
1334
1524
|
getMessageProgress: message => {
|
|
1335
1525
|
if (!activePrompt || !object(message)) return undefined;
|
|
1336
1526
|
activePrompt.messageProgress ??= { textEmitted: false, thoughtEmitted: false };
|
|
1337
1527
|
return activePrompt.messageProgress;
|
|
1338
1528
|
},
|
|
1339
|
-
}))
|
|
1529
|
+
})) {
|
|
1530
|
+
if (
|
|
1531
|
+
activePrompt &&
|
|
1532
|
+
notification.update.sessionUpdate === "agent_message_chunk" &&
|
|
1533
|
+
notification.update.content.type === "text"
|
|
1534
|
+
)
|
|
1535
|
+
activePrompt.emittedAssistantText += notification.update.content.text;
|
|
1340
1536
|
await this.#publishSessionUpdate(id, notification, adapter);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (toolCallId && event.type === "tool_execution_end") record.toolArgs.delete(toolCallId);
|
|
1540
|
+
if (event.type === "agent_start") {
|
|
1541
|
+
await this.#publishSessionUpdate(
|
|
1542
|
+
id,
|
|
1543
|
+
{
|
|
1544
|
+
sessionId: id,
|
|
1545
|
+
update: {
|
|
1546
|
+
sessionUpdate: "session_info_update",
|
|
1547
|
+
updatedAt: new Date().toISOString(),
|
|
1548
|
+
_meta: { gjcPhase: "working", running: true, gjcRunning: true },
|
|
1549
|
+
},
|
|
1550
|
+
},
|
|
1551
|
+
adapter,
|
|
1552
|
+
);
|
|
1341
1553
|
}
|
|
1342
1554
|
if (event.type === "message_end" && object(event.message)?.role === "assistant" && activePrompt)
|
|
1343
1555
|
activePrompt.messageProgress = undefined;
|
|
1344
|
-
if (event.type === "agent_end")
|
|
1556
|
+
if (event.type === "agent_end") {
|
|
1557
|
+
const finalText = typeof event.finalText === "string" ? event.finalText : "";
|
|
1558
|
+
if (activePrompt && finalText) {
|
|
1559
|
+
const resolution = resolveAcpFinalText(activePrompt.emittedAssistantText, finalText);
|
|
1560
|
+
if (resolution.kind === "emit") {
|
|
1561
|
+
activePrompt.emittedAssistantText += resolution.text;
|
|
1562
|
+
await this.#publishSessionUpdate(
|
|
1563
|
+
id,
|
|
1564
|
+
{
|
|
1565
|
+
sessionId: id,
|
|
1566
|
+
update: {
|
|
1567
|
+
sessionUpdate: "agent_message_chunk",
|
|
1568
|
+
content: { type: "text", text: resolution.text },
|
|
1569
|
+
...(resolution.final.truncated ? { _meta: { gjcFinalTextTruncated: true } } : {}),
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
adapter,
|
|
1573
|
+
);
|
|
1574
|
+
} else if (resolution.kind === "divergent") {
|
|
1575
|
+
logger.warn("acp_final_text_diverged", {
|
|
1576
|
+
sessionId: id,
|
|
1577
|
+
...(activePrompt.correlation.commandId ? { commandId: activePrompt.correlation.commandId } : {}),
|
|
1578
|
+
...(activePrompt.correlation.turnId ? { turnId: activePrompt.correlation.turnId } : {}),
|
|
1579
|
+
streamedLength: activePrompt.emittedAssistantText.length,
|
|
1580
|
+
finalLength: resolution.final.text.length,
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
await this.#emitEndOfTurnUpdates(id, adapter);
|
|
1585
|
+
}
|
|
1345
1586
|
if (activePrompt) this.#settlePrompt(record, activePrompt);
|
|
1346
1587
|
}
|
|
1347
1588
|
|
|
@@ -1360,6 +1601,7 @@ export class AcpAgent implements Agent {
|
|
|
1360
1601
|
|
|
1361
1602
|
async #emitEndOfTurnUpdates(id: string, adapter: AcpSdkAdapter): Promise<void> {
|
|
1362
1603
|
let usage: JsonObject | undefined;
|
|
1604
|
+
let title: string | undefined;
|
|
1363
1605
|
try {
|
|
1364
1606
|
const response = object(await adapter.query("context.get"));
|
|
1365
1607
|
const result = object(response?.result) ?? response;
|
|
@@ -1367,6 +1609,15 @@ export class AcpAgent implements Agent {
|
|
|
1367
1609
|
} catch {
|
|
1368
1610
|
// Context usage is advisory ACP metadata; prompt completion remains authoritative.
|
|
1369
1611
|
}
|
|
1612
|
+
try {
|
|
1613
|
+
const response = object(await adapter.query("session.metadata"));
|
|
1614
|
+
const result = object(response?.result) ?? response;
|
|
1615
|
+
const metadata = pageItems(result)[0];
|
|
1616
|
+
const item = object(metadata);
|
|
1617
|
+
if (typeof item?.name === "string" && item.name) title = item.name;
|
|
1618
|
+
} catch {
|
|
1619
|
+
// Session naming is advisory; prompt completion remains authoritative.
|
|
1620
|
+
}
|
|
1370
1621
|
if (typeof usage?.tokens === "number" && typeof usage.contextWindow === "number") {
|
|
1371
1622
|
await this.#publishSessionUpdate(
|
|
1372
1623
|
id,
|
|
@@ -1381,13 +1632,16 @@ export class AcpAgent implements Agent {
|
|
|
1381
1632
|
adapter,
|
|
1382
1633
|
);
|
|
1383
1634
|
}
|
|
1635
|
+
const updatedAt = new Date().toISOString();
|
|
1636
|
+
this.#knownSessionMetadata.set(id, { ...(title ? { title } : {}), updatedAt });
|
|
1384
1637
|
await this.#publishSessionUpdate(
|
|
1385
1638
|
id,
|
|
1386
1639
|
{
|
|
1387
1640
|
sessionId: id,
|
|
1388
1641
|
update: {
|
|
1389
1642
|
sessionUpdate: "session_info_update",
|
|
1390
|
-
|
|
1643
|
+
...(title ? { title } : {}),
|
|
1644
|
+
updatedAt,
|
|
1391
1645
|
_meta: { gjcPhase: "idle", running: false, gjcRunning: false },
|
|
1392
1646
|
},
|
|
1393
1647
|
},
|
|
@@ -1411,27 +1665,68 @@ export class AcpAgent implements Agent {
|
|
|
1411
1665
|
}
|
|
1412
1666
|
}
|
|
1413
1667
|
|
|
1414
|
-
async #sessionState(
|
|
1668
|
+
async #sessionState(
|
|
1669
|
+
id: string,
|
|
1670
|
+
rejectUnavailableStartupPreset = false,
|
|
1671
|
+
): Promise<Pick<NewSessionResponse, "configOptions" | "modes">> {
|
|
1415
1672
|
const record = this.#sessions.get(id);
|
|
1416
1673
|
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${id}`);
|
|
1417
|
-
const
|
|
1674
|
+
const modelPreset = this.#startupOptions?.modelPreset;
|
|
1675
|
+
const [config, modelCatalog] = await Promise.all([
|
|
1418
1676
|
record.adapter.query("config.list/get"),
|
|
1419
|
-
record.adapter.query("models.list/current"),
|
|
1677
|
+
record.adapter.query(modelPreset === undefined ? "models.list/current" : "models.profiles.list"),
|
|
1420
1678
|
]);
|
|
1421
|
-
|
|
1679
|
+
record.authFailure = undefined;
|
|
1680
|
+
if (modelPreset !== undefined) {
|
|
1681
|
+
const activePreset = configValues(config).get(MODEL_PRESET_CONFIG_KEY);
|
|
1682
|
+
const activeProfile = pageItems(modelCatalog)
|
|
1683
|
+
.map(item => object(item))
|
|
1684
|
+
.find(item => item?.id === activePreset);
|
|
1685
|
+
if (activePreset && activeProfile?.available === false) {
|
|
1686
|
+
record.authFailure =
|
|
1687
|
+
`Model preset "${activePreset}" has no usable provider credentials. ` +
|
|
1688
|
+
"Authenticate the required provider in Gajae Code or select an available preset before prompting.";
|
|
1689
|
+
if (rejectUnavailableStartupPreset && activePreset === modelPreset)
|
|
1690
|
+
throw new AcpSdkAdapterError("authentication_failed", record.authFailure);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return acpSessionStateFromConfig(config, modelCatalog, modelPreset);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
async #publishAvailableCommands(id: string, adapter: AcpSdkAdapter): Promise<void> {
|
|
1697
|
+
let skills: unknown;
|
|
1698
|
+
try {
|
|
1699
|
+
skills = await adapter.query("skill.list/state");
|
|
1700
|
+
} catch {
|
|
1701
|
+
// Builtins remain useful when an older SDK host cannot expose skill state.
|
|
1702
|
+
}
|
|
1703
|
+
await this.#publishSessionUpdate(
|
|
1704
|
+
id,
|
|
1705
|
+
{
|
|
1706
|
+
sessionId: id,
|
|
1707
|
+
update: {
|
|
1708
|
+
sessionUpdate: "available_commands_update",
|
|
1709
|
+
availableCommands: acpAvailableCommandsFromSkills(skills),
|
|
1710
|
+
},
|
|
1711
|
+
},
|
|
1712
|
+
adapter,
|
|
1713
|
+
);
|
|
1422
1714
|
}
|
|
1423
1715
|
|
|
1424
1716
|
async #replaySession(id: string): Promise<void> {
|
|
1425
1717
|
const adapter = this.#adapter(id);
|
|
1718
|
+
const record = this.#sessions.get(id);
|
|
1719
|
+
if (!record) return;
|
|
1426
1720
|
let cursor: string | undefined;
|
|
1427
1721
|
let imageLimitationReported = false;
|
|
1722
|
+
const replayTools = new Map<string, { name: string; args: unknown }>();
|
|
1428
1723
|
for (let pageCount = 0; pageCount < MAX_ACP_REPLAY_PAGES; pageCount++) {
|
|
1429
1724
|
const response = object(await adapter.query("transcript.list", {}, cursor));
|
|
1430
1725
|
const result = object(response?.result) ?? response;
|
|
1431
1726
|
const page = object(result?.page);
|
|
1432
1727
|
for (const item of Array.isArray(page?.items) ? page.items : []) {
|
|
1433
1728
|
const message = object(item);
|
|
1434
|
-
if (!message
|
|
1729
|
+
if (!message) continue;
|
|
1435
1730
|
const content = transcriptReplayContent(message);
|
|
1436
1731
|
if (!imageLimitationReported) {
|
|
1437
1732
|
imageLimitationReported = true;
|
|
@@ -1448,6 +1743,95 @@ export class AcpAgent implements Agent {
|
|
|
1448
1743
|
);
|
|
1449
1744
|
}
|
|
1450
1745
|
const messageId = typeof message.id === "string" ? message.id : undefined;
|
|
1746
|
+
const richContent = Array.isArray(message.content) ? message.content : undefined;
|
|
1747
|
+
if ((message.role === "user" || message.role === "assistant") && richContent) {
|
|
1748
|
+
for (const rawBlock of richContent) {
|
|
1749
|
+
const block = object(rawBlock);
|
|
1750
|
+
if (!block || typeof block.type !== "string") continue;
|
|
1751
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
|
1752
|
+
await this.#publishSessionUpdate(
|
|
1753
|
+
id,
|
|
1754
|
+
{
|
|
1755
|
+
sessionId: id,
|
|
1756
|
+
update: {
|
|
1757
|
+
sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
|
|
1758
|
+
content: { type: "text", text: block.text },
|
|
1759
|
+
...(messageId ? { messageId } : {}),
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
adapter,
|
|
1763
|
+
);
|
|
1764
|
+
} else if (
|
|
1765
|
+
message.role === "assistant" &&
|
|
1766
|
+
block.type === "thinking" &&
|
|
1767
|
+
typeof block.thinking === "string" &&
|
|
1768
|
+
block.thinking.length > 0
|
|
1769
|
+
) {
|
|
1770
|
+
await this.#publishSessionUpdate(
|
|
1771
|
+
id,
|
|
1772
|
+
{
|
|
1773
|
+
sessionId: id,
|
|
1774
|
+
update: {
|
|
1775
|
+
sessionUpdate: "agent_thought_chunk",
|
|
1776
|
+
content: { type: "text", text: block.thinking },
|
|
1777
|
+
...(messageId ? { messageId } : {}),
|
|
1778
|
+
},
|
|
1779
|
+
},
|
|
1780
|
+
adapter,
|
|
1781
|
+
);
|
|
1782
|
+
} else if (
|
|
1783
|
+
message.role === "assistant" &&
|
|
1784
|
+
block.type === "toolCall" &&
|
|
1785
|
+
typeof block.id === "string" &&
|
|
1786
|
+
typeof block.name === "string"
|
|
1787
|
+
) {
|
|
1788
|
+
const args = block.arguments ?? {};
|
|
1789
|
+
replayTools.set(block.id, { name: block.name, args });
|
|
1790
|
+
await this.#publishSessionUpdate(
|
|
1791
|
+
id,
|
|
1792
|
+
{
|
|
1793
|
+
sessionId: id,
|
|
1794
|
+
update: buildToolCallStartUpdate({
|
|
1795
|
+
toolCallId: block.id,
|
|
1796
|
+
toolName: block.name,
|
|
1797
|
+
args,
|
|
1798
|
+
cwd: record.cwd,
|
|
1799
|
+
}),
|
|
1800
|
+
},
|
|
1801
|
+
adapter,
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (message.role === "toolResult" && typeof message.toolCallId === "string") {
|
|
1808
|
+
const replayTool = replayTools.get(message.toolCallId);
|
|
1809
|
+
const toolName = typeof message.toolName === "string" ? message.toolName : replayTool?.name;
|
|
1810
|
+
if (!toolName) continue;
|
|
1811
|
+
const resultContent = richContent
|
|
1812
|
+
?.map(object)
|
|
1813
|
+
.filter(
|
|
1814
|
+
(block): block is JsonObject =>
|
|
1815
|
+
block !== undefined && block.type === "text" && typeof block.text === "string",
|
|
1816
|
+
)
|
|
1817
|
+
.map(block => ({ type: "text" as const, text: String(block.text) }));
|
|
1818
|
+
for (const notification of mapAgentSessionEventToAcpSessionUpdates(
|
|
1819
|
+
{
|
|
1820
|
+
type: "tool_execution_end",
|
|
1821
|
+
toolCallId: message.toolCallId,
|
|
1822
|
+
toolName,
|
|
1823
|
+
result: { content: resultContent ?? content.blocks },
|
|
1824
|
+
isError: message.isError === true,
|
|
1825
|
+
} as never,
|
|
1826
|
+
id,
|
|
1827
|
+
{ cwd: record.cwd, getToolArgs: toolCallId => replayTools.get(toolCallId)?.args },
|
|
1828
|
+
)) {
|
|
1829
|
+
await this.#publishSessionUpdate(id, notification, adapter);
|
|
1830
|
+
}
|
|
1831
|
+
replayTools.delete(message.toolCallId);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
1451
1835
|
for (const block of content.blocks) {
|
|
1452
1836
|
await this.#publishSessionUpdate(
|
|
1453
1837
|
id,
|
|
@@ -1473,17 +1857,33 @@ export class AcpAgent implements Agent {
|
|
|
1473
1857
|
setTimeout(() => {
|
|
1474
1858
|
const record = this.#sessions.get(id);
|
|
1475
1859
|
if (!record || this.#connection.signal.aborted) return;
|
|
1476
|
-
void
|
|
1477
|
-
id,
|
|
1478
|
-
{
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1860
|
+
void (async () => {
|
|
1861
|
+
await this.#publishAvailableCommands(id, record.adapter);
|
|
1862
|
+
if (record.authFailure) {
|
|
1863
|
+
await this.#publishSessionUpdate(
|
|
1864
|
+
id,
|
|
1865
|
+
{
|
|
1866
|
+
sessionId: id,
|
|
1867
|
+
update: {
|
|
1868
|
+
sessionUpdate: "agent_thought_chunk",
|
|
1869
|
+
content: { type: "text", text: `[error:auth] ${record.authFailure}\n` },
|
|
1870
|
+
},
|
|
1871
|
+
},
|
|
1872
|
+
record.adapter,
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
await this.#publishSessionUpdate(
|
|
1876
|
+
id,
|
|
1877
|
+
{
|
|
1878
|
+
sessionId: id,
|
|
1879
|
+
update: {
|
|
1880
|
+
sessionUpdate: "session_info_update",
|
|
1881
|
+
_meta: { gjcPhase: "idle", running: false, gjcRunning: false },
|
|
1882
|
+
},
|
|
1483
1883
|
},
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
).catch(() => undefined);
|
|
1884
|
+
record.adapter,
|
|
1885
|
+
);
|
|
1886
|
+
})().catch(() => undefined);
|
|
1487
1887
|
}, ACP_BOOTSTRAP_RACE_GUARD_MS);
|
|
1488
1888
|
}
|
|
1489
1889
|
|
|
@@ -1498,9 +1898,138 @@ export class AcpAgent implements Agent {
|
|
|
1498
1898
|
if (!path.isAbsolute(cwd)) throw new Error(`ACP cwd must be an absolute path: ${cwd}`);
|
|
1499
1899
|
}
|
|
1500
1900
|
|
|
1501
|
-
#
|
|
1502
|
-
if (
|
|
1503
|
-
throw new AcpSdkAdapterError("unsupported", "
|
|
1901
|
+
#assertNoAdditionalDirectories(directories: string[] | null | undefined): void {
|
|
1902
|
+
if (directories && directories.length > 0)
|
|
1903
|
+
throw new AcpSdkAdapterError("unsupported", "ACP additional directories are not supported.");
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
#mcpServers(params: { mcpServers?: unknown[] }): SessionLifecycleMcpServer[] {
|
|
1907
|
+
const servers = params.mcpServers ?? [];
|
|
1908
|
+
if (servers.length > 64)
|
|
1909
|
+
throw new AcpSdkAdapterError("unsupported", "ACP supports at most 64 MCP servers per session.");
|
|
1910
|
+
const result: SessionLifecycleMcpServer[] = [];
|
|
1911
|
+
const names = new Set<string>();
|
|
1912
|
+
for (const value of servers) {
|
|
1913
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1914
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP MCP server definitions must be objects.");
|
|
1915
|
+
const server = value as Record<string, unknown>;
|
|
1916
|
+
if (typeof server.name !== "string" || !/^[A-Za-z0-9_.-]{1,100}$/.test(server.name) || names.has(server.name))
|
|
1917
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP MCP servers must have unique safe names.");
|
|
1918
|
+
names.add(server.name);
|
|
1919
|
+
if (server.type === "http" || server.type === "sse") {
|
|
1920
|
+
if (
|
|
1921
|
+
typeof server.url !== "string" ||
|
|
1922
|
+
server.url.length > 8_192 ||
|
|
1923
|
+
!Array.isArray(server.headers) ||
|
|
1924
|
+
server.headers.length > 100
|
|
1925
|
+
)
|
|
1926
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP remote MCP servers require a valid URL and headers.");
|
|
1927
|
+
let parsedUrl: URL;
|
|
1928
|
+
try {
|
|
1929
|
+
parsedUrl = new URL(server.url);
|
|
1930
|
+
} catch {
|
|
1931
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP MCP URL is invalid.");
|
|
1932
|
+
}
|
|
1933
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:")
|
|
1934
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP MCP URLs must use HTTP or HTTPS.");
|
|
1935
|
+
const headers: Record<string, string> = {};
|
|
1936
|
+
for (const value of server.headers) {
|
|
1937
|
+
const header = object(value);
|
|
1938
|
+
if (
|
|
1939
|
+
typeof header?.name !== "string" ||
|
|
1940
|
+
header.name.length === 0 ||
|
|
1941
|
+
header.name.length > 256 ||
|
|
1942
|
+
header.name.includes("\r") ||
|
|
1943
|
+
header.name.includes("\n") ||
|
|
1944
|
+
typeof header.value !== "string" ||
|
|
1945
|
+
header.value.length > 8_192 ||
|
|
1946
|
+
header.value.includes("\r") ||
|
|
1947
|
+
header.value.includes("\n") ||
|
|
1948
|
+
Object.hasOwn(headers, header.name)
|
|
1949
|
+
)
|
|
1950
|
+
throw new AcpSdkAdapterError(
|
|
1951
|
+
"invalid_input",
|
|
1952
|
+
"ACP MCP headers must have unique valid names and values.",
|
|
1953
|
+
);
|
|
1954
|
+
headers[header.name] = header.value;
|
|
1955
|
+
}
|
|
1956
|
+
result.push({
|
|
1957
|
+
type: server.type,
|
|
1958
|
+
name: server.name,
|
|
1959
|
+
url: parsedUrl.toString(),
|
|
1960
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
1961
|
+
});
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
if (
|
|
1965
|
+
(server.type !== undefined && server.type !== "stdio") ||
|
|
1966
|
+
typeof server.command !== "string" ||
|
|
1967
|
+
server.command.length > 4_096 ||
|
|
1968
|
+
!path.isAbsolute(server.command) ||
|
|
1969
|
+
!Array.isArray(server.args) ||
|
|
1970
|
+
server.args.length > 100 ||
|
|
1971
|
+
!server.args.every(argument => typeof argument === "string" && argument.length <= 8_192) ||
|
|
1972
|
+
!Array.isArray(server.env) ||
|
|
1973
|
+
server.env.length > 100
|
|
1974
|
+
)
|
|
1975
|
+
throw new AcpSdkAdapterError(
|
|
1976
|
+
"invalid_input",
|
|
1977
|
+
"ACP stdio MCP servers require an absolute command and bounded arguments and environment variables.",
|
|
1978
|
+
);
|
|
1979
|
+
const env: Record<string, string> = {};
|
|
1980
|
+
for (const value of server.env) {
|
|
1981
|
+
const variable = object(value);
|
|
1982
|
+
if (
|
|
1983
|
+
typeof variable?.name !== "string" ||
|
|
1984
|
+
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable.name) ||
|
|
1985
|
+
typeof variable.value !== "string" ||
|
|
1986
|
+
variable.value.length > 32_768 ||
|
|
1987
|
+
Object.hasOwn(env, variable.name)
|
|
1988
|
+
)
|
|
1989
|
+
throw new AcpSdkAdapterError(
|
|
1990
|
+
"invalid_input",
|
|
1991
|
+
"ACP MCP environment variables must have unique valid names and string values.",
|
|
1992
|
+
);
|
|
1993
|
+
env[variable.name] = variable.value;
|
|
1994
|
+
}
|
|
1995
|
+
result.push({
|
|
1996
|
+
name: server.name,
|
|
1997
|
+
command: server.command,
|
|
1998
|
+
args: server.args as string[],
|
|
1999
|
+
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return result;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
async #launchSessionWithMcp(
|
|
2006
|
+
operation: "session.create" | "session.fork" | "session.resume",
|
|
2007
|
+
input: JsonObject,
|
|
2008
|
+
idempotencyKey: string,
|
|
2009
|
+
mcpServers: SessionLifecycleMcpServer[],
|
|
2010
|
+
): Promise<unknown> {
|
|
2011
|
+
try {
|
|
2012
|
+
return await (await this.#brokerAdapter()).global(operation, input, idempotencyKey);
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
const code =
|
|
2015
|
+
typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
2016
|
+
? error.code
|
|
2017
|
+
: undefined;
|
|
2018
|
+
if (
|
|
2019
|
+
mcpServers.length === 0 ||
|
|
2020
|
+
code === "invalid_input" ||
|
|
2021
|
+
code === "authentication_failed" ||
|
|
2022
|
+
code === "unknown_model_profile" ||
|
|
2023
|
+
code === "model_profile_registry_error"
|
|
2024
|
+
)
|
|
2025
|
+
throw error;
|
|
2026
|
+
const names = mcpServers
|
|
2027
|
+
.slice(0, 8)
|
|
2028
|
+
.map(server => server.name)
|
|
2029
|
+
.join(", ");
|
|
2030
|
+
const suffix = mcpServers.length > 8 ? `, and ${mcpServers.length - 8} more` : "";
|
|
2031
|
+
throw new AcpSdkAdapterError("unavailable", `MCP server request failed to start (${names}${suffix}).`);
|
|
2032
|
+
}
|
|
1504
2033
|
}
|
|
1505
2034
|
|
|
1506
2035
|
#beginDispose(): void {
|
|
@@ -1526,6 +2055,7 @@ export class AcpAgent implements Agent {
|
|
|
1526
2055
|
this.#attaching.clear();
|
|
1527
2056
|
this.#resolvingExisting.clear();
|
|
1528
2057
|
this.#knownSessionCwds.clear();
|
|
2058
|
+
this.#knownSessionMetadata.clear();
|
|
1529
2059
|
this.#pendingCloseIdempotencyKeys.clear();
|
|
1530
2060
|
if (this.#closing.size === 0) this.#closing.clear();
|
|
1531
2061
|
this.#tearingDown.clear();
|