@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,59 @@
|
|
|
1
|
+
import { AcpSdkAdapterError } from "../../sdk/acp";
|
|
2
|
+
|
|
3
|
+
type JsonObject = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export type PrimaryControlSurface = "cli" | "sdk";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Startup-control provenance is the authority record ACP needs before it may take
|
|
9
|
+
* permission, provider, and lifecycle ownership of a session. Hosts serving that
|
|
10
|
+
* record report capabilities carrying `promptTerminalOutcomeVersion`
|
|
11
|
+
* and `primaryControlSurface`; a host that predates the record reports neither, and a
|
|
12
|
+
* host that cannot answer at all reports nothing. Those are three different operator
|
|
13
|
+
* problems, so they get three different messages instead of one "restart the session".
|
|
14
|
+
*/
|
|
15
|
+
export function resolveStartupProvenance(input: {
|
|
16
|
+
sessionId: string;
|
|
17
|
+
capabilities: JsonObject | undefined;
|
|
18
|
+
queryFailure: unknown;
|
|
19
|
+
}): PrimaryControlSurface {
|
|
20
|
+
const { sessionId, capabilities, queryFailure } = input;
|
|
21
|
+
if (!capabilities)
|
|
22
|
+
throw new AcpSdkAdapterError(
|
|
23
|
+
"unavailable",
|
|
24
|
+
`ACP session ${sessionId} did not answer runtime.capabilities, so its startup control provenance is unknown${describeFailure(queryFailure)}.`,
|
|
25
|
+
);
|
|
26
|
+
const surface =
|
|
27
|
+
capabilities.primaryControlSurface === "sdk"
|
|
28
|
+
? "sdk"
|
|
29
|
+
: capabilities.primaryControlSurface === "cli"
|
|
30
|
+
? "cli"
|
|
31
|
+
: undefined;
|
|
32
|
+
if (capabilities.promptTerminalOutcomeVersion !== 1 || surface === undefined)
|
|
33
|
+
throw new AcpSdkAdapterError(
|
|
34
|
+
"unavailable",
|
|
35
|
+
`ACP session ${sessionId} is served by a live GJC host that predates startup control provenance. Upgrading the client does not upgrade that already-running host: stop it, then reopen the session so the current build resumes it from its stored record.`,
|
|
36
|
+
);
|
|
37
|
+
return surface;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function describeFailure(failure: unknown): string {
|
|
41
|
+
if (failure === undefined) return "";
|
|
42
|
+
let code: unknown;
|
|
43
|
+
try {
|
|
44
|
+
if ((typeof failure !== "object" || failure === null) && typeof failure !== "function") return " (query failed)";
|
|
45
|
+
code = (failure as { code?: unknown }).code;
|
|
46
|
+
} catch {
|
|
47
|
+
return " (query failed)";
|
|
48
|
+
}
|
|
49
|
+
switch (code) {
|
|
50
|
+
case "connection_closed":
|
|
51
|
+
return " (connection closed)";
|
|
52
|
+
case "timeout":
|
|
53
|
+
return " (query timed out)";
|
|
54
|
+
case "uncertain_after_send":
|
|
55
|
+
return " (query outcome uncertain)";
|
|
56
|
+
default:
|
|
57
|
+
return " (query failed)";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
1
2
|
import { Container, Input, matchesKey, SecretInput, Spacer, Text, TruncatedText } from "@gajae-code/tui";
|
|
2
3
|
import type { ProviderCompatibility, ProviderSetupInput } from "../../setup/provider-onboarding";
|
|
3
4
|
import { theme } from "../theme/theme";
|
|
@@ -12,6 +13,7 @@ type WizardStep =
|
|
|
12
13
|
| "base-url"
|
|
13
14
|
| "credential-source"
|
|
14
15
|
| "credential"
|
|
16
|
+
| "discover"
|
|
15
17
|
| "models"
|
|
16
18
|
| "confirm"
|
|
17
19
|
| "force-confirm";
|
|
@@ -22,11 +24,33 @@ interface WizardState {
|
|
|
22
24
|
baseUrl: string;
|
|
23
25
|
credentialSource: CustomProviderCredentialSource;
|
|
24
26
|
credential: string;
|
|
27
|
+
discoverModels: boolean;
|
|
28
|
+
discoveredModels: string[];
|
|
29
|
+
/** Inputs that produced discoveredModels; empty when results are stale/unprobed. */
|
|
30
|
+
discoveredForBaseUrl: string;
|
|
31
|
+
/** SHA-256 of the credential that produced the results — never the secret itself. */
|
|
32
|
+
discoveredForCredentialHash: string;
|
|
33
|
+
discoveredForCredentialSource: CustomProviderCredentialSource;
|
|
34
|
+
discoveryError: string | null;
|
|
25
35
|
models: string;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
export type CustomProviderWizardSubmit = ProviderSetupInput;
|
|
29
39
|
|
|
40
|
+
export interface CustomProviderWizardDiscoveryDeps {
|
|
41
|
+
discoverModels?: (input: {
|
|
42
|
+
baseUrl: string;
|
|
43
|
+
apiKeyEnv?: string;
|
|
44
|
+
apiKey?: string;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}) => Promise<{ models: string[] }>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Non-secret fingerprint binding probe results to the credential that produced them. */
|
|
50
|
+
function fingerprintCredential(credential: string): string {
|
|
51
|
+
return `sha256:${crypto.createHash("sha256").update(credential, "utf8").digest("hex")}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
export class CustomProviderWizardComponent extends Container {
|
|
31
55
|
#contentContainer: Container;
|
|
32
56
|
#input: Input | SecretInput | null = null;
|
|
@@ -39,22 +63,37 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
39
63
|
baseUrl: "",
|
|
40
64
|
credentialSource: "env",
|
|
41
65
|
credential: "",
|
|
66
|
+
discoverModels: false,
|
|
67
|
+
discoveredModels: [],
|
|
68
|
+
discoveredForBaseUrl: "",
|
|
69
|
+
discoveredForCredentialHash: "",
|
|
70
|
+
discoveredForCredentialSource: "env",
|
|
71
|
+
discoveryError: null,
|
|
42
72
|
models: "",
|
|
43
73
|
};
|
|
44
74
|
#submitInFlight = false;
|
|
75
|
+
#discoveryInFlight = false;
|
|
76
|
+
#discoveryGeneration = 0;
|
|
77
|
+
#submitGeneration = 0;
|
|
78
|
+
#discoveryAbort: AbortController | null = null;
|
|
79
|
+
#submitAbort: AbortController | null = null;
|
|
80
|
+
#dismissed = false;
|
|
45
81
|
#onSubmit: (input: CustomProviderWizardSubmit) => void;
|
|
46
82
|
#onCancel: () => void;
|
|
47
83
|
#onRender: () => void;
|
|
84
|
+
#discoveryDeps: CustomProviderWizardDiscoveryDeps = {};
|
|
48
85
|
|
|
49
86
|
constructor(
|
|
50
87
|
onSubmit: (input: CustomProviderWizardSubmit) => void,
|
|
51
88
|
onCancel: () => void,
|
|
52
89
|
onRender: () => void = () => {},
|
|
90
|
+
discoveryDeps: CustomProviderWizardDiscoveryDeps = {},
|
|
53
91
|
) {
|
|
54
92
|
super();
|
|
55
93
|
this.#onSubmit = onSubmit;
|
|
56
94
|
this.#onCancel = onCancel;
|
|
57
95
|
this.#onRender = onRender;
|
|
96
|
+
this.#discoveryDeps = discoveryDeps;
|
|
58
97
|
|
|
59
98
|
this.addChild(new DynamicBorder());
|
|
60
99
|
this.addChild(new Spacer(1));
|
|
@@ -83,6 +122,8 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
83
122
|
handleInput(keyData: string): void {
|
|
84
123
|
if (matchesAppInterrupt(keyData)) {
|
|
85
124
|
if (this.#step === "compatibility") {
|
|
125
|
+
this.#dismissed = true;
|
|
126
|
+
this.#cancelDiscovery();
|
|
86
127
|
this.#clearLiteralCredential();
|
|
87
128
|
this.#onCancel();
|
|
88
129
|
return;
|
|
@@ -155,6 +196,9 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
155
196
|
this.#renderSecretInputStep();
|
|
156
197
|
}
|
|
157
198
|
break;
|
|
199
|
+
case "discover":
|
|
200
|
+
this.#renderDiscoverStep();
|
|
201
|
+
break;
|
|
158
202
|
case "models":
|
|
159
203
|
this.#renderInputStep(
|
|
160
204
|
"Step 6: Model id(s)",
|
|
@@ -204,6 +248,49 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
204
248
|
this.#addHelp("[Enter to continue, Esc to go back]");
|
|
205
249
|
}
|
|
206
250
|
|
|
251
|
+
#renderDiscoverStep(): void {
|
|
252
|
+
this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 6: Model discovery")));
|
|
253
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
254
|
+
if (this.#state.compatibility !== "openai") {
|
|
255
|
+
this.#contentContainer.addChild(
|
|
256
|
+
new Text("Model discovery needs an OpenAI-compatible endpoint. Continue with manual model ids.", 0, 0),
|
|
257
|
+
);
|
|
258
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
259
|
+
this.#addOption(0, "Enter model ids manually");
|
|
260
|
+
this.#addHelp("[Enter to continue, Esc to go back]");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (this.#discoveryInFlight) {
|
|
264
|
+
this.#contentContainer.addChild(new Text("Discovering models from the provider /v1/models endpoint…", 0, 0));
|
|
265
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
266
|
+
this.#addHelp("The API key is sent only to your configured base URL.");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (this.#state.discoveryError) {
|
|
270
|
+
this.#contentContainer.addChild(
|
|
271
|
+
new Text(theme.fg("warning", `Discovery failed: ${this.#state.discoveryError}`), 0, 0),
|
|
272
|
+
);
|
|
273
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
274
|
+
}
|
|
275
|
+
if (this.#state.discoveredModels.length > 0) {
|
|
276
|
+
const preview = this.#state.discoveredModels.slice(0, 10).join(", ");
|
|
277
|
+
const extra =
|
|
278
|
+
this.#state.discoveredModels.length > 10 ? ` (+${this.#state.discoveredModels.length - 10} more)` : "";
|
|
279
|
+
this.#contentContainer.addChild(
|
|
280
|
+
new Text(`Discovered ${this.#state.discoveredModels.length} model(s): ${preview}${extra}`, 0, 0),
|
|
281
|
+
);
|
|
282
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
283
|
+
} else if (!this.#state.discoveryError) {
|
|
284
|
+
this.#contentContainer.addChild(
|
|
285
|
+
new Text("Probe the provider /v1/models endpoint, or enter model ids manually.", 0, 0),
|
|
286
|
+
);
|
|
287
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
288
|
+
}
|
|
289
|
+
this.#addOption(0, this.#state.discoveredModels.length > 0 ? "Use discovered models" : "Discover models now");
|
|
290
|
+
this.#addOption(1, "Enter model ids manually");
|
|
291
|
+
this.#addHelp("[↑↓ to navigate, Enter to select, Esc to go back]");
|
|
292
|
+
}
|
|
293
|
+
|
|
207
294
|
#renderSecretInputStep(): void {
|
|
208
295
|
this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 5: Credential")));
|
|
209
296
|
this.#contentContainer.addChild(new Spacer(1));
|
|
@@ -214,9 +301,7 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
214
301
|
const credential = secret.consume().trim();
|
|
215
302
|
if (!credential) return;
|
|
216
303
|
this.#state.credential = credential;
|
|
217
|
-
this.#
|
|
218
|
-
this.#renderStep();
|
|
219
|
-
this.#onRender();
|
|
304
|
+
this.#enterDiscovery();
|
|
220
305
|
};
|
|
221
306
|
this.#input = input;
|
|
222
307
|
this.#contentContainer.addChild(input);
|
|
@@ -267,16 +352,25 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
267
352
|
if (!value) return;
|
|
268
353
|
if (this.#step === "provider-id") {
|
|
269
354
|
this.#state.providerId = value;
|
|
355
|
+
// A provider-ID revision invalidates pending submits exactly
|
|
356
|
+
// like URL/credential revisions do.
|
|
357
|
+
this.#clearStaleDiscovery();
|
|
270
358
|
this.#step = "base-url";
|
|
271
359
|
} else if (this.#step === "base-url") {
|
|
272
360
|
this.#state.baseUrl = value;
|
|
361
|
+
this.#clearStaleDiscovery();
|
|
273
362
|
this.#step = "credential-source";
|
|
274
363
|
this.#selectedIndex = 0;
|
|
275
364
|
} else if (this.#step === "credential") {
|
|
276
365
|
this.#state.credential = value;
|
|
277
|
-
this.#
|
|
366
|
+
this.#enterDiscovery();
|
|
367
|
+
return;
|
|
278
368
|
} else if (this.#step === "models") {
|
|
279
369
|
this.#state.models = value;
|
|
370
|
+
// Manual entry supersedes any accepted discovery (clearStale
|
|
371
|
+
// also resets discoverModels): submitting both would silently
|
|
372
|
+
// persist live discovery the manual screen never disclosed.
|
|
373
|
+
this.#clearStaleDiscovery();
|
|
280
374
|
this.#step = "confirm";
|
|
281
375
|
this.#selectedIndex = 0;
|
|
282
376
|
this.#lastSubmitError = null;
|
|
@@ -285,6 +379,18 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
285
379
|
this.#onRender();
|
|
286
380
|
}
|
|
287
381
|
|
|
382
|
+
#enterDiscovery(): void {
|
|
383
|
+
this.#clearStaleDiscovery();
|
|
384
|
+
this.#step = "discover";
|
|
385
|
+
this.#selectedIndex = 0;
|
|
386
|
+
if (this.#state.compatibility === "openai" && this.#discoveryDeps.discoverModels) {
|
|
387
|
+
this.#runDiscovery();
|
|
388
|
+
} else {
|
|
389
|
+
this.#renderStep();
|
|
390
|
+
this.#onRender();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
288
394
|
#selectCurrentOption(): void {
|
|
289
395
|
if (this.#step === "compatibility") {
|
|
290
396
|
this.#state.compatibility = this.#selectedIndex === 0 ? "openai" : "anthropic";
|
|
@@ -293,37 +399,189 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
293
399
|
this.#state.credentialSource = this.#selectedIndex === 0 ? "env" : "literal";
|
|
294
400
|
this.#state.credential = "";
|
|
295
401
|
this.#step = "credential";
|
|
402
|
+
} else if (this.#step === "discover") {
|
|
403
|
+
if (this.#state.compatibility !== "openai") {
|
|
404
|
+
this.#step = "models";
|
|
405
|
+
} else if (this.#selectedIndex === 0) {
|
|
406
|
+
if (this.#state.discoveredModels.length > 0 && this.#discoveryResultsAreFresh()) {
|
|
407
|
+
this.#state.discoverModels = true;
|
|
408
|
+
this.#state.models = "";
|
|
409
|
+
this.#step = "confirm";
|
|
410
|
+
this.#selectedIndex = 0;
|
|
411
|
+
this.#lastSubmitError = null;
|
|
412
|
+
} else {
|
|
413
|
+
this.#runDiscovery();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
this.#state.discoverModels = false;
|
|
418
|
+
this.#cancelDiscovery();
|
|
419
|
+
this.#step = "models";
|
|
420
|
+
}
|
|
296
421
|
} else if (this.#step === "confirm" || this.#step === "force-confirm") {
|
|
297
422
|
if (this.#selectedIndex === 0) {
|
|
298
423
|
this.#submit();
|
|
299
424
|
return;
|
|
300
425
|
}
|
|
301
|
-
this.#
|
|
426
|
+
this.#goBack();
|
|
427
|
+
return;
|
|
302
428
|
}
|
|
303
429
|
this.#renderStep();
|
|
304
430
|
this.#onRender();
|
|
305
431
|
}
|
|
306
432
|
|
|
433
|
+
#clearStaleDiscovery(): void {
|
|
434
|
+
this.#discoveryGeneration += 1;
|
|
435
|
+
this.#submitGeneration += 1;
|
|
436
|
+
this.#discoveryAbort?.abort();
|
|
437
|
+
this.#discoveryAbort = null;
|
|
438
|
+
// A revision also invalidates an in-flight submit: without this,
|
|
439
|
+
// a submit-time probe started before the edit could still persist
|
|
440
|
+
// the old URL/key and close the wizard as successful.
|
|
441
|
+
this.#submitAbort?.abort();
|
|
442
|
+
this.#submitAbort = null;
|
|
443
|
+
this.#discoveryInFlight = false;
|
|
444
|
+
this.#state.discoveredModels = [];
|
|
445
|
+
this.#state.discoveredForBaseUrl = "";
|
|
446
|
+
this.#state.discoveredForCredentialHash = "";
|
|
447
|
+
this.#state.discoverModels = false;
|
|
448
|
+
this.#state.discoveryError = null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
#discoveryResultsAreFresh(): boolean {
|
|
452
|
+
return (
|
|
453
|
+
this.#state.discoveredForBaseUrl !== "" &&
|
|
454
|
+
this.#state.discoveredForBaseUrl === this.#state.baseUrl.trim() &&
|
|
455
|
+
this.#state.discoveredForCredentialHash !== "" &&
|
|
456
|
+
this.#state.discoveredForCredentialHash === fingerprintCredential(this.#state.credential.trim()) &&
|
|
457
|
+
this.#state.discoveredForCredentialSource === this.#state.credentialSource
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#runDiscovery(): void {
|
|
462
|
+
const discover = this.#discoveryDeps.discoverModels;
|
|
463
|
+
if (!discover || this.#discoveryInFlight) return;
|
|
464
|
+
this.#discoveryAbort?.abort();
|
|
465
|
+
const controller = new AbortController();
|
|
466
|
+
this.#discoveryAbort = controller;
|
|
467
|
+
this.#discoveryInFlight = true;
|
|
468
|
+
this.#discoveryGeneration += 1;
|
|
469
|
+
const generation = this.#discoveryGeneration;
|
|
470
|
+
this.#state.discoveryError = null;
|
|
471
|
+
this.#renderStep();
|
|
472
|
+
this.#onRender();
|
|
473
|
+
// Immutable fingerprint of the inputs being probed: completions are
|
|
474
|
+
// accepted only when both the generation and the live inputs still
|
|
475
|
+
// match, so an edit made while the request was in flight cannot
|
|
476
|
+
// inherit a catalog fetched for different inputs.
|
|
477
|
+
const credential = this.#state.credential.trim();
|
|
478
|
+
const fingerprint = {
|
|
479
|
+
baseUrl: this.#state.baseUrl.trim(),
|
|
480
|
+
credential,
|
|
481
|
+
credentialSource: this.#state.credentialSource,
|
|
482
|
+
};
|
|
483
|
+
const request = {
|
|
484
|
+
baseUrl: fingerprint.baseUrl,
|
|
485
|
+
apiKeyEnv: fingerprint.credentialSource === "env" ? fingerprint.credential : undefined,
|
|
486
|
+
apiKey: fingerprint.credentialSource === "literal" ? fingerprint.credential : undefined,
|
|
487
|
+
signal: controller.signal,
|
|
488
|
+
};
|
|
489
|
+
void Promise.resolve()
|
|
490
|
+
.then(() => discover(request))
|
|
491
|
+
.then(
|
|
492
|
+
result => {
|
|
493
|
+
if (!this.#isCurrentDiscoveryGeneration(generation)) return;
|
|
494
|
+
this.#discoveryInFlight = false;
|
|
495
|
+
if (!this.#discoveryFingerprintMatches(fingerprint)) {
|
|
496
|
+
if (this.#step === "discover") {
|
|
497
|
+
this.#renderStep();
|
|
498
|
+
this.#onRender();
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const models = [...new Set(result.models.map(model => model.trim()).filter(Boolean))].sort((a, b) =>
|
|
503
|
+
a.localeCompare(b),
|
|
504
|
+
);
|
|
505
|
+
this.#state.discoveredModels = models;
|
|
506
|
+
this.#state.discoveredForBaseUrl = fingerprint.baseUrl;
|
|
507
|
+
this.#state.discoveredForCredentialHash = fingerprintCredential(fingerprint.credential);
|
|
508
|
+
this.#state.discoveredForCredentialSource = fingerprint.credentialSource;
|
|
509
|
+
this.#state.discoveryError = models.length === 0 ? "The endpoint returned no models." : null;
|
|
510
|
+
this.#selectedIndex = 0;
|
|
511
|
+
if (this.#step === "discover") {
|
|
512
|
+
this.#renderStep();
|
|
513
|
+
this.#onRender();
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
error => {
|
|
517
|
+
if (!this.#isCurrentDiscoveryGeneration(generation)) return;
|
|
518
|
+
this.#discoveryInFlight = false;
|
|
519
|
+
if (!this.#discoveryFingerprintMatches(fingerprint)) {
|
|
520
|
+
if (this.#step === "discover") {
|
|
521
|
+
this.#renderStep();
|
|
522
|
+
this.#onRender();
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.#state.discoveredModels = [];
|
|
527
|
+
this.#state.discoveredForBaseUrl = "";
|
|
528
|
+
this.#state.discoveredForCredentialHash = "";
|
|
529
|
+
this.#state.discoveryError = error instanceof Error ? error.message : String(error);
|
|
530
|
+
this.#selectedIndex = 1;
|
|
531
|
+
if (this.#step === "discover") {
|
|
532
|
+
this.#renderStep();
|
|
533
|
+
this.#onRender();
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
#isCurrentDiscoveryGeneration(generation: number): boolean {
|
|
540
|
+
return generation === this.#discoveryGeneration;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
#discoveryFingerprintMatches(fingerprint: {
|
|
544
|
+
baseUrl: string;
|
|
545
|
+
credential: string;
|
|
546
|
+
credentialSource: CustomProviderCredentialSource;
|
|
547
|
+
}): boolean {
|
|
548
|
+
return (
|
|
549
|
+
fingerprint.baseUrl === this.#state.baseUrl.trim() &&
|
|
550
|
+
fingerprint.credential === this.#state.credential.trim() &&
|
|
551
|
+
fingerprint.credentialSource === this.#state.credentialSource
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
307
555
|
#submit(): void {
|
|
308
556
|
if (this.#submitInFlight) return;
|
|
309
557
|
this.#submitInFlight = true;
|
|
558
|
+
// Own the submit-time pre-write probe lifecycle: Esc-cancel during a
|
|
559
|
+
// stalled submit aborts the probe via #cancelDiscovery so the
|
|
560
|
+
// provider cannot be written after dismissal.
|
|
561
|
+
this.#submitAbort?.abort();
|
|
562
|
+
this.#submitAbort = new AbortController();
|
|
563
|
+
this.#submitGeneration += 1;
|
|
310
564
|
let submission: unknown;
|
|
311
565
|
try {
|
|
312
566
|
submission = this.#onSubmit(this.#buildInput(this.#step === "force-confirm"));
|
|
313
567
|
} catch (error) {
|
|
314
568
|
this.#submitInFlight = false;
|
|
569
|
+
this.#endSubmitDiscovery();
|
|
315
570
|
throw error;
|
|
316
571
|
}
|
|
317
572
|
if (!(submission instanceof Promise)) {
|
|
318
573
|
this.#submitInFlight = false;
|
|
574
|
+
this.#endSubmitDiscovery();
|
|
319
575
|
return;
|
|
320
576
|
}
|
|
321
577
|
void submission.then(
|
|
322
578
|
() => {
|
|
323
579
|
this.#submitInFlight = false;
|
|
580
|
+
this.#endSubmitDiscovery();
|
|
324
581
|
},
|
|
325
582
|
() => {
|
|
326
583
|
this.#submitInFlight = false;
|
|
584
|
+
this.#endSubmitDiscovery();
|
|
327
585
|
},
|
|
328
586
|
);
|
|
329
587
|
}
|
|
@@ -339,6 +597,8 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
339
597
|
.split(",")
|
|
340
598
|
.map(model => model.trim())
|
|
341
599
|
.filter(Boolean),
|
|
600
|
+
discover: this.#state.compatibility === "openai" ? this.#state.discoverModels : false,
|
|
601
|
+
discoverySignal: this.#submitAbort?.signal,
|
|
342
602
|
force,
|
|
343
603
|
};
|
|
344
604
|
|
|
@@ -346,9 +606,44 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
346
606
|
}
|
|
347
607
|
|
|
348
608
|
complete(): void {
|
|
609
|
+
this.#cancelDiscovery();
|
|
349
610
|
this.#clearLiteralCredential();
|
|
350
611
|
}
|
|
351
612
|
|
|
613
|
+
/**
|
|
614
|
+
* Abort any in-flight probe and invalidate its generation so a late
|
|
615
|
+
* completion cannot render into (or retain credentials from) a
|
|
616
|
+
* dismissed wizard. Used by both submit and cancel paths.
|
|
617
|
+
*/
|
|
618
|
+
#cancelDiscovery(): void {
|
|
619
|
+
this.#discoveryGeneration += 1;
|
|
620
|
+
this.#submitGeneration += 1;
|
|
621
|
+
this.#discoveryAbort?.abort();
|
|
622
|
+
this.#discoveryAbort = null;
|
|
623
|
+
this.#submitAbort?.abort();
|
|
624
|
+
this.#submitAbort = null;
|
|
625
|
+
this.#discoveryInFlight = false;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Clear the settled submit controller without aborting (submit finished). */
|
|
629
|
+
#endSubmitDiscovery(): void {
|
|
630
|
+
this.#submitAbort = null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Generation token for the latest submit attempt. */
|
|
634
|
+
currentSubmitGeneration(): number {
|
|
635
|
+
return this.#submitGeneration;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Whether a submit attempt is still current: same generation and not
|
|
640
|
+
* dismissed. Revision or cancel invalidates pending completions so a
|
|
641
|
+
* stale refresh cannot report success or rerender over new inputs.
|
|
642
|
+
*/
|
|
643
|
+
isSubmitCurrent(generation: number): boolean {
|
|
644
|
+
return generation === this.#submitGeneration && !this.#dismissed;
|
|
645
|
+
}
|
|
646
|
+
|
|
352
647
|
#clearLiteralCredential(): void {
|
|
353
648
|
if (this.#state.credentialSource === "literal") this.#state.credential = "";
|
|
354
649
|
}
|
|
@@ -358,7 +653,8 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
358
653
|
this.#step === "confirm" ||
|
|
359
654
|
this.#step === "force-confirm" ||
|
|
360
655
|
this.#step === "compatibility" ||
|
|
361
|
-
this.#step === "credential-source"
|
|
656
|
+
this.#step === "credential-source" ||
|
|
657
|
+
this.#step === "discover"
|
|
362
658
|
? 1
|
|
363
659
|
: 0;
|
|
364
660
|
this.#selectedIndex = (this.#selectedIndex + delta + maxIndex + 1) % (maxIndex + 1);
|
|
@@ -367,11 +663,16 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
367
663
|
}
|
|
368
664
|
|
|
369
665
|
#goBack(): void {
|
|
666
|
+
if (this.#step === "discover" || this.#step === "confirm" || this.#step === "force-confirm") {
|
|
667
|
+
// Revision starts on entry to the editor, not when its new value is saved.
|
|
668
|
+
this.#cancelDiscovery();
|
|
669
|
+
}
|
|
370
670
|
if (this.#step === "provider-id") this.#step = "compatibility";
|
|
371
671
|
else if (this.#step === "base-url") this.#step = "provider-id";
|
|
372
672
|
else if (this.#step === "credential-source") this.#step = "base-url";
|
|
373
673
|
else if (this.#step === "credential") this.#step = "credential-source";
|
|
374
|
-
else if (this.#step === "
|
|
674
|
+
else if (this.#step === "discover") this.#step = "credential";
|
|
675
|
+
else if (this.#step === "models") this.#step = "discover";
|
|
375
676
|
else if (this.#step === "confirm" || this.#step === "force-confirm") this.#step = "models";
|
|
376
677
|
this.#selectedIndex = 0;
|
|
377
678
|
this.#renderStep();
|
|
@@ -164,6 +164,15 @@ export class OAuthSelectorComponent extends Container {
|
|
|
164
164
|
{ kind: "auto", selectable: true },
|
|
165
165
|
{ kind: "add", selectable: this.#onAddAccount !== undefined },
|
|
166
166
|
);
|
|
167
|
+
const firstAccount = this.#accountEntries.find(entry => entry.kind === "account");
|
|
168
|
+
if (firstAccount && !firstAccount.selectable) {
|
|
169
|
+
const addAccountIndex =
|
|
170
|
+
firstAccount.row.disabled && this.#onAddAccount !== undefined
|
|
171
|
+
? this.#accountEntries.findIndex(entry => entry.kind === "add" && entry.selectable)
|
|
172
|
+
: -1;
|
|
173
|
+
this.#selectedIndex =
|
|
174
|
+
addAccountIndex >= 0 ? addAccountIndex : this.#accountEntries.findIndex(entry => entry.selectable);
|
|
175
|
+
}
|
|
167
176
|
} else if (this.#accountEntries.some(entry => entry.kind === "account" && entry.selectable)) {
|
|
168
177
|
this.#accountEntries.push({ kind: "all", selectable: this.#onAccountRemove !== undefined });
|
|
169
178
|
}
|
|
@@ -39,6 +39,7 @@ export type SessionInspector = (sessionPath: string) => Promise<ResumeTailInspec
|
|
|
39
39
|
|
|
40
40
|
type SelectorState =
|
|
41
41
|
| { kind: "browsing" }
|
|
42
|
+
| { kind: "starring"; token: number }
|
|
42
43
|
| { kind: "checking"; session: SessionInfo; token: number }
|
|
43
44
|
| { kind: "confirming"; session: SessionInfo; token: number; identity: ResumeSessionIdentity }
|
|
44
45
|
| { kind: "settled" };
|
|
@@ -52,12 +53,13 @@ class SessionList implements Component {
|
|
|
52
53
|
onCancel?: () => void;
|
|
53
54
|
onExit: () => void = () => {};
|
|
54
55
|
onDeleteRequest?: (session: SessionInfo) => void;
|
|
56
|
+
onToggleStarRequest?: (session: SessionInfo) => void;
|
|
55
57
|
|
|
56
58
|
constructor(
|
|
57
59
|
private readonly allSessions: SessionInfo[],
|
|
58
60
|
private readonly showCwd = false,
|
|
59
61
|
) {
|
|
60
|
-
this.#filteredSessions = allSessions;
|
|
62
|
+
this.#filteredSessions = prioritizeStarredSessions(allSessions);
|
|
61
63
|
this.#searchInput.onSubmit = () => {
|
|
62
64
|
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
63
65
|
if (selected) this.onSelect?.(selected);
|
|
@@ -100,6 +102,17 @@ class SessionList implements Component {
|
|
|
100
102
|
this.#filterSessions(this.#searchInput.getValue());
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
setSessionStarred(sessionPath: string, starred: boolean): void {
|
|
106
|
+
const session = this.allSessions.find(session => session.path === sessionPath);
|
|
107
|
+
if (!session) return;
|
|
108
|
+
session.starred = starred;
|
|
109
|
+
this.#filterSessions(this.#searchInput.getValue());
|
|
110
|
+
this.#selectedIndex = Math.max(
|
|
111
|
+
0,
|
|
112
|
+
this.#filteredSessions.findIndex(session => session.path === sessionPath),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
103
116
|
invalidate(): void {}
|
|
104
117
|
|
|
105
118
|
render(width: number): string[] {
|
|
@@ -155,6 +168,8 @@ class SessionList implements Component {
|
|
|
155
168
|
}
|
|
156
169
|
if (start > 0 || end < this.#filteredSessions.length)
|
|
157
170
|
lines.push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`));
|
|
171
|
+
if (this.onToggleStarRequest)
|
|
172
|
+
lines.push(theme.fg("muted", truncateToWidth(" [Ctrl+S to star/unstar selected session]", width)));
|
|
158
173
|
lines.push(
|
|
159
174
|
"",
|
|
160
175
|
theme.fg("muted", " [Del to delete selected transcript/artifacts, Enter to select, Esc to cancel]"),
|
|
@@ -177,7 +192,10 @@ class SessionList implements Component {
|
|
|
177
192
|
this.#selectedIndex += delta;
|
|
178
193
|
this.#clampSelectedIndex();
|
|
179
194
|
};
|
|
180
|
-
if (matchesKey(keyData, "
|
|
195
|
+
if (matchesKey(keyData, "ctrl+s")) {
|
|
196
|
+
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
197
|
+
if (selected) this.onToggleStarRequest?.(selected);
|
|
198
|
+
} else if (matchesKey(keyData, "delete")) {
|
|
181
199
|
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
182
200
|
if (selected) this.onDeleteRequest?.(selected);
|
|
183
201
|
} else if (matchesKey(keyData, "up")) move(-1);
|
|
@@ -194,7 +212,7 @@ class SessionList implements Component {
|
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
214
|
|
|
197
|
-
/** A one-shot resume consent selector
|
|
215
|
+
/** A one-shot resume consent selector with host-owned deletion and star persistence. */
|
|
198
216
|
export class SessionSelectorComponent extends Container {
|
|
199
217
|
#sessionList: SessionList;
|
|
200
218
|
#confirmationDialog: HookSelectorComponent | null = null;
|
|
@@ -211,6 +229,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
211
229
|
private readonly onDelete?: (session: SessionInfo) => Promise<boolean>,
|
|
212
230
|
private readonly inspector?: SessionInspector,
|
|
213
231
|
private readonly onSelection?: (selection: SessionSelectionResult) => void,
|
|
232
|
+
private readonly onSetStarred?: (session: SessionInfo, starred: boolean) => Promise<void>,
|
|
214
233
|
) {
|
|
215
234
|
super();
|
|
216
235
|
this.addChild(new Spacer(1));
|
|
@@ -219,7 +238,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
219
238
|
this.addChild(new DynamicBorder());
|
|
220
239
|
this.addChild(new Spacer(1));
|
|
221
240
|
this.addChild(this.#messageContainer);
|
|
222
|
-
this.#sessionList = new SessionList(
|
|
241
|
+
this.#sessionList = new SessionList([...sessions]);
|
|
223
242
|
this.#sessionList.onSelect = session => {
|
|
224
243
|
if (this.inspector) void this.#inspect(session);
|
|
225
244
|
else this.#dispatchSelect(session.path);
|
|
@@ -227,6 +246,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
227
246
|
this.#sessionList.onCancel = () => this.#cancel();
|
|
228
247
|
this.#sessionList.onExit = () => this.#exit();
|
|
229
248
|
this.#sessionList.onDeleteRequest = session => this.#showDeleteConfirmation(session);
|
|
249
|
+
if (this.onSetStarred) this.#sessionList.onToggleStarRequest = session => void this.#toggleStar(session);
|
|
230
250
|
this.addChild(this.#sessionList);
|
|
231
251
|
this.addChild(new Spacer(1));
|
|
232
252
|
this.addChild(new DynamicBorder());
|
|
@@ -294,6 +314,29 @@ export class SessionSelectorComponent extends Container {
|
|
|
294
314
|
this.#sessionList.setInputFrozen(true);
|
|
295
315
|
this.onExit();
|
|
296
316
|
}
|
|
317
|
+
async #toggleStar(session: SessionInfo): Promise<void> {
|
|
318
|
+
if (!this.onSetStarred || this.#state.kind !== "browsing" || this.#confirmationDialog) return;
|
|
319
|
+
const token = ++this.#nextToken;
|
|
320
|
+
const starred = !session.starred;
|
|
321
|
+
this.#state = { kind: "starring", token };
|
|
322
|
+
this.#sessionList.setInputFrozen(true);
|
|
323
|
+
this.#clearError();
|
|
324
|
+
this.#requestRender();
|
|
325
|
+
try {
|
|
326
|
+
await this.onSetStarred(session, starred);
|
|
327
|
+
if (this.#state.kind !== "starring" || this.#state.token !== token) return;
|
|
328
|
+
this.#sessionList.setSessionStarred(session.path, starred);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (this.#state.kind !== "starring" || this.#state.token !== token) return;
|
|
331
|
+
this.#showError(error instanceof Error ? error.message : String(error));
|
|
332
|
+
} finally {
|
|
333
|
+
if (this.#state.kind === "starring" && this.#state.token === token) {
|
|
334
|
+
this.#state = { kind: "browsing" };
|
|
335
|
+
this.#sessionList.setInputFrozen(false);
|
|
336
|
+
this.#requestRender();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
297
340
|
async #inspect(session: SessionInfo): Promise<void> {
|
|
298
341
|
const inspector = this.inspector;
|
|
299
342
|
if (!inspector || this.#state.kind !== "browsing") return;
|