@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
|
@@ -11,9 +11,16 @@ import {
|
|
|
11
11
|
safeResourceWrite,
|
|
12
12
|
} from "../../core/safe-resource-write.js";
|
|
13
13
|
import { clioConfigDir, resolveClioDirs } from "../../core/xdg.js";
|
|
14
|
+
import { parseFrontmatter } from "../agents/frontmatter.js";
|
|
15
|
+
import {
|
|
16
|
+
assertAgentSpecPolicy,
|
|
17
|
+
normalizeAgentSpec,
|
|
18
|
+
parseAgentRecipeSchema,
|
|
19
|
+
parseFleetContract,
|
|
20
|
+
} from "../agents/index.js";
|
|
14
21
|
|
|
15
22
|
export type ShareScope = "project" | "user";
|
|
16
|
-
export type ShareEntryType = "project-context" | "prompt" | "skill" | "settings" | "extension";
|
|
23
|
+
export type ShareEntryType = "project-context" | "prompt" | "skill" | "agent" | "fleet" | "settings" | "extension";
|
|
17
24
|
|
|
18
25
|
export interface ShareArchiveFile {
|
|
19
26
|
type: ShareEntryType;
|
|
@@ -61,6 +68,8 @@ export interface ShareExportOptions {
|
|
|
61
68
|
includeContext?: boolean;
|
|
62
69
|
includePrompts?: boolean;
|
|
63
70
|
includeSkills?: boolean;
|
|
71
|
+
includeAgents?: boolean;
|
|
72
|
+
includeFleets?: boolean;
|
|
64
73
|
includeSettings?: boolean;
|
|
65
74
|
includeExtensions?: boolean;
|
|
66
75
|
}
|
|
@@ -206,7 +215,13 @@ function defaultedIncludes(
|
|
|
206
215
|
): Required<
|
|
207
216
|
Pick<
|
|
208
217
|
ShareExportOptions,
|
|
209
|
-
|
|
218
|
+
| "includeContext"
|
|
219
|
+
| "includePrompts"
|
|
220
|
+
| "includeSkills"
|
|
221
|
+
| "includeAgents"
|
|
222
|
+
| "includeFleets"
|
|
223
|
+
| "includeSettings"
|
|
224
|
+
| "includeExtensions"
|
|
210
225
|
>
|
|
211
226
|
> {
|
|
212
227
|
// True when the caller named at least one include flag explicitly. In that
|
|
@@ -216,12 +231,16 @@ function defaultedIncludes(
|
|
|
216
231
|
options.includeContext !== undefined ||
|
|
217
232
|
options.includePrompts !== undefined ||
|
|
218
233
|
options.includeSkills !== undefined ||
|
|
234
|
+
options.includeAgents !== undefined ||
|
|
235
|
+
options.includeFleets !== undefined ||
|
|
219
236
|
options.includeSettings !== undefined ||
|
|
220
237
|
options.includeExtensions !== undefined;
|
|
221
238
|
return {
|
|
222
239
|
includeContext: hasExplicitInclude ? options.includeContext === true : true,
|
|
223
240
|
includePrompts: hasExplicitInclude ? options.includePrompts === true : true,
|
|
224
241
|
includeSkills: hasExplicitInclude ? options.includeSkills === true : true,
|
|
242
|
+
includeAgents: hasExplicitInclude ? options.includeAgents === true : true,
|
|
243
|
+
includeFleets: hasExplicitInclude ? options.includeFleets === true : true,
|
|
225
244
|
includeSettings: hasExplicitInclude ? options.includeSettings === true : true,
|
|
226
245
|
includeExtensions: hasExplicitInclude ? options.includeExtensions === true : true,
|
|
227
246
|
};
|
|
@@ -263,6 +282,18 @@ export function createShareArchive(options: ShareExportOptions = {}): ClioShareA
|
|
|
263
282
|
addTree(files, "skill", scope, root, `${scope}/skills`);
|
|
264
283
|
}
|
|
265
284
|
}
|
|
285
|
+
if (includes.includeAgents) {
|
|
286
|
+
for (const scope of scopes) {
|
|
287
|
+
const root = scope === "user" ? path.join(clioConfigDir(), "agents") : path.join(cwd, ".clio-coder", "agents");
|
|
288
|
+
addTree(files, "agent", scope, root, `${scope}/agents`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (includes.includeFleets) {
|
|
292
|
+
for (const scope of scopes) {
|
|
293
|
+
const root = scope === "user" ? path.join(clioConfigDir(), "fleets") : path.join(cwd, ".clio-coder", "fleets");
|
|
294
|
+
addTree(files, "fleet", scope, root, `${scope}/fleets`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
266
297
|
if (includes.includeExtensions) {
|
|
267
298
|
for (const scope of scopes) {
|
|
268
299
|
const root =
|
|
@@ -313,9 +344,19 @@ function parseArchive(raw: unknown): ClioShareArchive {
|
|
|
313
344
|
throw new Error("share archive manifest is malformed");
|
|
314
345
|
}
|
|
315
346
|
const archive = raw as unknown as ClioShareArchive;
|
|
347
|
+
const allowedTypes = new Set<ShareEntryType>([
|
|
348
|
+
"project-context",
|
|
349
|
+
"prompt",
|
|
350
|
+
"skill",
|
|
351
|
+
"agent",
|
|
352
|
+
"fleet",
|
|
353
|
+
"settings",
|
|
354
|
+
"extension",
|
|
355
|
+
]);
|
|
316
356
|
for (const file of archive.files) {
|
|
317
357
|
if (
|
|
318
358
|
!isRecord(file) ||
|
|
359
|
+
!allowedTypes.has(file.type as ShareEntryType) ||
|
|
319
360
|
typeof file.archivePath !== "string" ||
|
|
320
361
|
typeof file.relativePath !== "string" ||
|
|
321
362
|
typeof file.sha256 !== "string" ||
|
|
@@ -392,6 +433,10 @@ function targetRootForFile(entry: ShareArchiveFile, options: ShareImportOptions)
|
|
|
392
433
|
return scope === "user"
|
|
393
434
|
? { root: path.join(config, "skills"), containmentRoot: config, scope }
|
|
394
435
|
: { root: path.join(cwd, ".clio-coder", "skills"), containmentRoot: cwd, scope };
|
|
436
|
+
case "agent":
|
|
437
|
+
return { root: path.join(config, "agents"), containmentRoot: config, scope: "user" };
|
|
438
|
+
case "fleet":
|
|
439
|
+
return { root: path.join(config, "fleets"), containmentRoot: config, scope: "user" };
|
|
395
440
|
case "extension":
|
|
396
441
|
return scope === "user"
|
|
397
442
|
? { root: path.join(config, "extensions"), containmentRoot: config, scope }
|
|
@@ -563,6 +608,26 @@ function prepareShareImport(filePath: string, options: ShareImportOptions = {}):
|
|
|
563
608
|
}
|
|
564
609
|
for (const targetInfo of preflight.targets) {
|
|
565
610
|
const { entry, target, scope, buffer } = targetInfo;
|
|
611
|
+
if (entry.type === "fleet") {
|
|
612
|
+
try {
|
|
613
|
+
parseFleetContract(buffer.toString("utf8"), target);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
diagnostics.push({ type: "error", message: error instanceof Error ? error.message : String(error), path: target });
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (entry.type === "agent") {
|
|
620
|
+
try {
|
|
621
|
+
const parsed = parseFrontmatter(buffer.toString("utf8"), target);
|
|
622
|
+
const id = path.basename(target, ".md");
|
|
623
|
+
assertAgentSpecPolicy(
|
|
624
|
+
normalizeAgentSpec(parseAgentRecipeSchema({ id, source: "user", filepath: target, ...parsed })),
|
|
625
|
+
);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
diagnostics.push({ type: "error", message: error instanceof Error ? error.message : String(error), path: target });
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
566
631
|
if (entry.type === "settings") {
|
|
567
632
|
diagnostics.push(...settingsPlan(buffer, options));
|
|
568
633
|
actions.push({ action: "settings", type: entry.type, scope, path: target });
|
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
import { createMemoryInterventionRegistration } from "../domains/middleware/memory-intervention.js";
|
|
91
91
|
import { createTaskBoardReminderRegistration } from "../domains/middleware/task-board-reminder.js";
|
|
92
92
|
import { createTaskNudgeRegistration } from "../domains/middleware/task-nudge.js";
|
|
93
|
+
import { createWatchdogRegistration } from "../domains/middleware/watchdog.js";
|
|
93
94
|
import type { ObservabilityContract } from "../domains/observability/index.js";
|
|
94
95
|
import { ObservabilityDomainModule } from "../domains/observability/index.js";
|
|
95
96
|
import type { PromptsContract } from "../domains/prompts/contract.js";
|
|
@@ -177,6 +178,7 @@ import {
|
|
|
177
178
|
import { subscribeLoopGuardStop } from "../interactive/loop-guard-interrupt.js";
|
|
178
179
|
import { BUILTIN_SLASH_COMMANDS } from "../interactive/slash-commands.js";
|
|
179
180
|
import { createToolProseRegistration } from "../interactive/tool-prose-registration.js";
|
|
181
|
+
import { runWatchdogReview } from "../interactive/watchdog-run.js";
|
|
180
182
|
import { type AskUserHandler, cancelledAskUserResult } from "../tools/ask-user.js";
|
|
181
183
|
import { registerAllTools } from "../tools/bootstrap.js";
|
|
182
184
|
import { isGitRepository, recoverCleanupReadyCompeteGroups } from "../tools/compete-worktrees.js";
|
|
@@ -1069,6 +1071,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1069
1071
|
// Bound late: the registration is built here, but the buffer a deferred
|
|
1070
1072
|
// reminder lands in belongs to the chat loop that has not been composed yet.
|
|
1071
1073
|
let deferredMemoryReminderSink: ((message: string) => void) | null = null;
|
|
1074
|
+
// The watchdog's findings are for the operator, not the model, so they take
|
|
1075
|
+
// the transcript-notice path rather than the reminder buffer. Bound late for
|
|
1076
|
+
// the same reason: the chat loop that owns the transcript is composed below.
|
|
1077
|
+
let deferredWatchdogNoticeSink: ((text: string) => void) | null = null;
|
|
1072
1078
|
// Content-bearing, so it exists only when the operator named a file. The
|
|
1073
1079
|
// telemetry row says which silence happened; this says what the model wrote.
|
|
1074
1080
|
const memoryTracePath = taskMemoryTracePath();
|
|
@@ -1261,6 +1267,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1261
1267
|
getAutonomy: resolveEffectiveAutonomy,
|
|
1262
1268
|
...(interactive ? { dispatchBackground } : {}),
|
|
1263
1269
|
getCostCeilingUsd: () => result.getContract<SchedulingContract>("scheduling")?.ceilingUsd() ?? 0,
|
|
1270
|
+
...(config ? { getWorkerRosters: () => config.get().workers.rosters } : {}),
|
|
1264
1271
|
getSkillLoaderOptions: () => ({
|
|
1265
1272
|
trustProjectCompatRoots: config?.get().skills.trustProjectCompatRoots === true,
|
|
1266
1273
|
disableDiscovery: options.noSkills === true || options.headless?.noSkills === true,
|
|
@@ -1521,6 +1528,29 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1521
1528
|
middleware.registerHook(
|
|
1522
1529
|
createDetachedDispatchNudgeRegistration({ getOpenBatches: () => openDetachedBatchViews(dispatch) }),
|
|
1523
1530
|
);
|
|
1531
|
+
// The opt-in turn-end watchdog. Headless and ACP runs pass `false` for the
|
|
1532
|
+
// surface: neither has an operator reading a transcript, so a notice they
|
|
1533
|
+
// cannot see would be a worker run spent on nothing whatever the setting says.
|
|
1534
|
+
middleware.registerHook(
|
|
1535
|
+
createWatchdogRegistration({
|
|
1536
|
+
firesOnThisSurface: interactive,
|
|
1537
|
+
getSettings: () => (effectiveSettingsForDispatch?.() ?? getCurrentSettings()).watchdog,
|
|
1538
|
+
getScope: () => {
|
|
1539
|
+
const board = taskBoard.snapshot();
|
|
1540
|
+
if (board === null) return null;
|
|
1541
|
+
const active = board.tasks.find((task) => task.status === "active");
|
|
1542
|
+
return active ? `${board.title}: ${active.id} ${active.title}` : board.title;
|
|
1543
|
+
},
|
|
1544
|
+
run: (trigger) =>
|
|
1545
|
+
runWatchdogReview(trigger, {
|
|
1546
|
+
dispatch,
|
|
1547
|
+
bus,
|
|
1548
|
+
...(agents ? { getAgentRoleFacts: agentRoleFactsResolver((id: string) => agents.getSpec(id)) } : {}),
|
|
1549
|
+
target: (effectiveSettingsForDispatch?.() ?? getCurrentSettings()).watchdog.target,
|
|
1550
|
+
...(deferredWatchdogNoticeSink ? { emitNotice: deferredWatchdogNoticeSink } : {}),
|
|
1551
|
+
}),
|
|
1552
|
+
}),
|
|
1553
|
+
);
|
|
1524
1554
|
if (session) {
|
|
1525
1555
|
middleware.registerHook(
|
|
1526
1556
|
createFinishContractRegistration({
|
|
@@ -1584,6 +1614,9 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1584
1614
|
registerDeferredReminderSink: (sink) => {
|
|
1585
1615
|
deferredMemoryReminderSink = sink;
|
|
1586
1616
|
},
|
|
1617
|
+
registerDeferredNoticeSink: (sink) => {
|
|
1618
|
+
deferredWatchdogNoticeSink = sink;
|
|
1619
|
+
},
|
|
1587
1620
|
onAskUserFinalized: (policy) => {
|
|
1588
1621
|
decisionBoard.recordFinalizedInterview(policy);
|
|
1589
1622
|
},
|
|
@@ -1822,6 +1855,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1822
1855
|
providers,
|
|
1823
1856
|
dispatch,
|
|
1824
1857
|
...(agents ? { agents } : {}),
|
|
1858
|
+
...(() => {
|
|
1859
|
+
const scheduling = result.getContract<SchedulingContract>("scheduling");
|
|
1860
|
+
return scheduling ? { scheduling } : {};
|
|
1861
|
+
})(),
|
|
1825
1862
|
observability,
|
|
1826
1863
|
chat,
|
|
1827
1864
|
...(options.terminalLease
|
|
@@ -187,6 +187,32 @@ export function middlewareHookFailedSessionNotice(payload: unknown, seenBudgetWa
|
|
|
187
187
|
return middlewareHookFailedNotice(payload, { noteBudgetWarningSuppression: key !== null });
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/** How many failed checks a watchdog notice names before it counts the rest. */
|
|
191
|
+
export const WATCHDOG_NOTICE_CHECK_LIMIT = 3;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The watchdog's finding, as the one transcript line it is allowed.
|
|
195
|
+
*
|
|
196
|
+
* A passing report renders nothing: the operator asked for a warning, not a
|
|
197
|
+
* receipt, and a green line after every mutating turn is noise that trains
|
|
198
|
+
* people to stop reading the channel. Only the failed checks of the
|
|
199
|
+
* `verifier-report` contract reach here, and only their names: the evidence
|
|
200
|
+
* body belongs to the receipt, which the operator can open. Returning null
|
|
201
|
+
* means the turn passed or the report carried nothing usable.
|
|
202
|
+
*/
|
|
203
|
+
export function watchdogBlockersNotice(checks: ReadonlyArray<{ name: string; passed: boolean }>): BusNotice | null {
|
|
204
|
+
const failed = checks.filter((check) => !check.passed && check.name.trim().length > 0);
|
|
205
|
+
if (failed.length === 0) return null;
|
|
206
|
+
const named = failed.slice(0, WATCHDOG_NOTICE_CHECK_LIMIT).map((check) => check.name.trim());
|
|
207
|
+
const rest = failed.length - named.length;
|
|
208
|
+
const more = rest > 0 ? `, and ${rest} more` : "";
|
|
209
|
+
const blockers = failed.length === 1 ? "blocker" : "blockers";
|
|
210
|
+
return {
|
|
211
|
+
level: "warn",
|
|
212
|
+
text: `[watchdog] ${failed.length} ${blockers} after this turn: ${named.join("; ")}${more}. Nothing was changed or queued.`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
190
216
|
/**
|
|
191
217
|
* A tool call parked for one-shot approval (sd-01 §3.3). The text names the
|
|
192
218
|
* axis that produced the ask: a safety-net rail asks at every level, while an
|
|
@@ -17,16 +17,19 @@ import { BusChannels, type RunAbortSource } from "../core/bus-events.js";
|
|
|
17
17
|
import type { ClioSettings } from "../core/config.js";
|
|
18
18
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
19
19
|
import type { PendingSkillRequest } from "../core/skill-activation.js";
|
|
20
|
+
import { clioStateDir } from "../core/xdg.js";
|
|
20
21
|
import {
|
|
21
22
|
createMiddlewareToolChoiceControl,
|
|
22
23
|
type MiddlewareContract,
|
|
23
24
|
type MiddlewareToolChoiceControl,
|
|
24
25
|
} from "../domains/middleware/index.js";
|
|
25
26
|
import type { ObservabilityContract } from "../domains/observability/contract.js";
|
|
27
|
+
import type { CostEntryLabel } from "../domains/observability/cost.js";
|
|
28
|
+
import { appendOutOfTurnUsageRow, type OutOfTurnUsageRow } from "../domains/observability/out-of-turn-usage.js";
|
|
26
29
|
import type { PromptsContract } from "../domains/prompts/contract.js";
|
|
27
30
|
import { toContextOverflowError } from "../domains/providers/errors.js";
|
|
28
31
|
import type { ProvidersContract } from "../domains/providers/index.js";
|
|
29
|
-
import { runtimeTargetSnapshot } from "../domains/providers/index.js";
|
|
32
|
+
import { normalizeCostProvenance, runtimeTargetSnapshot, targetRequiresAuth } from "../domains/providers/index.js";
|
|
30
33
|
import type { ProtectedArtifactState } from "../domains/safety/protected-artifacts.js";
|
|
31
34
|
import type { CompactResult } from "../domains/session/compaction/compact.js";
|
|
32
35
|
import type { ContextSnapshot, ContextUsageSnapshot } from "../domains/session/context-accounting.js";
|
|
@@ -38,6 +41,7 @@ import { protectedArtifactStateFromSessionEntries } from "../domains/session/pro
|
|
|
38
41
|
import { isRetryableErrorMessage, type RetrySettings } from "../domains/session/retry.js";
|
|
39
42
|
import { createEngineAgent } from "../engine/agent.js";
|
|
40
43
|
import { resolveReservedOutputTokens } from "../engine/apis/output-budget.js";
|
|
44
|
+
import { cwdHash } from "../engine/session.js";
|
|
41
45
|
import type { AgentEvent, AgentMessage, ImageContent } from "../engine/types.js";
|
|
42
46
|
import { resolveSessionTools } from "../tools/agent-tools.js";
|
|
43
47
|
import { finalizeAskUserInterviewForHost } from "../tools/ask-user.js";
|
|
@@ -53,7 +57,9 @@ import {
|
|
|
53
57
|
toolSignatureFromState,
|
|
54
58
|
} from "./chat-loop-messages.js";
|
|
55
59
|
import { normalizeRetrySettings } from "./chat-loop-policy.js";
|
|
60
|
+
import { runHandoffRound } from "./handoff-round.js";
|
|
56
61
|
import type { ApprovalRequestView } from "./permission-overlay.js";
|
|
62
|
+
import { runSideQuestion, type SideQuestionResult } from "./side-question.js";
|
|
57
63
|
import type { AgentStatusEvent } from "./status/types.js";
|
|
58
64
|
import { createTurnContext } from "./turn-context.js";
|
|
59
65
|
import { createTurnMiddleware } from "./turn-middleware.js";
|
|
@@ -184,6 +190,13 @@ export interface ChatSubmitOptions {
|
|
|
184
190
|
onAdmitted?: () => void;
|
|
185
191
|
}
|
|
186
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Placeholder key a target that needs no auth still has to be handed. Mirrors
|
|
195
|
+
* the turn runtime's own local fallback, so a `/btw` round against a local
|
|
196
|
+
* server authenticates exactly the way a turn against it does.
|
|
197
|
+
*/
|
|
198
|
+
const LOCAL_SIDE_QUESTION_API_KEY = "clio-local-target";
|
|
199
|
+
|
|
187
200
|
/** Closing notice an operator interrupt leaves in the transcript and the ledger. */
|
|
188
201
|
const INTERRUPT_CANCEL_REASON = "[Clio Coder] run interrupted by operator; delivering the new message now.";
|
|
189
202
|
const ENGINE_ACTIVE_PROMPT_ERROR =
|
|
@@ -212,6 +225,24 @@ export interface ChatCancelOptions {
|
|
|
212
225
|
auditReason?: string;
|
|
213
226
|
}
|
|
214
227
|
|
|
228
|
+
export interface SideQuestionOptions {
|
|
229
|
+
/** Cancels the round. Esc and Ctrl+C in the overlay abort through it. */
|
|
230
|
+
signal?: AbortSignal;
|
|
231
|
+
/** Streamed answer text so the overlay fills as the provider produces it. */
|
|
232
|
+
onDelta?: (partialText: string) => void;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* How a `/btw` round ended. `refused` is a round that never started (a turn was
|
|
237
|
+
* in flight, or no orchestrator target is configured); `failed` is a round that
|
|
238
|
+
* started and the provider rejected.
|
|
239
|
+
*/
|
|
240
|
+
export type SideQuestionOutcome =
|
|
241
|
+
| { status: "answered"; text: string }
|
|
242
|
+
| { status: "aborted"; text: string }
|
|
243
|
+
| { status: "refused"; reason: string }
|
|
244
|
+
| { status: "failed"; reason: string };
|
|
245
|
+
|
|
215
246
|
export interface ChatLoop {
|
|
216
247
|
submit(text: string, options?: ChatSubmitOptions): Promise<void>;
|
|
217
248
|
steer(text: string): boolean;
|
|
@@ -250,6 +281,28 @@ export interface ChatLoop {
|
|
|
250
281
|
* notice so the `/context compact` handler does not have to mirror the logic.
|
|
251
282
|
*/
|
|
252
283
|
compact(instructions?: string): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* `/btw`: answer one side question against the session's active target,
|
|
286
|
+
* model, and compiled message history without starting a turn.
|
|
287
|
+
*
|
|
288
|
+
* Nothing this produces reaches the session ledger, the transcript, the
|
|
289
|
+
* context ledger, or the footer token counters; the message history is read,
|
|
290
|
+
* never mutated. The round's provider usage is still reported to `/cost`,
|
|
291
|
+
* labeled as a side question, because money was spent. Refused outright
|
|
292
|
+
* while a turn is in flight rather than queued.
|
|
293
|
+
*/
|
|
294
|
+
askSideQuestion(question: string, options?: SideQuestionOptions): Promise<SideQuestionOutcome>;
|
|
295
|
+
/**
|
|
296
|
+
* `/handoff`: run the extraction round for a goal against the same target,
|
|
297
|
+
* model, and compiled message history, and return its raw JSON answer.
|
|
298
|
+
*
|
|
299
|
+
* Like a side question this is out of turn: no tools are sent, the message
|
|
300
|
+
* history is read and never mutated, and nothing the round produces reaches
|
|
301
|
+
* the ledger. Validating, bounding, and reviewing the answer belong to the
|
|
302
|
+
* caller; this method only owns the provider call. Refused outright while a
|
|
303
|
+
* turn is in flight rather than queued.
|
|
304
|
+
*/
|
|
305
|
+
extractHandoff(goal: string, options?: SideQuestionOptions): Promise<SideQuestionOutcome>;
|
|
253
306
|
/**
|
|
254
307
|
* Drop or replace the chat-loop's in-memory state after a session switch
|
|
255
308
|
* (/resume, /fork, /new). `leafTurnId` is the id the next user turn
|
|
@@ -292,6 +345,20 @@ export interface CreateChatLoopDeps {
|
|
|
292
345
|
*/
|
|
293
346
|
prompts?: PromptsContract;
|
|
294
347
|
createAgent?: typeof createEngineAgent;
|
|
348
|
+
/**
|
|
349
|
+
* The `/btw` round. Defaults to the real provider call; contracts inject a
|
|
350
|
+
* stub so they can assert what a side question does to the session without
|
|
351
|
+
* standing up a provider, exactly as `createAgent` does for a turn.
|
|
352
|
+
*/
|
|
353
|
+
runSideQuestion?: typeof runSideQuestion;
|
|
354
|
+
/** The `/handoff` extraction round. Injectable for the same reason. */
|
|
355
|
+
runHandoffRound?: typeof runHandoffRound;
|
|
356
|
+
/**
|
|
357
|
+
* Append one priced out-of-turn call to the durable out-of-turn usage store.
|
|
358
|
+
* Defaults to the real writer under the state dir. Contracts inject a spy so
|
|
359
|
+
* they can assert the row was written without touching a real state dir.
|
|
360
|
+
*/
|
|
361
|
+
recordOutOfTurnUsageRow?: (row: OutOfTurnUsageRow) => void;
|
|
295
362
|
/**
|
|
296
363
|
* Return the current session's entries for token estimation. The chat-loop
|
|
297
364
|
* calls this on every submit so the auto-compaction threshold sees the
|
|
@@ -369,6 +436,12 @@ export interface CreateChatLoopDeps {
|
|
|
369
436
|
* composition; the loop owns the buffer the reminder lands in.
|
|
370
437
|
*/
|
|
371
438
|
registerDeferredReminderSink?: (sink: (message: string) => void) => void;
|
|
439
|
+
/**
|
|
440
|
+
* The same seam for findings that are for the operator rather than the model.
|
|
441
|
+
* The watchdog uses it: its run settles after the turn it reviewed, and its
|
|
442
|
+
* blockers become one transcript notice that never enters model context.
|
|
443
|
+
*/
|
|
444
|
+
registerDeferredNoticeSink?: (sink: (text: string) => void) => void;
|
|
372
445
|
/**
|
|
373
446
|
* Host-finalizer seam for branch-anchored interview snapshots. Called once,
|
|
374
447
|
* after the ask-user host finalizer has settled the policy and its transcript.
|
|
@@ -399,6 +472,8 @@ export function reloadProtectedArtifactsForSession(
|
|
|
399
472
|
export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
400
473
|
const listeners = new Set<(event: ChatLoopEvent) => void>();
|
|
401
474
|
const createAgent = deps.createAgent ?? createEngineAgent;
|
|
475
|
+
const sideQuestionRound = deps.runSideQuestion ?? runSideQuestion;
|
|
476
|
+
const handoffRound = deps.runHandoffRound ?? runHandoffRound;
|
|
402
477
|
const middlewareToolChoice = deps.middlewareToolChoice ?? createMiddlewareToolChoiceControl();
|
|
403
478
|
const state = createTurnState(deps.getSettings().orchestrator.thinkingLevel ?? "off");
|
|
404
479
|
const toolStartTimes = new Map<string, number>();
|
|
@@ -485,6 +560,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
485
560
|
|
|
486
561
|
try {
|
|
487
562
|
deps.registerDeferredReminderSink?.((message) => middleware.injectDeferredReminder(message));
|
|
563
|
+
deps.registerDeferredNoticeSink?.((text) => middleware.emitDeferredNotice(text));
|
|
488
564
|
} catch {
|
|
489
565
|
// A background observer losing its delivery path must not stop the loop
|
|
490
566
|
// from starting; it simply stays silent.
|
|
@@ -592,6 +668,102 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
592
668
|
return null;
|
|
593
669
|
};
|
|
594
670
|
|
|
671
|
+
/**
|
|
672
|
+
* Everything an out-of-turn round needs, or the reason it cannot run.
|
|
673
|
+
*
|
|
674
|
+
* `/btw` and `/handoff` are the two callers. Both read the compiled history
|
|
675
|
+
* the next turn would see, both authenticate exactly the way a turn against
|
|
676
|
+
* the same target does, and both are refused rather than queued while a turn
|
|
677
|
+
* is in flight, so the admission decision is made once here.
|
|
678
|
+
*/
|
|
679
|
+
type OutOfTurnPreparation =
|
|
680
|
+
| { ok: true; runtime: AgentRuntime; apiKey: string | undefined }
|
|
681
|
+
| { ok: false; reason: string };
|
|
682
|
+
|
|
683
|
+
const prepareOutOfTurnRound = async (inFlightRefusal: string, signal?: AbortSignal): Promise<OutOfTurnPreparation> => {
|
|
684
|
+
if (state.streaming) return { ok: false, reason: inFlightRefusal };
|
|
685
|
+
let agentRuntime: AgentRuntime | null;
|
|
686
|
+
try {
|
|
687
|
+
agentRuntime = turnRuntime.ensureRuntime();
|
|
688
|
+
} catch (err) {
|
|
689
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
690
|
+
}
|
|
691
|
+
if (!agentRuntime) return { ok: false, reason: notConfiguredNotice() };
|
|
692
|
+
const resolution = agentRuntime.runtimeResolution;
|
|
693
|
+
try {
|
|
694
|
+
const apiKey = targetRequiresAuth(resolution.target, resolution.runtime)
|
|
695
|
+
? (
|
|
696
|
+
await deps.providers.auth.resolveForTarget(resolution.target, resolution.runtime, signal ? { signal } : undefined)
|
|
697
|
+
).apiKey
|
|
698
|
+
: LOCAL_SIDE_QUESTION_API_KEY;
|
|
699
|
+
return { ok: true, runtime: agentRuntime, apiKey };
|
|
700
|
+
} catch (err) {
|
|
701
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
const writeOutOfTurnUsageRow =
|
|
706
|
+
deps.recordOutOfTurnUsageRow ?? ((row: OutOfTurnUsageRow): void => appendOutOfTurnUsageRow(clioStateDir(), row));
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Report an out-of-turn round's provider usage. Money was spent, so `/cost`
|
|
710
|
+
* says so under its own label; turn persistence, the working-set ledger,
|
|
711
|
+
* compaction inputs, and the footer counters never see it.
|
|
712
|
+
*
|
|
713
|
+
* The same call is also appended to the out-of-turn usage store under the
|
|
714
|
+
* state dir. That store exists because `/cost` only knows what this process
|
|
715
|
+
* spent: the round appends nothing to the session JSONL by design, so an
|
|
716
|
+
* archive reader such as `clio-coder usage report` had no record of the
|
|
717
|
+
* spend at all once the process exited. The session ledger stays untouched.
|
|
718
|
+
*/
|
|
719
|
+
const recordOutOfTurnUsage = (
|
|
720
|
+
runtime: AgentRuntime,
|
|
721
|
+
usage: SideQuestionResult["usage"],
|
|
722
|
+
label: CostEntryLabel,
|
|
723
|
+
): void => {
|
|
724
|
+
if (!usage) return;
|
|
725
|
+
const costProvenance = runtime.runtimeResolution.costProvenance;
|
|
726
|
+
deps.observability?.recordTokens(
|
|
727
|
+
runtime.targetId,
|
|
728
|
+
runtime.wireModelId,
|
|
729
|
+
usage.totalTokens,
|
|
730
|
+
usage.costUsd,
|
|
731
|
+
{
|
|
732
|
+
input: usage.input,
|
|
733
|
+
output: usage.output,
|
|
734
|
+
cacheRead: usage.cacheRead,
|
|
735
|
+
cacheWrite: usage.cacheWrite,
|
|
736
|
+
reasoningTokens: usage.reasoning,
|
|
737
|
+
totalTokens: usage.totalTokens,
|
|
738
|
+
apiCalls: 1,
|
|
739
|
+
},
|
|
740
|
+
costProvenance,
|
|
741
|
+
undefined,
|
|
742
|
+
label,
|
|
743
|
+
);
|
|
744
|
+
const meta = deps.session?.current() ?? null;
|
|
745
|
+
writeOutOfTurnUsageRow({
|
|
746
|
+
label,
|
|
747
|
+
sessionId: meta?.id ?? null,
|
|
748
|
+
// The identity the session ledger is filed under, so `usage report
|
|
749
|
+
// --repo` selects these rows with the same hash it selects ledgers with.
|
|
750
|
+
repoIdentity: meta ? meta.cwdHash || cwdHash(meta.cwd || process.cwd()) : null,
|
|
751
|
+
timestamp: new Date().toISOString(),
|
|
752
|
+
target: runtime.targetId,
|
|
753
|
+
attributedModelId: runtime.wireModelId,
|
|
754
|
+
usage: {
|
|
755
|
+
input: usage.input,
|
|
756
|
+
output: usage.output,
|
|
757
|
+
cacheRead: usage.cacheRead,
|
|
758
|
+
cacheWrite: usage.cacheWrite,
|
|
759
|
+
reasoning: usage.reasoning,
|
|
760
|
+
totalTokens: usage.totalTokens,
|
|
761
|
+
costUsd: usage.costUsd,
|
|
762
|
+
costProvenance: normalizeCostProvenance(costProvenance),
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
|
|
595
767
|
const api: ChatLoop = {
|
|
596
768
|
steer: (text) => queues.steer(text),
|
|
597
769
|
queueFollowUp: (text) => queues.queueFollowUp(text),
|
|
@@ -1024,6 +1196,68 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
|
|
|
1024
1196
|
middlewareToolChoice.reset();
|
|
1025
1197
|
},
|
|
1026
1198
|
|
|
1199
|
+
async askSideQuestion(question: string, options: SideQuestionOptions = {}): Promise<SideQuestionOutcome> {
|
|
1200
|
+
const text = question.trim();
|
|
1201
|
+
if (text.length === 0) {
|
|
1202
|
+
return { status: "refused", reason: "a side question needs a question" };
|
|
1203
|
+
}
|
|
1204
|
+
// Never queued. A side question exists to be answered now, beside a run
|
|
1205
|
+
// the operator is watching; holding it until the run settles would
|
|
1206
|
+
// deliver it after the moment it was asked in had passed.
|
|
1207
|
+
const prepared = await prepareOutOfTurnRound(
|
|
1208
|
+
"a turn is in flight; /btw runs beside the session, not in its queue",
|
|
1209
|
+
options.signal,
|
|
1210
|
+
);
|
|
1211
|
+
if (!prepared.ok) return { status: "refused", reason: prepared.reason };
|
|
1212
|
+
let result: SideQuestionResult;
|
|
1213
|
+
try {
|
|
1214
|
+
result = await sideQuestionRound({
|
|
1215
|
+
model: prepared.runtime.agent.state.model,
|
|
1216
|
+
// Read-only: runSideQuestion copies before appending its own
|
|
1217
|
+
// message, so the live agent's history is untouched.
|
|
1218
|
+
messages: prepared.runtime.agent.state.messages,
|
|
1219
|
+
question: text,
|
|
1220
|
+
...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
|
|
1221
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1222
|
+
...(options.onDelta ? { onDelta: options.onDelta } : {}),
|
|
1223
|
+
});
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
return { status: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
1226
|
+
}
|
|
1227
|
+
recordOutOfTurnUsage(prepared.runtime, result.usage, "side-question");
|
|
1228
|
+
return result.aborted ? { status: "aborted", text: result.text } : { status: "answered", text: result.text };
|
|
1229
|
+
},
|
|
1230
|
+
|
|
1231
|
+
async extractHandoff(goal: string, options: SideQuestionOptions = {}): Promise<SideQuestionOutcome> {
|
|
1232
|
+
const text = goal.trim();
|
|
1233
|
+
if (text.length === 0) {
|
|
1234
|
+
return { status: "refused", reason: "a handoff needs a goal" };
|
|
1235
|
+
}
|
|
1236
|
+
// Refused, never queued: a handoff describes a session that has stopped
|
|
1237
|
+
// working, and a turn still in flight is about to change what the
|
|
1238
|
+
// document would say.
|
|
1239
|
+
const prepared = await prepareOutOfTurnRound(
|
|
1240
|
+
"a turn is in flight; /handoff cannot summarize a session that is still moving",
|
|
1241
|
+
options.signal,
|
|
1242
|
+
);
|
|
1243
|
+
if (!prepared.ok) return { status: "refused", reason: prepared.reason };
|
|
1244
|
+
let result: SideQuestionResult;
|
|
1245
|
+
try {
|
|
1246
|
+
result = await handoffRound({
|
|
1247
|
+
model: prepared.runtime.agent.state.model,
|
|
1248
|
+
// Read-only, exactly as the side-question round treats it.
|
|
1249
|
+
messages: prepared.runtime.agent.state.messages,
|
|
1250
|
+
goal: text,
|
|
1251
|
+
...(prepared.apiKey !== undefined ? { apiKey: prepared.apiKey } : {}),
|
|
1252
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1253
|
+
});
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
return { status: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
1256
|
+
}
|
|
1257
|
+
recordOutOfTurnUsage(prepared.runtime, result.usage, "handoff");
|
|
1258
|
+
return result.aborted ? { status: "aborted", text: result.text } : { status: "answered", text: result.text };
|
|
1259
|
+
},
|
|
1260
|
+
|
|
1027
1261
|
async compact(instructions?: string): Promise<void> {
|
|
1028
1262
|
// Session check runs BEFORE orchestrator-configuration so a fresh
|
|
1029
1263
|
// TUI with nothing configured still reports the actionable "no
|
|
@@ -31,6 +31,13 @@ import type {
|
|
|
31
31
|
ThinkingLevelChangeEntry,
|
|
32
32
|
WorkerRunEntry,
|
|
33
33
|
} from "../domains/session/entries.js";
|
|
34
|
+
import {
|
|
35
|
+
HANDOFF_NOTE_CUSTOM_TYPE,
|
|
36
|
+
HANDOFF_SEED_CUSTOM_TYPE,
|
|
37
|
+
handoffSeedContextText,
|
|
38
|
+
isHandoffNoteData,
|
|
39
|
+
isHandoffSeedData,
|
|
40
|
+
} from "../domains/session/handoff.js";
|
|
34
41
|
import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
|
|
35
42
|
import {
|
|
36
43
|
type BashExecutionMessage,
|
|
@@ -795,11 +802,18 @@ function rendersCustomEntry(entry: CustomEntry): boolean {
|
|
|
795
802
|
if (entry.display === false) return false;
|
|
796
803
|
if (entry.customType === "retryStatus") return true;
|
|
797
804
|
if (entry.customType === "finishContractAdvisory" || entry.customType === "middlewareReminder") return true;
|
|
805
|
+
if (entry.customType === HANDOFF_SEED_CUSTOM_TYPE || entry.customType === HANDOFF_NOTE_CUSTOM_TYPE) return true;
|
|
798
806
|
return entry.display === true;
|
|
799
807
|
}
|
|
800
808
|
|
|
801
809
|
function renderCustomEntry(entry: CustomEntry, width: number): string[] {
|
|
802
810
|
if (entry.customType === "retryStatus") return renderRetryStatusEntry(entry, width);
|
|
811
|
+
if (entry.customType === HANDOFF_SEED_CUSTOM_TYPE && isHandoffSeedData(entry.data)) {
|
|
812
|
+
return wrapTextWithAnsi(styleTaggedNotice(`[handoff] carried from session ${entry.data.fromSessionId}`), width);
|
|
813
|
+
}
|
|
814
|
+
if (entry.customType === HANDOFF_NOTE_CUSTOM_TYPE && isHandoffNoteData(entry.data)) {
|
|
815
|
+
return wrapTextWithAnsi(styleTaggedNotice(`[handoff] handed off to session ${entry.data.toSessionId}`), width);
|
|
816
|
+
}
|
|
803
817
|
// "finishContractAdvisory" is the pre-middleware name for the same entry
|
|
804
818
|
// shape; older session ledgers still carry it.
|
|
805
819
|
if (entry.customType === "finishContractAdvisory" || entry.customType === "middlewareReminder") {
|
|
@@ -1039,7 +1053,15 @@ export function buildReplayAgentMessagesFromTurns(
|
|
|
1039
1053
|
case "skillActivation":
|
|
1040
1054
|
appendContextMessage(out, "user", skillActivationContextText(entry), entry.timestamp);
|
|
1041
1055
|
break;
|
|
1056
|
+
// The one custom entry that becomes a model message. `/handoff` seeds a
|
|
1057
|
+
// new session with a reviewed document, and the seed is the first thing
|
|
1058
|
+
// the model reads there; it is labelled by its origin session and
|
|
1059
|
+
// carried as data, never as a user turn the operator did not write.
|
|
1042
1060
|
case "custom":
|
|
1061
|
+
if (entry.customType === HANDOFF_SEED_CUSTOM_TYPE && isHandoffSeedData(entry.data)) {
|
|
1062
|
+
appendContextMessage(out, "user", handoffSeedContextText(entry.data), entry.timestamp);
|
|
1063
|
+
}
|
|
1064
|
+
break;
|
|
1043
1065
|
case "modelChange":
|
|
1044
1066
|
case "thinkingLevelChange":
|
|
1045
1067
|
case "fileEntry":
|