@gajae-code/coding-agent 0.3.1 → 0.4.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 +46 -0
- package/README.md +1 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +25 -10
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +84 -0
- package/dist/types/config/settings-schema.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +16 -0
- package/dist/types/edit/modes/replace.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/skills.d.ts +9 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
- package/dist/types/harness-control-plane/storage.d.ts +7 -0
- package/dist/types/lsp/client.d.ts +1 -0
- package/dist/types/main.d.ts +10 -1
- package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/sdk.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/blob-store.d.ts +17 -0
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/skill-state/active-state.d.ts +13 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/thinking.d.ts +3 -2
- package/dist/types/tools/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +7 -4
- package/package.json +9 -7
- package/src/cli/args.ts +10 -0
- package/src/cli.ts +14 -0
- package/src/commands/harness.ts +192 -7
- package/src/commands/launch.ts +8 -0
- package/src/commands/ultragoal.ts +1 -21
- package/src/config/model-equivalence.ts +1 -1
- package/src/config/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +51 -5
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +42 -1
- package/src/config/settings-schema.ts +14 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +11 -1
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/discovery/claude-plugins.ts +25 -5
- package/src/edit/diff.ts +64 -1
- package/src/edit/modes/replace.ts +60 -2
- package/src/extensibility/gjc-plugins/activation.ts +87 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +114 -0
- package/src/extensibility/gjc-plugins/loader.ts +131 -0
- package/src/extensibility/gjc-plugins/paths.ts +66 -0
- package/src/extensibility/gjc-plugins/schema.ts +79 -0
- package/src/extensibility/gjc-plugins/state.ts +29 -0
- package/src/extensibility/gjc-plugins/tools.ts +47 -0
- package/src/extensibility/gjc-plugins/types.ts +97 -0
- package/src/extensibility/gjc-plugins/validation.ts +76 -0
- package/src/extensibility/skills.ts +39 -7
- package/src/gjc-runtime/state-runtime.ts +93 -2
- package/src/gjc-runtime/state-writer.ts +17 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +62 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
- package/src/gjc-runtime/workflow-manifest.ts +2 -2
- package/src/harness-control-plane/storage.ts +144 -2
- package/src/hashline/hash.ts +23 -0
- package/src/hooks/skill-state.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +8 -11
- package/src/lsp/client.ts +7 -0
- package/src/main.ts +67 -1
- package/src/modes/acp/acp-agent.ts +25 -2
- package/src/modes/bridge/bridge-mode.ts +124 -2
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/controllers/input-controller.ts +14 -2
- package/src/modes/controllers/selector-controller.ts +57 -1
- package/src/modes/prompt-action-autocomplete.ts +49 -10
- package/src/modes/rpc/rpc-client.ts +57 -3
- package/src/modes/rpc/rpc-mode.ts +67 -0
- package/src/modes/rpc/rpc-types.ts +224 -2
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
- package/src/modes/shared/agent-wire/command-validation.ts +25 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
- package/src/modes/shared/agent-wire/handshake.ts +43 -3
- package/src/modes/shared/agent-wire/protocol.ts +7 -0
- package/src/modes/shared/agent-wire/responses.ts +2 -2
- package/src/modes/shared/agent-wire/scopes.ts +2 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +1 -0
- package/src/runtime-mcp/client.ts +7 -4
- package/src/runtime-mcp/manager.ts +45 -13
- package/src/runtime-mcp/transports/http.ts +40 -14
- package/src/runtime-mcp/transports/stdio.ts +11 -10
- package/src/sdk.ts +48 -1
- package/src/session/agent-session.ts +211 -2
- package/src/session/blob-store.ts +84 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-manager.ts +390 -33
- package/src/session/session-storage.ts +26 -0
- package/src/setup/provider-onboarding.ts +2 -2
- package/src/skill-state/active-state.ts +89 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/task/discovery.ts +7 -1
- package/src/task/executor.ts +18 -2
- package/src/task/index.ts +2 -0
- package/src/thinking.ts +8 -2
- package/src/tools/ask.ts +39 -9
- package/src/tools/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +7 -18
- package/src/tools/read.ts +3 -3
- package/src/tools/skill.ts +15 -3
- package/src/utils/edit-mode.ts +1 -1
package/src/modes/types.ts
CHANGED
|
@@ -147,6 +147,7 @@ export interface InteractiveModeContext {
|
|
|
147
147
|
showStatus(message: string, options?: { dim?: boolean }): void;
|
|
148
148
|
showError(message: string): void;
|
|
149
149
|
showWarning(message: string): void;
|
|
150
|
+
notifyConfigChanged?: () => Promise<void> | void;
|
|
150
151
|
showNewVersionNotification(newVersion: string): void;
|
|
151
152
|
clearEditor(): void;
|
|
152
153
|
updatePendingMessagesDisplay(): void;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# Memory Guidance
|
|
2
|
-
Memory
|
|
2
|
+
Memory backend: local private runtime state
|
|
3
3
|
Operational rules:
|
|
4
|
-
1)
|
|
5
|
-
2)
|
|
6
|
-
3)
|
|
7
|
-
4)
|
|
8
|
-
5)
|
|
9
|
-
6) Escalate confidence only after repository verification. Memory alone is NEVER sufficient proof.
|
|
4
|
+
1) The memory summary below is already injected; do not try to call or invent a `memory` tool.
|
|
5
|
+
2) Treat memory as heuristic process context. Trust current repo files, runtime output, and user instruction for factual state and final decisions.
|
|
6
|
+
3) When memory changes your plan, pair it with current-repo evidence before acting.
|
|
7
|
+
4) If memory disagrees with repo state or user instruction, prefer repo/user. Treat memory as stale. Proceed with corrected behavior, then update/regenerate memory artifacts through supported memory commands when available.
|
|
8
|
+
5) Escalate confidence only after repository verification. Memory alone is NEVER sufficient proof.
|
|
10
9
|
Memory summary:
|
|
11
10
|
{{memory_summary}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Memory Guidance
|
|
2
|
-
Memory
|
|
2
|
+
Memory backend: local private runtime state
|
|
3
3
|
Status: local memory is enabled, but no confirmed memory payload is available for this project yet.
|
|
4
4
|
|
|
5
5
|
Operational rules:
|
|
6
6
|
1) Do not claim that a user preference, fact, or instruction has been saved, remembered, or persisted unless a backend operation or a non-empty memory payload confirms it.
|
|
7
7
|
2) If the user asks you to save or remember something now, explain that durable memory is not confirmed because local memory has no available payload/readback yet. You may use the instruction for the current session only.
|
|
8
|
-
3)
|
|
8
|
+
3) Do not try to call or invent a `memory` tool; local memory readback is unavailable in this session.
|
|
9
9
|
4) The local backend consolidates prior session rollouts asynchronously; an empty payload is a degraded/unconfirmed state, not a successful save.
|
|
@@ -6,7 +6,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
6
6
|
- Quote variable expansions like `"$NAME"` to preserve exact content
|
|
7
7
|
- PTY mode is opt-in: set `pty: true` only when the command needs a real terminal (e.g. `sudo`, `ssh` requiring user input); default is `false`
|
|
8
8
|
- Use `;` only when later commands should run regardless of earlier failures
|
|
9
|
-
- Internal URIs (`agent://`, `artifact://`, `
|
|
9
|
+
- Internal URIs (`agent://`, `artifact://`, `rule://`, `local://`) are auto-resolved to filesystem paths
|
|
10
10
|
{{#if asyncEnabled}}
|
|
11
11
|
- Use `async: true` for long-running commands when you don't need immediate output; the call returns a background job ID and the result is delivered automatically as a follow-up.
|
|
12
12
|
{{/if}}
|
package/src/prompts/tools/irc.md
CHANGED
|
@@ -24,7 +24,7 @@ These rules apply to both sending and replying.
|
|
|
24
24
|
- **Do not quote the message you are replying to.** The sender already saw it; the TUI already renders it. Lead with the answer.
|
|
25
25
|
- **Use IRC, not terminal tools, to learn about peers.** Do not `grep` artifacts, read other sessions' JSONL files, or shell-poke around to figure out what another agent is doing. DM them — they have the live answer and you do not.
|
|
26
26
|
- **One round-trip is enough.** Replies arrive synchronously when the recipient is reachable. Do not follow up with "did you get my message?" — they did. If `delivered` is empty or the result was `failed`, the peer is unavailable; move on or report the blocker, do not retry in a loop.
|
|
27
|
-
- **Stay terse.** A DM is a chat message, not a memo. One question per send when you can. Share file paths and artifacts via `local://` / `
|
|
27
|
+
- **Stay terse.** A DM is a chat message, not a memo. One question per send when you can. Share file paths and artifacts via `local://` / `artifact://` URLs instead of pasting blobs.
|
|
28
28
|
- **Address peers by id.** Use the exact id from `op: "list"` (e.g. `0-AuthLoader`, `0-Main`). Do not invent friendly names.
|
|
29
29
|
- **Do not IRC for things a tool would answer.** If a `read`, `grep`, or build command would resolve the question, do that first.
|
|
30
30
|
- **When you receive an IRC message, answer it before continuing.** The recipient injects the question + your auto-reply into your history; address it directly, do not repeat it back to the user.
|
|
@@ -8,7 +8,7 @@ Read files, directories, archives, SQLite databases, images, documents, internal
|
|
|
8
8
|
|
|
9
9
|
## Parameters
|
|
10
10
|
|
|
11
|
-
- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `
|
|
11
|
+
- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
|
|
12
12
|
|
|
13
13
|
## Selectors
|
|
14
14
|
|
|
@@ -70,7 +70,7 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
|
|
|
70
70
|
|
|
71
71
|
# Internal URIs
|
|
72
72
|
|
|
73
|
-
`agent://<id>`, `artifact://<id>`, `
|
|
73
|
+
`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.
|
|
74
74
|
|
|
75
75
|
<critical>
|
|
76
76
|
- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN — any such bash call is a bug, regardless of how short or convenient it looks.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Compatibility-only legacy Hindsight helper. This prompt is retained for backend/tool-call compatibility and is not part of the public gajae-code coding harness tool surface.
|
|
1
2
|
Search long-term memory for relevant information. Returns raw matching entries ranked by relevance.
|
|
2
3
|
|
|
3
4
|
Use proactively — before answering questions about past conversations, user preferences, project decisions, or any topic where prior context would help accuracy. When in doubt, recall first.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Compatibility-only legacy Hindsight helper. This prompt is retained for backend/tool-call compatibility and is not part of the public gajae-code coding harness tool surface.
|
|
1
2
|
Generate a synthesised answer by reasoning over long-term memory. Unlike `recall`, `reflect` blends relevant memories into a coherent response.
|
|
2
3
|
|
|
3
4
|
Use for open-ended questions spanning many stored facts: "What do you know about this user?", "Summarize project decisions.", "What are my preferences for X?"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Compatibility-only legacy Hindsight helper. This prompt is retained for backend/tool-call compatibility and is not part of the public gajae-code coding harness tool surface.
|
|
1
2
|
Store one or more facts in long-term memory for future sessions.
|
|
2
3
|
|
|
3
4
|
Use for durable, reusable knowledge: user preferences, project decisions, architectural choices, anything that improves future responses.
|
|
@@ -141,6 +141,8 @@ export async function connectToServer(
|
|
|
141
141
|
): Promise<MCPServerConnection> {
|
|
142
142
|
const timeoutMs = config.timeout ?? CONNECTION_TIMEOUT_MS;
|
|
143
143
|
let transport: MCPTransport | undefined;
|
|
144
|
+
const connectAbort = new AbortController();
|
|
145
|
+
const connectSignal = options?.signal ? AbortSignal.any([options.signal, connectAbort.signal]) : connectAbort.signal;
|
|
144
146
|
|
|
145
147
|
const connect = async (): Promise<MCPServerConnection> => {
|
|
146
148
|
transport = await createTransport(config);
|
|
@@ -155,7 +157,7 @@ export async function connectToServer(
|
|
|
155
157
|
|
|
156
158
|
try {
|
|
157
159
|
const initResult = await initializeConnection(transport, {
|
|
158
|
-
signal:
|
|
160
|
+
signal: connectSignal,
|
|
159
161
|
async onInitialized() {
|
|
160
162
|
// Open the SSE stream before sending initialized, so server-to-client
|
|
161
163
|
// requests triggered by on_initialized (e.g. roots/list) are delivered.
|
|
@@ -184,13 +186,14 @@ export async function connectToServer(
|
|
|
184
186
|
connect(),
|
|
185
187
|
timeoutMs,
|
|
186
188
|
`Connection to MCP server "${name}" timed out after ${timeoutMs}ms`,
|
|
187
|
-
|
|
189
|
+
connectSignal,
|
|
188
190
|
);
|
|
189
191
|
} catch (error) {
|
|
190
192
|
// If withTimeout rejected (timeout/abort) while connect() was still pending,
|
|
191
|
-
//
|
|
193
|
+
// abort initialization and wait for transport cleanup before returning.
|
|
194
|
+
connectAbort.abort(error);
|
|
192
195
|
if (transport) {
|
|
193
|
-
|
|
196
|
+
await transport.close().catch(() => {});
|
|
194
197
|
}
|
|
195
198
|
throw error;
|
|
196
199
|
}
|
|
@@ -152,6 +152,7 @@ export class MCPManager {
|
|
|
152
152
|
#connections = new Map<string, MCPServerConnection>();
|
|
153
153
|
#tools: CustomTool<TSchema, MCPToolDetails>[] = [];
|
|
154
154
|
#pendingConnections = new Map<string, Promise<MCPServerConnection>>();
|
|
155
|
+
#pendingConnectionControllers = new Map<string, AbortController>();
|
|
155
156
|
#pendingToolLoads = new Map<string, Promise<ToolLoadResult>>();
|
|
156
157
|
#sources = new Map<string, SourceMeta>();
|
|
157
158
|
#authStorage: AuthStorage | null = null;
|
|
@@ -164,6 +165,7 @@ export class MCPManager {
|
|
|
164
165
|
#subscribedResources = new Map<string, Set<string>>();
|
|
165
166
|
#pendingResourceRefresh = new Map<string, { connection: MCPServerConnection; promise: Promise<void> }>();
|
|
166
167
|
#pendingReconnections = new Map<string, Promise<MCPServerConnection | null>>();
|
|
168
|
+
#disconnectEpochs = new Map<string, number>();
|
|
167
169
|
/** Preserved configs for reconnection after connection loss. */
|
|
168
170
|
#serverConfigs = new Map<string, MCPServerConfig>();
|
|
169
171
|
/** Monotonic epoch incremented on disconnectAll to invalidate stale reconnections. */
|
|
@@ -348,10 +350,14 @@ export class MCPManager {
|
|
|
348
350
|
// and falls back to cached/deferred tools.
|
|
349
351
|
this.#serverConfigs.set(name, config);
|
|
350
352
|
|
|
353
|
+
const connectionEpoch = this.#epoch;
|
|
354
|
+
const connectionAbort = new AbortController();
|
|
355
|
+
this.#pendingConnectionControllers.set(name, connectionAbort);
|
|
351
356
|
// Resolve auth config before connecting, but do so per-server in parallel.
|
|
352
357
|
const connectionPromise = (async () => {
|
|
353
358
|
const resolvedConfig = await this.#resolveAuthConfig(config);
|
|
354
359
|
return connectToServer(name, resolvedConfig, {
|
|
360
|
+
signal: connectionAbort.signal,
|
|
355
361
|
onNotification: (method, params) => {
|
|
356
362
|
this.#handleServerNotification(name, method, params);
|
|
357
363
|
},
|
|
@@ -360,18 +366,26 @@ export class MCPManager {
|
|
|
360
366
|
},
|
|
361
367
|
});
|
|
362
368
|
})().then(
|
|
363
|
-
connection => {
|
|
369
|
+
async connection => {
|
|
364
370
|
// Store original config (without resolved tokens) to keep
|
|
365
371
|
// cache keys stable and avoid leaking rotating credentials.
|
|
366
372
|
connection.config = config;
|
|
367
|
-
this.#serverConfigs.set(name, config);
|
|
368
373
|
if (sources[name]) {
|
|
369
374
|
connection._source = sources[name];
|
|
370
375
|
}
|
|
371
|
-
|
|
376
|
+
const stillPending = this.#pendingConnections.get(name) === connectionPromise;
|
|
377
|
+
const stillCurrent = this.#epoch === connectionEpoch && this.#serverConfigs.get(name) === config;
|
|
378
|
+
if (stillPending) {
|
|
372
379
|
this.#pendingConnections.delete(name);
|
|
373
|
-
this.#
|
|
380
|
+
this.#pendingConnectionControllers.delete(name);
|
|
374
381
|
}
|
|
382
|
+
if (!stillPending || !stillCurrent) {
|
|
383
|
+
connection.transport.onClose = undefined;
|
|
384
|
+
await connection.transport.close().catch(() => {});
|
|
385
|
+
throw new Error(`Server "${name}" was disconnected during connection`);
|
|
386
|
+
}
|
|
387
|
+
this.#connections.set(name, connection);
|
|
388
|
+
this.#serverConfigs.set(name, config);
|
|
375
389
|
|
|
376
390
|
// Wire auth refresh for HTTP transports so 401s trigger token refresh.
|
|
377
391
|
if (connection.transport instanceof HttpTransport && config.auth?.type === "oauth") {
|
|
@@ -396,6 +410,7 @@ export class MCPManager {
|
|
|
396
410
|
error => {
|
|
397
411
|
if (this.#pendingConnections.get(name) === connectionPromise) {
|
|
398
412
|
this.#pendingConnections.delete(name);
|
|
413
|
+
this.#pendingConnectionControllers.delete(name);
|
|
399
414
|
}
|
|
400
415
|
throw error;
|
|
401
416
|
},
|
|
@@ -660,13 +675,16 @@ export class MCPManager {
|
|
|
660
675
|
* Disconnect from a specific server.
|
|
661
676
|
*/
|
|
662
677
|
async disconnectServer(name: string): Promise<void> {
|
|
678
|
+
const nextEpoch = (this.#disconnectEpochs.get(name) ?? 0) + 1;
|
|
679
|
+
this.#disconnectEpochs.set(name, nextEpoch);
|
|
680
|
+
this.#pendingConnectionControllers.get(name)?.abort(new Error(`MCP server disconnected: ${name}`));
|
|
681
|
+
this.#pendingConnectionControllers.delete(name);
|
|
663
682
|
this.#pendingConnections.delete(name);
|
|
664
683
|
this.#pendingToolLoads.delete(name);
|
|
665
684
|
this.#pendingReconnections.delete(name);
|
|
666
685
|
this.#sources.delete(name);
|
|
667
686
|
this.#serverConfigs.delete(name);
|
|
668
687
|
this.#pendingResourceRefresh.delete(name);
|
|
669
|
-
|
|
670
688
|
const connection = this.#connections.get(name);
|
|
671
689
|
|
|
672
690
|
const subscribedUris = this.#subscribedResources.get(name);
|
|
@@ -705,6 +723,10 @@ export class MCPManager {
|
|
|
705
723
|
const promises = Array.from(this.#connections.values()).map(conn => disconnectServer(conn));
|
|
706
724
|
await Promise.allSettled(promises);
|
|
707
725
|
|
|
726
|
+
for (const controller of this.#pendingConnectionControllers.values()) {
|
|
727
|
+
controller.abort(new Error("MCP manager disconnected"));
|
|
728
|
+
}
|
|
729
|
+
this.#pendingConnectionControllers.clear();
|
|
708
730
|
this.#pendingConnections.clear();
|
|
709
731
|
this.#pendingToolLoads.clear();
|
|
710
732
|
this.#pendingReconnections.clear();
|
|
@@ -808,14 +830,24 @@ export class MCPManager {
|
|
|
808
830
|
reconnectEpoch: number,
|
|
809
831
|
): Promise<MCPServerConnection> {
|
|
810
832
|
const resolvedConfig = await this.#resolveAuthConfig(config);
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
833
|
+
const connectionAbort = new AbortController();
|
|
834
|
+
this.#pendingConnectionControllers.set(name, connectionAbort);
|
|
835
|
+
let connection: MCPServerConnection;
|
|
836
|
+
try {
|
|
837
|
+
connection = await connectToServer(name, resolvedConfig, {
|
|
838
|
+
signal: connectionAbort.signal,
|
|
839
|
+
onNotification: (method, params) => {
|
|
840
|
+
this.#handleServerNotification(name, method, params);
|
|
841
|
+
},
|
|
842
|
+
onRequest: (method, params) => {
|
|
843
|
+
return this.#handleServerRequest(method, params);
|
|
844
|
+
},
|
|
845
|
+
});
|
|
846
|
+
} finally {
|
|
847
|
+
if (this.#pendingConnectionControllers.get(name) === connectionAbort) {
|
|
848
|
+
this.#pendingConnectionControllers.delete(name);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
819
851
|
|
|
820
852
|
connection.config = config;
|
|
821
853
|
if (source) connection._source = source;
|
|
@@ -25,6 +25,8 @@ export class HttpTransport implements MCPTransport {
|
|
|
25
25
|
#connected = false;
|
|
26
26
|
#sessionId: string | null = null;
|
|
27
27
|
#sseConnection: AbortController | null = null;
|
|
28
|
+
#streamControllers = new Set<AbortController>();
|
|
29
|
+
#streamReaders = new Set<Promise<void>>();
|
|
28
30
|
|
|
29
31
|
onClose?: () => void;
|
|
30
32
|
onError?: (error: Error) => void;
|
|
@@ -52,6 +54,15 @@ export class HttpTransport implements MCPTransport {
|
|
|
52
54
|
this.#connected = true;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
#trackReader(promise: Promise<void>, controller?: AbortController): void {
|
|
58
|
+
if (controller) this.#streamControllers.add(controller);
|
|
59
|
+
this.#streamReaders.add(promise);
|
|
60
|
+
void promise.finally(() => {
|
|
61
|
+
this.#streamReaders.delete(promise);
|
|
62
|
+
if (controller) this.#streamControllers.delete(controller);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
/**
|
|
56
67
|
* Start SSE listener for server-initiated messages.
|
|
57
68
|
* Resolves once the SSE connection is established (or fails/unsupported).
|
|
@@ -61,7 +72,8 @@ export class HttpTransport implements MCPTransport {
|
|
|
61
72
|
if (!this.#connected) return;
|
|
62
73
|
if (this.#sseConnection) return;
|
|
63
74
|
|
|
64
|
-
|
|
75
|
+
const sseConnection = new AbortController();
|
|
76
|
+
this.#sseConnection = sseConnection;
|
|
65
77
|
const headers: Record<string, string> = {
|
|
66
78
|
Accept: "text/event-stream",
|
|
67
79
|
...this.config.headers,
|
|
@@ -76,10 +88,10 @@ export class HttpTransport implements MCPTransport {
|
|
|
76
88
|
response = await fetch(this.config.url, {
|
|
77
89
|
method: "GET",
|
|
78
90
|
headers,
|
|
79
|
-
signal:
|
|
91
|
+
signal: sseConnection.signal,
|
|
80
92
|
});
|
|
81
93
|
} catch (error) {
|
|
82
|
-
this.#sseConnection = null;
|
|
94
|
+
this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
|
|
83
95
|
if (error instanceof Error && error.name !== "AbortError") {
|
|
84
96
|
this.onError?.(error);
|
|
85
97
|
}
|
|
@@ -87,19 +99,20 @@ export class HttpTransport implements MCPTransport {
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
if (response.status === 405 || !response.ok || !response.body) {
|
|
90
|
-
this.#sseConnection = null;
|
|
102
|
+
this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
|
|
91
103
|
return;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
// Connection established — read messages in background.
|
|
95
107
|
// If the stream ends unexpectedly (server restart, network drop),
|
|
96
108
|
// fire onClose so the manager can trigger reconnection.
|
|
97
|
-
const signal =
|
|
98
|
-
|
|
109
|
+
const signal = sseConnection.signal;
|
|
110
|
+
const reader = this.#readSSEStream(response.body!, signal).finally(() => {
|
|
99
111
|
const wasConnected = this.#connected;
|
|
100
|
-
this.#sseConnection = null;
|
|
101
|
-
if (wasConnected) this.onClose?.();
|
|
112
|
+
if (this.#sseConnection === sseConnection) this.#sseConnection = null;
|
|
113
|
+
if (wasConnected && !signal.aborted) this.onClose?.();
|
|
102
114
|
});
|
|
115
|
+
this.#trackReader(reader, sseConnection);
|
|
103
116
|
}
|
|
104
117
|
async #readSSEStream(body: ReadableStream<Uint8Array>, signal: AbortSignal): Promise<void> {
|
|
105
118
|
try {
|
|
@@ -266,6 +279,8 @@ export class HttpTransport implements MCPTransport {
|
|
|
266
279
|
// Re-reading `response.body` after `for await` breaks would lock the
|
|
267
280
|
// stream a second time and surface as "ReadableStream already has a
|
|
268
281
|
// controller", so we must not exit the loop early.
|
|
282
|
+
const drainController = abortController;
|
|
283
|
+
this.#streamControllers.add(drainController);
|
|
269
284
|
const drain = async (): Promise<void> => {
|
|
270
285
|
try {
|
|
271
286
|
for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(response.body!, operationSignal)) {
|
|
@@ -306,10 +321,11 @@ export class HttpTransport implements MCPTransport {
|
|
|
306
321
|
}
|
|
307
322
|
} finally {
|
|
308
323
|
clearTimeout(timeoutId);
|
|
324
|
+
this.#streamControllers.delete(drainController);
|
|
309
325
|
}
|
|
310
326
|
};
|
|
311
327
|
|
|
312
|
-
|
|
328
|
+
this.#trackReader(drain());
|
|
313
329
|
return promise;
|
|
314
330
|
}
|
|
315
331
|
|
|
@@ -417,9 +433,13 @@ export class HttpTransport implements MCPTransport {
|
|
|
417
433
|
// on the notification response (MCP Streamable HTTP spec). Read them.
|
|
418
434
|
const contentType = response.headers.get("Content-Type") ?? "";
|
|
419
435
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
436
|
+
const streamController = new AbortController();
|
|
437
|
+
const streamTimeout = AbortSignal.timeout(this.config.timeout ?? 30000);
|
|
438
|
+
const signals = this.#sseConnection
|
|
439
|
+
? [this.#sseConnection.signal, streamController.signal, streamTimeout]
|
|
440
|
+
: [streamController.signal, streamTimeout];
|
|
441
|
+
const reader = this.#readSSEStream(response.body, AbortSignal.any(signals));
|
|
442
|
+
this.#trackReader(reader, streamController);
|
|
423
443
|
} else {
|
|
424
444
|
await response.body?.cancel();
|
|
425
445
|
}
|
|
@@ -433,14 +453,20 @@ export class HttpTransport implements MCPTransport {
|
|
|
433
453
|
}
|
|
434
454
|
|
|
435
455
|
async close(): Promise<void> {
|
|
436
|
-
|
|
456
|
+
const wasConnected = this.#connected;
|
|
437
457
|
this.#connected = false;
|
|
438
458
|
|
|
439
|
-
// Abort SSE
|
|
459
|
+
// Abort all SSE/background readers and wait for them to settle.
|
|
460
|
+
for (const controller of this.#streamControllers) {
|
|
461
|
+
controller.abort();
|
|
462
|
+
}
|
|
440
463
|
if (this.#sseConnection) {
|
|
441
464
|
this.#sseConnection.abort();
|
|
442
465
|
this.#sseConnection = null;
|
|
443
466
|
}
|
|
467
|
+
await Promise.allSettled(Array.from(this.#streamReaders));
|
|
468
|
+
|
|
469
|
+
if (!wasConnected && !this.#sessionId) return;
|
|
444
470
|
|
|
445
471
|
// Send session termination if we have a session
|
|
446
472
|
if (this.#sessionId) {
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* Messages are newline-delimited JSON.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getProjectDir, readJsonl, Snowflake } from "@gajae-code/utils";
|
|
9
|
-
import { type Subprocess, spawn } from "bun";
|
|
8
|
+
import { getProjectDir, ptree, readJsonl, Snowflake } from "@gajae-code/utils";
|
|
10
9
|
import type {
|
|
11
10
|
JsonRpcError,
|
|
12
11
|
JsonRpcMessage,
|
|
@@ -22,8 +21,10 @@ import { toJsonRpcError } from "../../runtime-mcp/types";
|
|
|
22
21
|
* Stdio transport for MCP servers.
|
|
23
22
|
* Spawns a subprocess and communicates via stdin/stdout.
|
|
24
23
|
*/
|
|
24
|
+
const CLOSE_WAIT_MS = 1_000;
|
|
25
|
+
|
|
25
26
|
export class StdioTransport implements MCPTransport {
|
|
26
|
-
#process:
|
|
27
|
+
#process: ptree.ChildProcess<"pipe"> | null = null;
|
|
27
28
|
#pendingRequests = new Map<
|
|
28
29
|
string | number,
|
|
29
30
|
{
|
|
@@ -57,13 +58,11 @@ export class StdioTransport implements MCPTransport {
|
|
|
57
58
|
...this.config.env,
|
|
58
59
|
};
|
|
59
60
|
|
|
60
|
-
this.#process = spawn({
|
|
61
|
-
cmd: [this.config.command, ...args],
|
|
61
|
+
this.#process = ptree.spawn([this.config.command, ...args], {
|
|
62
62
|
cwd: this.config.cwd ?? getProjectDir(),
|
|
63
63
|
env,
|
|
64
64
|
stdin: "pipe",
|
|
65
|
-
|
|
66
|
-
stderr: "pipe",
|
|
65
|
+
stderr: "full",
|
|
67
66
|
});
|
|
68
67
|
|
|
69
68
|
this.#connected = true;
|
|
@@ -299,9 +298,11 @@ export class StdioTransport implements MCPTransport {
|
|
|
299
298
|
}
|
|
300
299
|
this.#pendingRequests.clear();
|
|
301
300
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
// Terminate the subprocess tree and keep the handle until exit is observed.
|
|
302
|
+
const process = this.#process;
|
|
303
|
+
if (process) {
|
|
304
|
+
process.kill();
|
|
305
|
+
await Promise.race([process.exited.catch(() => {}), Bun.sleep(CLOSE_WAIT_MS)]);
|
|
305
306
|
this.#process = null;
|
|
306
307
|
}
|
|
307
308
|
|
package/src/sdk.ts
CHANGED
|
@@ -68,6 +68,8 @@ import {
|
|
|
68
68
|
wrapRegisteredTools,
|
|
69
69
|
} from "./extensibility/extensions";
|
|
70
70
|
import { ExtensionRuntime } from "./extensibility/extensions/loader";
|
|
71
|
+
import { resolveCurrentPhaseForParent } from "./extensibility/gjc-plugins/injection";
|
|
72
|
+
import { loadActiveSubskillTools } from "./extensibility/gjc-plugins/tools";
|
|
71
73
|
import { loadSkills, type Skill, type SkillWarning, setActiveSkills } from "./extensibility/skills";
|
|
72
74
|
import type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
73
75
|
import type { HindsightSessionState } from "./hindsight/state";
|
|
@@ -243,6 +245,8 @@ export interface CreateAgentSessionOptions {
|
|
|
243
245
|
|
|
244
246
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
245
247
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
248
|
+
/** Explicit parent/phase used to load active GJC sub-skill tools for this session. */
|
|
249
|
+
gjcSubskillToolContext?: { parent: string; phase: string; sessionId?: string; cwd?: string };
|
|
246
250
|
/** Inline extensions (merged with discovery). */
|
|
247
251
|
extensions?: ExtensionFactory[];
|
|
248
252
|
/** Additional extension paths to load (merged with discovery). */
|
|
@@ -291,7 +295,7 @@ export interface CreateAgentSessionOptions {
|
|
|
291
295
|
taskDepth?: number;
|
|
292
296
|
/** Current role-agent type/name for nested task sessions. */
|
|
293
297
|
currentAgentType?: string;
|
|
294
|
-
/** Parent Hindsight state to alias for subagent memory
|
|
298
|
+
/** Parent Hindsight state to alias for subagent private memory backend compatibility. */
|
|
295
299
|
parentHindsightSessionState?: HindsightSessionState;
|
|
296
300
|
/** Pre-allocated agent identity for IRC routing. Default: "0-Main" for top-level, parentTaskPrefix-derived for sub. */
|
|
297
301
|
agentId?: string;
|
|
@@ -1183,6 +1187,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1183
1187
|
getActiveModelString,
|
|
1184
1188
|
getPlanModeState: () => session?.getPlanModeState(),
|
|
1185
1189
|
getGoalModeState: () => session?.getGoalModeState(),
|
|
1190
|
+
getWorkflowGateEmitter: () => session?.getWorkflowGateEmitter(),
|
|
1186
1191
|
getGoalRuntime: () => session?.goalRuntime,
|
|
1187
1192
|
getClientBridge: () => session?.clientBridge,
|
|
1188
1193
|
getCompactContext: () => session.formatCompactContext(),
|
|
@@ -1281,6 +1286,47 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1281
1286
|
customTools.push(...getSearchTools());
|
|
1282
1287
|
}
|
|
1283
1288
|
|
|
1289
|
+
const getReservedSubskillToolNames = () => [
|
|
1290
|
+
...new Set([
|
|
1291
|
+
...builtinTools.map(tool => tool.name),
|
|
1292
|
+
...(options.toolNames?.map(name => name.toLowerCase()) ?? []),
|
|
1293
|
+
...(options.customTools?.map(tool => (isCustomTool(tool) ? tool.name : tool.name)) ?? []),
|
|
1294
|
+
...customTools.map(tool => tool.name),
|
|
1295
|
+
]),
|
|
1296
|
+
];
|
|
1297
|
+
|
|
1298
|
+
const gjcSubskillToolContext = options.gjcSubskillToolContext;
|
|
1299
|
+
if (gjcSubskillToolContext?.parent.trim() && gjcSubskillToolContext.phase.trim()) {
|
|
1300
|
+
const pluginTools = await loadActiveSubskillTools({
|
|
1301
|
+
cwd: gjcSubskillToolContext.cwd ?? cwd,
|
|
1302
|
+
sessionId: gjcSubskillToolContext.sessionId ?? logicalSessionId,
|
|
1303
|
+
parent: gjcSubskillToolContext.parent,
|
|
1304
|
+
phase: gjcSubskillToolContext.phase,
|
|
1305
|
+
reservedToolNames: getReservedSubskillToolNames(),
|
|
1306
|
+
});
|
|
1307
|
+
if (pluginTools.length > 0) {
|
|
1308
|
+
customTools.push(...pluginTools);
|
|
1309
|
+
}
|
|
1310
|
+
} else {
|
|
1311
|
+
for (const skill of skills) {
|
|
1312
|
+
const phase = await resolveCurrentPhaseForParent({
|
|
1313
|
+
cwd,
|
|
1314
|
+
sessionId: logicalSessionId,
|
|
1315
|
+
parent: skill.name,
|
|
1316
|
+
});
|
|
1317
|
+
const pluginTools = await loadActiveSubskillTools({
|
|
1318
|
+
cwd,
|
|
1319
|
+
sessionId: logicalSessionId,
|
|
1320
|
+
parent: skill.name,
|
|
1321
|
+
phase,
|
|
1322
|
+
reservedToolNames: getReservedSubskillToolNames(),
|
|
1323
|
+
});
|
|
1324
|
+
if (pluginTools.length > 0) {
|
|
1325
|
+
customTools.push(...pluginTools);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1284
1330
|
// Custom tool and extension discovery is quarantined from the public GJC utility surface.
|
|
1285
1331
|
// Explicit SDK extension factories are still honored; callers use them to
|
|
1286
1332
|
// register in-process tools/providers without enabling filesystem discovery.
|
|
@@ -1889,6 +1935,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1889
1935
|
modelRegistry,
|
|
1890
1936
|
taskDepth,
|
|
1891
1937
|
toolRegistry,
|
|
1938
|
+
workflowGateToolSession: toolSession,
|
|
1892
1939
|
transformContext,
|
|
1893
1940
|
onPayload,
|
|
1894
1941
|
onResponse,
|