@sayknow-cli/coding-agent 0.3.7 → 0.3.9
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +119 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/components/thinking-selector.d.ts +2 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +30 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +8 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +12 -3
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon.d.ts +45 -3
- package/dist/types/notifications/telegram-reference.d.ts +7 -5
- package/dist/types/notifications/threaded-render.d.ts +14 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +103 -17
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +148 -33
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +76 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +17 -8
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +12 -11
- package/src/modes/controllers/input-controller.ts +192 -35
- package/src/modes/controllers/selector-controller.ts +168 -24
- package/src/modes/interactive-mode.ts +56 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +52 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +144 -18
- package/src/notifications/lifecycle-commands.ts +74 -23
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +606 -98
- package/src/notifications/telegram-reference.ts +17 -16
- package/src/notifications/threaded-render.ts +45 -2
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +6 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +88 -36
- package/src/session/agent-session.ts +1013 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/tmux-common.ts +1 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1672 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +296 -72
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/utils/pasted-image-path.ts +170 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
package/src/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@sayknow-cli/utils/cli";
|
|
8
8
|
import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@sayknow-cli/utils/dirs";
|
|
9
|
+
import { runFixtureReport } from "./cli/fixture-report";
|
|
9
10
|
|
|
10
11
|
if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
11
12
|
process.stderr.write(
|
|
@@ -35,6 +36,7 @@ export const commands: CommandEntry[] = [
|
|
|
35
36
|
{ name: "gc", load: () => import("./commands/gc").then(m => m.default) },
|
|
36
37
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
37
38
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
39
|
+
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
|
|
38
40
|
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
39
41
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
40
42
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
@@ -51,6 +53,7 @@ export const commands: CommandEntry[] = [
|
|
|
51
53
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
52
54
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
53
55
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
56
|
+
{ name: "completion", load: () => import("./commands/completion").then(m => m.default) },
|
|
54
57
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
55
58
|
{ name: "telegram", load: () => import("./commands/telegram").then(m => m.default) },
|
|
56
59
|
];
|
|
@@ -74,6 +77,9 @@ async function installRuntimeGlobals(): Promise<void> {
|
|
|
74
77
|
// `HTTP2Unsupported`. See @sayknow-cli/ai/utils/h2-fetch for details.
|
|
75
78
|
installH2Fetch();
|
|
76
79
|
|
|
80
|
+
const { warnIfMacOSNoFileLimitTooLow } = await import("./cli/nofile-limit");
|
|
81
|
+
warnIfMacOSNoFileLimitTooLow();
|
|
82
|
+
|
|
77
83
|
// Strip macOS malloc-stack-logging env vars before any subprocess is spawned.
|
|
78
84
|
// Otherwise every child bun process (subagents, plugin installs, ptree spawns,
|
|
79
85
|
// etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
|
|
@@ -94,6 +100,17 @@ async function runNotifyDaemonInternalFastPath(argv: string[]): Promise<void> {
|
|
|
94
100
|
await runNotifyCommand(cmd);
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
function rootFixtureArg(argv: string[]): { present: boolean; id: string | undefined } {
|
|
104
|
+
for (let i = 0; i < argv.length; i++) {
|
|
105
|
+
const arg = argv[i];
|
|
106
|
+
// Stop at the first subcommand token so a `--fixture` flag belonging to a
|
|
107
|
+
// subcommand never hijacks the root fast-path into fixture-report mode.
|
|
108
|
+
if (isSubcommand(arg)) return { present: false, id: undefined };
|
|
109
|
+
if (arg === "--fixture") return { present: true, id: argv[i + 1] };
|
|
110
|
+
}
|
|
111
|
+
return { present: false, id: undefined };
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
|
|
98
115
|
for (const arg of argv) {
|
|
99
116
|
if (isSubcommand(arg)) return false;
|
|
@@ -110,7 +127,7 @@ function hasRootVersionFlag(argv: string[]): boolean {
|
|
|
110
127
|
return hasRootFastFlag(argv, versionFlags);
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
class RootHelpCommand extends Command {
|
|
130
|
+
export class RootHelpCommand extends Command {
|
|
114
131
|
static description = "Sayknow-CLI — an AI coding assistant";
|
|
115
132
|
static hidden = true;
|
|
116
133
|
static args = {
|
|
@@ -231,6 +248,17 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
231
248
|
await runSmokeTest();
|
|
232
249
|
return;
|
|
233
250
|
}
|
|
251
|
+
const fixtureArg = rootFixtureArg(argv);
|
|
252
|
+
if (fixtureArg.present) {
|
|
253
|
+
const id = fixtureArg.id;
|
|
254
|
+
if (!id || id.startsWith("-")) {
|
|
255
|
+
process.stderr.write(`${APP_NAME} --fixture requires a fixture id\n`);
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
process.exitCode = await runFixtureReport(id);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
234
262
|
if (hasRootHelpFlag(argv)) {
|
|
235
263
|
const { renderRootHelp } = await import("@sayknow-cli/utils/cli");
|
|
236
264
|
const { getExtraHelpText } = await import("./cli/fast-help");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate shell completion specs for external completion engines.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
|
+
import { commands, RootHelpCommand } from "../cli";
|
|
6
|
+
import {
|
|
7
|
+
buildSkcFigSpec,
|
|
8
|
+
defaultInshellisenseSpecDir,
|
|
9
|
+
type InstallSkcInshellisenseSpecResult,
|
|
10
|
+
installSkcInshellisenseSpec,
|
|
11
|
+
renderFigSpecModule,
|
|
12
|
+
} from "../cli/completion-cli";
|
|
13
|
+
|
|
14
|
+
const TARGETS = ["inshellisense", "fig"] as const;
|
|
15
|
+
|
|
16
|
+
type CompletionTarget = (typeof TARGETS)[number];
|
|
17
|
+
|
|
18
|
+
function normalizeTarget(target: CompletionTarget): "inshellisense" {
|
|
19
|
+
return target === "fig" ? "inshellisense" : target;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatInstallResult(result: InstallSkcInshellisenseSpecResult, customDir: boolean): string {
|
|
23
|
+
const lines = [
|
|
24
|
+
"Installed SKC inshellisense completion spec:",
|
|
25
|
+
` spec: ${result.specPath}`,
|
|
26
|
+
` index: ${result.indexPath} (${result.indexStatus})`,
|
|
27
|
+
];
|
|
28
|
+
if (customDir) {
|
|
29
|
+
lines.push(
|
|
30
|
+
"Add the install directory to inshellisense's [specs].path setting, then restart the inshellisense session.",
|
|
31
|
+
);
|
|
32
|
+
} else {
|
|
33
|
+
lines.push("Restart the inshellisense session, or run `is reinit` if your shell integration needs a refresh.");
|
|
34
|
+
}
|
|
35
|
+
return `${lines.join("\n")}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default class Completion extends Command {
|
|
39
|
+
static description = "Generate shell completion specs";
|
|
40
|
+
|
|
41
|
+
static args = {
|
|
42
|
+
target: Args.string({
|
|
43
|
+
description: "Completion format",
|
|
44
|
+
required: false,
|
|
45
|
+
options: TARGETS,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
static flags = {
|
|
50
|
+
install: Flags.boolean({ description: "Install the generated spec for inshellisense" }),
|
|
51
|
+
dir: Flags.string({ description: "Install directory (defaults to ~/.fig/autocomplete/build)" }),
|
|
52
|
+
force: Flags.boolean({ description: "Overwrite an existing non-SKC inshellisense index.js" }),
|
|
53
|
+
json: Flags.boolean({ description: "Output JSON instead of JavaScript or status text" }),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async run(): Promise<void> {
|
|
57
|
+
const { args, flags } = await this.parse(Completion);
|
|
58
|
+
const target = normalizeTarget((args.target ?? "inshellisense") as CompletionTarget);
|
|
59
|
+
if (target !== "inshellisense") {
|
|
60
|
+
process.stderr.write(`Unsupported completion target: ${target}\n`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if ((flags.dir || flags.force) && !flags.install) {
|
|
65
|
+
process.stderr.write("--dir and --force only apply with --install\n");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const spec = await buildSkcFigSpec(commands, RootHelpCommand);
|
|
71
|
+
if (!flags.install) {
|
|
72
|
+
process.stdout.write(flags.json ? `${JSON.stringify(spec, null, 2)}\n` : renderFigSpecModule(spec));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const directory = flags.dir ?? defaultInshellisenseSpecDir();
|
|
77
|
+
const result = await installSkcInshellisenseSpec(spec, { dir: directory, force: flags.force });
|
|
78
|
+
if (flags.json) {
|
|
79
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write(formatInstallResult(result, flags.dir !== undefined));
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/commands/config.ts
CHANGED
|
@@ -29,6 +29,7 @@ export default class Config extends Command {
|
|
|
29
29
|
|
|
30
30
|
static flags = {
|
|
31
31
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
32
|
+
"show-secrets": Flags.boolean({ description: "Show secret-like setting values without redaction (unsafe)" }),
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
async run(): Promise<void> {
|
|
@@ -42,6 +43,7 @@ export default class Config extends Command {
|
|
|
42
43
|
value,
|
|
43
44
|
flags: {
|
|
44
45
|
json: flags.json,
|
|
46
|
+
showSecrets: flags["show-secrets"],
|
|
45
47
|
},
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Command, Flags } from "@sayknow-cli/utils/cli";
|
|
2
|
-
import { prepareContributionPrep } from "../session/contribution-prep";
|
|
3
2
|
|
|
4
3
|
function writeText(lines: string[]): void {
|
|
5
4
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
@@ -19,6 +18,7 @@ export default class ContributionPrep extends Command {
|
|
|
19
18
|
|
|
20
19
|
async run(): Promise<void> {
|
|
21
20
|
const { flags } = await this.parse(ContributionPrep);
|
|
21
|
+
const { prepareContributionPrep } = await import("../session/contribution-prep");
|
|
22
22
|
const cwd = process.cwd();
|
|
23
23
|
const result = await prepareContributionPrep(
|
|
24
24
|
{
|
|
@@ -8,6 +8,7 @@ export default class DeepInterview extends Command {
|
|
|
8
8
|
quick: Flags.boolean({ description: "Seed a quick deep-interview run" }),
|
|
9
9
|
standard: Flags.boolean({ description: "Seed a standard deep-interview run" }),
|
|
10
10
|
deep: Flags.boolean({ description: "Seed a deep deep-interview run" }),
|
|
11
|
+
trace: Flags.boolean({ description: "Run a bounded trace evidence pre-step before interview questions" }),
|
|
11
12
|
threshold: Flags.string({ description: "Override ambiguity threshold for kickoff" }),
|
|
12
13
|
"threshold-source": Flags.string({ description: "Describe the threshold override source" }),
|
|
13
14
|
"session-id": Flags.string({
|
|
@@ -25,7 +26,7 @@ export default class DeepInterview extends Command {
|
|
|
25
26
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
26
27
|
};
|
|
27
28
|
static examples = [
|
|
28
|
-
'$ skc deep-interview --standard "<idea>"',
|
|
29
|
+
'$ skc deep-interview --trace --standard "<idea>"',
|
|
29
30
|
"$ skc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md",
|
|
30
31
|
"$ skc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md --deliberate",
|
|
31
32
|
];
|
package/src/config/file-lock.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import { isEnoent } from "@sayknow-cli/utils/fs-error";
|
|
3
4
|
|
|
@@ -22,9 +23,10 @@ function getLockPath(filePath: string): string {
|
|
|
22
23
|
return `${filePath}.lock`;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
async function writeLockInfo(lockPath: string): Promise<
|
|
26
|
+
async function writeLockInfo(lockPath: string): Promise<LockInfo> {
|
|
26
27
|
const info: LockInfo = { pid: process.pid, timestamp: Date.now() };
|
|
27
28
|
await Bun.write(`${lockPath}/info`, JSON.stringify(info));
|
|
29
|
+
return info;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
async function readLockInfo(lockPath: string): Promise<LockInfo | null> {
|
|
@@ -51,17 +53,30 @@ export interface FileLockOwnerToken {
|
|
|
51
53
|
timestamp: number;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
/** Outcome of a guarded
|
|
56
|
+
/** Outcome of a guarded lock-dir removal attempt (`removeFileLockDirForGc`). */
|
|
55
57
|
export type FileLockGcRemoval = "removed" | "owner_changed" | "missing";
|
|
56
58
|
|
|
59
|
+
interface LockDirStatToken {
|
|
60
|
+
dev: number;
|
|
61
|
+
ino: number;
|
|
62
|
+
mtimeMs: number;
|
|
63
|
+
ctimeMs: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type LockStaleSnapshot =
|
|
67
|
+
| { stale: false }
|
|
68
|
+
| { stale: true; owner: FileLockOwnerToken }
|
|
69
|
+
| { stale: true; owner: null; stat: LockDirStatToken };
|
|
70
|
+
|
|
57
71
|
/**
|
|
58
72
|
* @internal
|
|
59
|
-
* Fail-closed removal of a
|
|
60
|
-
* token as close to the unlink as possible
|
|
61
|
-
* STILL holds the exact `{pid, timestamp}`
|
|
73
|
+
* Fail-closed removal of a lock dir whose owner is expected to be dead or
|
|
74
|
+
* finished. Re-reads the on-disk owner token as close to the unlink as possible
|
|
75
|
+
* and only deletes the dir when it STILL holds the exact `{pid, timestamp}`
|
|
76
|
+
* identity the caller observed.
|
|
62
77
|
*
|
|
63
|
-
* Closes
|
|
64
|
-
*
|
|
78
|
+
* Closes stale-cleanup TOCTOU windows (#606): between a dead/stale re-read and
|
|
79
|
+
* the unlink, a live process can reclaim a stale lock at the same path
|
|
65
80
|
* (`acquireLock` rms the stale dir, then re-`mkdir`s and rewrites `info` with a
|
|
66
81
|
* fresh pid+timestamp). Deleting by path alone would reap that LIVE lock. Any
|
|
67
82
|
* mismatch (`owner_changed`) or absent/unreadable info (`missing` — e.g. a
|
|
@@ -99,14 +114,28 @@ function ownerLiveness(pid: number): OwnerLiveness {
|
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
function statToken(stats: Stats): LockDirStatToken {
|
|
118
|
+
return {
|
|
119
|
+
dev: stats.dev,
|
|
120
|
+
ino: stats.ino,
|
|
121
|
+
mtimeMs: stats.mtimeMs,
|
|
122
|
+
ctimeMs: stats.ctimeMs,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sameStatToken(a: LockDirStatToken, b: LockDirStatToken): boolean {
|
|
127
|
+
return a.dev === b.dev && a.ino === b.ino && a.mtimeMs === b.mtimeMs && a.ctimeMs === b.ctimeMs;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function staleLockSnapshot(lockPath: string, staleMs: number): Promise<LockStaleSnapshot> {
|
|
103
131
|
const info = await readLockInfo(lockPath);
|
|
104
132
|
if (!info) {
|
|
105
133
|
try {
|
|
106
134
|
const stats = await fs.stat(lockPath);
|
|
107
|
-
|
|
135
|
+
if (Date.now() - stats.mtimeMs <= staleMs) return { stale: false };
|
|
136
|
+
return { stale: true, owner: null, stat: statToken(stats) };
|
|
108
137
|
} catch (err) {
|
|
109
|
-
if (isEnoent(err)) return false;
|
|
138
|
+
if (isEnoent(err)) return { stale: false };
|
|
110
139
|
throw err;
|
|
111
140
|
}
|
|
112
141
|
}
|
|
@@ -115,53 +144,63 @@ async function isLockStale(lockPath: string, staleMs: number): Promise<boolean>
|
|
|
115
144
|
// not have its lock stolen (#652). Reclaim a dead owner immediately. Only when owner
|
|
116
145
|
// liveness is indeterminate do we fall back to the staleMs elapsed-time heuristic.
|
|
117
146
|
const liveness = ownerLiveness(info.pid);
|
|
118
|
-
if (liveness === "
|
|
119
|
-
if (liveness === "
|
|
120
|
-
|
|
147
|
+
if (liveness === "alive") return { stale: false };
|
|
148
|
+
if (liveness === "dead" || Date.now() - info.timestamp > staleMs) {
|
|
149
|
+
return { stale: true, owner: { pid: info.pid, timestamp: info.timestamp } };
|
|
150
|
+
}
|
|
151
|
+
return { stale: false };
|
|
121
152
|
}
|
|
122
153
|
|
|
123
|
-
async function
|
|
154
|
+
async function removeStaleLockForAcquire(lockPath: string, snapshot: LockStaleSnapshot): Promise<boolean> {
|
|
155
|
+
if (!snapshot.stale) return false;
|
|
156
|
+
if (snapshot.owner) {
|
|
157
|
+
return (await removeFileLockDirForGc(lockPath, snapshot.owner)) === "removed";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const currentInfo = await readLockInfo(lockPath);
|
|
161
|
+
if (currentInfo) return false;
|
|
124
162
|
try {
|
|
125
|
-
await fs.
|
|
126
|
-
|
|
163
|
+
const currentStats = await fs.stat(lockPath);
|
|
164
|
+
if (!sameStatToken(statToken(currentStats), snapshot.stat)) return false;
|
|
165
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
127
166
|
return true;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (isEnoent(err)) return false;
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function tryAcquireLock(lockPath: string): Promise<LockInfo | null> {
|
|
174
|
+
try {
|
|
175
|
+
await fs.mkdir(lockPath);
|
|
176
|
+
return await writeLockInfo(lockPath);
|
|
128
177
|
} catch (error) {
|
|
129
178
|
if ((error as NodeJS.ErrnoException).code === "EEXIST") {
|
|
130
|
-
return
|
|
179
|
+
return null;
|
|
131
180
|
}
|
|
132
181
|
throw error;
|
|
133
182
|
}
|
|
134
183
|
}
|
|
135
184
|
|
|
136
|
-
async function releaseLock(lockPath: string): Promise<void> {
|
|
185
|
+
async function releaseLock(lockPath: string, owner: FileLockOwnerToken): Promise<void> {
|
|
137
186
|
try {
|
|
138
|
-
await
|
|
187
|
+
await removeFileLockDirForGc(lockPath, owner);
|
|
139
188
|
} catch {
|
|
140
189
|
// Ignore errors on release
|
|
141
190
|
}
|
|
142
191
|
}
|
|
143
|
-
|
|
144
|
-
async function lockExists(lockPath: string): Promise<boolean> {
|
|
145
|
-
try {
|
|
146
|
-
await fs.stat(lockPath);
|
|
147
|
-
return true;
|
|
148
|
-
} catch (err) {
|
|
149
|
-
if (isEnoent(err)) return false;
|
|
150
|
-
throw err;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
192
|
async function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<() => Promise<void>> {
|
|
155
193
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
156
194
|
const lockPath = getLockPath(filePath);
|
|
157
195
|
|
|
158
196
|
for (let attempt = 0; attempt < opts.retries; attempt++) {
|
|
159
|
-
|
|
160
|
-
|
|
197
|
+
const owner = await tryAcquireLock(lockPath);
|
|
198
|
+
if (owner) {
|
|
199
|
+
return () => releaseLock(lockPath, owner);
|
|
161
200
|
}
|
|
162
201
|
|
|
163
|
-
|
|
164
|
-
|
|
202
|
+
const stale = await staleLockSnapshot(lockPath, opts.staleMs);
|
|
203
|
+
if (await removeStaleLockForAcquire(lockPath, stale)) {
|
|
165
204
|
continue;
|
|
166
205
|
}
|
|
167
206
|
|
|
@@ -61,7 +61,7 @@ declare module "@sayknow-cli/tui" {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function defaultMessageQueueKeysForPlatform(platform: NodeJS.Platform = process.platform): KeyId {
|
|
64
|
-
return platform === "win32" ? "alt+q" : "alt+enter";
|
|
64
|
+
return platform === "win32" || platform === "darwin" ? "alt+q" : "alt+enter";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -134,8 +134,8 @@ export const KEYBINDINGS = {
|
|
|
134
134
|
description: "Queue message for next turn",
|
|
135
135
|
},
|
|
136
136
|
"app.message.dequeue": {
|
|
137
|
-
defaultKeys: "alt+up",
|
|
138
|
-
description: "
|
|
137
|
+
defaultKeys: ["alt+up", "alt+down"],
|
|
138
|
+
description: "Select queued message to edit",
|
|
139
139
|
},
|
|
140
140
|
"app.clipboard.pasteImage": {
|
|
141
141
|
defaultKeys: process.platform === "win32" ? "alt+v" : "ctrl+v",
|
|
@@ -76,6 +76,34 @@ export interface MaterializeModelProfileAssignmentOptions {
|
|
|
76
76
|
selector: string;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export interface MaterializeModelProfileAssignmentsOptions {
|
|
80
|
+
session: Pick<
|
|
81
|
+
ModelProfileActivationSession,
|
|
82
|
+
"model" | "thinkingLevel" | "setActiveModelProfile" | "getActiveModelProfile"
|
|
83
|
+
>;
|
|
84
|
+
settings: Pick<Settings, "clearOverride" | "get" | "override" | "set">;
|
|
85
|
+
assignments: ReadonlyMap<SkcModelAssignmentTargetId, string> | Partial<Record<SkcModelAssignmentTargetId, string>>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isReadonlyAssignmentMap(
|
|
89
|
+
assignments: ReadonlyMap<SkcModelAssignmentTargetId, string> | Partial<Record<SkcModelAssignmentTargetId, string>>,
|
|
90
|
+
): assignments is ReadonlyMap<SkcModelAssignmentTargetId, string> {
|
|
91
|
+
return typeof (assignments as { entries?: unknown }).entries === "function";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getMaterializedAssignments(
|
|
95
|
+
assignments: ReadonlyMap<SkcModelAssignmentTargetId, string> | Partial<Record<SkcModelAssignmentTargetId, string>>,
|
|
96
|
+
): Array<[SkcModelAssignmentTargetId, string]> {
|
|
97
|
+
if (isReadonlyAssignmentMap(assignments)) return [...assignments.entries()];
|
|
98
|
+
const assignmentRecord: Partial<Record<SkcModelAssignmentTargetId, string>> = assignments;
|
|
99
|
+
const result: Array<[SkcModelAssignmentTargetId, string]> = [];
|
|
100
|
+
for (const role of Object.keys(assignmentRecord) as SkcModelAssignmentTargetId[]) {
|
|
101
|
+
const selector = assignmentRecord[role];
|
|
102
|
+
if (selector !== undefined) result.push([role, selector]);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
export function materializeActiveModelProfileAssignment(options: MaterializeModelProfileAssignmentOptions): boolean {
|
|
80
108
|
const activeProfile = options.session.getActiveModelProfile?.() ?? options.settings.get("modelProfile.default");
|
|
81
109
|
if (!activeProfile) return false;
|
|
@@ -109,6 +137,43 @@ export function materializeActiveModelProfileAssignment(options: MaterializeMode
|
|
|
109
137
|
return true;
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
export function materializeActiveModelProfileAssignments(options: MaterializeModelProfileAssignmentsOptions): boolean {
|
|
141
|
+
const activeProfile = options.session.getActiveModelProfile?.() ?? options.settings.get("modelProfile.default");
|
|
142
|
+
if (!activeProfile) return false;
|
|
143
|
+
|
|
144
|
+
const materializedAssignments = getMaterializedAssignments(options.assignments);
|
|
145
|
+
if (materializedAssignments.length === 0) return true;
|
|
146
|
+
|
|
147
|
+
const nextModelRoles = { ...options.settings.get("modelRoles") };
|
|
148
|
+
const nextAgentModelOverrides = { ...options.settings.get("task.agentModelOverrides") };
|
|
149
|
+
const includesDefault = materializedAssignments.some(([role]) => role === "default");
|
|
150
|
+
|
|
151
|
+
if (!includesDefault && !nextModelRoles.default && options.session.model) {
|
|
152
|
+
nextModelRoles.default = formatModelSelectorValue(
|
|
153
|
+
`${options.session.model.provider}/${options.session.model.id}`,
|
|
154
|
+
options.session.thinkingLevel,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const [role, selector] of materializedAssignments) {
|
|
159
|
+
const target = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
160
|
+
if (target.settingsPath === "modelRoles") {
|
|
161
|
+
nextModelRoles[role] = selector;
|
|
162
|
+
} else {
|
|
163
|
+
nextAgentModelOverrides[role] = selector;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
options.settings.set("modelRoles", nextModelRoles);
|
|
168
|
+
options.settings.set("task.agentModelOverrides", nextAgentModelOverrides);
|
|
169
|
+
options.settings.set("modelProfile.default", undefined);
|
|
170
|
+
options.settings.clearOverride("modelProfile.default");
|
|
171
|
+
options.settings.override("modelRoles", nextModelRoles);
|
|
172
|
+
options.settings.override("task.agentModelOverrides", nextAgentModelOverrides);
|
|
173
|
+
options.session.setActiveModelProfile?.(undefined);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
112
177
|
export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
|
|
113
178
|
return `Model profile "${profileName}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
|
|
114
179
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeText } from "@sayknow-cli/utils";
|
|
1
2
|
import type { SkcModelAssignmentTargetId } from "./model-registry";
|
|
2
3
|
import type { ModelsConfig } from "./models-config-schema";
|
|
3
4
|
|
|
@@ -98,11 +99,18 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
98
99
|
}),
|
|
99
100
|
profile("claude-opus", ["anthropic"], {
|
|
100
101
|
default: "anthropic/claude-opus-4-8:xhigh",
|
|
101
|
-
executor: "anthropic/claude-sonnet-
|
|
102
|
+
executor: "anthropic/claude-sonnet-5",
|
|
102
103
|
planner: "anthropic/claude-opus-4-8:low",
|
|
103
104
|
critic: "anthropic/claude-opus-4-8:high",
|
|
104
105
|
architect: "anthropic/claude-opus-4-8:xhigh",
|
|
105
106
|
}),
|
|
107
|
+
profile("claude-fable", ["anthropic"], {
|
|
108
|
+
default: "anthropic/claude-fable-5:xhigh",
|
|
109
|
+
executor: "anthropic/claude-sonnet-5",
|
|
110
|
+
planner: "anthropic/claude-fable-5:low",
|
|
111
|
+
critic: "anthropic/claude-fable-5:high",
|
|
112
|
+
architect: "anthropic/claude-fable-5:xhigh",
|
|
113
|
+
}),
|
|
106
114
|
profile("glm-eco", ["zai"], {
|
|
107
115
|
default: "zai/glm-5.2:low",
|
|
108
116
|
executor: "zai/glm-5.2:minimal",
|
|
@@ -260,6 +268,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
260
268
|
critic: "opencode-go/mimo-v2.5-pro",
|
|
261
269
|
architect: "openai-codex/gpt-5.5:xhigh",
|
|
262
270
|
}),
|
|
271
|
+
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
272
|
+
default: "anthropic/claude-fable-5:high",
|
|
273
|
+
executor: "openai-codex/gpt-5.5:high",
|
|
274
|
+
planner: "anthropic/claude-opus-4-8:medium",
|
|
275
|
+
critic: "anthropic/claude-opus-4-8:high",
|
|
276
|
+
architect: "openai-codex/gpt-5.5:xhigh",
|
|
277
|
+
}),
|
|
263
278
|
];
|
|
264
279
|
|
|
265
280
|
export interface ModelProfilePresentation {
|
|
@@ -267,12 +282,17 @@ export interface ModelProfilePresentation {
|
|
|
267
282
|
providerGroup: string;
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
function sanitizeModelProfileLabel(value: string): string {
|
|
286
|
+
return sanitizeText(value).replace(/\s+/g, " ").trim();
|
|
287
|
+
}
|
|
288
|
+
|
|
270
289
|
const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
271
290
|
"codex-eco": { displayName: "Codex Eco", providerGroup: "CODEX" },
|
|
272
291
|
"codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
|
|
273
292
|
"codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
|
|
274
293
|
opencodego: { displayName: "OpenCodeGo", providerGroup: "OPENCODEGO" },
|
|
275
294
|
"claude-opus": { displayName: "Claude Opus", providerGroup: "CLAUDE" },
|
|
295
|
+
"claude-fable": { displayName: "Claude Fable", providerGroup: "CLAUDE" },
|
|
276
296
|
"glm-eco": { displayName: "GLM Eco", providerGroup: "GLM" },
|
|
277
297
|
"glm-medium": { displayName: "GLM Medium", providerGroup: "GLM" },
|
|
278
298
|
"glm-pro": { displayName: "GLM Pro", providerGroup: "GLM" },
|
|
@@ -294,6 +314,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
294
314
|
"minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
|
|
295
315
|
"opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
|
|
296
316
|
"codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
|
|
317
|
+
"fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
|
|
297
318
|
};
|
|
298
319
|
|
|
299
320
|
const PROFILE_GROUP_ORDER = [
|
|
@@ -332,7 +353,15 @@ export function getModelProfilePresentation(
|
|
|
332
353
|
const displayName = typeof profile === "string" ? undefined : profile.displayName;
|
|
333
354
|
const presentation = PROFILE_PRESENTATION[name];
|
|
334
355
|
if (presentation) return presentation;
|
|
335
|
-
return { displayName:
|
|
356
|
+
return { displayName: formatModelProfileDisplayLabel({ name, displayName }), providerGroup: "CUSTOM" };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function formatModelProfileDisplayLabel(profile: Pick<ModelProfileDefinition, "name" | "displayName">): string {
|
|
360
|
+
return (
|
|
361
|
+
sanitizeModelProfileLabel(profile.displayName ?? profile.name) ||
|
|
362
|
+
sanitizeModelProfileLabel(profile.name) ||
|
|
363
|
+
"Unnamed profile"
|
|
364
|
+
);
|
|
336
365
|
}
|
|
337
366
|
|
|
338
367
|
export function groupModelProfilesForPresetLanding(
|