@gajae-code/coding-agent 0.16.7 → 0.17.1
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 +180 -1
- package/dist/types/capability/fs.d.ts +37 -4
- package/dist/types/capability/index.d.ts +19 -0
- package/dist/types/capability/types.d.ts +9 -0
- package/dist/types/cli/doctor/args.d.ts +36 -0
- package/dist/types/cli/doctor/checks.d.ts +18 -0
- package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
- package/dist/types/cli/doctor/context.d.ts +89 -0
- package/dist/types/cli/doctor/files.d.ts +27 -0
- package/dist/types/cli/doctor/ids.d.ts +20 -0
- package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
- package/dist/types/cli/doctor/journal.d.ts +43 -0
- package/dist/types/cli/doctor/managed-link.d.ts +39 -0
- package/dist/types/cli/doctor/native-probe.d.ts +2 -0
- package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
- package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
- package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
- package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
- package/dist/types/cli/doctor/probe-client.d.ts +9 -0
- package/dist/types/cli/doctor/probe-types.d.ts +18 -0
- package/dist/types/cli/doctor/probe.d.ts +3 -0
- package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
- package/dist/types/cli/doctor/repairs.d.ts +28 -0
- package/dist/types/cli/doctor/report.d.ts +27 -0
- package/dist/types/cli/doctor/runner.d.ts +4 -0
- package/dist/types/cli/doctor/safety.d.ts +10 -0
- package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
- package/dist/types/cli/doctor/service-targets.d.ts +41 -0
- package/dist/types/cli/doctor/types.d.ts +145 -0
- package/dist/types/cli/doctor-cli.d.ts +7 -0
- package/dist/types/cli/doctor-supervisor.d.ts +121 -0
- package/dist/types/cli/doctor-worker.d.ts +29 -0
- package/dist/types/cli/install-activation.d.ts +158 -0
- package/dist/types/cli/macos-community-app.d.ts +41 -0
- package/dist/types/cli/mcp-cli.d.ts +0 -11
- package/dist/types/cli/update-cli.d.ts +41 -4
- package/dist/types/cli-main.d.ts +174 -0
- package/dist/types/cli.d.ts +1 -171
- package/dist/types/commands/doctor.d.ts +7 -0
- package/dist/types/commands/sdk.d.ts +17 -8
- package/dist/types/config/autorouting-tier-map.d.ts +19 -0
- package/dist/types/config/file-lock.d.ts +27 -3
- package/dist/types/config/model-preset-registry.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +12 -5
- package/dist/types/config/model-resolver.d.ts +16 -3
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
- package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
- package/dist/types/cursor.d.ts +9 -9
- package/dist/types/discovery/agents-md.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +36 -7
- package/dist/types/discovery/index.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +22 -1
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/plugins/manager.d.ts +33 -4
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
- package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/main.d.ts +19 -1
- package/dist/types/modes/acp/acp-agent.d.ts +40 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
- package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -2
- package/dist/types/sdk/acp/adapter.d.ts +5 -1
- package/dist/types/sdk/broker/broker.d.ts +51 -0
- package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
- package/dist/types/sdk/broker/discovery.d.ts +38 -0
- package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
- package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
- package/dist/types/sdk/broker/runtime.d.ts +26 -0
- package/dist/types/sdk/broker/session-index.d.ts +13 -4
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
- package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
- package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
- package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/bus/notification-service.d.ts +3 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
- package/dist/types/sdk/cli/session-cli.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/sdk/host/surface-policy.d.ts +3 -1
- package/dist/types/sdk/lifecycle/service.d.ts +44 -10
- package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
- package/dist/types/sdk/prompt-failure.d.ts +47 -0
- package/dist/types/sdk/prompt-status.d.ts +19 -0
- package/dist/types/sdk/router/session-router.d.ts +3 -4
- package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
- package/dist/types/sdk/session-authority.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +1 -1
- package/dist/types/session/fallback-chain-controller.d.ts +3 -6
- package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
- package/dist/types/session/primary-control-surface.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +6 -0
- package/dist/types/tools/ask.d.ts +25 -0
- package/dist/types/tools/image-gen.d.ts +0 -4
- package/dist/types/tools/skill-discovery.d.ts +7 -5
- package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
- package/dist/types/utils/changelog.d.ts +0 -9
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +9 -8
- package/scripts/compile-args.ts +8 -4
- package/src/capability/fs.ts +395 -17
- package/src/capability/index.ts +273 -10
- package/src/capability/types.ts +9 -0
- package/src/cli/customize-doctor.ts +18 -12
- package/src/cli/doctor/args.ts +236 -0
- package/src/cli/doctor/checks.ts +431 -0
- package/src/cli/doctor/config-repairs.ts +517 -0
- package/src/cli/doctor/context.ts +149 -0
- package/src/cli/doctor/files.ts +149 -0
- package/src/cli/doctor/ids.ts +115 -0
- package/src/cli/doctor/install-repairs.ts +335 -0
- package/src/cli/doctor/journal.ts +423 -0
- package/src/cli/doctor/managed-link.ts +514 -0
- package/src/cli/doctor/native-probe.ts +13 -0
- package/src/cli/doctor/permission-repairs.ts +242 -0
- package/src/cli/doctor/plugin-quarantine.ts +377 -0
- package/src/cli/doctor/plugin-restore.ts +59 -0
- package/src/cli/doctor/plugin-targets.ts +373 -0
- package/src/cli/doctor/probe-client.ts +195 -0
- package/src/cli/doctor/probe-types.ts +21 -0
- package/src/cli/doctor/probe.ts +42 -0
- package/src/cli/doctor/projection-probe.ts +249 -0
- package/src/cli/doctor/repairs.ts +1157 -0
- package/src/cli/doctor/report.ts +176 -0
- package/src/cli/doctor/runner.ts +342 -0
- package/src/cli/doctor/safety.ts +40 -0
- package/src/cli/doctor/service-repairs.ts +452 -0
- package/src/cli/doctor/service-targets.ts +274 -0
- package/src/cli/doctor/types.ts +164 -0
- package/src/cli/doctor-cli.ts +62 -0
- package/src/cli/doctor-supervisor.ts +571 -0
- package/src/cli/doctor-worker.ts +181 -0
- package/src/cli/install-activation.ts +666 -0
- package/src/cli/macos-community-app.ts +1449 -0
- package/src/cli/mcp-cli.ts +11 -21
- package/src/cli/nofile-limit.ts +5 -1
- package/src/cli/plugin-cli.ts +6 -1
- package/src/cli/update-cli.ts +362 -264
- package/src/cli-main.ts +517 -0
- package/src/cli.ts +64 -433
- package/src/commands/autoresearch.ts +11 -5
- package/src/commands/completion.ts +1 -1
- package/src/commands/doctor.ts +13 -0
- package/src/commands/sdk.ts +58 -10
- package/src/commands/ultragoal.ts +11 -5
- package/src/config/autorouting-tier-map.ts +7 -0
- package/src/config/file-lock.ts +736 -71
- package/src/config/mcp-schema.json +3 -3
- package/src/config/model-preset-registry.ts +1 -0
- package/src/config/model-registry.ts +80 -27
- package/src/config/model-resolver.ts +33 -4
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -0
- package/src/coordinator-mcp/server.ts +534 -51
- package/src/coordinator-mcp/session-reaper.ts +64 -8
- package/src/cursor.ts +223 -53
- package/src/discovery/agents-md.ts +74 -8
- package/src/discovery/agents.ts +32 -7
- package/src/discovery/builtin.ts +184 -75
- package/src/discovery/claude-plugins.ts +85 -17
- package/src/discovery/cline.ts +38 -10
- package/src/discovery/cursor.ts +19 -6
- package/src/discovery/gemini.ts +62 -22
- package/src/discovery/github.ts +13 -3
- package/src/discovery/helpers.ts +483 -89
- package/src/discovery/index.ts +1 -0
- package/src/discovery/mcp-json.ts +36 -10
- package/src/discovery/opencode.ts +36 -19
- package/src/discovery/ssh.ts +11 -8
- package/src/discovery/windsurf.ts +12 -3
- package/src/exec/bash-executor.ts +10 -3
- package/src/exec/isolated-shell.ts +15 -2
- package/src/extensibility/extensions/loader.ts +88 -26
- package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
- package/src/extensibility/gjc-plugins/paths.ts +4 -1
- package/src/extensibility/gjc-plugins/types.ts +27 -0
- package/src/extensibility/plugins/manager.ts +200 -22
- package/src/extensibility/plugins/marketplace/cache.ts +55 -2
- package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
- package/src/extensibility/plugins/marketplace/registry.ts +153 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
- package/src/extensibility/runtime-skill-discovery.ts +61 -9
- package/src/extensibility/skill-management.ts +7 -1
- package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
- package/src/gjc-runtime/launch-worktree.test.ts +248 -0
- package/src/gjc-runtime/launch-worktree.ts +275 -38
- package/src/gjc-runtime/repository-binding.ts +26 -1
- package/src/gjc-runtime/session-layout.ts +10 -0
- package/src/gjc-runtime/session-state-lock.ts +330 -23
- package/src/gjc-runtime/session-state-sidecar.ts +328 -80
- package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +28 -26
- package/src/main.ts +150 -29
- package/src/memories/index.ts +24 -2
- package/src/modes/acp/acp-agent.ts +522 -68
- package/src/modes/components/agent-dashboard.ts +3 -1
- package/src/modes/components/model-selector.ts +28 -19
- package/src/modes/components/plugin-settings.ts +2 -1
- package/src/modes/controllers/selector-controller.ts +15 -9
- package/src/modes/interactive-mode.ts +21 -3
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/move-session.md +4 -0
- package/src/prompts/tools/read.md +2 -1
- package/src/prompts/tools/skill-discovery.md +6 -5
- package/src/prompts/tools/task.md +1 -0
- package/src/runtime-mcp/config-writer.ts +228 -0
- package/src/runtime-mcp/oauth-discovery.ts +114 -14
- package/src/runtime-mcp/policy-json-edit.ts +129 -0
- package/src/runtime-mcp/startup-policy.ts +14 -0
- package/src/runtime-mcp/types.ts +2 -2
- package/src/sdk/acp/adapter.ts +27 -12
- package/src/sdk/broker/broker.ts +273 -44
- package/src/sdk/broker/daemon-entry.ts +119 -0
- package/src/sdk/broker/discovery.ts +173 -20
- package/src/sdk/broker/doctor-restart.ts +206 -0
- package/src/sdk/broker/endpoint-authority.ts +36 -0
- package/src/sdk/broker/ensure.ts +49 -14
- package/src/sdk/broker/lifecycle.ts +174 -42
- package/src/sdk/broker/process-incarnation.ts +86 -2
- package/src/sdk/broker/runtime.ts +145 -21
- package/src/sdk/broker/session-index.ts +136 -22
- package/src/sdk/broker/spawn-substrate.ts +4 -1
- package/src/sdk/broker/transport.ts +5 -0
- package/src/sdk/broker/worktree-occupancy.ts +21 -1
- package/src/sdk/bus/chat-daemon-cli.ts +63 -0
- package/src/sdk/bus/chat-daemon-control.ts +260 -64
- package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
- package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
- package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
- package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
- package/src/sdk/bus/index.ts +252 -51
- package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
- package/src/sdk/bus/notification-service.ts +21 -3
- package/src/sdk/bus/reconciliation-store.ts +17 -11
- package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +47 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
- package/src/sdk/bus/telegram-daemon.ts +178 -13
- package/src/sdk/cli/session-cli.ts +100 -12
- package/src/sdk/host/host.ts +2 -7
- package/src/sdk/host/session-runtime.test.ts +329 -8
- package/src/sdk/host/session-runtime.ts +202 -50
- package/src/sdk/host/surface-policy.ts +8 -1
- package/src/sdk/lifecycle/service.ts +199 -22
- package/src/sdk/mcp/server.ts +1 -1
- package/src/sdk/prompt-deadline-lease.ts +12 -4
- package/src/sdk/prompt-deadline-manager.ts +5 -7
- package/src/sdk/prompt-failure.ts +148 -0
- package/src/sdk/prompt-status.ts +30 -1
- package/src/sdk/prompt-watchdog.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +20 -8
- package/src/sdk/router/session-router.ts +8 -3
- package/src/sdk/service-artifact-paths.ts +26 -0
- package/src/sdk/session-authority.ts +10 -0
- package/src/sdk/session.ts +361 -194
- package/src/session/agent-session.ts +398 -93
- package/src/session/blob-store.ts +9 -9
- package/src/session/fallback-chain-controller.ts +3 -7
- package/src/session/internal/managed-session-scope.ts +445 -29
- package/src/session/internal/managed-session-storage.ts +38 -6
- package/src/session/primary-control-surface.ts +12 -0
- package/src/session/session-manager.ts +11 -3
- package/src/session/session-storage.ts +1 -1
- package/src/session-import/codex.ts +6 -1
- package/src/session-import/redact.ts +7 -0
- package/src/skill-state/workflow-mutation-guard.ts +645 -39
- package/src/slash-commands/builtin-registry.ts +7 -3
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +36 -3
- package/src/task/types.ts +27 -5
- package/src/tools/ask.ts +114 -34
- package/src/tools/browser/actions.ts +2 -2
- package/src/tools/browser/tab-worker.ts +11 -8
- package/src/tools/descriptors.ts +6 -1
- package/src/tools/fetch.ts +7 -2
- package/src/tools/image-gen.ts +33 -34
- package/src/tools/read.ts +78 -32
- package/src/tools/skill-discovery.ts +17 -7
- package/src/tools/tool-catalog.generated.ts +8 -8
- package/src/tools/ultragoal-ask-guard.ts +16 -26
- package/src/utils/changelog.ts +7 -1
- package/src/utils/git.ts +3 -1
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +26 -0
- package/src/web/search/providers/openai-compatible.ts +191 -14
package/src/sdk/session.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
postmortem,
|
|
40
40
|
prompt,
|
|
41
41
|
Snowflake,
|
|
42
|
+
safeErrorDescription,
|
|
42
43
|
setProjectDir,
|
|
43
44
|
} from "@gajae-code/utils";
|
|
44
45
|
import {
|
|
@@ -92,6 +93,7 @@ import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "../e
|
|
|
92
93
|
import { CustomToolAdapter } from "../extensibility/custom-tools/wrapper";
|
|
93
94
|
import {
|
|
94
95
|
createCustomToolSettings,
|
|
96
|
+
discoverAndLoadExtensions,
|
|
95
97
|
type ExtensionContext,
|
|
96
98
|
type ExtensionEvent,
|
|
97
99
|
type ExtensionFactory,
|
|
@@ -103,6 +105,7 @@ import {
|
|
|
103
105
|
type FunctionHookResult,
|
|
104
106
|
type LoadExtensionsResult,
|
|
105
107
|
loadExtensionFromFactory,
|
|
108
|
+
loadExtensions,
|
|
106
109
|
type ToolDefinition,
|
|
107
110
|
wrapRegisteredTools,
|
|
108
111
|
} from "../extensibility/extensions";
|
|
@@ -142,6 +145,7 @@ import {
|
|
|
142
145
|
type OptionalRuntimeServicesOverrides,
|
|
143
146
|
} from "../runtime/optional-runtime-services";
|
|
144
147
|
import { loadAllMCPConfigs, MCPManager } from "../runtime-mcp";
|
|
148
|
+
import type { MCPLoadResult } from "../runtime-mcp/manager";
|
|
145
149
|
import type { MCPServerConfig } from "../runtime-mcp/types";
|
|
146
150
|
import {
|
|
147
151
|
getNotificationConfig,
|
|
@@ -160,6 +164,7 @@ import type { SecretObfuscator } from "../secrets";
|
|
|
160
164
|
import { AgentSession, type ForkContextSeed, isSessionDisposalIncompleteError } from "../session/agent-session";
|
|
161
165
|
import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "../session/auth-storage";
|
|
162
166
|
import { type CustomMessage, convertToLlm } from "../session/messages";
|
|
167
|
+
import { primaryControlSurfaceFor } from "../session/primary-control-surface";
|
|
163
168
|
import { createReadonlySessionManager, SessionManager } from "../session/session-manager";
|
|
164
169
|
import {
|
|
165
170
|
parsePersistedCredentialSelector,
|
|
@@ -505,7 +510,9 @@ export interface CreateAgentSessionOptions {
|
|
|
505
510
|
/** Disable extension discovery (explicit paths still load). */
|
|
506
511
|
disableExtensionDiscovery?: boolean;
|
|
507
512
|
/**
|
|
508
|
-
* Pre-loaded extensions (skips file discovery).
|
|
513
|
+
* Pre-loaded extensions (skips file discovery). A COMPLETE result is required:
|
|
514
|
+
* the session reuses its live `runtime`, so a partial result dereferences a
|
|
515
|
+
* missing runtime instead of degrading.
|
|
509
516
|
* @internal Used by CLI when extensions are loaded early to parse custom flags.
|
|
510
517
|
*/
|
|
511
518
|
preloadedExtensions?: LoadExtensionsResult;
|
|
@@ -1271,28 +1278,6 @@ class McpManagerCleanupDiagnosticError extends Error {
|
|
|
1271
1278
|
}
|
|
1272
1279
|
}
|
|
1273
1280
|
|
|
1274
|
-
function safeErrorDescription(value: unknown): string {
|
|
1275
|
-
let isError = false;
|
|
1276
|
-
try {
|
|
1277
|
-
isError = value instanceof Error;
|
|
1278
|
-
} catch {
|
|
1279
|
-
// Hostile proxies can throw from getPrototypeOf during instanceof.
|
|
1280
|
-
}
|
|
1281
|
-
if (isError) {
|
|
1282
|
-
try {
|
|
1283
|
-
const message = (value as { message?: unknown }).message;
|
|
1284
|
-
if (typeof message === "string") return message;
|
|
1285
|
-
} catch {
|
|
1286
|
-
// Hostile error getters must not replace the primary failure.
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
try {
|
|
1290
|
-
return String(value);
|
|
1291
|
-
} catch {
|
|
1292
|
-
return "<unprintable error>";
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
1281
|
function safeIsInstanceOf<T extends object>(value: unknown, ctor: abstract new (...args: any[]) => T): boolean {
|
|
1297
1282
|
try {
|
|
1298
1283
|
return value instanceof ctor;
|
|
@@ -1454,6 +1439,7 @@ function validateAutomationTools(options: CreateAgentSessionOptions): Automation
|
|
|
1454
1439
|
|
|
1455
1440
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
1456
1441
|
const automationTools = validateAutomationTools(options);
|
|
1442
|
+
const primaryControlSurface = primaryControlSurfaceFor(options);
|
|
1457
1443
|
const masterModeContext = options.masterModeContext;
|
|
1458
1444
|
const lifecycleStartupCapability = (
|
|
1459
1445
|
options as CreateAgentSessionOptions & { [lifecycleStartupCapabilityOption]?: SdkStartupCapability }
|
|
@@ -1496,10 +1482,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1496
1482
|
// Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
|
|
1497
1483
|
// failures through that instance, so any divergent storage handed to the bridge / mcpManager
|
|
1498
1484
|
// / session would silently miss credential_disabled events.
|
|
1499
|
-
// Injected auth is
|
|
1500
|
-
// startup
|
|
1485
|
+
// Injected auth is the caller's authority. Only the CLI root can hand off its
|
|
1486
|
+
// explicit startup snapshot; never discover global config for injected SDK auth.
|
|
1501
1487
|
const startupAuthConfig = hasInjectedAuth
|
|
1502
|
-
?
|
|
1488
|
+
? options.modelRegistryStartupMutation?.owner === "cli-root"
|
|
1489
|
+
? options.startupAuthConfig
|
|
1490
|
+
: undefined
|
|
1503
1491
|
: (options.startupAuthConfig ?? (await resolveStartupAuthConfig(agentDir)));
|
|
1504
1492
|
const ownsModelRegistry = options.modelRegistry === undefined;
|
|
1505
1493
|
const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
|
|
@@ -1647,9 +1635,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1647
1635
|
);
|
|
1648
1636
|
}
|
|
1649
1637
|
}
|
|
1650
|
-
if (!options.modelRegistry && !attemptedStartupCacheAdmission) {
|
|
1651
|
-
modelRegistry.refreshInBackground();
|
|
1652
|
-
}
|
|
1653
1638
|
// Resolve the workspace tree through its runtime service. The compatibility
|
|
1654
1639
|
// default starts the native scan at the legacy startup trigger; lazy mode
|
|
1655
1640
|
// leaves the service idle until the first-turn prompt barrier. The native scan
|
|
@@ -1731,7 +1716,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1731
1716
|
const scopeAlreadyLeased = authStorage.hasCredentialScopeLease(credentialSessionId);
|
|
1732
1717
|
authStorage.acquireCredentialScope(credentialSessionId);
|
|
1733
1718
|
credentialScopeLeased = true;
|
|
1734
|
-
const configuredPins =
|
|
1719
|
+
const configuredPins = startupAuthConfig?.credentialPins ?? {};
|
|
1735
1720
|
const persistedPinStoreMatches =
|
|
1736
1721
|
startupAuthConfig?.credentialPinStoreIdentity === startupAuthConfig?.credentialStoreIdentity;
|
|
1737
1722
|
// AuthStorage has no scope-level "pinned but unavailable" marker. Keep the
|
|
@@ -1871,8 +1856,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1871
1856
|
staleDurableCredentialPins.delete(resolveOAuthStorageProvider(record.provider));
|
|
1872
1857
|
}
|
|
1873
1858
|
} catch {
|
|
1859
|
+
const unavailableSelector: AuthCredentialSelector | undefined =
|
|
1860
|
+
pin &&
|
|
1861
|
+
(pin.kind === "id" || pin.kind === "email" || pin.kind === "account" || pin.kind === "project") &&
|
|
1862
|
+
typeof pin.value === "string"
|
|
1863
|
+
? { kind: pin.kind, value: pin.value }
|
|
1864
|
+
: undefined;
|
|
1874
1865
|
// A newer stale pin must not leave an earlier replayed selector active.
|
|
1875
1866
|
authStorage.clearSessionCredentialSelector(record.provider, credentialSessionId);
|
|
1867
|
+
if (unavailableSelector) {
|
|
1868
|
+
authStorage.markSessionCredentialUnavailable(credentialSessionId, record.provider, unavailableSelector);
|
|
1869
|
+
}
|
|
1876
1870
|
staleDurableCredentialPins.add(resolveOAuthStorageProvider(record.provider));
|
|
1877
1871
|
}
|
|
1878
1872
|
}
|
|
@@ -1907,6 +1901,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1907
1901
|
options.credentialSelector.selector,
|
|
1908
1902
|
);
|
|
1909
1903
|
}
|
|
1904
|
+
if (!options.modelRegistry && !attemptedStartupCacheAdmission) {
|
|
1905
|
+
modelRegistry.refreshInBackground("online-if-uncached", credentialSessionId);
|
|
1906
|
+
}
|
|
1910
1907
|
|
|
1911
1908
|
const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
|
|
1912
1909
|
const modelMatchPreferences = {
|
|
@@ -2984,6 +2981,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2984
2981
|
const pluginMcpToolNames: string[] = [];
|
|
2985
2982
|
const conventionalMcpToolNames: string[] = [];
|
|
2986
2983
|
let deferredExactMcpConfig: { manager: MCPManager; configPath: string } | undefined;
|
|
2984
|
+
let deferredConventionalMcp:
|
|
2985
|
+
| {
|
|
2986
|
+
manager: MCPManager;
|
|
2987
|
+
configs: Record<string, MCPServerConfig>;
|
|
2988
|
+
sources: Record<string, SourceMeta>;
|
|
2989
|
+
conventionalConfigs: Record<string, MCPServerConfig>;
|
|
2990
|
+
}
|
|
2991
|
+
| undefined;
|
|
2987
2992
|
|
|
2988
2993
|
// Add image tools when an image role model is configured.
|
|
2989
2994
|
const { getImageGenTools } = await import("../tools/image-gen");
|
|
@@ -3089,6 +3094,88 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3089
3094
|
}
|
|
3090
3095
|
|
|
3091
3096
|
const preExactCustomToolNames = customTools.map(tool => tool.name);
|
|
3097
|
+
// Connect the conventional-autoload manager and run its registration
|
|
3098
|
+
// bookkeeping. Shared by the eager startup path and the deferred starter so
|
|
3099
|
+
// a deferred session registers exactly what an eager one would.
|
|
3100
|
+
const connectOwnedConventionalMcp = async (
|
|
3101
|
+
owned: MCPManager,
|
|
3102
|
+
mergedConfigs: Record<string, MCPServerConfig>,
|
|
3103
|
+
mergedSources: Record<string, SourceMeta>,
|
|
3104
|
+
conventionalConfigs: Record<string, MCPServerConfig>,
|
|
3105
|
+
pluginNames: ReadonlySet<string>,
|
|
3106
|
+
): Promise<MCPLoadResult> => {
|
|
3107
|
+
let result: MCPLoadResult;
|
|
3108
|
+
try {
|
|
3109
|
+
result = await owned.connectServers(mergedConfigs, mergedSources as never);
|
|
3110
|
+
} catch (error) {
|
|
3111
|
+
if (safeIsInstanceOf(error, McpManagerCleanupError)) throw error;
|
|
3112
|
+
// Avoid leaking partially-started server processes on failure.
|
|
3113
|
+
let cleanupError: unknown;
|
|
3114
|
+
try {
|
|
3115
|
+
await owned.disconnectAll();
|
|
3116
|
+
} catch (disconnectError) {
|
|
3117
|
+
cleanupError = disconnectError;
|
|
3118
|
+
} finally {
|
|
3119
|
+
cleanupOwnedMcpManager = undefined;
|
|
3120
|
+
}
|
|
3121
|
+
if (cleanupError !== undefined) throw attachMcpCleanupDiagnostic(error, cleanupError);
|
|
3122
|
+
throw error;
|
|
3123
|
+
}
|
|
3124
|
+
for (const [server, err] of result.errors) {
|
|
3125
|
+
// A server that failed to connect leaves this generation incomplete: its
|
|
3126
|
+
// surfaces produced no evidence, so publishing would present a partial
|
|
3127
|
+
// pass as a clear one. On the deferred path this flag is already
|
|
3128
|
+
// consumed (publication happens before the starter can run), which is
|
|
3129
|
+
// acceptable only because deferral never carries plugin-bundle servers.
|
|
3130
|
+
gjcProducersComplete = false;
|
|
3131
|
+
logger.warn("GJC plugin MCP connect failed", {
|
|
3132
|
+
path: `mcp:${server}`,
|
|
3133
|
+
error: safeErrorForLog(err),
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
const connectedPluginNames = new Set(result.connectedServers.filter(name => pluginNames.has(name)));
|
|
3137
|
+
// Retain while any conventional server is still live: "connecting"
|
|
3138
|
+
// covers the declared-timeout window, and a server that landed in
|
|
3139
|
+
// "connected" inside the microtask between connectServers() resolving
|
|
3140
|
+
// and this synchronous check must not be torn down either.
|
|
3141
|
+
const unsettledConventionalNames = Object.keys(conventionalConfigs).filter(
|
|
3142
|
+
name => owned.getConnectionStatus(name) !== "disconnected",
|
|
3143
|
+
);
|
|
3144
|
+
const retainOwnedManager = result.connectedServers.length > 0 || unsettledConventionalNames.length > 0;
|
|
3145
|
+
if (retainOwnedManager) {
|
|
3146
|
+
mcpManager = owned;
|
|
3147
|
+
ownsMcpManager = true;
|
|
3148
|
+
customTools.push(...(result.tools as CustomTool[]));
|
|
3149
|
+
cwdCapturingToolNames.push(...result.tools.map(tool => tool.name));
|
|
3150
|
+
for (const name of Object.keys(conventionalConfigs)) ownedConventionalMcpServerNames.add(name);
|
|
3151
|
+
pluginMcpManagerServers.set(owned, connectedPluginNames);
|
|
3152
|
+
conventionalMcpManagerServers.set(owned, new Set(Object.keys(conventionalConfigs)));
|
|
3153
|
+
for (const tool of result.tools) {
|
|
3154
|
+
const serverName = tool.mcpServerName;
|
|
3155
|
+
if (serverName === undefined) continue;
|
|
3156
|
+
if (connectedPluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
|
|
3157
|
+
else {
|
|
3158
|
+
conventionalMcpToolNames.push(tool.name);
|
|
3159
|
+
ownedConventionalMcpToolNames.push(tool.name);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
publishOwnedConventionalMcpTools = ownedConventionalMcpServerNames.size > 0;
|
|
3163
|
+
ownedPluginServersConnected = connectedPluginNames.size > 0;
|
|
3164
|
+
// Plugin-bundle connections are fixed for the session lifetime only
|
|
3165
|
+
// when no conventional server is still connecting in the same manager;
|
|
3166
|
+
// otherwise the seal is re-applied once they settle (below).
|
|
3167
|
+
if (connectedPluginNames.size > 0 && unsettledConventionalNames.length === 0) owned.sealConnectionSet();
|
|
3168
|
+
} else {
|
|
3169
|
+
try {
|
|
3170
|
+
await owned.disconnectAll();
|
|
3171
|
+
cleanupOwnedMcpManager = undefined;
|
|
3172
|
+
} catch (cleanupError) {
|
|
3173
|
+
cleanupOwnedMcpManager = undefined;
|
|
3174
|
+
throw new McpManagerCleanupError(cleanupError);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
return result;
|
|
3178
|
+
};
|
|
3092
3179
|
if (explicitMcpConfigPath !== undefined) {
|
|
3093
3180
|
const owned = new MCPManager(cwd, null, {
|
|
3094
3181
|
toolsOnly: true,
|
|
@@ -3191,78 +3278,39 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3191
3278
|
]),
|
|
3192
3279
|
),
|
|
3193
3280
|
};
|
|
3281
|
+
// Plugin-source entries omit SourceMeta.path (see connectOwnedConventionalMcp's
|
|
3282
|
+
// own bypass of the same mismatch at the manager boundary).
|
|
3283
|
+
const mergedSourceMetas = mergedSources as Record<string, SourceMeta>;
|
|
3194
3284
|
if (Object.keys(mergedConfigs).length > 0) {
|
|
3195
3285
|
const owned = new MCPManager(cwd, null, { sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs") });
|
|
3196
3286
|
owned.setAuthStorage(authStorage);
|
|
3197
3287
|
cleanupOwnedMcpManager = () => owned.disconnectAll();
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
//
|
|
3212
|
-
//
|
|
3213
|
-
//
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3288
|
+
// Interactive launches may paint before MCP connects; the deferred
|
|
3289
|
+
// starter reconnects this manager after first paint and a startup
|
|
3290
|
+
// turn barrier keeps the first prompt from racing tool registration.
|
|
3291
|
+
// Plugin-bundle servers never defer: their connect evidence feeds the
|
|
3292
|
+
// GJC runtime snapshot published during creation, and a late failure
|
|
3293
|
+
// would present a partial pass as a clear one.
|
|
3294
|
+
if (options.deferMcpConfigStartup && pluginNames.size === 0) {
|
|
3295
|
+
deferredConventionalMcp = {
|
|
3296
|
+
manager: owned,
|
|
3297
|
+
configs: mergedConfigs,
|
|
3298
|
+
sources: mergedSourceMetas,
|
|
3299
|
+
conventionalConfigs,
|
|
3300
|
+
};
|
|
3301
|
+
// Publish the manager immediately (empty until the starter connects)
|
|
3302
|
+
// so /mcp status, reconnect, and resource inspection resolve it once
|
|
3303
|
+
// the deferred tools land instead of reporting no manager.
|
|
3304
|
+
mcpManager = owned;
|
|
3305
|
+
ownsMcpManager = true;
|
|
3306
|
+
} else {
|
|
3307
|
+
await connectOwnedConventionalMcp(
|
|
3308
|
+
owned,
|
|
3309
|
+
mergedConfigs,
|
|
3310
|
+
mergedSourceMetas,
|
|
3311
|
+
conventionalConfigs,
|
|
3312
|
+
pluginNames,
|
|
3217
3313
|
);
|
|
3218
|
-
const retainOwnedManager =
|
|
3219
|
-
result.connectedServers.length > 0 || unsettledConventionalNames.length > 0;
|
|
3220
|
-
if (retainOwnedManager) {
|
|
3221
|
-
mcpManager = owned;
|
|
3222
|
-
ownsMcpManager = true;
|
|
3223
|
-
customTools.push(...(result.tools as CustomTool[]));
|
|
3224
|
-
cwdCapturingToolNames.push(...result.tools.map(tool => tool.name));
|
|
3225
|
-
for (const name of Object.keys(conventionalConfigs)) ownedConventionalMcpServerNames.add(name);
|
|
3226
|
-
pluginMcpManagerServers.set(owned, connectedPluginNames);
|
|
3227
|
-
conventionalMcpManagerServers.set(owned, new Set(Object.keys(conventionalConfigs)));
|
|
3228
|
-
for (const tool of result.tools) {
|
|
3229
|
-
const serverName = tool.mcpServerName;
|
|
3230
|
-
if (serverName === undefined) continue;
|
|
3231
|
-
if (connectedPluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
|
|
3232
|
-
else {
|
|
3233
|
-
conventionalMcpToolNames.push(tool.name);
|
|
3234
|
-
ownedConventionalMcpToolNames.push(tool.name);
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
publishOwnedConventionalMcpTools = ownedConventionalMcpServerNames.size > 0;
|
|
3238
|
-
ownedPluginServersConnected = connectedPluginNames.size > 0;
|
|
3239
|
-
// Plugin-bundle connections are fixed for the session lifetime only
|
|
3240
|
-
// when no conventional server is still connecting in the same manager;
|
|
3241
|
-
// otherwise the seal is re-applied once they settle (below).
|
|
3242
|
-
if (connectedPluginNames.size > 0 && unsettledConventionalNames.length === 0)
|
|
3243
|
-
owned.sealConnectionSet();
|
|
3244
|
-
} else {
|
|
3245
|
-
try {
|
|
3246
|
-
await owned.disconnectAll();
|
|
3247
|
-
cleanupOwnedMcpManager = undefined;
|
|
3248
|
-
} catch (cleanupError) {
|
|
3249
|
-
cleanupOwnedMcpManager = undefined;
|
|
3250
|
-
throw new McpManagerCleanupError(cleanupError);
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
} catch (error) {
|
|
3254
|
-
if (safeIsInstanceOf(error, McpManagerCleanupError)) throw error;
|
|
3255
|
-
// Avoid leaking partially-started server processes on failure.
|
|
3256
|
-
let cleanupError: unknown;
|
|
3257
|
-
try {
|
|
3258
|
-
await owned.disconnectAll();
|
|
3259
|
-
} catch (disconnectError) {
|
|
3260
|
-
cleanupError = disconnectError;
|
|
3261
|
-
} finally {
|
|
3262
|
-
cleanupOwnedMcpManager = undefined;
|
|
3263
|
-
}
|
|
3264
|
-
if (cleanupError !== undefined) throw attachMcpCleanupDiagnostic(error, cleanupError);
|
|
3265
|
-
throw error;
|
|
3266
3314
|
}
|
|
3267
3315
|
}
|
|
3268
3316
|
} catch (error) {
|
|
@@ -3299,9 +3347,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3299
3347
|
}
|
|
3300
3348
|
// MCP routing is scope-held; no process-global manager registration.
|
|
3301
3349
|
|
|
3302
|
-
//
|
|
3303
|
-
// Recognized hook conventions are the bounded exception: their descriptors
|
|
3350
|
+
// Recognized hook conventions are a bounded additional source: their descriptors
|
|
3304
3351
|
// normalize before import and then adapt into the authoritative ExtensionRunner.
|
|
3352
|
+
// General extension-module discovery runs further below, before that runner is built.
|
|
3305
3353
|
const inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];
|
|
3306
3354
|
const discoveredHookExtensions: Array<{ factory: ExtensionFactory; name: string }> = [];
|
|
3307
3355
|
if (customTools.length > 0) {
|
|
@@ -3406,6 +3454,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3406
3454
|
const createNotificationsExtension = await notificationAdapterService.get("session-extension");
|
|
3407
3455
|
createNotificationsExtension(api, {
|
|
3408
3456
|
settings,
|
|
3457
|
+
primaryControlSurface,
|
|
3409
3458
|
|
|
3410
3459
|
controller: notificationSessionController,
|
|
3411
3460
|
spawnedByGjc,
|
|
@@ -3470,6 +3519,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3470
3519
|
createTransport: input => createSdkWebSocketTransport(input),
|
|
3471
3520
|
settings,
|
|
3472
3521
|
configOverrides: new Map(),
|
|
3522
|
+
primaryControlSurface,
|
|
3473
3523
|
...(masterModeContext
|
|
3474
3524
|
? {
|
|
3475
3525
|
masterCapability: masterModeContext.getCapability(),
|
|
@@ -3545,15 +3595,60 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3545
3595
|
});
|
|
3546
3596
|
}
|
|
3547
3597
|
|
|
3548
|
-
// Extension
|
|
3549
|
-
//
|
|
3550
|
-
//
|
|
3551
|
-
//
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3598
|
+
// Extension modules are discovered at session startup from the canonical
|
|
3599
|
+
// native locations (`<agentDir>/extensions`, `<cwd>/.gjc/extensions`),
|
|
3600
|
+
// installed plugin bundles, the `extensions` setting, and explicitly
|
|
3601
|
+
// supplied `additionalExtensionPaths`. `disableExtensionDiscovery` keeps its
|
|
3602
|
+
// documented opt-out semantics: explicit paths still load, matching
|
|
3603
|
+
// `cli/list-models.ts`. A caller-supplied `preloadedExtensions` result
|
|
3604
|
+
// suppresses discovery entirely.
|
|
3605
|
+
const explicitExtensionPaths = options.additionalExtensionPaths ?? [];
|
|
3606
|
+
// The `extensions` setting is schema-typed as an array of paths, but a
|
|
3607
|
+
// hand-edited config can hold an invalid shape; discovery must degrade to
|
|
3608
|
+
// no extra paths instead of aborting startup for every session.
|
|
3609
|
+
const extensionsSettingPaths = Array.isArray(settings.get("extensions")) ? settings.get("extensions") : [];
|
|
3610
|
+
// Discovery must never block session creation: a filesystem or plugin-registry
|
|
3611
|
+
// failure degrades to the explicit paths and then to no extensions at all, and
|
|
3612
|
+
// the failure stays observable in `extensionsResult.errors`.
|
|
3613
|
+
const loadStartupExtensions = async (): Promise<LoadExtensionsResult> => {
|
|
3614
|
+
try {
|
|
3615
|
+
return options.disableExtensionDiscovery
|
|
3616
|
+
? await loadExtensions(explicitExtensionPaths, cwd, eventBus)
|
|
3617
|
+
: await discoverAndLoadExtensions(
|
|
3618
|
+
[...explicitExtensionPaths, ...extensionsSettingPaths],
|
|
3619
|
+
cwd,
|
|
3620
|
+
eventBus,
|
|
3621
|
+
settings.get("disabledExtensions"),
|
|
3622
|
+
{ agentDir, profileAuthority, settings },
|
|
3623
|
+
);
|
|
3624
|
+
} catch (error) {
|
|
3625
|
+
logger.warn(
|
|
3626
|
+
options.disableExtensionDiscovery
|
|
3627
|
+
? "Failed to load extension modules"
|
|
3628
|
+
: "Failed to discover extension modules",
|
|
3629
|
+
{ error: safeErrorForLog(error) },
|
|
3630
|
+
);
|
|
3631
|
+
const failure = { path: "<extension-discovery>", error: safeErrorDescription(error) };
|
|
3632
|
+
if (options.disableExtensionDiscovery) {
|
|
3633
|
+
return { extensions: [], errors: [failure], runtime: new ExtensionRuntime() };
|
|
3634
|
+
}
|
|
3635
|
+
try {
|
|
3636
|
+
const fallback = await loadExtensions(explicitExtensionPaths, cwd, eventBus);
|
|
3637
|
+
return { ...fallback, errors: [failure, ...fallback.errors] };
|
|
3638
|
+
} catch (fallbackError) {
|
|
3639
|
+
// Last resort: `loadExtensions` does not throw for a bad module, so this
|
|
3640
|
+
// rung guards its contract rather than a known input.
|
|
3641
|
+
logger.warn("Failed to load explicit extension paths", { error: safeErrorForLog(fallbackError) });
|
|
3642
|
+
return { extensions: [], errors: [failure], runtime: new ExtensionRuntime() };
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3556
3645
|
};
|
|
3646
|
+
const extensionsResult: LoadExtensionsResult = options.preloadedExtensions ?? (await loadStartupExtensions());
|
|
3647
|
+
if (options.preloadedExtensions === undefined) {
|
|
3648
|
+
for (const { path: extPath, error } of extensionsResult.errors) {
|
|
3649
|
+
logger.warn("Failed to load extension", { path: extPath, error });
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3557
3652
|
|
|
3558
3653
|
if (!extensionsResult.extensions.some(extension => extension.path === BUNDLED_GROK_BUILD_EXTENSION_ID)) {
|
|
3559
3654
|
const bundledGrokExtension = await loadExtensionFromFactory(
|
|
@@ -4055,6 +4150,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4055
4150
|
toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
|
|
4056
4151
|
eagerTasks: resolveEagerTasks(),
|
|
4057
4152
|
secretsEnabled,
|
|
4153
|
+
taskIsolationEnabled: settings.get("task.isolation.mode") !== "none",
|
|
4058
4154
|
workspaceTree: workspaceTreePromise,
|
|
4059
4155
|
subagent: options.parentTaskPrefix !== undefined,
|
|
4060
4156
|
});
|
|
@@ -4141,13 +4237,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4141
4237
|
);
|
|
4142
4238
|
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
4143
4239
|
if (mcpDiscoveryEnabled) {
|
|
4144
|
-
|
|
4240
|
+
// A pending deferred MCP connect (exact or conventional) registers its
|
|
4241
|
+
// tools after construction, so persisted selections must be preserved
|
|
4242
|
+
// verbatim instead of being trimmed to names the registry currently has.
|
|
4243
|
+
const deferredMcpConnectPending =
|
|
4244
|
+
deferredExactMcpConfig !== undefined || deferredConventionalMcp !== undefined;
|
|
4245
|
+
const restoredSelectedMCPToolNames = deferredMcpConnectPending
|
|
4145
4246
|
? existingSession.selectedMCPToolNames.filter(name => !mandatoryMCPToolNameSet.has(name))
|
|
4146
4247
|
: existingSession.selectedMCPToolNames.filter(
|
|
4147
4248
|
name => toolRegistry.has(name) && !mandatoryMCPToolNameSet.has(name),
|
|
4148
4249
|
);
|
|
4149
4250
|
if (
|
|
4150
|
-
!
|
|
4251
|
+
!deferredMcpConnectPending &&
|
|
4151
4252
|
existingSession.hasPersistedMCPToolSelection &&
|
|
4152
4253
|
restoredSelectedMCPToolNames.length !== existingSession.selectedMCPToolNames.length
|
|
4153
4254
|
) {
|
|
@@ -4342,10 +4443,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4342
4443
|
guardToolForUltragoalAsk(
|
|
4343
4444
|
tool,
|
|
4344
4445
|
() => sessionManager.getCwd(),
|
|
4345
|
-
() => ({
|
|
4346
|
-
activeSkillState: session?.getActiveSkillState(),
|
|
4347
|
-
sessionId: sessionManager.getSessionId?.() ?? null,
|
|
4348
|
-
}),
|
|
4446
|
+
() => ({ sessionId: sessionManager.getSessionId?.() ?? null }),
|
|
4349
4447
|
() => session?.getSessionAgentDir(),
|
|
4350
4448
|
),
|
|
4351
4449
|
);
|
|
@@ -4575,7 +4673,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4575
4673
|
}
|
|
4576
4674
|
}
|
|
4577
4675
|
|
|
4578
|
-
const deferredMcpTurnReady =
|
|
4676
|
+
const deferredMcpTurnReady =
|
|
4677
|
+
deferredExactMcpConfig || deferredConventionalMcp ? Promise.withResolvers<void>() : undefined;
|
|
4579
4678
|
if (deferredMcpTurnReady) void deferredMcpTurnReady.promise.catch(() => {});
|
|
4580
4679
|
|
|
4581
4680
|
session = new AgentSession({
|
|
@@ -4660,7 +4759,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4660
4759
|
mcpDiscoveryEnabled,
|
|
4661
4760
|
discoveryMode: effectiveDiscoveryMode,
|
|
4662
4761
|
initialSelectedMCPToolNames,
|
|
4663
|
-
|
|
4762
|
+
// Both deferral shapes register their tools only in the deferred starter,
|
|
4763
|
+
// so the registry is empty at construction and persisted selections must be
|
|
4764
|
+
// preserved verbatim until the starter publishes the real catalog.
|
|
4765
|
+
preserveUnavailableInitialMCPToolSelection:
|
|
4766
|
+
deferredExactMcpConfig !== undefined || deferredConventionalMcp !== undefined,
|
|
4664
4767
|
initialMCPToolSelectionIsExplicit: hasExplicitMCPToolSelection,
|
|
4665
4768
|
initialDiscoveredBuiltinToolSelectionIsExplicit: hasExplicitDiscoveredBuiltinToolSelection,
|
|
4666
4769
|
initialSelectedDiscoveredBuiltinToolNames,
|
|
@@ -4872,68 +4975,82 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4872
4975
|
// createAgentSession rather than surfacing as an unhandled rejection.
|
|
4873
4976
|
if (!options.deferMemoryBackendStartup) await startMemoryBackend();
|
|
4874
4977
|
|
|
4978
|
+
// Wire the owned manager's late tool publications into the live session.
|
|
4979
|
+
// Shared by the eager startup path below and by the deferred conventional
|
|
4980
|
+
// starter once its connect lands. No-op when there is no owned manager or
|
|
4981
|
+
// nothing conventional to publish.
|
|
4982
|
+
const wireOwnedConventionalToolSync = (): void => {
|
|
4983
|
+
if (!mcpManager || !publishOwnedConventionalMcpTools) return;
|
|
4984
|
+
// Late conventional connections can publish near-simultaneously.
|
|
4985
|
+
// Serialize the swaps so an older snapshot cannot interleave with a
|
|
4986
|
+
// newer one inside replaceNamedCustomTools and leave a stale list.
|
|
4987
|
+
// Each link swallows (and logs) its own failure so one bad
|
|
4988
|
+
// publication cannot kill the chain for every later one.
|
|
4989
|
+
let conventionalToolsSync: Promise<void> = Promise.resolve();
|
|
4990
|
+
const syncConventionalTools = (tools: CustomTool[]): Promise<void> => {
|
|
4991
|
+
conventionalToolsSync = conventionalToolsSync
|
|
4992
|
+
.then(async () => {
|
|
4993
|
+
if (session.isDisposed) return;
|
|
4994
|
+
const nextTools = tools.filter(tool =>
|
|
4995
|
+
tool.mcpServerName ? ownedConventionalMcpServerNames.has(tool.mcpServerName) : false,
|
|
4996
|
+
);
|
|
4997
|
+
const previousNames = ownedConventionalMcpToolNames;
|
|
4998
|
+
ownedConventionalMcpToolNames = nextTools.map(tool => tool.name);
|
|
4999
|
+
const previousSet = new Set(previousNames);
|
|
5000
|
+
cwdCapturingToolNames.splice(
|
|
5001
|
+
0,
|
|
5002
|
+
cwdCapturingToolNames.length,
|
|
5003
|
+
...cwdCapturingToolNames.filter(name => !previousSet.has(name)),
|
|
5004
|
+
...ownedConventionalMcpToolNames,
|
|
5005
|
+
);
|
|
5006
|
+
await session.replaceNamedCustomTools(previousNames, nextTools);
|
|
5007
|
+
// Mixed plugin + conventional sessions deferred the seal while
|
|
5008
|
+
// a conventional server was still connecting; restore the
|
|
5009
|
+
// fixed-connection plugin contract once every conventional
|
|
5010
|
+
// server has reached a terminal state.
|
|
5011
|
+
if (
|
|
5012
|
+
ownedPluginServersConnected &&
|
|
5013
|
+
mcpManager !== undefined &&
|
|
5014
|
+
!mcpManager.isConnectionSetSealed() &&
|
|
5015
|
+
![...ownedConventionalMcpServerNames].some(
|
|
5016
|
+
name => mcpManager?.getConnectionStatus(name) === "connecting",
|
|
5017
|
+
)
|
|
5018
|
+
) {
|
|
5019
|
+
mcpManager.sealConnectionSet();
|
|
5020
|
+
}
|
|
5021
|
+
})
|
|
5022
|
+
.catch(error => {
|
|
5023
|
+
logger.warn("Failed to publish conventional MCP tools", { error: safeErrorForLog(error) });
|
|
5024
|
+
});
|
|
5025
|
+
return conventionalToolsSync;
|
|
5026
|
+
};
|
|
5027
|
+
mcpManager.setOnToolsChanged(tools => {
|
|
5028
|
+
void syncConventionalTools(tools as CustomTool[]);
|
|
5029
|
+
});
|
|
5030
|
+
void syncConventionalTools(mcpManager.getTools() as CustomTool[]);
|
|
5031
|
+
};
|
|
5032
|
+
const wireOwnedMcpManagerLifecycle = (): void => {
|
|
5033
|
+
if (!mcpManager) return;
|
|
5034
|
+
const clearDebounceTimers = () => {
|
|
5035
|
+
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
5036
|
+
notificationDebounceTimers.clear();
|
|
5037
|
+
};
|
|
5038
|
+
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
5039
|
+
wireMcpManagerCallbacks(mcpManager);
|
|
5040
|
+
};
|
|
4875
5041
|
// Exact-config managers do not receive reactive callbacks; their tools are
|
|
4876
|
-
// registered once in the session-owned catalog.
|
|
4877
|
-
|
|
5042
|
+
// registered once in the session-owned catalog. A pending conventional
|
|
5043
|
+
// deferral wires its own callbacks from the starter after it connects, so
|
|
5044
|
+
// nothing is wired here yet.
|
|
5045
|
+
if (mcpManager && !options.mcpManager && explicitMcpConfigPath === undefined && !deferredConventionalMcp) {
|
|
4878
5046
|
if (publishOwnedConventionalMcpTools) {
|
|
4879
|
-
|
|
4880
|
-
// Serialize the swaps so an older snapshot cannot interleave with a
|
|
4881
|
-
// newer one inside replaceNamedCustomTools and leave a stale list.
|
|
4882
|
-
// Each link swallows (and logs) its own failure so one bad
|
|
4883
|
-
// publication cannot kill the chain for every later one.
|
|
4884
|
-
let conventionalToolsSync: Promise<void> = Promise.resolve();
|
|
4885
|
-
const syncConventionalTools = (tools: CustomTool[]): Promise<void> => {
|
|
4886
|
-
conventionalToolsSync = conventionalToolsSync
|
|
4887
|
-
.then(async () => {
|
|
4888
|
-
if (session.isDisposed) return;
|
|
4889
|
-
const nextTools = tools.filter(tool =>
|
|
4890
|
-
tool.mcpServerName ? ownedConventionalMcpServerNames.has(tool.mcpServerName) : false,
|
|
4891
|
-
);
|
|
4892
|
-
const previousNames = ownedConventionalMcpToolNames;
|
|
4893
|
-
ownedConventionalMcpToolNames = nextTools.map(tool => tool.name);
|
|
4894
|
-
const previousSet = new Set(previousNames);
|
|
4895
|
-
cwdCapturingToolNames.splice(
|
|
4896
|
-
0,
|
|
4897
|
-
cwdCapturingToolNames.length,
|
|
4898
|
-
...cwdCapturingToolNames.filter(name => !previousSet.has(name)),
|
|
4899
|
-
...ownedConventionalMcpToolNames,
|
|
4900
|
-
);
|
|
4901
|
-
await session.replaceNamedCustomTools(previousNames, nextTools);
|
|
4902
|
-
// Mixed plugin + conventional sessions deferred the seal while
|
|
4903
|
-
// a conventional server was still connecting; restore the
|
|
4904
|
-
// fixed-connection plugin contract once every conventional
|
|
4905
|
-
// server has reached a terminal state.
|
|
4906
|
-
if (
|
|
4907
|
-
ownedPluginServersConnected &&
|
|
4908
|
-
mcpManager !== undefined &&
|
|
4909
|
-
!mcpManager.isConnectionSetSealed() &&
|
|
4910
|
-
![...ownedConventionalMcpServerNames].some(
|
|
4911
|
-
name => mcpManager?.getConnectionStatus(name) === "connecting",
|
|
4912
|
-
)
|
|
4913
|
-
) {
|
|
4914
|
-
mcpManager.sealConnectionSet();
|
|
4915
|
-
}
|
|
4916
|
-
})
|
|
4917
|
-
.catch(error => {
|
|
4918
|
-
logger.warn("Failed to publish conventional MCP tools", { error: safeErrorForLog(error) });
|
|
4919
|
-
});
|
|
4920
|
-
return conventionalToolsSync;
|
|
4921
|
-
};
|
|
4922
|
-
mcpManager.setOnToolsChanged(tools => {
|
|
4923
|
-
void syncConventionalTools(tools as CustomTool[]);
|
|
4924
|
-
});
|
|
4925
|
-
void syncConventionalTools(mcpManager.getTools() as CustomTool[]);
|
|
5047
|
+
wireOwnedConventionalToolSync();
|
|
4926
5048
|
} else if (!ownsMcpManager) {
|
|
4927
5049
|
mcpManager.setOnToolsChanged(tools => {
|
|
4928
5050
|
void session.refreshMCPTools(tools);
|
|
4929
5051
|
});
|
|
4930
5052
|
}
|
|
4931
|
-
|
|
4932
|
-
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
4933
|
-
notificationDebounceTimers.clear();
|
|
4934
|
-
};
|
|
4935
|
-
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
4936
|
-
wireMcpManagerCallbacks(mcpManager);
|
|
5053
|
+
wireOwnedMcpManagerLifecycle();
|
|
4937
5054
|
}
|
|
4938
5055
|
|
|
4939
5056
|
// Constructor-time workflow-gate tool restoration is deferred by one
|
|
@@ -4941,15 +5058,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4941
5058
|
// so a resumed canonical workflow session returns with `ask` resident.
|
|
4942
5059
|
await session.workflowGateToolRestoration;
|
|
4943
5060
|
let startDeferredMcpConfig: (() => Promise<DeferredMcpConfigStartupResult>) | undefined;
|
|
4944
|
-
if (deferredExactMcpConfig) {
|
|
4945
|
-
const { manager, configPath } = deferredExactMcpConfig;
|
|
5061
|
+
if (deferredExactMcpConfig || deferredConventionalMcp) {
|
|
4946
5062
|
let cancelled = false;
|
|
4947
5063
|
let startupPromise: Promise<DeferredMcpConfigStartupResult> | undefined;
|
|
4948
5064
|
session.registerToolSessionCleanup(async () => {
|
|
4949
5065
|
cancelled = true;
|
|
4950
5066
|
deferredMcpTurnReady?.resolve();
|
|
4951
5067
|
if (startupPromise) {
|
|
4952
|
-
await manager.disconnectAll().catch(() => {});
|
|
5068
|
+
await deferredExactMcpConfig?.manager.disconnectAll().catch(() => {});
|
|
5069
|
+
await deferredConventionalMcp?.manager.disconnectAll().catch(() => {});
|
|
4953
5070
|
await startupPromise.catch(() => {});
|
|
4954
5071
|
}
|
|
4955
5072
|
});
|
|
@@ -4959,33 +5076,83 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4959
5076
|
}
|
|
4960
5077
|
startupPromise ??= (async () => {
|
|
4961
5078
|
try {
|
|
4962
|
-
const
|
|
4963
|
-
const
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
if (
|
|
4975
|
-
|
|
4976
|
-
(!mcpDiscoveryEnabled || !existingSession.hasPersistedMCPToolSelection)
|
|
4977
|
-
) {
|
|
4978
|
-
await session.activateDiscoveredTools(toolNames);
|
|
5079
|
+
const exact = deferredExactMcpConfig;
|
|
5080
|
+
const conventional = deferredConventionalMcp;
|
|
5081
|
+
let loadedToolCount = 0;
|
|
5082
|
+
let hasErrors = false;
|
|
5083
|
+
if (exact) {
|
|
5084
|
+
const result = await exact.manager.discoverAndConnect({ configPath: exact.configPath });
|
|
5085
|
+
const resultTools = result.tools as CustomTool[];
|
|
5086
|
+
const toolNames = resultTools.map(tool => tool.name);
|
|
5087
|
+
const collidingToolNames = findDeferredExactMcpToolNameCollisions(
|
|
5088
|
+
toolNames,
|
|
5089
|
+
exactMcpCatalogToolNames,
|
|
5090
|
+
);
|
|
5091
|
+
if (collidingToolNames.length > 0) {
|
|
5092
|
+
throw new ExactMcpToolNameCollisionError(collidingToolNames);
|
|
4979
5093
|
}
|
|
5094
|
+
if (!cancelled && !session.isDisposed) {
|
|
5095
|
+
await session.refreshMCPTools(resultTools);
|
|
5096
|
+
if (
|
|
5097
|
+
!session.isDisposed &&
|
|
5098
|
+
(!mcpDiscoveryEnabled || !existingSession.hasPersistedMCPToolSelection)
|
|
5099
|
+
) {
|
|
5100
|
+
await session.activateDiscoveredTools(toolNames);
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
loadedToolCount = resultTools.length;
|
|
5104
|
+
hasErrors = result.errors.size > 0 || resultTools.length === 0;
|
|
5105
|
+
} else if (conventional) {
|
|
5106
|
+
// Deferral never carries plugin-bundle servers, so this merge is
|
|
5107
|
+
// purely conventional and the GJC evidence snapshot published
|
|
5108
|
+
// during creation has no deferred producer to wait for.
|
|
5109
|
+
const result = await connectOwnedConventionalMcp(
|
|
5110
|
+
conventional.manager,
|
|
5111
|
+
conventional.configs,
|
|
5112
|
+
conventional.sources,
|
|
5113
|
+
conventional.conventionalConfigs,
|
|
5114
|
+
new Set<string>(),
|
|
5115
|
+
);
|
|
5116
|
+
const resultTools = result.tools as CustomTool[];
|
|
5117
|
+
if (!cancelled && !session.isDisposed) {
|
|
5118
|
+
// Register the deferred tools before wiring the reactive conventional-tool
|
|
5119
|
+
// sync. The sync's initial publication calls replaceNamedCustomTools, which
|
|
5120
|
+
// only re-activates a swapped tool that was already active; running it
|
|
5121
|
+
// before this registration would therefore drop a persisted conventional
|
|
5122
|
+
// MCP selection and persist the empty set.
|
|
5123
|
+
await session.refreshMCPTools(resultTools);
|
|
5124
|
+
wireOwnedConventionalToolSync();
|
|
5125
|
+
wireOwnedMcpManagerLifecycle();
|
|
5126
|
+
if (
|
|
5127
|
+
!session.isDisposed &&
|
|
5128
|
+
(!mcpDiscoveryEnabled || !existingSession.hasPersistedMCPToolSelection)
|
|
5129
|
+
) {
|
|
5130
|
+
await session.activateDiscoveredTools(resultTools.map(tool => tool.name));
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
loadedToolCount = resultTools.length;
|
|
5134
|
+
// A server whose declared timeout outlived the startup budget is
|
|
5135
|
+
// still connecting and publishes reactively (same steady state as
|
|
5136
|
+
// the eager path): that is not a failure, so do not warn or count
|
|
5137
|
+
// it as one while its handshake window is open.
|
|
5138
|
+
const stillConnectingServers = Object.keys(conventional.conventionalConfigs).filter(
|
|
5139
|
+
name => conventional.manager.getConnectionStatus(name) === "connecting",
|
|
5140
|
+
);
|
|
5141
|
+
hasErrors =
|
|
5142
|
+
result.errors.size > 0 || (resultTools.length === 0 && stillConnectingServers.length === 0);
|
|
4980
5143
|
}
|
|
4981
5144
|
deferredMcpTurnReady?.resolve();
|
|
4982
|
-
const hasErrors = result.errors.size > 0 || result.tools.length === 0;
|
|
4983
5145
|
if (hasErrors) logger.warn(DEFERRED_MCP_CONFIG_STARTUP_ERROR);
|
|
4984
|
-
return { loadedToolCount: cancelled || session.isDisposed ? 0 :
|
|
5146
|
+
return { loadedToolCount: cancelled || session.isDisposed ? 0 : loadedToolCount, hasErrors };
|
|
4985
5147
|
} catch {
|
|
4986
5148
|
const startupError = new Error(DEFERRED_MCP_CONFIG_STARTUP_ERROR);
|
|
4987
|
-
|
|
4988
|
-
|
|
5149
|
+
// Report the failure through this promise (the caller surfaces the MCP
|
|
5150
|
+
// error), but release the startup turn barrier: rejecting it would leave
|
|
5151
|
+
// it installed forever, so every later prompt would await a rejected
|
|
5152
|
+
// barrier and the interactive session would be unusable.
|
|
5153
|
+
deferredMcpTurnReady?.resolve();
|
|
5154
|
+
await deferredExactMcpConfig?.manager.disconnectAll().catch(() => {});
|
|
5155
|
+
await deferredConventionalMcp?.manager.disconnectAll().catch(() => {});
|
|
4989
5156
|
throw startupError;
|
|
4990
5157
|
}
|
|
4991
5158
|
})();
|