@iowarp/clio-coder 0.3.6 → 0.3.7
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 +28 -0
- package/README.md +16 -5
- package/dist/{acp-2BEHC4DL.js → acp-SK4MD6MM.js} +10 -10
- package/dist/{agents-LNNFTM53.js → agents-2FN2K6ME.js} +30 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-QIYZWM5I.js} +13 -13
- package/dist/{chunk-KHSFENX2.js → chunk-3HAPLH5M.js} +10 -10
- package/dist/{chunk-24I7BN55.js → chunk-465YSENW.js} +2 -2
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CYQKWTG3.js → chunk-4DWFMQDR.js} +4 -4
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-K7T3E2SR.js → chunk-5UJ6ECTS.js} +10 -9
- package/dist/{chunk-6US73PDB.js → chunk-6M7VS3J3.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-6TUKSZVF.js} +5 -5
- package/dist/{chunk-5JGRAMKL.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-R46L2BIR.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-XXQNGV4M.js → chunk-CEYBNUGC.js} +243 -63
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-DMD2AGVS.js} +5 -4
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-XF5N4U5A.js → chunk-EBEFWSGL.js} +6 -5
- package/dist/{chunk-EYPA3EGJ.js → chunk-EELBMBT6.js} +120 -13
- package/dist/{chunk-CKXWIANG.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-LYF7OHWH.js → chunk-J7PIKKWC.js} +8 -463
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-6XXKFVSN.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-QNQHSOLF.js → chunk-LADCF22A.js} +12 -12
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-XYDYPRZI.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-G7MUEIGA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-EKY57CSP.js → chunk-OBMAI2DP.js} +61 -767
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ZRGEBJ4T.js → chunk-QCTRSGHQ.js} +21 -21
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QM3F2GKX.js → chunk-UUANF5CR.js} +2247 -2096
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-DJVECN66.js → chunk-VQNODYQ4.js} +14 -14
- package/dist/{chunk-3BPUFZDL.js → chunk-VREKEFLL.js} +3 -3
- package/dist/{chunk-PBTHKCPN.js → chunk-WJHBC77E.js} +6 -6
- package/dist/{chunk-XE2VEJHX.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-ZXF4XRKW.js → chunk-XEGB6BCN.js} +157 -7
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-NCBTHHN2.js → chunk-YTYFXUI3.js} +65 -322
- package/dist/{chunk-OH3TOQTB.js → chunk-ZGH7FGS5.js} +13 -7
- package/dist/cli/index.js +32 -30
- package/dist/{clio-M2KGYUFZ.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +3 -3
- package/dist/{components-5TTYYX6G.js → components-F7OEATSO.js} +4 -4
- package/dist/{config-XUUYQIWO.js → config-TRBL3RCF.js} +34 -29
- package/dist/{configure-IHJ7YOMV.js → configure-OLCVPHNM.js} +15 -15
- package/dist/{context-74JLXAWD.js → context-MJIJ6GOX.js} +11 -11
- package/dist/{context-ZQ7SIFJV.js → context-WFPKQSM6.js} +19 -3
- package/dist/{context-75MIWW3U.js → context-XEWE3MOJ.js} +31 -26
- package/dist/{context-clear-GYKWNUML.js → context-clear-KNOS2JPB.js} +31 -26
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-EUXAZI6N.js} +8 -8
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-B7MTOVKL.js} +313 -53
- package/dist/{docs-6FZSCG5B.js → docs-FLJTIDSE.js} +4 -4
- package/dist/{doctor-SVJ5BZCW.js → doctor-RN4YKO2X.js} +14 -14
- package/dist/{eval-CG6LLBLD.js → eval-RUBJVSNQ.js} +8 -7
- package/dist/{evidence-ZYFIEN42.js → evidence-JZNBUOQZ.js} +30 -25
- package/dist/{evolve-QGEXEMDW.js → evolve-FJVC4KKI.js} +30 -25
- package/dist/{extensions-ADGNCJJD.js → extensions-IQL36S7K.js} +4 -4
- package/dist/{fleet-S5R4ZOQY.js → fleet-BDKYJFCP.js} +214 -360
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-5DRU55YR.js → init-LQUB5COQ.js} +44 -37
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-OG6HOYKM.js} +31 -26
- package/dist/{models-ZPOLRU2C.js → models-5ZG5XY7J.js} +21 -20
- package/dist/{monitor-US5F5YGZ.js → monitor-TJ7AMTGB.js} +49 -30
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-WZYB54DM.js} +3827 -581
- package/dist/{paths-E7KYAQWE.js → paths-XUC7GS6E.js} +4 -4
- package/dist/{reset-KZ652EK6.js → reset-PXQT45IY.js} +7 -7
- package/dist/{run-SRNBKDWD.js → run-FQ74YF62.js} +53 -45
- package/dist/{share-CGZE33UP.js → share-FW7SVCL3.js} +33 -9
- package/dist/{skills-S2X4DLY5.js → skills-7E7IRB3R.js} +24 -8
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-LI75W6OK.js} +34 -27
- package/dist/{targets-54SWINWB.js → targets-4CIFKCTW.js} +23 -22
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WUZY7ZV5.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-7FV7IP4E.js} +4 -4
- package/dist/{upgrade-JKAR27XC.js → upgrade-K2HVIVMQ.js} +20 -19
- package/dist/{usage-MSAWCLX4.js → usage-GTZELZQX.js} +116 -49
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-ASIFASCN.js} +45 -37
- package/dist/worker/entry.js +38 -35
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +82 -2
- package/docs/configuration-and-targets.md +79 -2
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +5 -5
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +197 -10
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +79 -64
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/usage.ts +111 -19
- package/src/core/bus-events.ts +4 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/result-contract.ts +235 -1
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +34 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +258 -9
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/receipt-integrity.ts +8 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +51 -6
- package/src/domains/dispatch/validation.ts +66 -6
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +260 -16
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +101 -4
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +432 -5
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +7 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +57 -1
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +16 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +2 -1
- package/src/worker/spec-contract.ts +1 -0
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resource library's install confirmation, as an overlay.
|
|
3
|
+
*
|
|
4
|
+
* `clio-coder library add` prints every destination and SHA-256 hash and writes
|
|
5
|
+
* nothing until `--yes` is present. This overlay is that same gate for the
|
|
6
|
+
* Skills Hub: it states every path the install would write and the hash it
|
|
7
|
+
* would write there, and nothing reaches disk until the operator presses Enter.
|
|
8
|
+
* Esc leaves the plan unexecuted, so a cancelled confirmation is a run in which
|
|
9
|
+
* no file changed.
|
|
10
|
+
*
|
|
11
|
+
* The body is a pure function of the subject so the wording is testable without
|
|
12
|
+
* a TUI.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type Component,
|
|
17
|
+
isKeyRelease,
|
|
18
|
+
matchesKey,
|
|
19
|
+
type OverlayHandle,
|
|
20
|
+
type TUI,
|
|
21
|
+
truncateToWidth,
|
|
22
|
+
wrapTextWithAnsi,
|
|
23
|
+
} from "../../engine/tui.js";
|
|
24
|
+
import { buildResponsiveHint, FocusBox, showClioOverlayFrame } from "../overlay-frame.js";
|
|
25
|
+
import { clioTheme, rule } from "../theme/index.js";
|
|
26
|
+
|
|
27
|
+
export const LIBRARY_INSTALL_CONFIRM_TITLE = "Library install";
|
|
28
|
+
|
|
29
|
+
const MIN_WIDTH = 48;
|
|
30
|
+
const MAX_WIDTH = 110;
|
|
31
|
+
|
|
32
|
+
/** One planned write, in the terms `library add` reports it. */
|
|
33
|
+
export interface LibraryInstallWrite {
|
|
34
|
+
ref: string;
|
|
35
|
+
path: string;
|
|
36
|
+
sha256: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface LibraryInstallConfirmSubject {
|
|
40
|
+
/** The typed reference the operator asked for, e.g. `fleet:release`. */
|
|
41
|
+
entryRef: string;
|
|
42
|
+
/** Every write, in dependency order, the requested entry last. */
|
|
43
|
+
writes: ReadonlyArray<LibraryInstallWrite>;
|
|
44
|
+
/** Requirements this confirmation would install alongside the entry. */
|
|
45
|
+
requirements: ReadonlyArray<string>;
|
|
46
|
+
/** Requirements already on disk, named so the plan accounts for all of them. */
|
|
47
|
+
satisfied: ReadonlyArray<string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface OpenLibraryInstallConfirmOverlayOptions {
|
|
51
|
+
subject: LibraryInstallConfirmSubject;
|
|
52
|
+
/** Live terminal width, so the box tracks the window it opened in. */
|
|
53
|
+
columns: number;
|
|
54
|
+
/** Enter: perform exactly the writes this body named. */
|
|
55
|
+
onAccept: () => void;
|
|
56
|
+
/** Esc: nothing is written. */
|
|
57
|
+
onCancel: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function confirmOverlayWidth(columns: number): number {
|
|
61
|
+
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, columns - 4));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Render the plan: what is being installed, alongside what, and to where. */
|
|
65
|
+
export function formatLibraryInstallConfirmBody(subject: LibraryInstallConfirmSubject, width: number): string[] {
|
|
66
|
+
const theme = clioTheme();
|
|
67
|
+
const contentWidth = Math.max(1, Math.floor(width));
|
|
68
|
+
const rows: string[] = [];
|
|
69
|
+
|
|
70
|
+
const headline =
|
|
71
|
+
subject.requirements.length > 0
|
|
72
|
+
? `install ${subject.entryRef} with its requirements: ${subject.requirements.join(", ")}`
|
|
73
|
+
: `install ${subject.entryRef}`;
|
|
74
|
+
for (const line of wrapTextWithAnsi(theme.fg("accent", headline), contentWidth)) rows.push(line);
|
|
75
|
+
rows.push(rule(theme, contentWidth));
|
|
76
|
+
|
|
77
|
+
for (const write of subject.writes) {
|
|
78
|
+
rows.push(theme.fg("muted", truncateToWidth(`${write.ref} → ${write.path}`, contentWidth, "…", false)));
|
|
79
|
+
rows.push(theme.fg("dim", truncateToWidth(` sha256 ${write.sha256}`, contentWidth, "…", false)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
rows.push(rule(theme, contentWidth));
|
|
83
|
+
const satisfied = subject.satisfied.length > 0 ? subject.satisfied.join(", ") : "none";
|
|
84
|
+
for (const line of wrapTextWithAnsi(theme.fg("info", `satisfied requirements: ${satisfied}`), contentWidth)) {
|
|
85
|
+
rows.push(line);
|
|
86
|
+
}
|
|
87
|
+
for (const line of wrapTextWithAnsi(
|
|
88
|
+
theme.fg("dim", `${subject.writes.length} file or files are written only after Enter; Esc writes nothing`),
|
|
89
|
+
contentWidth,
|
|
90
|
+
)) {
|
|
91
|
+
rows.push(line);
|
|
92
|
+
}
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class LibraryInstallConfirmBody implements Component {
|
|
97
|
+
constructor(private readonly subject: LibraryInstallConfirmSubject) {}
|
|
98
|
+
|
|
99
|
+
render(width: number): string[] {
|
|
100
|
+
return formatLibraryInstallConfirmBody(this.subject, width);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
invalidate(): void {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function openLibraryInstallConfirmOverlay(
|
|
107
|
+
tui: TUI,
|
|
108
|
+
options: OpenLibraryInstallConfirmOverlayOptions,
|
|
109
|
+
): OverlayHandle {
|
|
110
|
+
let settled = false;
|
|
111
|
+
|
|
112
|
+
const accept = (): void => {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
settled = true;
|
|
115
|
+
options.onAccept();
|
|
116
|
+
};
|
|
117
|
+
const cancel = (): void => {
|
|
118
|
+
if (settled) return;
|
|
119
|
+
settled = true;
|
|
120
|
+
options.onCancel();
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const focus = new FocusBox(new LibraryInstallConfirmBody(options.subject), {
|
|
124
|
+
// Keys are matched by name, never by raw bytes: under the kitty keyboard
|
|
125
|
+
// protocol Esc arrives as CSI 27 u, and a byte comparison against "\x1b"
|
|
126
|
+
// left the overlay unanswerable. Everything unmatched is swallowed.
|
|
127
|
+
onInput: (data: string): void => {
|
|
128
|
+
if (settled || isKeyRelease(data)) return;
|
|
129
|
+
if (matchesKey(data, "enter")) {
|
|
130
|
+
accept();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (matchesKey(data, "escape")) cancel();
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const handle = showClioOverlayFrame(tui, focus, {
|
|
138
|
+
anchor: "center",
|
|
139
|
+
width: confirmOverlayWidth(options.columns),
|
|
140
|
+
title: LIBRARY_INSTALL_CONFIRM_TITLE,
|
|
141
|
+
footerHint: buildResponsiveHint([{ key: "Enter", verb: "install" }], { key: "Esc", verb: "cancel" }),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...handle,
|
|
146
|
+
hide(): void {
|
|
147
|
+
cancel();
|
|
148
|
+
handle.hide();
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -32,9 +32,34 @@ export interface ListOverlayItem {
|
|
|
32
32
|
detail?: (width: number) => string[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* One tab of a tabbed list overlay.
|
|
37
|
+
*
|
|
38
|
+
* A tab owns its own row set and rebuilds it on demand, so switching tabs
|
|
39
|
+
* re-reads whatever the rows describe rather than serving a snapshot taken when
|
|
40
|
+
* the overlay opened. The counts drawn in the tab bar come from the same call,
|
|
41
|
+
* which is why an install that lands on one tab corrects every tab's count.
|
|
42
|
+
*/
|
|
43
|
+
export interface ListOverlayTab {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
items: () => ReadonlyArray<ListOverlayItem>;
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
export interface ListOverlayOptions {
|
|
36
50
|
title: string;
|
|
37
51
|
items: ReadonlyArray<ListOverlayItem>;
|
|
52
|
+
/**
|
|
53
|
+
* Tabs, drawn as a bar above the filter row and switched with ←/→, which is
|
|
54
|
+
* the key vocabulary the Settings Center already uses to move between
|
|
55
|
+
* sections. Omitted leaves the overlay untabbed and every tab code path
|
|
56
|
+
* inert.
|
|
57
|
+
*/
|
|
58
|
+
tabs?: ReadonlyArray<ListOverlayTab>;
|
|
59
|
+
/** Tab the overlay opens on; defaults to the first. */
|
|
60
|
+
activeTabId?: string;
|
|
61
|
+
/** Notified after ←/→ changes the active tab. */
|
|
62
|
+
onTabChange?: (tabId: string) => void;
|
|
38
63
|
/** Enables the type-to-filter input row. */
|
|
39
64
|
filterable?: boolean;
|
|
40
65
|
/** Pre-applied filter text (e.g. /skill <query> in milestone 04). */
|
|
@@ -91,6 +116,10 @@ export class ListOverlayView implements Component {
|
|
|
91
116
|
private renderMemo: { key: string; lines: string[] } | null = null;
|
|
92
117
|
/** Bumped on every keystroke routed into the filter input; part of the memo key. */
|
|
93
118
|
private inputEpoch = 0;
|
|
119
|
+
/** Active tab id, or the empty string on an untabbed overlay. */
|
|
120
|
+
private activeTabId = "";
|
|
121
|
+
/** Row count per tab id, refreshed whenever the tab set is rebuilt. */
|
|
122
|
+
private tabCounts = new Map<string, number>();
|
|
94
123
|
|
|
95
124
|
constructor(
|
|
96
125
|
private readonly options: ListOverlayOptions,
|
|
@@ -104,6 +133,89 @@ export class ListOverlayView implements Component {
|
|
|
104
133
|
if (options.initialFilter) {
|
|
105
134
|
this.input.setValue(options.initialFilter);
|
|
106
135
|
}
|
|
136
|
+
const tabs = options.tabs;
|
|
137
|
+
if (tabs && tabs.length > 0) {
|
|
138
|
+
const requested = tabs.find((tab) => tab.id === options.activeTabId);
|
|
139
|
+
this.activeTabId = (requested ?? tabs[0])?.id ?? "";
|
|
140
|
+
this.refreshTabs();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The tab the overlay is on, or undefined when it is untabbed. */
|
|
145
|
+
activeTab(): ListOverlayTab | undefined {
|
|
146
|
+
return this.options.tabs?.find((tab) => tab.id === this.activeTabId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Rebuild every tab's rows, adopt the active tab's, and record each count.
|
|
151
|
+
*
|
|
152
|
+
* Every tab is rebuilt rather than only the active one because the tab bar
|
|
153
|
+
* states each tab's count, and an install performed on one tab can satisfy a
|
|
154
|
+
* requirement another tab reports. A stale count there is the same lie a
|
|
155
|
+
* stale row is.
|
|
156
|
+
*/
|
|
157
|
+
refreshTabs(): void {
|
|
158
|
+
const tabs = this.options.tabs;
|
|
159
|
+
if (!tabs || tabs.length === 0) return;
|
|
160
|
+
const counts = new Map<string, number>();
|
|
161
|
+
let activeItems: ReadonlyArray<ListOverlayItem> = [];
|
|
162
|
+
for (const tab of tabs) {
|
|
163
|
+
const rows = tab.items();
|
|
164
|
+
counts.set(tab.id, rows.length);
|
|
165
|
+
if (tab.id === this.activeTabId) activeItems = rows;
|
|
166
|
+
}
|
|
167
|
+
this.tabCounts = counts;
|
|
168
|
+
this.setItems(activeItems);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Switch tabs and rebuild. A tab id this overlay does not carry is ignored. */
|
|
172
|
+
setActiveTab(tabId: string): void {
|
|
173
|
+
const tabs = this.options.tabs;
|
|
174
|
+
if (!tabs?.some((tab) => tab.id === tabId)) return;
|
|
175
|
+
this.activeTabId = tabId;
|
|
176
|
+
this.refreshTabs();
|
|
177
|
+
// After the rows, not before: setItems re-anchors the cursor on the id it
|
|
178
|
+
// was on, and on a tab switch that id belongs to a list the operator has
|
|
179
|
+
// left. A new tab starts at its first row.
|
|
180
|
+
this.selectedIndex = 0;
|
|
181
|
+
this.listScrollOffset = 0;
|
|
182
|
+
this.detailScrollOffset = 0;
|
|
183
|
+
this.renderMemo = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The frame title. A tabbed overlay names the tab it is on, so the border
|
|
188
|
+
* and the rows below it describe the same thing.
|
|
189
|
+
*/
|
|
190
|
+
title(): string {
|
|
191
|
+
const tab = this.activeTab();
|
|
192
|
+
return tab === undefined ? this.options.title : `${this.options.title} · ${tab.label}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private stepTab(delta: number): boolean {
|
|
196
|
+
const tabs = this.options.tabs;
|
|
197
|
+
if (!tabs || tabs.length === 0) return false;
|
|
198
|
+
const current = tabs.findIndex((tab) => tab.id === this.activeTabId);
|
|
199
|
+
const next = tabs[((((current === -1 ? 0 : current) + delta) % tabs.length) + tabs.length) % tabs.length];
|
|
200
|
+
if (!next || next.id === this.activeTabId) return false;
|
|
201
|
+
this.setActiveTab(next.id);
|
|
202
|
+
this.options.onTabChange?.(next.id);
|
|
203
|
+
this.onChange();
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The tab bar: every tab with its count, the active one in the accent token. */
|
|
208
|
+
private renderTabBar(width: number): string[] {
|
|
209
|
+
const tabs = this.options.tabs;
|
|
210
|
+
if (!tabs || tabs.length === 0) return [];
|
|
211
|
+
const theme = clioTheme();
|
|
212
|
+
const parts = tabs.map((tab) => {
|
|
213
|
+
const text = `${tab.label} ${this.tabCounts.get(tab.id) ?? 0}`;
|
|
214
|
+
return tab.id === this.activeTabId
|
|
215
|
+
? theme.style("accent", `${GLYPH.cursor} ${text}`, { bold: true })
|
|
216
|
+
: theme.fg("dim", ` ${text}`);
|
|
217
|
+
});
|
|
218
|
+
return [this.padLine(parts.join(theme.fg("frame", " │ ")), width)];
|
|
107
219
|
}
|
|
108
220
|
|
|
109
221
|
/**
|
|
@@ -170,12 +282,27 @@ export class ListOverlayView implements Component {
|
|
|
170
282
|
this.options.onClose();
|
|
171
283
|
}
|
|
172
284
|
|
|
285
|
+
/**
|
|
286
|
+
* The tab entry a tabbed footer carries. The verb names the active tab and
|
|
287
|
+
* how many rows it holds, so the count a reader sees is the count of the tab
|
|
288
|
+
* they are on rather than of the overlay as a whole.
|
|
289
|
+
*/
|
|
290
|
+
private tabHintEntry(): HintEntry | null {
|
|
291
|
+
const tab = this.activeTab();
|
|
292
|
+
if (tab === undefined) return null;
|
|
293
|
+
const count = this.tabCounts.get(tab.id) ?? this.items.length;
|
|
294
|
+
return { key: "←→", verb: `tab · ${count} ${tab.label.toLowerCase()}`, short: "tab", critical: true };
|
|
295
|
+
}
|
|
296
|
+
|
|
173
297
|
getHint(): string {
|
|
298
|
+
const tabEntry = this.tabHintEntry();
|
|
174
299
|
// A list with no rows has no row to select, invoke, or act on. Offering
|
|
175
300
|
// those keys anyway is the same lie the empty state exists to stop telling.
|
|
176
|
-
|
|
301
|
+
// A tab key is the exception: on an empty tab it is the way to a full one.
|
|
302
|
+
if (this.items.length === 0) return buildHint(tabEntry ? [tabEntry] : [], "close");
|
|
177
303
|
|
|
178
304
|
const hintEntries: HintEntry[] = [];
|
|
305
|
+
if (tabEntry) hintEntries.push(tabEntry);
|
|
179
306
|
hintEntries.push({ key: "↑↓", verb: "select" });
|
|
180
307
|
if (this.options.filterable) {
|
|
181
308
|
hintEntries.push(FILTER_HINT);
|
|
@@ -396,11 +523,14 @@ export class ListOverlayView implements Component {
|
|
|
396
523
|
this.detailScrollOffset,
|
|
397
524
|
this.inputEpoch,
|
|
398
525
|
this.itemsEpoch,
|
|
526
|
+
this.activeTabId,
|
|
399
527
|
].join("|");
|
|
400
528
|
if (this.renderMemo?.key === memoKey) return this.renderMemo.lines;
|
|
401
529
|
|
|
402
530
|
const lines: string[] = [];
|
|
403
531
|
|
|
532
|
+
lines.push(...this.renderTabBar(width));
|
|
533
|
+
|
|
404
534
|
if (this.options.filterable) {
|
|
405
535
|
this.input.focused = this.isFilterFocused;
|
|
406
536
|
const inputLines = this.input.render(width);
|
|
@@ -442,6 +572,20 @@ export class ListOverlayView implements Component {
|
|
|
442
572
|
}
|
|
443
573
|
|
|
444
574
|
handleInput(data: string): void {
|
|
575
|
+
// ←/→ switch tabs from either pane, ahead of the filter input, which is
|
|
576
|
+
// the arrangement the Settings Center already uses to move between
|
|
577
|
+
// sections. On an untabbed overlay both keys fall through untouched.
|
|
578
|
+
if (this.options.tabs && this.options.tabs.length > 0) {
|
|
579
|
+
if (matchesKey(data, "left")) {
|
|
580
|
+
this.stepTab(-1);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (matchesKey(data, "right")) {
|
|
584
|
+
this.stepTab(1);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
445
589
|
// PgDn / Ctrl+D and PgUp / Ctrl+U scroll the detail pane, but only
|
|
446
590
|
// while one is visible; otherwise the keys fall through untouched.
|
|
447
591
|
if (this.detailPaneVisible()) {
|
|
@@ -587,6 +731,15 @@ export interface ListOverlayHandle extends OverlayHandle {
|
|
|
587
731
|
* the frame from before its own change until some other key moves the memo key.
|
|
588
732
|
*/
|
|
589
733
|
setItems(items: ReadonlyArray<ListOverlayItem>): void;
|
|
734
|
+
/**
|
|
735
|
+
* Rebuild every tab's rows in place, keeping the active tab and the
|
|
736
|
+
* operator's place in it. Inert on an untabbed overlay.
|
|
737
|
+
*/
|
|
738
|
+
refreshTabs(): void;
|
|
739
|
+
/** Switch tabs programmatically. An id this overlay does not carry is ignored. */
|
|
740
|
+
setActiveTab(tabId: string): void;
|
|
741
|
+
/** The active tab's id, or the empty string on an untabbed overlay. */
|
|
742
|
+
activeTabId(): string;
|
|
590
743
|
}
|
|
591
744
|
|
|
592
745
|
export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOverlayHandle {
|
|
@@ -594,7 +747,9 @@ export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOver
|
|
|
594
747
|
const handle = showClioOverlayFrame(tui, view, {
|
|
595
748
|
anchor: "center",
|
|
596
749
|
width: 100,
|
|
597
|
-
|
|
750
|
+
// A function, not the string: on a tabbed overlay the title names the tab
|
|
751
|
+
// and must be re-read after every switch.
|
|
752
|
+
title: () => view.title(),
|
|
598
753
|
footerHint: () => view.getHint(),
|
|
599
754
|
});
|
|
600
755
|
return Object.assign(handle, {
|
|
@@ -602,5 +757,16 @@ export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOver
|
|
|
602
757
|
view.setItems(items);
|
|
603
758
|
tui.requestRender();
|
|
604
759
|
},
|
|
760
|
+
refreshTabs(): void {
|
|
761
|
+
view.refreshTabs();
|
|
762
|
+
tui.requestRender();
|
|
763
|
+
},
|
|
764
|
+
setActiveTab(tabId: string): void {
|
|
765
|
+
view.setActiveTab(tabId);
|
|
766
|
+
tui.requestRender();
|
|
767
|
+
},
|
|
768
|
+
activeTabId(): string {
|
|
769
|
+
return view.activeTab()?.id ?? "";
|
|
770
|
+
},
|
|
605
771
|
});
|
|
606
772
|
}
|
|
@@ -135,6 +135,7 @@ export const SETTINGS_SECTIONS = [
|
|
|
135
135
|
{ id: "compaction", label: "Compaction", group: "RUNTIME" },
|
|
136
136
|
{ id: "retry", label: "Retry", group: "RUNTIME" },
|
|
137
137
|
{ id: "terminal", label: "Terminal", group: "EXPERIENCE" },
|
|
138
|
+
{ id: "watchdog", label: "Watchdog", group: "EXPERIENCE" },
|
|
138
139
|
{ id: "advanced", label: "Advanced", group: "EXPERIENCE" },
|
|
139
140
|
] as const;
|
|
140
141
|
|
|
@@ -150,6 +151,7 @@ const SETTINGS_SECTION_DESCRIPTIONS = {
|
|
|
150
151
|
compaction: "When and how the context window is summarized under pressure.",
|
|
151
152
|
retry: "Automatic recovery from transient provider and network errors.",
|
|
152
153
|
terminal: "Terminal integration and the Clio color palette.",
|
|
154
|
+
watchdog: "The opt-in turn-end verifier run, where it is routed, and whether it also fires mid-turn.",
|
|
153
155
|
advanced: "Commit provenance, runtime plugins, delegation timeouts, and links to other surfaces.",
|
|
154
156
|
} as const satisfies Record<SettingsSectionId, string>;
|
|
155
157
|
|
|
@@ -199,6 +201,10 @@ export const SETTINGS_LABELS_BY_ID = {
|
|
|
199
201
|
"terminal.tuiMode": "TUI mode",
|
|
200
202
|
"terminal.fullscreenScrollbar": "Fullscreen scrollbar",
|
|
201
203
|
"terminal.smoothStreaming": "Smooth streaming",
|
|
204
|
+
"terminal.notify": "Desktop notifications",
|
|
205
|
+
"watchdog.enabled": "Turn-end watchdog",
|
|
206
|
+
"watchdog.target": "Watchdog target",
|
|
207
|
+
"watchdog.cadenceToolCalls": "Watchdog cadence (tools)",
|
|
202
208
|
theme: "Theme",
|
|
203
209
|
runtimePlugins: "Runtime plugins",
|
|
204
210
|
"compaction.model": "Compaction model",
|
|
@@ -269,8 +275,10 @@ export const SETTINGS_SECTION_ROWS = {
|
|
|
269
275
|
"terminal.tuiMode",
|
|
270
276
|
"terminal.fullscreenScrollbar",
|
|
271
277
|
"terminal.smoothStreaming",
|
|
278
|
+
"terminal.notify",
|
|
272
279
|
"theme",
|
|
273
280
|
],
|
|
281
|
+
watchdog: ["watchdog.enabled", "watchdog.target", "watchdog.cadenceToolCalls"],
|
|
274
282
|
advanced: [
|
|
275
283
|
"runtimePlugins",
|
|
276
284
|
"attribution.gitCommits",
|
|
@@ -329,6 +337,11 @@ const SETTINGS_DESCRIPTIONS_BY_ID = {
|
|
|
329
337
|
"terminal.tuiMode": "Use regular terminal scrollback or a fullscreen transcript with a sticky composer and footer.",
|
|
330
338
|
"terminal.fullscreenScrollbar": "When the draggable transcript scrollbar is visible in fullscreen mode.",
|
|
331
339
|
"terminal.smoothStreaming": "Presentation-only pacing for streamed assistant text and thinking.",
|
|
340
|
+
"terminal.notify":
|
|
341
|
+
"Content-free desktop notification when a turn ends, a detached batch settles, or an approval parks.",
|
|
342
|
+
"watchdog.enabled": "When enabled, a turn that changed the tree is reviewed by one read-only verifier run.",
|
|
343
|
+
"watchdog.target": "Set target to route the run at a cheap local model; blank uses the session's active target.",
|
|
344
|
+
"watchdog.cadenceToolCalls": "Also fire every N tool calls inside a turn; blank fires at turn end only.",
|
|
332
345
|
theme: "Color palette. Clio ships a single tuned palette.",
|
|
333
346
|
runtimePlugins: "npm packages exporting clioRuntimes: RuntimeDescriptor[].",
|
|
334
347
|
"compaction.model": "Dedicated summarization model; blank uses the orchestrator.",
|
|
@@ -362,6 +375,14 @@ const SETTINGS_HELP_BY_ID: Partial<Record<EditableSettingId, string>> = {
|
|
|
362
375
|
"clio-policy gates the agent through Clio's safety net; agent-managed trusts the agent; deny-all blocks every tool.",
|
|
363
376
|
scope: "Choose target-level or exact target/model refs. Alt+J / Alt+K step the chat target through this list.",
|
|
364
377
|
runtimePlugins: "Comma-separated package names, loaded at startup. Restart Clio after changing.",
|
|
378
|
+
"terminal.notify":
|
|
379
|
+
"OSC 777, or OSC 9 on iTerm2, Windows Terminal, and ConEmu. Interactive TTY runs only; the body never carries prompt text, file paths, or model output.",
|
|
380
|
+
"watchdog.enabled":
|
|
381
|
+
"The verifier run is briefed with the turn's coalesced diff and the task board's current scope; its blockers become one transcript notice and nothing else. Headless and ACP runs never fire it.",
|
|
382
|
+
"watchdog.target":
|
|
383
|
+
"A watchdog run costs a worker run per mutating turn, so routing it at a local target keeps the review cheap. Leave blank to reuse whatever the session is already talking to.",
|
|
384
|
+
"watchdog.cadenceToolCalls":
|
|
385
|
+
"Mid-turn firing is how scope drift becomes visible before the turn ends. Leave blank and the watchdog fires at turn end only.",
|
|
365
386
|
keybindings:
|
|
366
387
|
"Renderer controls: Alt+O newest tool or worker details, Ctrl+Alt+O or Alt+Shift+O all of them, Alt+P live tool output, Alt+R latest reasoning, Ctrl+Alt+R or Alt+Shift+R all reasoning. Override these in settings.yaml or use /help.",
|
|
367
388
|
};
|
|
@@ -422,6 +443,14 @@ const SETTINGS_VALUE_HELP_BY_ID: Partial<Record<EditableSettingId, Record<string
|
|
|
422
443
|
auto: "show the scrollbar while scrolling or dragging",
|
|
423
444
|
always: "reserve the rightmost column for the scrollbar",
|
|
424
445
|
},
|
|
446
|
+
"terminal.notify": {
|
|
447
|
+
true: "post a content-free desktop notification on turn end, batch settlement, and a parked approval",
|
|
448
|
+
false: "never post a desktop notification",
|
|
449
|
+
},
|
|
450
|
+
"watchdog.enabled": {
|
|
451
|
+
true: "review every mutating turn with one read-only verifier run",
|
|
452
|
+
false: "no verifier run; a turn ends without a second opinion",
|
|
453
|
+
},
|
|
425
454
|
"terminal.smoothStreaming": {
|
|
426
455
|
off: "preserve the current immediate 16ms-coalesced streaming behavior",
|
|
427
456
|
auto: "pace only on a capable local TTY without accessibility or backpressure risk",
|
|
@@ -478,6 +507,13 @@ export interface SettingsCenterItem extends SettingItem {
|
|
|
478
507
|
help?: string;
|
|
479
508
|
valueHelp?: Record<string, string>;
|
|
480
509
|
defaultValue?: string;
|
|
510
|
+
/**
|
|
511
|
+
* What a free-text editor opens with when it differs from the rendered
|
|
512
|
+
* value. A row that renders its absence as prose such as `(session target)`
|
|
513
|
+
* must not hand that prose to the editor, or the operator's typing lands
|
|
514
|
+
* behind it and the placeholder is written into settings.yaml.
|
|
515
|
+
*/
|
|
516
|
+
editValue?: string;
|
|
481
517
|
}
|
|
482
518
|
|
|
483
519
|
export interface SettingsCenterSection {
|
|
@@ -657,14 +693,19 @@ function editTextSubmenu(title: string, note?: string): SettingSubmenuBuilder {
|
|
|
657
693
|
return textInputSubmenu(title, note);
|
|
658
694
|
}
|
|
659
695
|
|
|
660
|
-
function editNumberSubmenu(title: string): SettingSubmenuBuilder {
|
|
696
|
+
function editNumberSubmenu(title: string, options: { min?: number; allowBlank?: boolean } = {}): SettingSubmenuBuilder {
|
|
697
|
+
const min = options.min ?? 0;
|
|
661
698
|
return (currentValue: string, done: (val?: string) => void) => {
|
|
662
699
|
const input = new Input();
|
|
663
700
|
input.setValue(currentValue);
|
|
664
701
|
input.focused = true;
|
|
665
702
|
input.onSubmit = (val) => {
|
|
703
|
+
if (options.allowBlank === true && val.trim().length === 0) {
|
|
704
|
+
done("");
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
666
707
|
const num = Number(val);
|
|
667
|
-
if (Number.isFinite(num) && num >=
|
|
708
|
+
if (Number.isFinite(num) && num >= min) {
|
|
668
709
|
done(val);
|
|
669
710
|
} else {
|
|
670
711
|
done();
|
|
@@ -675,7 +716,9 @@ function editNumberSubmenu(title: string): SettingSubmenuBuilder {
|
|
|
675
716
|
title,
|
|
676
717
|
input,
|
|
677
718
|
buildHint([{ key: "Enter", verb: "confirm" }], "back"),
|
|
678
|
-
|
|
719
|
+
min > 0
|
|
720
|
+
? `Use a whole number of at least ${min}${options.allowBlank === true ? "; blank clears it" : ""}.`
|
|
721
|
+
: "Use a non-negative number.",
|
|
679
722
|
);
|
|
680
723
|
};
|
|
681
724
|
}
|
|
@@ -1122,6 +1165,7 @@ function settingItem(
|
|
|
1122
1165
|
label?: string;
|
|
1123
1166
|
description?: string;
|
|
1124
1167
|
help?: string;
|
|
1168
|
+
editValue?: string;
|
|
1125
1169
|
},
|
|
1126
1170
|
): SettingsCenterItem {
|
|
1127
1171
|
const item: SettingsCenterItem = {
|
|
@@ -1139,6 +1183,7 @@ function settingItem(
|
|
|
1139
1183
|
};
|
|
1140
1184
|
const help = options.help ?? SETTINGS_HELP_BY_ID[id];
|
|
1141
1185
|
if (help) item.help = help;
|
|
1186
|
+
if (options.editValue !== undefined) item.editValue = options.editValue;
|
|
1142
1187
|
const valueHelp = SETTINGS_VALUE_HELP_BY_ID[id];
|
|
1143
1188
|
if (valueHelp) item.valueHelp = valueHelp;
|
|
1144
1189
|
const def = defaultValueFor(id);
|
|
@@ -1211,6 +1256,7 @@ export function buildSettingItems(
|
|
|
1211
1256
|
const compaction = settings.compaction;
|
|
1212
1257
|
const retry = settings.retry;
|
|
1213
1258
|
const terminal = settings.terminal;
|
|
1259
|
+
const watchdog = settings.watchdog;
|
|
1214
1260
|
const orchestratorThinking = thinkingChoices(
|
|
1215
1261
|
options?.providers,
|
|
1216
1262
|
settings.orchestrator.target,
|
|
@@ -1411,10 +1457,36 @@ export function buildSettingItems(
|
|
|
1411
1457
|
settingItem("terminal.smoothStreaming", terminal.smoothStreaming, {
|
|
1412
1458
|
values: ["off", "auto", "on"],
|
|
1413
1459
|
}),
|
|
1460
|
+
settingItem("terminal.notify", String(terminal.notify), {
|
|
1461
|
+
values: ["false", "true"],
|
|
1462
|
+
}),
|
|
1414
1463
|
settingItem("theme", settings.theme, {
|
|
1415
1464
|
affordance: "single clio-coder palette",
|
|
1416
1465
|
readOnly: true,
|
|
1417
1466
|
}),
|
|
1467
|
+
settingItem("watchdog.enabled", String(watchdog.enabled), {
|
|
1468
|
+
values: ["false", "true"],
|
|
1469
|
+
}),
|
|
1470
|
+
// Both optional keys render their absence rather than a fabricated value:
|
|
1471
|
+
// an unset target means the session's own, and an unset cadence means the
|
|
1472
|
+
// watchdog fires at turn end only. Submitting an empty value clears them.
|
|
1473
|
+
settingItem("watchdog.target", watchdog.target ?? "(session target)", {
|
|
1474
|
+
submenu: editTextSubmenu("Edit watchdog target; blank uses the session's active target"),
|
|
1475
|
+
affordance: "free text",
|
|
1476
|
+
editValue: watchdog.target ?? "",
|
|
1477
|
+
}),
|
|
1478
|
+
settingItem(
|
|
1479
|
+
"watchdog.cadenceToolCalls",
|
|
1480
|
+
watchdog.cadenceToolCalls === undefined ? "(turn end only)" : String(watchdog.cadenceToolCalls),
|
|
1481
|
+
{
|
|
1482
|
+
submenu: editNumberSubmenu("Edit watchdog cadence in tool calls; blank fires at turn end only", {
|
|
1483
|
+
min: 1,
|
|
1484
|
+
allowBlank: true,
|
|
1485
|
+
}),
|
|
1486
|
+
affordance: "free text",
|
|
1487
|
+
editValue: watchdog.cadenceToolCalls === undefined ? "" : String(watchdog.cadenceToolCalls),
|
|
1488
|
+
},
|
|
1489
|
+
),
|
|
1418
1490
|
settingItem("runtimePlugins", settings.runtimePlugins.length > 0 ? settings.runtimePlugins.join(", ") : "(none)", {
|
|
1419
1491
|
submenu: editTextSubmenu("Edit runtime plugins comma-separated list", "Restart Clio to load changes."),
|
|
1420
1492
|
affordance: "free text",
|
|
@@ -1898,6 +1970,31 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
|
|
|
1898
1970
|
settings.terminal.fullscreenScrollbar = value;
|
|
1899
1971
|
}
|
|
1900
1972
|
return;
|
|
1973
|
+
case "terminal.notify":
|
|
1974
|
+
if (value === "true" || value === "false") settings.terminal.notify = value === "true";
|
|
1975
|
+
return;
|
|
1976
|
+
case "watchdog.enabled":
|
|
1977
|
+
if (value === "true" || value === "false") settings.watchdog.enabled = value === "true";
|
|
1978
|
+
return;
|
|
1979
|
+
// Both watchdog options are absent-by-default, and an empty submission is
|
|
1980
|
+
// how the operator says "go back to the default" from a text row. Deleting
|
|
1981
|
+
// the key rather than storing a blank keeps settings.yaml matching what the
|
|
1982
|
+
// config validator accepts.
|
|
1983
|
+
case "watchdog.target": {
|
|
1984
|
+
const trimmed = value.trim();
|
|
1985
|
+
if (trimmed) settings.watchdog.target = trimmed;
|
|
1986
|
+
else delete settings.watchdog.target;
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
case "watchdog.cadenceToolCalls": {
|
|
1990
|
+
if (value.trim().length === 0) {
|
|
1991
|
+
delete settings.watchdog.cadenceToolCalls;
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const parsed = Number(value);
|
|
1995
|
+
if (Number.isFinite(parsed) && Math.floor(parsed) >= 1) settings.watchdog.cadenceToolCalls = Math.floor(parsed);
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1901
1998
|
case "terminal.smoothStreaming":
|
|
1902
1999
|
if (value === "off" || value === "auto" || value === "on") settings.terminal.smoothStreaming = value;
|
|
1903
2000
|
return;
|
|
@@ -2851,7 +2948,7 @@ export class SettingsCenter implements Component {
|
|
|
2851
2948
|
const item = this.selectedItem();
|
|
2852
2949
|
if (!item || item.readOnly) return;
|
|
2853
2950
|
if (item.submenu) {
|
|
2854
|
-
this.submenuComponent = item.submenu(item.currentValue, (selectedValue) => {
|
|
2951
|
+
this.submenuComponent = item.submenu(item.editValue ?? item.currentValue, (selectedValue) => {
|
|
2855
2952
|
this.submenuComponent = null;
|
|
2856
2953
|
if (selectedValue !== undefined) this.prepareScopeConfirm(item, selectedValue);
|
|
2857
2954
|
this.options.requestRender?.();
|