@gajae-code/coding-agent 0.17.2 → 0.17.4
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 +194 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +9 -1
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +11 -2
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +55 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +56 -0
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/broker/broker.d.ts +13 -2
- package/dist/types/sdk/broker/identity.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +168 -21
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +22 -1
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +535 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +9 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +14 -0
- package/src/config/file-lock.ts +147 -51
- package/src/config/model-profile-activation.ts +334 -43
- package/src/config/model-registry.ts +183 -25
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +175 -8
- package/src/coordinator-mcp/server.ts +507 -81
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +123 -55
- package/src/modes/acp/acp-agent.ts +408 -35
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +161 -15
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/manager.ts +689 -55
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +27 -8
- package/src/sdk/broker/broker.ts +378 -52
- package/src/sdk/broker/identity.ts +9 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +35 -7
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +690 -80
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +687 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +138 -2
- package/src/sdk/host/session-runtime.ts +311 -97
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/prompt-deadline-flush.ts +334 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +182 -7
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2437 -400
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +58 -18
- package/src/session/internal/managed-session-storage.ts +304 -11
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/slash-commands/builtin-registry.ts +258 -14
- package/src/task/index.ts +14 -2
- package/src/tools/bash.ts +98 -78
- package/src/tools/fetch.ts +119 -20
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf.ts +129 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Reproduce the patched MIT converter; --check is strictly local and read-only. */
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { $ } from "bun";
|
|
8
|
+
|
|
9
|
+
const packageRoot = path.resolve(import.meta.dir, "..");
|
|
10
|
+
const vendorRoot = path.join(packageRoot, "vendor");
|
|
11
|
+
const vendorDir = path.join(vendorRoot, "markit-ai");
|
|
12
|
+
const patchPath = path.join(vendorRoot, "markit-ai.patch");
|
|
13
|
+
const sourceUrl = "https://registry.npmjs.org/markit-ai/-/markit-ai-0.5.3.tgz";
|
|
14
|
+
const sourceIntegrity =
|
|
15
|
+
"sha512-h4nhn6a/SNXEdc3kLVtL37TspxjUNCNL0OM7LRWxd389ZByI/B7bjNNgxFdVAT0O+H7ZekSwLdVe/lws1l2AZQ==";
|
|
16
|
+
const provenanceName = "MANIFEST.json";
|
|
17
|
+
const maxArchiveBytes = 16 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
interface FileHash {
|
|
20
|
+
path: string;
|
|
21
|
+
sha256: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sha256(bytes: Uint8Array): string {
|
|
25
|
+
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function selectedFile(name: string): boolean {
|
|
29
|
+
return name === "LICENSE" || name === "package.json" || name.startsWith("dist/");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function inventory(directory: string): Promise<FileHash[]> {
|
|
33
|
+
const files: FileHash[] = [];
|
|
34
|
+
async function walk(relative: string): Promise<void> {
|
|
35
|
+
const absolute = path.join(directory, relative);
|
|
36
|
+
const metadata = await fs.lstat(absolute);
|
|
37
|
+
if (metadata.isDirectory()) {
|
|
38
|
+
if (relative !== "" && relative !== "dist" && !relative.startsWith("dist/")) {
|
|
39
|
+
throw new Error(`Unexpected vendor directory: ${relative}`);
|
|
40
|
+
}
|
|
41
|
+
for (const name of (await fs.readdir(absolute)).sort()) {
|
|
42
|
+
await walk(relative ? `${relative}/${name}` : name);
|
|
43
|
+
}
|
|
44
|
+
} else if (metadata.isFile()) {
|
|
45
|
+
if (relative === provenanceName) return;
|
|
46
|
+
if (!selectedFile(relative)) throw new Error(`Unexpected vendor file: ${relative}`);
|
|
47
|
+
files.push({ path: relative, sha256: sha256(await Bun.file(absolute).bytes()) });
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error(`Vendor entries must be regular files or directories: ${relative}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await walk("");
|
|
53
|
+
for (const required of [
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"package.json",
|
|
56
|
+
"dist/index.js",
|
|
57
|
+
"dist/index.d.ts",
|
|
58
|
+
"dist/converters/pdf/extract.js",
|
|
59
|
+
"dist/markit.js",
|
|
60
|
+
]) {
|
|
61
|
+
if (!files.some(file => file.path === required)) throw new Error(`Missing vendor file: ${required}`);
|
|
62
|
+
}
|
|
63
|
+
return files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function provenance(directory: string): Promise<string> {
|
|
67
|
+
return `${JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
upstream: { name: "markit-ai", version: "0.5.3", license: "MIT", url: sourceUrl, integrity: sourceIntegrity },
|
|
71
|
+
patch: { path: "../markit-ai.patch", sha256: sha256(await Bun.file(patchPath).bytes()) },
|
|
72
|
+
files: await inventory(directory),
|
|
73
|
+
},
|
|
74
|
+
null,
|
|
75
|
+
"\t",
|
|
76
|
+
)}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function verifyContents(directory: string): Promise<void> {
|
|
80
|
+
const upstream = await Bun.file(path.join(directory, "package.json")).json();
|
|
81
|
+
if (upstream.name !== "markit-ai" || upstream.version !== "0.5.3" || upstream.license !== "MIT") {
|
|
82
|
+
throw new Error("Vendored manifest must identify MIT markit-ai@0.5.3");
|
|
83
|
+
}
|
|
84
|
+
const manifest = await Bun.file(path.join(packageRoot, "package.json")).json();
|
|
85
|
+
for (const dependency of Object.keys(upstream.dependencies)) {
|
|
86
|
+
if (typeof manifest.dependencies?.[dependency] !== "string") {
|
|
87
|
+
throw new Error(`Missing direct converter runtime dependency: ${dependency}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (manifest.dependencies.mupdf !== "1.28.0" || manifest.dependencies["markit-ai"] !== undefined) {
|
|
91
|
+
throw new Error("Converter must use vendored Markit and pinned mupdf@1.28.0");
|
|
92
|
+
}
|
|
93
|
+
const extract = await Bun.file(path.join(directory, "dist/converters/pdf/extract.js")).text();
|
|
94
|
+
const mupdfLoader = await Bun.file(path.join(directory, "dist/converters/pdf/mupdf-loader.js")).text();
|
|
95
|
+
const markit = await Bun.file(path.join(directory, "dist/markit.js")).text();
|
|
96
|
+
if (
|
|
97
|
+
/require\(["']mupdf["']\)/u.test(extract) ||
|
|
98
|
+
!extract.includes("\nlet mupdf;\n") ||
|
|
99
|
+
!extract.includes('import { loadMuPdf } from "./mupdf-loader.js";') ||
|
|
100
|
+
!extract.includes("mupdf = await loadMuPdf()") ||
|
|
101
|
+
!mupdfLoader.includes('import("mupdf")') ||
|
|
102
|
+
!extract.includes('new Error("MuPDF module initialization failed", { cause })') ||
|
|
103
|
+
!markit.includes("new AggregateError(errors.map((entry) => entry.error)") ||
|
|
104
|
+
!markit.includes("{ cause: errors[0].error }")
|
|
105
|
+
)
|
|
106
|
+
throw new Error("Vendored Markit is missing the required MuPDF/diagnostics patch");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function check(): Promise<void> {
|
|
110
|
+
if (!(await fs.lstat(vendorRoot)).isDirectory()) throw new Error("Vendor root must be a real directory");
|
|
111
|
+
if (!(await fs.lstat(patchPath)).isFile()) throw new Error("Vendor patch must be a regular file");
|
|
112
|
+
// Inventory rejects symlinks before any content verification reads them.
|
|
113
|
+
const expected = await provenance(vendorDir);
|
|
114
|
+
if ((await Bun.file(path.join(vendorDir, provenanceName)).text()) !== expected) {
|
|
115
|
+
throw new Error("Markit vendor inventory or patch hash differs; regenerate from the pinned artifact");
|
|
116
|
+
}
|
|
117
|
+
await verifyContents(vendorDir);
|
|
118
|
+
process.stdout.write("Markit vendor verification passed (markit-ai@0.5.3, local hashes)\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function download(): Promise<Uint8Array> {
|
|
122
|
+
const response = await fetch(sourceUrl, { redirect: "error", signal: AbortSignal.timeout(60_000) });
|
|
123
|
+
if (!response.ok || !response.body) throw new Error(`Pinned Markit download failed: HTTP ${response.status}`);
|
|
124
|
+
const chunks: Uint8Array[] = [];
|
|
125
|
+
let length = 0;
|
|
126
|
+
for await (const chunk of response.body) {
|
|
127
|
+
length += chunk.byteLength;
|
|
128
|
+
if (length > maxArchiveBytes) throw new Error("Pinned Markit archive exceeds size limit");
|
|
129
|
+
chunks.push(chunk);
|
|
130
|
+
}
|
|
131
|
+
const bytes = Buffer.concat(chunks, length);
|
|
132
|
+
if (`sha512-${crypto.createHash("sha512").update(bytes).digest("base64")}` !== sourceIntegrity) {
|
|
133
|
+
throw new Error("Pinned Markit archive integrity mismatch");
|
|
134
|
+
}
|
|
135
|
+
return bytes;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function generate(): Promise<void> {
|
|
139
|
+
const temporaryRoot = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-vendor-markit-"));
|
|
140
|
+
try {
|
|
141
|
+
const bytes = await download();
|
|
142
|
+
await Bun.write(path.join(temporaryRoot, "markit-ai-0.5.3.tgz"), bytes);
|
|
143
|
+
const source = path.join(temporaryRoot, "source");
|
|
144
|
+
await fs.mkdir(source);
|
|
145
|
+
// Read verified archive entries as data and create only regular files. Never
|
|
146
|
+
// ask an extractor to recreate upstream symlinks, permissions, or devices.
|
|
147
|
+
const entries = await new Bun.Archive(bytes).files();
|
|
148
|
+
let unpackedBytes = 0;
|
|
149
|
+
for (const [name, file] of entries) {
|
|
150
|
+
if (
|
|
151
|
+
!name.startsWith("package/") ||
|
|
152
|
+
name.includes("\\") ||
|
|
153
|
+
name.includes("\0") ||
|
|
154
|
+
name.split("/").some(part => part === "" || part === "." || part === "..")
|
|
155
|
+
) {
|
|
156
|
+
throw new Error(`Unsafe pinned archive entry: ${name}`);
|
|
157
|
+
}
|
|
158
|
+
unpackedBytes += file.size;
|
|
159
|
+
if (unpackedBytes > maxArchiveBytes) throw new Error("Pinned Markit unpacked files exceed size limit");
|
|
160
|
+
const relative = name.slice("package/".length);
|
|
161
|
+
if (!selectedFile(relative)) continue;
|
|
162
|
+
const destination = path.join(source, relative);
|
|
163
|
+
await Bun.write(destination, file);
|
|
164
|
+
await fs.chmod(destination, 0o644);
|
|
165
|
+
}
|
|
166
|
+
await $`git apply --check ${patchPath}`.cwd(source);
|
|
167
|
+
await $`git apply ${patchPath}`.cwd(source);
|
|
168
|
+
await verifyContents(source);
|
|
169
|
+
await Bun.write(path.join(source, provenanceName), await provenance(source));
|
|
170
|
+
if (!(await fs.lstat(vendorRoot)).isDirectory()) throw new Error("Vendor root must be a real directory");
|
|
171
|
+
const existing = await fs.lstat(vendorDir).catch((error: NodeJS.ErrnoException) => {
|
|
172
|
+
if (error.code === "ENOENT") return undefined;
|
|
173
|
+
throw error;
|
|
174
|
+
});
|
|
175
|
+
if (existing) {
|
|
176
|
+
// Refuse unrelated contents and symlinks rather than silently deleting them.
|
|
177
|
+
await inventory(vendorDir);
|
|
178
|
+
const oldManifest = await Bun.file(path.join(vendorDir, "package.json")).json();
|
|
179
|
+
if (oldManifest.name !== "markit-ai") throw new Error("Refusing to replace an unrelated vendor tree");
|
|
180
|
+
await fs.rm(vendorDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
await fs.cp(source, vendorDir, { recursive: true, force: false, errorOnExist: true });
|
|
183
|
+
await check();
|
|
184
|
+
} finally {
|
|
185
|
+
await fs.rm(temporaryRoot, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (import.meta.main) {
|
|
190
|
+
const args = process.argv.slice(2);
|
|
191
|
+
if (args.length !== 1 || (args[0] !== "--check" && args[0] !== "--generate")) {
|
|
192
|
+
throw new Error("Use exactly --check (offline verification) or --generate (pinned regeneration)");
|
|
193
|
+
}
|
|
194
|
+
if (args[0] === "--generate") await generate();
|
|
195
|
+
else await check();
|
|
196
|
+
}
|
package/src/async/job-manager.ts
CHANGED
|
@@ -2713,7 +2713,28 @@ export class AsyncJobManager {
|
|
|
2713
2713
|
markBackgrounded(jobId: string, generation: string, reason: FoldReason): boolean {
|
|
2714
2714
|
const job = this.#jobs.get(jobId);
|
|
2715
2715
|
if (!job || job.generation !== generation) return false;
|
|
2716
|
-
if (job.metadata?.backgrounded === true)
|
|
2716
|
+
if (job.metadata?.backgrounded === true) {
|
|
2717
|
+
// A direct async start is already backgrounded, but a later `job` await
|
|
2718
|
+
// can still be folded by a steer. Preserve the existing background state
|
|
2719
|
+
// while recording the first fold reason and publishing its event.
|
|
2720
|
+
if (job.metadata.foldReason !== undefined) return true;
|
|
2721
|
+
job.metadata = { ...job.metadata, foldReason: reason };
|
|
2722
|
+
for (const delivery of [...this.#deliveries, ...this.#inFlightDeliveries]) {
|
|
2723
|
+
if (delivery.jobId === jobId && delivery.generation === generation) delivery.foldReason = reason;
|
|
2724
|
+
}
|
|
2725
|
+
this.#notifyChange();
|
|
2726
|
+
const event: JobFoldEvent = { jobId, generation, reason };
|
|
2727
|
+
for (const cb of this.#foldListeners) {
|
|
2728
|
+
try {
|
|
2729
|
+
cb(event);
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
logger.warn("Async job fold listener failed", {
|
|
2732
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return true;
|
|
2737
|
+
}
|
|
2717
2738
|
job.metadata = { ...job.metadata, backgrounded: true, foldReason: reason };
|
|
2718
2739
|
for (const delivery of [...this.#deliveries, ...this.#inFlightDeliveries]) {
|
|
2719
2740
|
if (delivery.jobId === jobId && delivery.generation === generation) {
|
package/src/cli/daemon-cli.ts
CHANGED
|
@@ -12,26 +12,28 @@ import type {
|
|
|
12
12
|
DaemonKind,
|
|
13
13
|
DaemonOperationOptions,
|
|
14
14
|
DaemonOperationResult,
|
|
15
|
+
DaemonStatus,
|
|
15
16
|
} from "../daemon/control-types";
|
|
16
17
|
import {
|
|
17
18
|
DAEMON_ACTION_TOKENS,
|
|
18
|
-
|
|
19
|
+
daemonOperationOutcome,
|
|
19
20
|
formatDaemonResult,
|
|
20
21
|
formatDaemonStatus,
|
|
21
22
|
resolveDaemonAction,
|
|
22
23
|
} from "../daemon/operator-contract";
|
|
23
24
|
import { runChatDaemonInternal } from "../sdk/bus/chat-daemon-cli";
|
|
25
|
+
import { PublicCommandFailure, type PublicDaemonTargetOutcome } from "./public-command-errors";
|
|
24
26
|
|
|
25
27
|
export type DaemonCliAction = "list" | "status" | "stop" | "restart";
|
|
26
28
|
export type DaemonInternalCliAction = "discord-internal" | "slack-internal";
|
|
27
29
|
export type DaemonCommandAction = DaemonCliAction | DaemonInternalCliAction;
|
|
28
30
|
|
|
29
|
-
export class UnknownDaemonKindError extends
|
|
31
|
+
export class UnknownDaemonKindError extends PublicCommandFailure {
|
|
30
32
|
constructor(
|
|
31
33
|
readonly kinds: readonly string[],
|
|
32
34
|
readonly knownKinds: readonly DaemonKind[],
|
|
33
35
|
) {
|
|
34
|
-
super(
|
|
36
|
+
super({ kind: "operation_failed", proof: "pre-effect" });
|
|
35
37
|
this.name = "UnknownDaemonKindError";
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -60,6 +62,7 @@ export interface DaemonCommandArgs {
|
|
|
60
62
|
export interface DaemonCommandDeps {
|
|
61
63
|
settings?: Settings;
|
|
62
64
|
controllers?: BuiltInDaemonController[];
|
|
65
|
+
/** Internal update orchestration retains result rendering and failure aggregation. */
|
|
63
66
|
setExitCode?: (code: number) => void;
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -131,15 +134,36 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
|
|
|
131
134
|
}
|
|
132
135
|
const unknownKinds = cmd.kinds.filter(kind => !(KNOWN_KINDS as readonly string[]).includes(kind));
|
|
133
136
|
if (unknownKinds.length > 0) throw new UnknownDaemonKindError(unknownKinds, KNOWN_KINDS);
|
|
137
|
+
// Settings and controller construction failures are NOT an availability
|
|
138
|
+
// condition. Malformed configuration, permission/IO errors, and programming
|
|
139
|
+
// errors are permanent and each needs its own root cause: collapsing them into
|
|
140
|
+
// `unavailable` tells an operator (or an AI caller) to retry a failure that
|
|
141
|
+
// will never succeed. Let them propagate to the normal CLI error rendering,
|
|
142
|
+
// which is the pre-existing contract for this path.
|
|
134
143
|
const settings = deps.settings ?? (await Settings.init());
|
|
135
|
-
const controllers =
|
|
144
|
+
const controllers: BuiltInDaemonController[] =
|
|
145
|
+
deps.controllers ?? selectDaemonControllers(settings, cmd.kinds, cmd.all);
|
|
136
146
|
|
|
137
147
|
if (cmd.action === "list" || cmd.action === "status") {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
const statusResults = await Promise.allSettled(controllers.map(controller => controller.status()));
|
|
149
|
+
const statuses: DaemonStatus[] = [];
|
|
150
|
+
const failedTargets: PublicDaemonTargetOutcome[] = [];
|
|
151
|
+
for (const [index, result] of statusResults.entries()) {
|
|
152
|
+
if (result.status === "fulfilled") statuses.push(result.value);
|
|
153
|
+
else failedTargets.push({ kind: controllers[index]!.kind, outcome: "unknown" });
|
|
154
|
+
}
|
|
155
|
+
if (failedTargets.length > 0) {
|
|
156
|
+
throw new PublicCommandFailure({
|
|
157
|
+
kind: "daemon_mixed",
|
|
158
|
+
proof: "pre-effect",
|
|
159
|
+
targets: failedTargets,
|
|
160
|
+
partialStatuses: statuses,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (statuses.length > 0) {
|
|
164
|
+
if (cmd.json) process.stdout.write(`${JSON.stringify(statuses, null, 2)}\n`);
|
|
165
|
+
else
|
|
166
|
+
process.stdout.write(`${statuses.map(s => formatDaemonStatus(s, { verbose: cmd.verbose })).join("\n")}\n`);
|
|
143
167
|
}
|
|
144
168
|
return;
|
|
145
169
|
}
|
|
@@ -152,19 +176,30 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
|
|
|
152
176
|
allowDisabledNoop: cmd.allowDisabledNoop,
|
|
153
177
|
};
|
|
154
178
|
const results: DaemonOperationResult[] = [];
|
|
155
|
-
|
|
156
|
-
|
|
179
|
+
const targets: PublicDaemonTargetOutcome[] = [];
|
|
180
|
+
for (const [index, controller] of controllers.entries()) {
|
|
181
|
+
let result: DaemonOperationResult;
|
|
182
|
+
try {
|
|
183
|
+
result = cmd.action === "restart" ? await controller.reload(opts) : await controller.stop(opts);
|
|
184
|
+
} catch {
|
|
185
|
+
targets.push({ kind: controller.kind, outcome: "unknown" });
|
|
186
|
+
for (const pending of controllers.slice(index + 1))
|
|
187
|
+
targets.push({ kind: pending.kind, outcome: "not-applied" });
|
|
188
|
+
// Completed targets keep their full results: the envelope warns against
|
|
189
|
+
// replaying applied mutations, so it must carry the resulting state the
|
|
190
|
+
// caller has to reconcile against.
|
|
191
|
+
throw new PublicCommandFailure({ kind: "daemon_mixed", targets, partialResults: results });
|
|
192
|
+
}
|
|
193
|
+
results.push(result);
|
|
194
|
+
targets.push({ kind: controller.kind, outcome: daemonOperationOutcome(result) });
|
|
157
195
|
}
|
|
196
|
+
const failed = results.some(result => !result.ok);
|
|
197
|
+
if (failed && !deps.setExitCode)
|
|
198
|
+
throw new PublicCommandFailure({ kind: "daemon_mixed", targets, partialResults: results });
|
|
158
199
|
if (cmd.json) {
|
|
159
200
|
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
|
160
201
|
} else {
|
|
161
202
|
process.stdout.write(`${results.map(formatDaemonResult).join("\n")}\n`);
|
|
162
203
|
}
|
|
163
|
-
if (
|
|
164
|
-
(
|
|
165
|
-
deps.setExitCode ??
|
|
166
|
-
(code => {
|
|
167
|
-
process.exitCode = code;
|
|
168
|
-
})
|
|
169
|
-
)(DAEMON_EXIT.failure);
|
|
204
|
+
if (failed) deps.setExitCode?.(1);
|
|
170
205
|
}
|
|
@@ -10,7 +10,8 @@ import type { UpdateChannel } from "../config/update-channel";
|
|
|
10
10
|
|
|
11
11
|
export const RELEASE_REPO = "Yeachan-Heo/gajae-code";
|
|
12
12
|
export const GITHUB_API_ORIGIN = "https://api.github.com";
|
|
13
|
-
export const
|
|
13
|
+
export const GITHUB_WEB_ORIGIN = "https://github.com";
|
|
14
|
+
export const GITHUB_RELEASE_DOWNLOAD_ORIGIN = `${GITHUB_WEB_ORIGIN}/${RELEASE_REPO}/releases/download`;
|
|
14
15
|
export const BINARY_SHA256_ASSET = "gajae-release-binaries.sha256";
|
|
15
16
|
export const BINARY_MANIFEST_ASSET = "gajae-release-binaries-v1.json";
|
|
16
17
|
|
|
@@ -34,10 +35,30 @@ export interface GithubReleaseLookupOptions {
|
|
|
34
35
|
fetchImpl?: GithubFetch;
|
|
35
36
|
lookupEnv?: (name: string) => string | undefined;
|
|
36
37
|
apiOrigin?: string;
|
|
38
|
+
webOrigin?: string;
|
|
37
39
|
timeoutMs?: number;
|
|
38
40
|
useAmbientToken?: boolean;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
/** A GitHub REST response that failed, carrying the status the caller must branch on. */
|
|
44
|
+
export class GithubApiStatusError extends Error {
|
|
45
|
+
constructor(
|
|
46
|
+
readonly url: string,
|
|
47
|
+
readonly status: number,
|
|
48
|
+
) {
|
|
49
|
+
super(`${url} responded ${status}`);
|
|
50
|
+
this.name = "GithubApiStatusError";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 403/429 is how api.github.com reports an exhausted unauthenticated budget. */
|
|
54
|
+
get isRateLimited(): boolean {
|
|
55
|
+
return this.status === 403 || this.status === 429;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const TOKEN_HINT =
|
|
60
|
+
"Set GITHUB_TOKEN or GH_TOKEN to raise the api.github.com rate limit (60 requests/hour per IP when unauthenticated).";
|
|
61
|
+
|
|
41
62
|
interface GithubReleaseJson {
|
|
42
63
|
tag_name?: unknown;
|
|
43
64
|
draft?: unknown;
|
|
@@ -79,7 +100,7 @@ async function readGithubJson(url: string, options: GithubReleaseLookupOptions):
|
|
|
79
100
|
redirect: "follow",
|
|
80
101
|
});
|
|
81
102
|
if (!response.ok) {
|
|
82
|
-
throw new
|
|
103
|
+
throw new GithubApiStatusError(url, response.status);
|
|
83
104
|
}
|
|
84
105
|
return await response.json();
|
|
85
106
|
}
|
|
@@ -109,12 +130,57 @@ function parseRelease(value: unknown, expectedChannel: UpdateChannel): GithubRel
|
|
|
109
130
|
};
|
|
110
131
|
}
|
|
111
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Resolve the stable tag through the `github.com` web route, which 302s from
|
|
135
|
+
* `/releases/latest` to `/releases/tag/<tag>`. That origin serves the binary
|
|
136
|
+
* downloads too and is not bound by the api.github.com rate limit, so it keeps
|
|
137
|
+
* `gjc update` working on a shared IP whose unauthenticated budget is spent.
|
|
138
|
+
*/
|
|
139
|
+
export async function resolveStableReleaseTagFromWeb(options: GithubReleaseLookupOptions = {}): Promise<string> {
|
|
140
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
141
|
+
const webOrigin = options.webOrigin ?? GITHUB_WEB_ORIGIN;
|
|
142
|
+
const url = `${webOrigin}/${RELEASE_REPO}/releases/latest`;
|
|
143
|
+
const response = await fetchImpl(url, {
|
|
144
|
+
method: "HEAD",
|
|
145
|
+
headers: { "User-Agent": "gjc-update" },
|
|
146
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 20_000),
|
|
147
|
+
redirect: "manual",
|
|
148
|
+
});
|
|
149
|
+
// `redirect: "manual"` keeps the 302 and its Location; a runtime that follows
|
|
150
|
+
// it anyway still exposes the resolved tag URL through `response.url`.
|
|
151
|
+
const location = response.headers.get("location") ?? (response.redirected ? response.url : undefined);
|
|
152
|
+
if (!location) {
|
|
153
|
+
throw new Error(`${url} responded ${response.status} without a release redirect`);
|
|
154
|
+
}
|
|
155
|
+
const target = new URL(location, url);
|
|
156
|
+
// The redirect decides which tag gets installed, so it must stay on the
|
|
157
|
+
// release origin and repository it was issued for; an off-origin Location
|
|
158
|
+
// would let a hijacked hop name any tag and choose the downloaded binary.
|
|
159
|
+
const expectedPrefix = `/${RELEASE_REPO}/releases/tag/`;
|
|
160
|
+
if (target.origin !== new URL(webOrigin).origin || !target.pathname.startsWith(expectedPrefix)) {
|
|
161
|
+
throw new Error(`Refusing a GitHub release redirect outside ${webOrigin}${expectedPrefix}: ${target.href}`);
|
|
162
|
+
}
|
|
163
|
+
const tag = decodeURIComponent(target.pathname.slice(expectedPrefix.length));
|
|
164
|
+
if (!isSafeReleaseTag(tag)) throw new Error(`Refusing unsafe GitHub release tag: ${tag || "<empty>"}`);
|
|
165
|
+
if (!STABLE_VERSION_RE.test(versionFromTag(tag))) {
|
|
166
|
+
throw new Error(`GitHub release ${tag} is not a stable vX.Y.Z release`);
|
|
167
|
+
}
|
|
168
|
+
return tag;
|
|
169
|
+
}
|
|
170
|
+
|
|
112
171
|
export async function fetchGithubChannelRelease(options: GithubReleaseLookupOptions = {}): Promise<GithubReleaseInfo> {
|
|
113
172
|
const channel = options.channel ?? "stable";
|
|
114
173
|
const apiOrigin = options.apiOrigin ?? GITHUB_API_ORIGIN;
|
|
115
174
|
if (channel === "nightly") {
|
|
116
175
|
const url = `${apiOrigin}/repos/${RELEASE_REPO}/releases?per_page=40`;
|
|
117
|
-
|
|
176
|
+
// Nightly has no unauthenticated web listing to fall back to, so a spent
|
|
177
|
+
// budget can only be answered with the token hint.
|
|
178
|
+
const payload = await readGithubJson(url, options).catch((error: unknown) => {
|
|
179
|
+
if (error instanceof GithubApiStatusError && error.isRateLimited) {
|
|
180
|
+
throw new Error(`${error.message}. ${TOKEN_HINT}`);
|
|
181
|
+
}
|
|
182
|
+
throw error;
|
|
183
|
+
});
|
|
118
184
|
if (!Array.isArray(payload)) {
|
|
119
185
|
throw new Error(`${url} did not return a release list`);
|
|
120
186
|
}
|
|
@@ -139,8 +205,24 @@ export async function fetchGithubChannelRelease(options: GithubReleaseLookupOpti
|
|
|
139
205
|
);
|
|
140
206
|
}
|
|
141
207
|
const url = `${apiOrigin}/repos/${RELEASE_REPO}/releases/latest`;
|
|
142
|
-
|
|
143
|
-
|
|
208
|
+
try {
|
|
209
|
+
return parseRelease(await readGithubJson(url, options), "stable");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (!(error instanceof GithubApiStatusError) || !error.isRateLimited) throw error;
|
|
212
|
+
try {
|
|
213
|
+
const tag = await resolveStableReleaseTagFromWeb(options);
|
|
214
|
+
return {
|
|
215
|
+
tag,
|
|
216
|
+
version: versionFromTag(tag),
|
|
217
|
+
channel: "stable",
|
|
218
|
+
htmlUrl: `${options.webOrigin ?? GITHUB_WEB_ORIGIN}/${RELEASE_REPO}/releases/tag/${tag}`,
|
|
219
|
+
warnings: [`${error.message}; resolved the latest stable tag through github.com instead. ${TOKEN_HINT}`],
|
|
220
|
+
};
|
|
221
|
+
} catch (fallbackError) {
|
|
222
|
+
const detail = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
223
|
+
throw new Error(`${error.message}, and the github.com fallback failed: ${detail}. ${TOKEN_HINT}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
144
226
|
}
|
|
145
227
|
|
|
146
228
|
export function parseChecksumForAsset(text: string, assetName: string): string | undefined {
|
package/src/cli/mcp-cli.ts
CHANGED
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
resolveScopeMCPConfigPath,
|
|
20
20
|
upsertScopeMCPServer,
|
|
21
21
|
} from "../runtime-mcp/scope-config";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
type AutoloadStatus,
|
|
24
|
+
computeAutoloadStatus,
|
|
25
|
+
DEFAULT_MCP_STARTUP_WAIT_MS,
|
|
26
|
+
MAX_MCP_STARTUP_WAIT_MS,
|
|
27
|
+
MCP_STARTUP_WAIT_GRACE_MS,
|
|
28
|
+
} from "../runtime-mcp/startup-policy";
|
|
23
29
|
import type { MCPServerConfig } from "../runtime-mcp/types";
|
|
24
30
|
|
|
25
31
|
export type MCPAction = "add" | "list" | "remove";
|
|
@@ -64,6 +70,8 @@ interface RuntimeServerEntry extends RedactedServerEntry {
|
|
|
64
70
|
runtimeStatus: AutoloadStatus;
|
|
65
71
|
/** Human-readable explanation of the runtime status. */
|
|
66
72
|
runtimeNote: string;
|
|
73
|
+
/** Actionable guidance when startup can disconnect an untimed registration. */
|
|
74
|
+
startupDiagnostic?: string;
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
const REDACTED = "<redacted>";
|
|
@@ -90,6 +98,18 @@ function autoloadStatusNote(status: AutoloadStatus): string {
|
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
function startupDiagnostic(config: MCPServerConfig, status: AutoloadStatus): string | undefined {
|
|
102
|
+
if (status !== "autoload") return undefined;
|
|
103
|
+
const timeout = config.timeout;
|
|
104
|
+
if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0) return undefined;
|
|
105
|
+
return (
|
|
106
|
+
`No per-server timeout is declared. Ordinary standalone sessions wait ${DEFAULT_MCP_STARTUP_WAIT_MS}ms when no ` +
|
|
107
|
+
`registration declares a positive timeout; otherwise the batch wait uses the largest declared timeout plus ` +
|
|
108
|
+
`${MCP_STARTUP_WAIT_GRACE_MS}ms grace, capped at ${MAX_MCP_STARTUP_WAIT_MS}ms. Unfinished connections are ` +
|
|
109
|
+
"disconnected; add --timeout <ms> when registering slower servers."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
function resolvePath(args: MCPCommandArgs): ScopedPath {
|
|
94
114
|
const scope: MCPConfigScope = args.flags.project ? "project" : "user";
|
|
95
115
|
return { scope, path: resolveScopeMCPConfigPath(scope, args.cwd ?? getProjectDir()) };
|
|
@@ -243,8 +263,14 @@ export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig
|
|
|
243
263
|
function withRuntimeDisclosure<T extends object>(
|
|
244
264
|
value: T,
|
|
245
265
|
status: AutoloadStatus,
|
|
266
|
+
diagnostic?: string,
|
|
246
267
|
): T & { runtimeStatus: AutoloadStatus; runtimeNote: string } {
|
|
247
|
-
return {
|
|
268
|
+
return {
|
|
269
|
+
...value,
|
|
270
|
+
runtimeStatus: status,
|
|
271
|
+
runtimeNote: autoloadStatusNote(status),
|
|
272
|
+
...(diagnostic === undefined ? {} : { startupDiagnostic: diagnostic }),
|
|
273
|
+
};
|
|
248
274
|
}
|
|
249
275
|
|
|
250
276
|
async function collectEntries(scoped: ScopedPath, cwd?: string): Promise<RuntimeServerEntry[]> {
|
|
@@ -257,12 +283,14 @@ async function collectEntries(scoped: ScopedPath, cwd?: string): Promise<Runtime
|
|
|
257
283
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
258
284
|
.map(([name, serverConfig]) => {
|
|
259
285
|
const runtimeStatus = computeAutoloadStatus(name, serverConfig, disabledSet);
|
|
286
|
+
const diagnostic = startupDiagnostic(serverConfig, runtimeStatus);
|
|
260
287
|
return {
|
|
261
288
|
name,
|
|
262
289
|
scope: scoped.scope,
|
|
263
290
|
path: scoped.path,
|
|
264
291
|
runtimeStatus,
|
|
265
292
|
runtimeNote: autoloadStatusNote(runtimeStatus),
|
|
293
|
+
...(diagnostic === undefined ? {} : { startupDiagnostic: diagnostic }),
|
|
266
294
|
config: redactMCPServerConfig(serverConfig),
|
|
267
295
|
};
|
|
268
296
|
});
|
|
@@ -297,9 +325,11 @@ async function runAdd(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
|
297
325
|
},
|
|
298
326
|
args.cwd,
|
|
299
327
|
);
|
|
300
|
-
const
|
|
328
|
+
const disclosedConfig = result.status === "skipped" ? (storedConfig.mcpServers?.[args.name] ?? config) : config;
|
|
329
|
+
const redacted = redactMCPServerConfig(disclosedConfig);
|
|
301
330
|
const disabled = new Set(storedConfig.disabledServers ?? (await readScopeDisabledServers(scoped.scope, args.cwd)));
|
|
302
|
-
const runtimeStatus = computeAutoloadStatus(args.name,
|
|
331
|
+
const runtimeStatus = computeAutoloadStatus(args.name, disclosedConfig, disabled);
|
|
332
|
+
const diagnostic = startupDiagnostic(disclosedConfig, runtimeStatus);
|
|
303
333
|
if (args.flags.json) {
|
|
304
334
|
writeJson(
|
|
305
335
|
withRuntimeDisclosure(
|
|
@@ -312,6 +342,7 @@ async function runAdd(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
|
312
342
|
config: redacted,
|
|
313
343
|
},
|
|
314
344
|
runtimeStatus,
|
|
345
|
+
diagnostic,
|
|
315
346
|
),
|
|
316
347
|
);
|
|
317
348
|
return;
|
|
@@ -321,12 +352,14 @@ async function runAdd(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
|
321
352
|
`MCP server "${args.name}" already exists in ${scoped.scope} config. Pass --force to overwrite. ` +
|
|
322
353
|
`Runtime: ${autoloadStatusNote(runtimeStatus)}\n`,
|
|
323
354
|
);
|
|
355
|
+
if (diagnostic) process.stdout.write(`Startup: ${diagnostic}\n`);
|
|
324
356
|
return;
|
|
325
357
|
}
|
|
326
358
|
process.stdout.write(
|
|
327
359
|
`MCP server "${args.name}" ${result.status} in ${scoped.scope} config: ${scoped.path}\n` +
|
|
328
360
|
`Runtime: ${autoloadStatusNote(runtimeStatus)}\n`,
|
|
329
361
|
);
|
|
362
|
+
if (diagnostic) process.stdout.write(`Startup: ${diagnostic}\n`);
|
|
330
363
|
}
|
|
331
364
|
|
|
332
365
|
async function runList(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
@@ -348,6 +381,7 @@ async function runList(args: MCPCommandArgs, scoped: ScopedPath): Promise<void>
|
|
|
348
381
|
for (const entry of entries) {
|
|
349
382
|
process.stdout.write(`${renderDetails(entry)}\n`);
|
|
350
383
|
process.stdout.write(`Runtime: ${entry.runtimeNote}\n`);
|
|
384
|
+
if (entry.startupDiagnostic) process.stdout.write(`Startup: ${entry.startupDiagnostic}\n`);
|
|
351
385
|
}
|
|
352
386
|
}
|
|
353
387
|
|