@gajae-code/coding-agent 0.18.7 → 0.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +279 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +2 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +6 -1
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +62 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +17 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +28 -8
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +159 -20
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -9
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/internal-urls/docs-index.generated.ts +18 -17
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +88 -4
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +75 -6
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2647 -294
- package/src/sdk/host/session-runtime.ts +426 -109
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +75 -12
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +67 -38
- package/src/session/agent-session.ts +623 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +376 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +417 -14
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -50,13 +50,17 @@ export async function writeMCPConfigFile(filePath: string, config: MCPConfigFile
|
|
|
50
50
|
const dir = path.dirname(filePath);
|
|
51
51
|
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
53
|
+
// A predictable `${file}.tmp` can already be a symlink planted in the project.
|
|
54
|
+
// Write a unique sibling and fail closed if that path exists.
|
|
55
|
+
const tmpPath = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
55
56
|
const content = JSON.stringify(withSchema(config), null, 2);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try {
|
|
58
|
+
await fs.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 0o600, flag: "wx" });
|
|
59
|
+
await fs.promises.rename(tmpPath, filePath);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
await fs.promises.rm(tmpPath, { force: true });
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
60
64
|
// Invalidate the capability fs cache so subsequent reads see the new content
|
|
61
65
|
invalidateFsCache(filePath);
|
|
62
66
|
}
|
|
@@ -103,6 +103,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
103
103
|
url: server.url ?? "",
|
|
104
104
|
};
|
|
105
105
|
if (server.headers) config.headers = server.headers;
|
|
106
|
+
if (server.publicNetwork === true) config.publicNetwork = true;
|
|
106
107
|
canonicalizeMCPEndpoint(config.url);
|
|
107
108
|
return config;
|
|
108
109
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { OAuthCallbackFlowOptions } from "@gajae-code/ai/utils/oauth/callback-server";
|
|
9
9
|
import { OAuthCallbackFlow } from "@gajae-code/ai/utils/oauth/callback-server";
|
|
10
10
|
import type { OAuthController, OAuthCredentials } from "@gajae-code/ai/utils/oauth/types";
|
|
11
|
+
import { assertPublicOAuthUrl } from "./oauth-public-url";
|
|
11
12
|
|
|
12
13
|
const DEFAULT_PORT = 3000;
|
|
13
14
|
const CALLBACK_PATH = "/callback";
|
|
@@ -261,8 +262,10 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
261
262
|
params.set("client_secret", clientSecret);
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
await assertPublicOAuthUrl(this.config.tokenUrl);
|
|
264
266
|
const response = await fetch(this.config.tokenUrl, {
|
|
265
267
|
method: "POST",
|
|
268
|
+
redirect: "error",
|
|
266
269
|
headers: {
|
|
267
270
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
268
271
|
},
|
|
@@ -340,8 +343,10 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
340
343
|
if (!registrationEndpoint) return;
|
|
341
344
|
|
|
342
345
|
try {
|
|
346
|
+
await assertPublicOAuthUrl(registrationEndpoint);
|
|
343
347
|
const response = await fetch(registrationEndpoint, {
|
|
344
348
|
method: "POST",
|
|
349
|
+
redirect: "error",
|
|
345
350
|
headers: {
|
|
346
351
|
"Content-Type": "application/json",
|
|
347
352
|
Accept: "application/json",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
2
|
+
|
|
3
|
+
export async function assertPublicOAuthUrl(url: string): Promise<void> {
|
|
4
|
+
const checked = await validatePublicHttpUrl(url);
|
|
5
|
+
if (!checked.ok) {
|
|
6
|
+
throw new Error(`Refusing non-public OAuth endpoint: ${checked.reason ?? url}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -19,6 +19,10 @@ export function bindPluginMcpToPublicNetwork<T extends MCPHttpServerConfig | MCP
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** The marker is an internal symbol, so persisted user MCP configs cannot opt into or forge this path. */
|
|
22
|
+
export function usesPublicNetworkMcpFetch(config: MCPHttpServerConfig | MCPSseServerConfig): boolean {
|
|
23
|
+
return ("publicNetwork" in config && config.publicNetwork === true) || isPluginMcpPublicNetworkBound(config);
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
export function isPluginMcpPublicNetworkBound(config: MCPHttpServerConfig | MCPSseServerConfig): boolean {
|
|
23
27
|
return (
|
|
24
28
|
(config as MCPHttpServerConfig & { [PLUGIN_MCP_PUBLIC_NETWORK_BOUNDARY]?: boolean })[
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from "@gajae-code/utils";
|
|
2
|
+
import { type AddressResolver, validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
2
3
|
import type { MCPServerConfig } from "./types";
|
|
3
4
|
|
|
4
5
|
const SMITHERY_REGISTRY_BASE_URL = "https://registry.smithery.ai";
|
|
@@ -289,6 +290,20 @@ function chooseConnection(
|
|
|
289
290
|
return null;
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
export async function resolveSmitheryServerConfig(
|
|
294
|
+
qualifiedName: string,
|
|
295
|
+
selected: { connection: SmitheryConnection; useDirectHttp: boolean },
|
|
296
|
+
options?: { resolver?: AddressResolver },
|
|
297
|
+
): Promise<MCPServerConfig | null> {
|
|
298
|
+
let useDirectHttp = selected.useDirectHttp;
|
|
299
|
+
const deploymentUrl = selected.connection.deploymentUrl;
|
|
300
|
+
if (useDirectHttp && selected.connection.type === "http" && deploymentUrl) {
|
|
301
|
+
const checked = await validatePublicHttpUrl(deploymentUrl, { resolver: options?.resolver });
|
|
302
|
+
if (!checked.ok) useDirectHttp = false;
|
|
303
|
+
}
|
|
304
|
+
return createConfig(qualifiedName, { connection: selected.connection, useDirectHttp });
|
|
305
|
+
}
|
|
306
|
+
|
|
292
307
|
function createConfig(
|
|
293
308
|
qualifiedName: string,
|
|
294
309
|
selected: { connection: SmitheryConnection; useDirectHttp: boolean },
|
|
@@ -297,6 +312,7 @@ function createConfig(
|
|
|
297
312
|
return {
|
|
298
313
|
type: "http",
|
|
299
314
|
url: selected.connection.deploymentUrl,
|
|
315
|
+
publicNetwork: true,
|
|
300
316
|
};
|
|
301
317
|
}
|
|
302
318
|
|
|
@@ -335,7 +351,10 @@ async function fetchServerDetailsFromEntry(
|
|
|
335
351
|
return null;
|
|
336
352
|
}
|
|
337
353
|
|
|
338
|
-
function toSearchResult(
|
|
354
|
+
async function toSearchResult(
|
|
355
|
+
entry: SmitherySearchEntry,
|
|
356
|
+
details: SmitheryServerDetails,
|
|
357
|
+
): Promise<SmitherySearchResult | null> {
|
|
339
358
|
if (!entry.id) return null;
|
|
340
359
|
const qualifiedName = normalizeQualifiedName(
|
|
341
360
|
details.qualifiedName ?? entry.qualifiedName ?? `${entry.namespace}/${entry.slug}`,
|
|
@@ -343,7 +362,7 @@ function toSearchResult(entry: SmitherySearchEntry, details: SmitheryServerDetai
|
|
|
343
362
|
const selected = chooseConnection(details);
|
|
344
363
|
if (!selected) return null;
|
|
345
364
|
|
|
346
|
-
const config =
|
|
365
|
+
const config = await resolveSmitheryServerConfig(qualifiedName, selected);
|
|
347
366
|
if (!config) return null;
|
|
348
367
|
|
|
349
368
|
const requiredInputs = getSchemaInputs(selected.connection.configSchema);
|
|
@@ -450,7 +469,7 @@ export async function searchSmitheryRegistry(
|
|
|
450
469
|
try {
|
|
451
470
|
const details = await fetchServerDetailsFromEntry(entry, { apiKey: options?.apiKey });
|
|
452
471
|
if (!details) return null;
|
|
453
|
-
return toSearchResult(entry, details);
|
|
472
|
+
return await toSearchResult(entry, details);
|
|
454
473
|
} catch (error) {
|
|
455
474
|
detailFailures.push({
|
|
456
475
|
identity: getEntryIdentityKey(entry) ?? entry.id ?? "unknown",
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
MCP_MAX_SSE_REQUEST_MESSAGES,
|
|
29
29
|
readMCPResponseText,
|
|
30
30
|
} from "../content-limits";
|
|
31
|
-
import { fetchPluginMcpRequest,
|
|
31
|
+
import { fetchPluginMcpRequest, usesPublicNetworkMcpFetch } from "../plugin-network-boundary";
|
|
32
32
|
import { buildModernMcpHeaders, type MCPModernClientContext, type MCPProtocolEra, withModernMeta } from "../protocol";
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -155,7 +155,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
#fetch(init: BunFetchRequestInit): Promise<Response> {
|
|
158
|
-
return
|
|
158
|
+
return usesPublicNetworkMcpFetch(this.config)
|
|
159
159
|
? fetchPluginMcpRequest(this.config.url, init)
|
|
160
160
|
: fetch(this.config.url, init);
|
|
161
161
|
}
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -139,6 +139,12 @@ export interface MCPHttpServerConfig extends MCPServerConfigBase {
|
|
|
139
139
|
type: "http";
|
|
140
140
|
url: string;
|
|
141
141
|
headers?: Record<string, string>;
|
|
142
|
+
/**
|
|
143
|
+
* When true, every request and redirect for this URL is rechecked against
|
|
144
|
+
* the public-network policy. Persisted so a later process cannot fall back
|
|
145
|
+
* to an unguarded fetch. Ordinary localhost MCP servers leave this unset.
|
|
146
|
+
*/
|
|
147
|
+
publicNetwork?: boolean;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** SSE server configuration (deprecated, use HTTP) */
|
package/src/sdk/acp/adapter.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { logger } from "@gajae-code/utils";
|
|
3
3
|
import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_SDK_REQUEST_TIMEOUT_MS,
|
|
6
|
+
type SdkClient,
|
|
7
|
+
SdkClientError,
|
|
8
|
+
type SdkDispatchContext,
|
|
9
|
+
type SdkSentRecord,
|
|
10
|
+
} from "../client";
|
|
5
11
|
import type { AbortScope } from "../host/control/operations";
|
|
6
12
|
import { assertReverseResponseFrame, ReverseLeaseError } from "../host/reverse-leases";
|
|
7
13
|
import {
|
|
@@ -143,6 +149,7 @@ const ACP_MCP_PRESERVED_LAUNCH_CODES = new Set([
|
|
|
143
149
|
"readiness_timeout",
|
|
144
150
|
"spawn_failed",
|
|
145
151
|
"worktree_in_use",
|
|
152
|
+
"uncertain_after_send",
|
|
146
153
|
]);
|
|
147
154
|
|
|
148
155
|
/**
|
|
@@ -531,7 +538,11 @@ export class AcpSdkAdapter {
|
|
|
531
538
|
return envelope?.result ?? response;
|
|
532
539
|
}
|
|
533
540
|
|
|
534
|
-
async #requestSession(
|
|
541
|
+
async #requestSession(
|
|
542
|
+
frame: JsonObject,
|
|
543
|
+
raw = false,
|
|
544
|
+
options?: { timeoutMs: number; deadline?: number },
|
|
545
|
+
): Promise<unknown> {
|
|
535
546
|
const router = this.#router;
|
|
536
547
|
if (!router)
|
|
537
548
|
throw new AcpSdkAdapterError(
|
|
@@ -603,11 +614,68 @@ export class AcpSdkAdapter {
|
|
|
603
614
|
// request that named no readiness budget is queued for the default one, so it
|
|
604
615
|
// needs the same extension rather than the client's generic request deadline.
|
|
605
616
|
const timeoutMs = lifecycleRequestTimeoutMs(operation, input);
|
|
606
|
-
const
|
|
617
|
+
const budgetMs = timeoutMs ?? DEFAULT_SDK_REQUEST_TIMEOUT_MS;
|
|
618
|
+
const deadline = Date.now() + budgetMs;
|
|
619
|
+
let sent = false;
|
|
620
|
+
let sentRecord: SdkSentRecord | undefined;
|
|
621
|
+
let uncertainty: SdkClientError | undefined;
|
|
622
|
+
const deadlineFailure = () =>
|
|
623
|
+
uncertainty ??
|
|
624
|
+
(sent
|
|
625
|
+
? new SdkClientError(
|
|
626
|
+
"uncertain_after_send",
|
|
627
|
+
"ACP lifecycle deadline elapsed after dispatch.",
|
|
628
|
+
sentRecord ?? {
|
|
629
|
+
operation,
|
|
630
|
+
idempotencyKey,
|
|
631
|
+
},
|
|
632
|
+
)
|
|
633
|
+
: new SdkClientError("timeout", "ACP lifecycle deadline elapsed before dispatch.", undefined, undefined, {
|
|
634
|
+
transport: true,
|
|
635
|
+
}));
|
|
636
|
+
const options = {
|
|
607
637
|
idempotencyKey,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
638
|
+
deadline,
|
|
639
|
+
// SdkClient reads this after connecting, so reconnect and replay spend the same allowance.
|
|
640
|
+
get timeoutMs() {
|
|
641
|
+
return Math.max(0, deadline - Date.now());
|
|
642
|
+
},
|
|
643
|
+
beforeDispatch: () => {
|
|
644
|
+
if (Date.now() >= deadline) throw deadlineFailure();
|
|
645
|
+
},
|
|
646
|
+
onDispatch: (context: SdkDispatchContext) => {
|
|
647
|
+
sent = true;
|
|
648
|
+
if (typeof context.frame.id === "string") sentRecord = client.getSentRecord(context.frame.id);
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
const expired = Promise.withResolvers<never>();
|
|
652
|
+
const timer = setTimeout(() => expired.reject(deadlineFailure()), budgetMs);
|
|
653
|
+
const recover = async () => {
|
|
654
|
+
try {
|
|
655
|
+
const result = await client.global(operation, input, options);
|
|
656
|
+
if (Date.now() >= deadline) throw deadlineFailure();
|
|
657
|
+
return result;
|
|
658
|
+
} catch (firstError) {
|
|
659
|
+
if (!(firstError instanceof SdkClientError) || firstError.code !== "uncertain_after_send") throw firstError;
|
|
660
|
+
uncertainty = firstError;
|
|
661
|
+
// Same-key replay joins the original durable claim, within the original deadline.
|
|
662
|
+
// Failure to recover does not prove that the committed operation failed.
|
|
663
|
+
if (Date.now() >= deadline) throw firstError;
|
|
664
|
+
try {
|
|
665
|
+
const result = await client.global(operation, input, options);
|
|
666
|
+
if (Date.now() >= deadline) throw firstError;
|
|
667
|
+
return result;
|
|
668
|
+
} catch (replayError) {
|
|
669
|
+
if (replayError !== firstError) Object.assign(firstError, { recovery: replayError });
|
|
670
|
+
throw firstError;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
try {
|
|
675
|
+
return await Promise.race([recover(), expired.promise]);
|
|
676
|
+
} finally {
|
|
677
|
+
clearTimeout(timer);
|
|
678
|
+
}
|
|
611
679
|
}
|
|
612
680
|
|
|
613
681
|
async sdkControl(params: { operation: string; input?: JsonObject }): Promise<unknown> {
|
|
@@ -646,7 +714,7 @@ export class AcpSdkAdapter {
|
|
|
646
714
|
throw new AcpSdkAdapterError("method_not_found", `Unsupported ACP SDK method: ${method}`);
|
|
647
715
|
}
|
|
648
716
|
|
|
649
|
-
async registerProvider(provider: AcpProviderRegistration): Promise<void> {
|
|
717
|
+
async registerProvider(provider: AcpProviderRegistration, timeoutMs?: number, deadline?: number): Promise<void> {
|
|
650
718
|
if (!this.#router)
|
|
651
719
|
throw new AcpSdkAdapterError(
|
|
652
720
|
"operation_prohibited",
|
|
@@ -665,10 +733,15 @@ export class AcpSdkAdapter {
|
|
|
665
733
|
...(previousLeaseId ? { expectedLeaseId: previousLeaseId } : {}),
|
|
666
734
|
},
|
|
667
735
|
true,
|
|
736
|
+
timeoutMs === undefined ? undefined : { timeoutMs, ...(deadline === undefined ? {} : { deadline }) },
|
|
668
737
|
);
|
|
669
738
|
const result = object(object(response)?.result) ?? object(response) ?? {};
|
|
670
739
|
if (typeof result.leaseId !== "string")
|
|
671
740
|
throw new AcpSdkAdapterError("invalid_reverse_frame", "Provider registration omitted leaseId.");
|
|
741
|
+
if (this.#closed)
|
|
742
|
+
throw new AcpSdkAdapterError("connection_closed", "ACP provider activation closed before acknowledgement.");
|
|
743
|
+
if (deadline !== undefined && Date.now() >= deadline)
|
|
744
|
+
throw new AcpSdkAdapterError("provider_activation_exhausted", "ACP provider activation deadline elapsed.");
|
|
672
745
|
this.#leases.set(provider.capability, result.leaseId);
|
|
673
746
|
if (previousLeaseId && previousLeaseId !== result.leaseId) this.#abortReverseForCapability(provider.capability);
|
|
674
747
|
}
|
|
@@ -694,9 +767,20 @@ export class AcpSdkAdapter {
|
|
|
694
767
|
const connectionId = attachment?.connectionId;
|
|
695
768
|
try {
|
|
696
769
|
for (const provider of this.#providers) {
|
|
770
|
+
const providerRemainingMs = PROVIDER_ACTIVATION_BUDGET_MS - (Date.now() - startedAt);
|
|
771
|
+
if (providerRemainingMs <= 0) throw this.#providerActivationExhausted(attempt, startedAt);
|
|
697
772
|
try {
|
|
698
|
-
await this.registerProvider(
|
|
773
|
+
await this.registerProvider(
|
|
774
|
+
provider,
|
|
775
|
+
Math.max(1, providerRemainingMs),
|
|
776
|
+
startedAt + PROVIDER_ACTIVATION_BUDGET_MS,
|
|
777
|
+
);
|
|
778
|
+
if (Date.now() - startedAt >= PROVIDER_ACTIVATION_BUDGET_MS)
|
|
779
|
+
throw this.#providerActivationExhausted(attempt, startedAt);
|
|
699
780
|
} catch (error) {
|
|
781
|
+
if (providerErrorCode(error) === "uncertain_after_send") throw error;
|
|
782
|
+
if (Date.now() - startedAt >= PROVIDER_ACTIVATION_BUDGET_MS)
|
|
783
|
+
throw this.#providerActivationExhausted(attempt, startedAt);
|
|
700
784
|
if (providerErrorCode(error) === "provider_lease_conflict") {
|
|
701
785
|
this.#leases.delete(provider.capability);
|
|
702
786
|
this.#abortReverseForCapability(provider.capability);
|
|
@@ -725,6 +809,7 @@ export class AcpSdkAdapter {
|
|
|
725
809
|
this.#providersActivated = true;
|
|
726
810
|
return;
|
|
727
811
|
} catch (error) {
|
|
812
|
+
if (providerErrorCode(error) === "uncertain_after_send") throw error;
|
|
728
813
|
if (this.#router && attachment !== this.#attachment && !this.#closed) {
|
|
729
814
|
this.#providersActivated = false;
|
|
730
815
|
continue;
|
|
@@ -22,6 +22,7 @@ export type BrokerFenceReason = "suspect-unpublished" | "observation-ambiguous"
|
|
|
22
22
|
export type BrokerExitReason =
|
|
23
23
|
| "ownership-fence-expired"
|
|
24
24
|
| "publication-liveness-timeout"
|
|
25
|
+
| "heartbeat-renewal-blocked"
|
|
25
26
|
| "restart-committed"
|
|
26
27
|
| "shutdown-request"
|
|
27
28
|
| "startup-failure"
|
|
@@ -39,6 +40,8 @@ export interface BrokerExitRecord {
|
|
|
39
40
|
fenceReason: BrokerFenceReason | null;
|
|
40
41
|
fencedForMs: number;
|
|
41
42
|
uptimeMs: number;
|
|
43
|
+
/** For a blocked heartbeat renewal: retained removal transition path. */
|
|
44
|
+
blockingLockPath?: string;
|
|
42
45
|
pid: number;
|
|
43
46
|
signal: "SIGINT" | "SIGTERM" | null;
|
|
44
47
|
writtenAt: number;
|
|
@@ -47,7 +50,7 @@ export interface BrokerExitRecord {
|
|
|
47
50
|
export interface BrokerStartupExitRecord {
|
|
48
51
|
version: 1;
|
|
49
52
|
mode: "startup";
|
|
50
|
-
reason: "startup-deadline" | "startup-signal";
|
|
53
|
+
reason: "startup-deadline" | "startup-signal" | "startup-lock-blocked";
|
|
51
54
|
fenceReason: null;
|
|
52
55
|
fencedForMs: 0;
|
|
53
56
|
uptimeMs: number;
|
|
@@ -56,6 +59,8 @@ export interface BrokerStartupExitRecord {
|
|
|
56
59
|
exitCode: 1 | 130 | 143;
|
|
57
60
|
timeoutMs: number | null;
|
|
58
61
|
writtenAt: number;
|
|
62
|
+
/** For lock-blocked reason: path to the retained removal transition. */
|
|
63
|
+
blockingLockPath?: string;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export type BrokerStartupExitWriteStatus =
|
|
@@ -71,6 +76,7 @@ const lastWriteGenerationByPath = new Map<string, number>();
|
|
|
71
76
|
const EXIT_REASONS = new Set<BrokerExitReason>([
|
|
72
77
|
"ownership-fence-expired",
|
|
73
78
|
"publication-liveness-timeout",
|
|
79
|
+
"heartbeat-renewal-blocked",
|
|
74
80
|
"restart-committed",
|
|
75
81
|
"shutdown-request",
|
|
76
82
|
"startup-failure",
|
|
@@ -94,11 +100,20 @@ function isBrokerExitRecord(value: unknown): value is BrokerExitRecord {
|
|
|
94
100
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
95
101
|
const record = value as Partial<BrokerExitRecord>;
|
|
96
102
|
const keys = Object.keys(record).sort();
|
|
103
|
+
const requiredKeys = [
|
|
104
|
+
"fenceReason",
|
|
105
|
+
"fencedForMs",
|
|
106
|
+
"mode",
|
|
107
|
+
"pid",
|
|
108
|
+
"reason",
|
|
109
|
+
"signal",
|
|
110
|
+
"uptimeMs",
|
|
111
|
+
"version",
|
|
112
|
+
"writtenAt",
|
|
113
|
+
];
|
|
97
114
|
return (
|
|
98
|
-
keys.
|
|
99
|
-
|
|
100
|
-
.sort()
|
|
101
|
-
.join(",") &&
|
|
115
|
+
keys.every(key => requiredKeys.includes(key) || key === "blockingLockPath") &&
|
|
116
|
+
requiredKeys.every(key => keys.includes(key)) &&
|
|
102
117
|
record.version === 1 &&
|
|
103
118
|
(record.mode === "owned-root" || record.mode === "lost-root") &&
|
|
104
119
|
typeof record.reason === "string" &&
|
|
@@ -115,6 +130,14 @@ function isBrokerExitRecord(value: unknown): value is BrokerExitRecord {
|
|
|
115
130
|
(record.pid as number) > 0 &&
|
|
116
131
|
(record.signal === null || record.signal === "SIGINT" || record.signal === "SIGTERM") &&
|
|
117
132
|
(record.reason === "signal") === (record.signal !== null) &&
|
|
133
|
+
(record.reason === "heartbeat-renewal-blocked"
|
|
134
|
+
? record.mode === "owned-root" &&
|
|
135
|
+
record.fenceReason === null &&
|
|
136
|
+
record.fencedForMs === 0 &&
|
|
137
|
+
record.signal === null &&
|
|
138
|
+
typeof record.blockingLockPath === "string" &&
|
|
139
|
+
record.blockingLockPath.length > 0
|
|
140
|
+
: record.blockingLockPath === undefined) &&
|
|
118
141
|
Number.isSafeInteger(record.writtenAt) &&
|
|
119
142
|
(record.writtenAt as number) > 0
|
|
120
143
|
);
|
|
@@ -125,26 +148,29 @@ function isBrokerStartupExitRecord(value: unknown): value is BrokerStartupExitRe
|
|
|
125
148
|
const record = value as Partial<BrokerStartupExitRecord>;
|
|
126
149
|
const signalExitCode = record.signal === "SIGINT" ? 130 : record.signal === "SIGTERM" ? 143 : null;
|
|
127
150
|
const keys = Object.keys(record).sort();
|
|
151
|
+
const requiredKeys = [
|
|
152
|
+
"exitCode",
|
|
153
|
+
"fenceReason",
|
|
154
|
+
"fencedForMs",
|
|
155
|
+
"mode",
|
|
156
|
+
"pid",
|
|
157
|
+
"reason",
|
|
158
|
+
"signal",
|
|
159
|
+
"timeoutMs",
|
|
160
|
+
"uptimeMs",
|
|
161
|
+
"version",
|
|
162
|
+
"writtenAt",
|
|
163
|
+
];
|
|
164
|
+
const isValidShape =
|
|
165
|
+
keys.every(k => requiredKeys.includes(k) || k === "blockingLockPath") &&
|
|
166
|
+
requiredKeys.every(k => keys.includes(k));
|
|
128
167
|
return (
|
|
129
|
-
|
|
130
|
-
[
|
|
131
|
-
"exitCode",
|
|
132
|
-
"fenceReason",
|
|
133
|
-
"fencedForMs",
|
|
134
|
-
"mode",
|
|
135
|
-
"pid",
|
|
136
|
-
"reason",
|
|
137
|
-
"signal",
|
|
138
|
-
"timeoutMs",
|
|
139
|
-
"uptimeMs",
|
|
140
|
-
"version",
|
|
141
|
-
"writtenAt",
|
|
142
|
-
]
|
|
143
|
-
.sort()
|
|
144
|
-
.join(",") &&
|
|
168
|
+
isValidShape &&
|
|
145
169
|
record.version === 1 &&
|
|
146
170
|
record.mode === "startup" &&
|
|
147
|
-
(record.reason === "startup-deadline" ||
|
|
171
|
+
(record.reason === "startup-deadline" ||
|
|
172
|
+
record.reason === "startup-signal" ||
|
|
173
|
+
record.reason === "startup-lock-blocked") &&
|
|
148
174
|
record.fenceReason === null &&
|
|
149
175
|
record.fencedForMs === 0 &&
|
|
150
176
|
Number.isSafeInteger(record.uptimeMs) &&
|
|
@@ -154,7 +180,10 @@ function isBrokerStartupExitRecord(value: unknown): value is BrokerStartupExitRe
|
|
|
154
180
|
(record.signal === null || record.signal === "SIGINT" || record.signal === "SIGTERM") &&
|
|
155
181
|
(record.exitCode === 1 || record.exitCode === 130 || record.exitCode === 143) &&
|
|
156
182
|
(record.timeoutMs === null || (Number.isSafeInteger(record.timeoutMs) && (record.timeoutMs as number) > 0)) &&
|
|
157
|
-
(record.reason === "startup-
|
|
183
|
+
(record.reason === "startup-lock-blocked"
|
|
184
|
+
? typeof record.blockingLockPath === "string" && record.blockingLockPath.length > 0
|
|
185
|
+
: record.blockingLockPath === undefined) &&
|
|
186
|
+
(record.reason === "startup-deadline" || record.reason === "startup-lock-blocked"
|
|
158
187
|
? record.exitCode === 1 && record.signal === null && record.timeoutMs !== null
|
|
159
188
|
: record.exitCode === signalExitCode && signalExitCode !== null && record.timeoutMs === null) &&
|
|
160
189
|
Number.isSafeInteger(record.writtenAt) &&
|
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -6,9 +6,14 @@ import path from "node:path";
|
|
|
6
6
|
import type { NativeBrokerRestartIntent, NativeDirectoryTreeSnapshot } from "@gajae-code/natives";
|
|
7
7
|
import { logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
8
8
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
9
|
+
import { FileLockAcquireError } from "../../config/file-lock";
|
|
9
10
|
import type { ModelProfileErrorDetails } from "../../config/model-profile-contract";
|
|
10
11
|
import { planLaunchWorktree } from "../../gjc-runtime/launch-worktree";
|
|
11
12
|
import { readExistingStateForMutation, withWorkflowStateLock } from "../../gjc-runtime/state-writer";
|
|
13
|
+
import type {
|
|
14
|
+
TaskArtifactOwnerDeletionEvidence,
|
|
15
|
+
TaskArtifactOwnerRetirementContinuation,
|
|
16
|
+
} from "../../session/task-artifact-owner-codec";
|
|
12
17
|
import { SdkClient, SdkClientError } from "../client";
|
|
13
18
|
import {
|
|
14
19
|
BROKER_RUNTIME_ABORT_CAPABILITY_FIELD,
|
|
@@ -127,6 +132,7 @@ import {
|
|
|
127
132
|
type SpawnSubstrateProvider,
|
|
128
133
|
} from "./spawn-authority";
|
|
129
134
|
import { createSpawnSubstrateProvider } from "./spawn-substrate";
|
|
135
|
+
import type { BrokerTaskArtifactOwnerRetirementDisposition } from "./task-artifact-owner-validation";
|
|
130
136
|
import { BrokerTransport } from "./transport";
|
|
131
137
|
|
|
132
138
|
export interface BrokerSettings {
|
|
@@ -158,6 +164,8 @@ export interface BrokerSettings {
|
|
|
158
164
|
restartRequestId?: string;
|
|
159
165
|
/** Cancel bootstrap before retained publication when the owning CLI receives a signal. */
|
|
160
166
|
startupAbortSignal?: AbortSignal;
|
|
167
|
+
/** Monotonic checkpoint deadline, leaving time for retained publication. */
|
|
168
|
+
startupCheckpointDeadline?: number;
|
|
161
169
|
/** Called synchronously when retained publication establishes broker readiness. */
|
|
162
170
|
onStartupReady?: () => void;
|
|
163
171
|
/** Test-only delay after session checkpoint to verify unpublished discovery ownership. */
|
|
@@ -293,6 +301,19 @@ export type BrokerCleanupEvidence = {
|
|
|
293
301
|
retainedTranscriptSuccessorPath?: string;
|
|
294
302
|
retainedTranscriptPlaceholderPath?: string;
|
|
295
303
|
retainedTranscriptUnknownPath?: string;
|
|
304
|
+
/** Immutable task-artifact owner authority captured before the first deletion effect. */
|
|
305
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
306
|
+
/** Latest exact native remnant; it never replaces the original deletion evidence. */
|
|
307
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
308
|
+
/** Strict owner-only wire disposition, decoded only with its separately stored evidence. */
|
|
309
|
+
taskArtifactOwnerRetirementOutcome?: BrokerTaskArtifactOwnerRetirementDisposition;
|
|
310
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
311
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
312
|
+
taskArtifactOwnerRetired?: true;
|
|
313
|
+
/** Durable fact that transcript retirement completed while the owner namespace remains. */
|
|
314
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
315
|
+
/** Stable artifacts-phase owner refusal diagnostic. */
|
|
316
|
+
taskArtifactOwnerCleanupError?: string;
|
|
296
317
|
/** Durable proof that artifact cleanup completed before transcript mutation. */
|
|
297
318
|
artifactsRemoved?: boolean;
|
|
298
319
|
artifactsAbsentAtAuthorization?: true;
|
|
@@ -756,6 +777,17 @@ function normalizeAliasedString(
|
|
|
756
777
|
return { value: values[0] };
|
|
757
778
|
}
|
|
758
779
|
|
|
780
|
+
const BROKER_TASK_ARTIFACT_OWNER_CLEANUP_FIELDS = [
|
|
781
|
+
"taskArtifactOwnerDeletionEvidence",
|
|
782
|
+
"taskArtifactOwnerRetirementContinuation",
|
|
783
|
+
"taskArtifactOwnerRetirementOutcome",
|
|
784
|
+
"taskArtifactOwnerPayloadRetired",
|
|
785
|
+
"taskArtifactOwnerNamespaceRetained",
|
|
786
|
+
"taskArtifactOwnerRetired",
|
|
787
|
+
"taskArtifactOwnerTranscriptDeleted",
|
|
788
|
+
"taskArtifactOwnerCleanupError",
|
|
789
|
+
] as const;
|
|
790
|
+
|
|
759
791
|
export function normalizeBrokerInput(operation: string, input: Record<string, unknown>): InputNormalization {
|
|
760
792
|
const normalized: Record<string, unknown> = { ...input };
|
|
761
793
|
const session = normalizeAliasedString(input, "sessionId", ["id"]);
|
|
@@ -834,6 +866,18 @@ export function normalizeBrokerInput(operation: string, input: Record<string, un
|
|
|
834
866
|
if (cwd.value !== undefined) normalized.stateRoot = path.join(cwd.value, ".gjc", "state");
|
|
835
867
|
else if (stateRoot.value !== undefined) return error("invalid_input", "stateRoot requires cwd.");
|
|
836
868
|
|
|
869
|
+
if (operation === "session.delete") {
|
|
870
|
+
const hasOwnerField = (value: unknown): boolean =>
|
|
871
|
+
typeof value === "object" &&
|
|
872
|
+
value !== null &&
|
|
873
|
+
!Array.isArray(value) &&
|
|
874
|
+
BROKER_TASK_ARTIFACT_OWNER_CLEANUP_FIELDS.some(key => Object.hasOwn(value, key));
|
|
875
|
+
if (hasOwnerField(input) || hasOwnerField(target) || hasOwnerField(input.cleanup))
|
|
876
|
+
return error(
|
|
877
|
+
"invalid_input",
|
|
878
|
+
"Task-artifact owner cleanup state is broker-managed and cannot be supplied by clients.",
|
|
879
|
+
);
|
|
880
|
+
}
|
|
837
881
|
if (target) {
|
|
838
882
|
const normalizedTarget = { ...target };
|
|
839
883
|
delete normalizedTarget.path;
|
|
@@ -1530,6 +1574,7 @@ export class Broker {
|
|
|
1530
1574
|
#transport: BrokerTransport | null = null;
|
|
1531
1575
|
#heartbeatTimer: NodeJS.Timeout | null = null;
|
|
1532
1576
|
#startupAbortSignal: AbortSignal | undefined;
|
|
1577
|
+
#startupCheckpointDeadline: number | undefined;
|
|
1533
1578
|
#onStartupReady: (() => void) | undefined;
|
|
1534
1579
|
#startupPrePublicationDelayMs: number;
|
|
1535
1580
|
#startupPrePublicationTestHook: (() => Promise<void>) | undefined;
|
|
@@ -1569,6 +1614,7 @@ export class Broker {
|
|
|
1569
1614
|
this.#ownsResolveModelPin = settings.resolveModelPin === undefined;
|
|
1570
1615
|
this.#resolveModelPin = settings.resolveModelPin ?? createDefaultSdkHostModelResolver(this.settings.agentDir);
|
|
1571
1616
|
this.#startupAbortSignal = settings.startupAbortSignal;
|
|
1617
|
+
this.#startupCheckpointDeadline = settings.startupCheckpointDeadline;
|
|
1572
1618
|
this.#onStartupReady = settings.onStartupReady;
|
|
1573
1619
|
this.#startupPrePublicationDelayMs =
|
|
1574
1620
|
Number.isSafeInteger(settings.startupPrePublicationDelayMs) &&
|
|
@@ -3898,7 +3944,7 @@ export class Broker {
|
|
|
3898
3944
|
// checkpoint settles. The bootstrap watchdog owns this pre-publication
|
|
3899
3945
|
// interval; publishing first allowed it to kill an endpoint already handed
|
|
3900
3946
|
// to callers when a legitimate index-lock wait outlived the fence.
|
|
3901
|
-
await this.#checkpointSessionHeartbeats();
|
|
3947
|
+
await this.#checkpointSessionHeartbeats(this.#startupAbortSignal, this.#startupCheckpointDeadline);
|
|
3902
3948
|
this.#throwIfStartupAborted();
|
|
3903
3949
|
await this.#startupPrePublicationTestHook?.();
|
|
3904
3950
|
if (this.#startupPrePublicationDelayMs > 0) await Bun.sleep(this.#startupPrePublicationDelayMs);
|
|
@@ -4231,15 +4277,29 @@ export class Broker {
|
|
|
4231
4277
|
if (publication) await this.#writeHeartbeat(publication);
|
|
4232
4278
|
}
|
|
4233
4279
|
/** Re-observes provably live session hosts and checkpoints their liveness. */
|
|
4234
|
-
async heartbeatSessions(now = Date.now()): Promise<number> {
|
|
4235
|
-
return await this.index.checkpointLiveHeartbeats(now);
|
|
4280
|
+
async heartbeatSessions(now = Date.now(), abortSignal?: AbortSignal, deadlineAt?: number): Promise<number> {
|
|
4281
|
+
return await this.index.checkpointLiveHeartbeats(now, abortSignal, deadlineAt);
|
|
4236
4282
|
}
|
|
4237
|
-
async #checkpointSessionHeartbeats(): Promise<void> {
|
|
4283
|
+
async #checkpointSessionHeartbeats(abortSignal?: AbortSignal, startupCheckpointDeadline?: number): Promise<void> {
|
|
4238
4284
|
if (this.#checkpointInFlight || this.#stopping) return;
|
|
4239
4285
|
this.#checkpointInFlight = true;
|
|
4240
4286
|
try {
|
|
4241
|
-
|
|
4287
|
+
// The CLI reserves publication headroom in this startup deadline.
|
|
4288
|
+
// Periodic passes use their own budget, not a retired startup signal.
|
|
4289
|
+
await this.heartbeatSessions(Date.now(), abortSignal, startupCheckpointDeadline);
|
|
4242
4290
|
} catch (error) {
|
|
4291
|
+
if (
|
|
4292
|
+
error instanceof FileLockAcquireError &&
|
|
4293
|
+
error.orphanPath &&
|
|
4294
|
+
this.#publication !== null &&
|
|
4295
|
+
this.#publicationState === "healthy-owned" &&
|
|
4296
|
+
this.#fenceReason === null &&
|
|
4297
|
+
this.#completionTask === null &&
|
|
4298
|
+
this.#provenOwnedRoot()
|
|
4299
|
+
) {
|
|
4300
|
+
await this.#complete("owned-root", "heartbeat-renewal-blocked", null, error.orphanPath);
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4243
4303
|
logger.warn(`sdk broker: session heartbeat checkpoint failed: ${String(error)}`);
|
|
4244
4304
|
} finally {
|
|
4245
4305
|
this.#checkpointInFlight = false;
|
|
@@ -4249,6 +4309,7 @@ export class Broker {
|
|
|
4249
4309
|
mode: BrokerExitMode,
|
|
4250
4310
|
reason: BrokerExitReason,
|
|
4251
4311
|
signal: BrokerExitRecord["signal"] = null,
|
|
4312
|
+
blockingLockPath?: string,
|
|
4252
4313
|
): Promise<void> {
|
|
4253
4314
|
if (this.#completionTask) return this.#completionTask;
|
|
4254
4315
|
const now = process.hrtime.bigint();
|
|
@@ -4260,11 +4321,19 @@ export class Broker {
|
|
|
4260
4321
|
fenceReason: this.#fenceReason,
|
|
4261
4322
|
fencedForMs: fenceStartedAt === null ? 0 : Math.max(0, Number((now - fenceStartedAt) / 1_000_000n)),
|
|
4262
4323
|
uptimeMs: this.#startedAt === null ? 0 : Math.max(0, Number((now - this.#startedAt) / 1_000_000n)),
|
|
4324
|
+
...(blockingLockPath === undefined ? {} : { blockingLockPath }),
|
|
4263
4325
|
pid: process.pid,
|
|
4264
4326
|
signal,
|
|
4265
4327
|
writtenAt: Date.now(),
|
|
4266
4328
|
};
|
|
4267
|
-
|
|
4329
|
+
const message =
|
|
4330
|
+
reason === "heartbeat-renewal-blocked" && blockingLockPath
|
|
4331
|
+
? `blocked by retained removal transition ${blockingLockPath}`
|
|
4332
|
+
: undefined;
|
|
4333
|
+
(mode === "lost-root" || message !== undefined ? logger.warn : logger.info)("sdk broker: exiting", {
|
|
4334
|
+
...exitRecord,
|
|
4335
|
+
...(message === undefined ? {} : { message }),
|
|
4336
|
+
});
|
|
4268
4337
|
this.#stopping = true;
|
|
4269
4338
|
this.#checkpointInFlight = false;
|
|
4270
4339
|
this.#publicationState = "stopping";
|
package/src/sdk/broker/ensure.ts
CHANGED
|
@@ -41,6 +41,8 @@ function brokerStartupFailureReason(marker: BrokerStartupFailureMarker | undefin
|
|
|
41
41
|
|
|
42
42
|
function brokerStartupExitReason(record: BrokerStartupExitRecord | undefined): string | undefined {
|
|
43
43
|
if (!record) return undefined;
|
|
44
|
+
if (record.reason === "startup-lock-blocked")
|
|
45
|
+
return `SDK broker startup blocked by retained removal transition ${record.blockingLockPath}.`;
|
|
44
46
|
if (record.reason === "startup-deadline")
|
|
45
47
|
return `SDK broker startup exceeded its ${record.timeoutMs}ms fence deadline.`;
|
|
46
48
|
return `SDK broker startup interrupted by ${record.signal} before readiness.`;
|