toolroll 0.9.72 → 0.9.74

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/dist/agent-fence.d.ts +5 -0
  4. package/dist/agent-fence.js +42 -7
  5. package/dist/agent-turns.js +5 -0
  6. package/dist/agents-live.d.ts +31 -0
  7. package/dist/agents-live.js +43 -0
  8. package/dist/browser/THIRD_PARTY_NOTICES.txt +40 -0
  9. package/dist/browser/workspace.css +2 -2
  10. package/dist/browser/workspace.js +31 -30
  11. package/dist/browser-workspace.d.ts +6 -0
  12. package/dist/builder.js +1 -1
  13. package/dist/chat-actions.js +13 -8
  14. package/dist/chat-continuity.js +10 -0
  15. package/dist/chat-decide.d.ts +47 -1
  16. package/dist/chat-decide.js +170 -4
  17. package/dist/chat-quiet.js +3 -3
  18. package/dist/check-report.js +2 -1
  19. package/dist/coding-provider.js +4 -2
  20. package/dist/commit-extract.d.ts +17 -0
  21. package/dist/commit-extract.js +82 -4
  22. package/dist/contracts/console-api.d.ts +3 -3
  23. package/dist/contracts/console-api.js +4 -4
  24. package/dist/contracts/release-terms.d.ts +7 -1
  25. package/dist/contracts/release-terms.js +6 -2
  26. package/dist/deploy-gate.js +5 -2
  27. package/dist/desktop-update.d.ts +1 -1
  28. package/dist/dispose.js +35 -2
  29. package/dist/exec.d.ts +1 -1
  30. package/dist/exec.js +20 -19
  31. package/dist/first-run.d.ts +17 -1
  32. package/dist/first-run.js +38 -0
  33. package/dist/flow-gallery-ui.js +4 -2
  34. package/dist/flow-gallery.d.ts +3 -0
  35. package/dist/flow-gallery.js +6 -2
  36. package/dist/flow-triggers.d.ts +8 -0
  37. package/dist/flow-triggers.js +15 -0
  38. package/dist/flows-cli.js +13 -3
  39. package/dist/guides.js +10 -1
  40. package/dist/invoke.d.ts +13 -0
  41. package/dist/invoke.js +21 -12
  42. package/dist/kits-ui.js +4 -2
  43. package/dist/kits.d.ts +6 -4
  44. package/dist/kits.js +9 -11
  45. package/dist/knowledge-ui.d.ts +4 -1
  46. package/dist/knowledge-ui.js +10 -5
  47. package/dist/lead-progress.d.ts +4 -1
  48. package/dist/lead-progress.js +3 -1
  49. package/dist/lead.js +4 -0
  50. package/dist/live-bus.d.ts +2 -0
  51. package/dist/live-bus.js +1 -0
  52. package/dist/live-children-read.d.ts +52 -0
  53. package/dist/live-children-read.js +333 -0
  54. package/dist/live-children.d.ts +73 -0
  55. package/dist/live-children.js +318 -0
  56. package/dist/live-detail.d.ts +4 -3
  57. package/dist/live-detail.js +9 -5
  58. package/dist/live-input.js +7 -8
  59. package/dist/live-main-read.d.ts +19 -0
  60. package/dist/live-main-read.js +91 -0
  61. package/dist/live-open.d.ts +28 -0
  62. package/dist/live-open.js +125 -0
  63. package/dist/live-records.d.ts +10 -1
  64. package/dist/live-records.js +16 -5
  65. package/dist/live.d.ts +54 -22
  66. package/dist/live.js +218 -69
  67. package/dist/mcp-connect.d.ts +10 -0
  68. package/dist/mcp-connect.js +2 -0
  69. package/dist/operate.js +3 -2
  70. package/dist/peek-cli.d.ts +10 -3
  71. package/dist/peek-cli.js +17 -8
  72. package/dist/planner.js +1 -1
  73. package/dist/project-tools.js +3 -1
  74. package/dist/protected-storage.d.ts +67 -0
  75. package/dist/protected-storage.js +398 -0
  76. package/dist/provider-limits.d.ts +3 -1
  77. package/dist/provider-limits.js +5 -2
  78. package/dist/provider.js +6 -12
  79. package/dist/release-adapters.d.ts +8 -1
  80. package/dist/release-adapters.js +84 -14
  81. package/dist/release-authority.d.ts +6 -0
  82. package/dist/release-authority.js +9 -0
  83. package/dist/release-card.d.ts +76 -0
  84. package/dist/release-card.js +252 -0
  85. package/dist/release-carry.d.ts +3 -0
  86. package/dist/release-carry.js +23 -4
  87. package/dist/release-cli.js +36 -3
  88. package/dist/release-git.d.ts +43 -0
  89. package/dist/release-git.js +106 -0
  90. package/dist/release.d.ts +11 -2
  91. package/dist/release.js +82 -6
  92. package/dist/repository-context-ui.js +2 -1
  93. package/dist/run-efficiency.d.ts +3 -3
  94. package/dist/run-efficiency.js +20 -8
  95. package/dist/scope.d.ts +7 -0
  96. package/dist/scope.js +22 -5
  97. package/dist/scout.d.ts +3 -0
  98. package/dist/scout.js +5 -3
  99. package/dist/serve.js +34 -23
  100. package/dist/server/agents-live-lead.d.ts +47 -0
  101. package/dist/server/agents-live-lead.js +116 -0
  102. package/dist/server/agents-live-room.d.ts +23 -0
  103. package/dist/server/agents-live-room.js +88 -0
  104. package/dist/server/chat.js +84 -30
  105. package/dist/server/chrome.js +2 -2
  106. package/dist/server/console.css +68 -40
  107. package/dist/server/flows.js +40 -10
  108. package/dist/server/live.d.ts +4 -0
  109. package/dist/server/live.js +42 -2
  110. package/dist/server/pages.js +81 -1
  111. package/dist/server/render-chat.d.ts +15 -1
  112. package/dist/server/render-chat.js +14 -7
  113. package/dist/server/render-pages.d.ts +8 -4
  114. package/dist/server/render-pages.js +55 -50
  115. package/dist/server/render-settings.js +1 -1
  116. package/dist/server/render-tasks.js +10 -3
  117. package/dist/server/route-table.js +5 -1
  118. package/dist/server/runtime.d.ts +1 -1
  119. package/dist/server/settings.js +29 -8
  120. package/dist/server/task-views.js +1 -1
  121. package/dist/server/tasks.js +59 -41
  122. package/dist/setup-guide.d.ts +2 -2
  123. package/dist/store.d.ts +34 -1
  124. package/dist/store.js +81 -2
  125. package/dist/stream-scope.d.ts +18 -0
  126. package/dist/stream-scope.js +28 -0
  127. package/dist/subagent-memory.d.ts +1 -0
  128. package/dist/subagent-memory.js +1 -1
  129. package/dist/subagent-tools.d.ts +22 -6
  130. package/dist/subagent-tools.js +44 -8
  131. package/dist/subagent-work.d.ts +2 -0
  132. package/dist/subagent-work.js +5 -2
  133. package/dist/subagents-ui.js +6 -4
  134. package/dist/subscription-chat.js +5 -3
  135. package/dist/telegram.d.ts +6 -1
  136. package/dist/telegram.js +58 -6
  137. package/package.json +1 -1
@@ -368,6 +368,18 @@ export function rawTakesDeliveries(raw) {
368
368
  const one = raw;
369
369
  return one["kind"] === "webhook" || ((one["kind"] === "github" || one["kind"] === "linear") && one["delivery"] === "webhook");
370
370
  }
371
+ /** Whether a trigger brings in items from anyone (chat-management.md §0a.3): GitHub issues or pull requests from anyone,
372
+ * or mail from any sender. A widening: it takes a person's password, on every road that adds one. */
373
+ export function admitsAnyone(config) {
374
+ return (config.kind === "github" && config.from === "anyone") || (config.kind === "email" && config.sender === null);
375
+ }
376
+ /** The same, read leniently from a raw trigger before validation (so the form can ask for the password first). */
377
+ export function rawAdmitsAnyone(raw) {
378
+ if (raw === null || typeof raw !== "object")
379
+ return false;
380
+ const one = raw;
381
+ return (one["kind"] === "github" && one["from"] === "anyone") || (one["kind"] === "email" && (typeof one["sender"] !== "string" || one["sender"].trim() === ""));
382
+ }
371
383
  export function takesDeliveries(config) {
372
384
  return config.kind === "webhook" || ((config.kind === "github" || config.kind === "linear") && config.delivery === "webhook");
373
385
  }
@@ -495,6 +507,9 @@ export function addFlowTriggerTo(store, flow, raw, actor, now, dir, options = {}
495
507
  return { ok: false, message: "Webhook addresses are set up on the console." };
496
508
  if (takesDeliveries(config) && config.kind !== "webhook" && options.ingress !== "proved")
497
509
  return { ok: false, message: "Webhook delivery is set up on the flow's trigger panel, with your password." };
510
+ // Items from anyone come in only on a person's proof (chat-management.md §0a.3): never from a card, an import or a template alone.
511
+ if (admitsAnyone(config) && options.ingress !== "proved" && options.widening !== "proved")
512
+ return { ok: false, message: "A trigger that brings in items from anyone takes your password. Add it on the flow's Triggers panel." };
498
513
  // v126: a webhook made where no one is shown its address (the gallery, an import) is made with none: its first
499
514
  // set-up or New address makes one and shows it. A webhook made on its panel is shown its address now.
500
515
  const token = takesDeliveries(config) && options.ingress === "proved" && (options.shown !== false || config.kind !== "webhook") ? randomBytes(24).toString("base64url") : null;
package/dist/flows-cli.js CHANGED
@@ -21,7 +21,8 @@ import { deciderOf, FLOW_KIND_WORDS, FLOW_TEMPLATES, flowDigest, flowFromSteps,
21
21
  import { prepareUnattendedCandidates, activeUnattendedGrant, parseUnattendedAsk, revokeUnattended, signUnattended, unattendedViewOf, unattendedWords, untilWords, UNATTENDED_MAX_RUNS } from './flow-unattended.js';
22
22
  import { MODE_MAX_DAYS } from './modes.js';
23
23
  import { verifyApproverByPassword } from './principal.js';
24
- import { proveCredential } from './scope.js';
24
+ import { authenticateApprover, proveCredential } from './scope.js';
25
+ import { personProof } from './person-proof.js';
25
26
  import { projectName } from './project.js';
26
27
  const EXIT = { ok: 0, failed: 1, usage: 2, refused: 3 };
27
28
  const json = { name: 'json', takesValue: false, meaning: 'answer with one machine envelope on stdout' };
@@ -157,6 +158,12 @@ export async function runFlowsCommand(positional, flags, context) {
157
158
  store.recordAction({ at: context.clock().toISOString(), actor: who, repo: project, taskId: null, runId: null, action: command, outcome, source: 'request', detail: `command line${detail === null ? '' : ` · ${detail}`}` });
158
159
  };
159
160
  const refuse = (project, reason, message, code = EXIT.refused) => { record(project, 'refused', reason); return fail(reason, message, code); };
161
+ // A trigger from anyone widens what comes in (chat-management.md §0a.3): only the person's own proof counts, never a
162
+ // machine credential standing in for them (a remote run's API token, a lead's token), though both pass the check above.
163
+ const widening = (project) => {
164
+ const proved = authenticateApprover(store, acting.name, acting.token, project);
165
+ return proved.ok && personProof(proved.method) ? { widening: 'proved' } : {};
166
+ };
160
167
  const settle = (project) => { try {
161
168
  advanceFlows(store, project, context.clock(), { evidenceRoot: context.evidenceRoot });
162
169
  }
@@ -243,11 +250,13 @@ export async function runFlowsCommand(positional, flags, context) {
243
250
  if (!flags.has('yes'))
244
251
  return preview(project, `Create the ${flowName} flow in ${projectName(project)}`, terms, { definition });
245
252
  let id;
253
+ const proof = template?.trigger === undefined ? {} : widening(project);
246
254
  try {
247
255
  id = store.transact(() => {
248
256
  const made = store.createFlow({ repo: project, name: flowName, definitionJson: JSON.stringify(definition), by: who }, now);
249
257
  if (template?.trigger !== undefined) {
250
- const trigger = addFlowTriggerTo(store, store.getFlow(made), template.trigger, who, now, context.configDir);
258
+ // A trigger from anyone only on the person's own proof (widening above); a machine credential's is refused.
259
+ const trigger = addFlowTriggerTo(store, store.getFlow(made), template.trigger, who, now, context.configDir, proof);
251
260
  if (!trigger.ok)
252
261
  throw new Error(trigger.message);
253
262
  }
@@ -508,7 +517,8 @@ export async function runFlowsCommand(positional, flags, context) {
508
517
  const ingress = rawTakesDeliveries(input.value);
509
518
  if (ingress && !proveCredential(store, acting.name, acting.token).ok)
510
519
  return refuse(flow.repo, 'unauthenticated', 'Making a webhook address takes your password, typed: pass --token.');
511
- const made = addFlowTriggerTo(store, flow, input.value, who, now, context.configDir, ingress ? { ingress: 'proved' } : {});
520
+ // A trigger from anyone only on the person's own proof (widening above): a machine credential's is refused.
521
+ const made = addFlowTriggerTo(store, flow, input.value, who, now, context.configDir, ingress ? { ingress: 'proved' } : widening(flow.repo));
512
522
  if (!made.ok)
513
523
  return refuse(flow.repo, 'invalid-trigger', made.message);
514
524
  await prepareUnattendedCandidates(store, flow.id);
package/dist/guides.js CHANGED
@@ -445,7 +445,13 @@ with a time limit.
445
445
 
446
446
  ## What it does, in order
447
447
 
448
- 1. **gate**: files task \`release-<version>-<sha7>\` pinned to the
448
+ 1. **gate**: before filing, looks at the pull request's checks on the
449
+ branch's exact commit: a required check that failed stops here, before
450
+ anything is filed or sent for approval. Checks not started or still
451
+ running are named and waited for (GitHub not answering is waited out
452
+ too); if they don't finish within the gate's limit (60 min, \`--limit
453
+ gate=<min>\`), it stops here as well. A task already filed for the commit is kept and
454
+ not looked at again. Then it files task \`release-<version>-<sha7>\` pinned to the
449
455
  branch's exact commit with \`--checks full\`, regardless of the project's default.
450
456
  2. **approval**: waits for the owner to approve that exact scope
451
457
  (\`toolroll task approve <id> --digest <d>\`, or the console). A
@@ -499,6 +505,9 @@ instead of making them twice.
499
505
  checks and obtain a passing result; Quick and Off cannot authorize a release.
500
506
  - \`check-failed\`: fix the branch and release the new commit with \`--new\`.
501
507
  - \`ci-failed\`: fix the failed GitHub check, then rerun. Nothing was merged.
508
+ A stop at the gate comes before the task is filed, so nothing was filed
509
+ either: a fix pushed as a new commit is released with \`--new\`; after
510
+ rerunning the failed job on GitHub, run the same command again.
502
511
  - \`publish-failed\` / \`homebrew-failed\`: fix the failed workflow before
503
512
  resuming. A failed tag check cannot finish publication or update Homebrew.
504
513
  - \`base-moved\`: main gained a commit the branch doesn't have, so the
package/dist/invoke.d.ts CHANGED
@@ -190,3 +190,16 @@ export declare function checkoutRunner(store: Store, repo: string, checkout: str
190
190
  export declare function checkoutSandboxOf(store: Store, repo: string, runIds: readonly number[]): LaunchSandbox;
191
191
  /** The digest of a launch's whole fence (its denied paths and grants, canonical), as stamped on its run (C11). */
192
192
  export declare function fenceDigest(fence: readonly string[], grants: FenceGrants): string;
193
+ /** What a Claude turn's own stream says about how it billed (v105). */
194
+ export type ClaudeBillingSeen = {
195
+ keySource: string | null;
196
+ model: string | null;
197
+ planWindows: boolean;
198
+ answered: boolean;
199
+ };
200
+ /**
201
+ * One stream event's billing observation: the key source and model from the session's own init, plan windows from a
202
+ * rate-limit event, and whether the main query answered. The session's own events only (stream-scope.ts): a
203
+ * subagent's init, rate-limit event or result says nothing about this turn's billing.
204
+ */
205
+ export declare function observeClaudeBilling(seen: ClaudeBillingSeen, event: Record<string, unknown>): void;
package/dist/invoke.js CHANGED
@@ -44,6 +44,7 @@ import { buildCacheEnv, buildCacheFolder, grantFolder, writeAllowlist, WRITES_DE
44
44
  import { proxyEnv, providerOnlyNetwork, repositoryEcosystems, resolveNetwork, startEgressProxy } from "./egress.js";
45
45
  import { claudeConfigDir, claudeDefaults, codexDefaults, codexHomeDir, isolatedCodexHome } from "./provider-home.js";
46
46
  import { billingOf, claudeBillingFrom, seenBilling } from "./spend.js";
47
+ import { primaryResultEvent, topLevelEvent } from "./stream-scope.js";
47
48
  /** "Within seconds": how soon a silent failed run must end for its stderr to
48
49
  * be read for the sign-in signal. */
49
50
  const AUTH_EARLY_EXIT_MS = 30_000;
@@ -507,18 +508,7 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
507
508
  ...(workspace === null ? {} : { cwd: workspace }),
508
509
  // v105: how the CLI really billed this turn (its key source, its plan's windows), read off its own stream.
509
510
  onStreamEvent: event => {
510
- if (event["type"] === "system" && event["subtype"] === "init") {
511
- if (typeof event["apiKeySource"] === "string")
512
- billingSeen.keySource = event["apiKeySource"].slice(0, 80);
513
- if (typeof event["model"] === "string")
514
- billingSeen.model = event["model"].slice(0, 200);
515
- }
516
- else if (event["type"] === "rate_limit_event") {
517
- billingSeen.planWindows = true;
518
- }
519
- else if (event["type"] === "result" && event["is_error"] !== true && event["subtype"] === "success") {
520
- billingSeen.answered = true;
521
- }
511
+ observeClaudeBilling(billingSeen, event);
522
512
  runOptions.onStreamEvent?.(event);
523
513
  },
524
514
  owner: runOwnerTag(store, runId),
@@ -1303,3 +1293,22 @@ export function checkoutSandboxOf(store, repo, runIds) {
1303
1293
  export function fenceDigest(fence, grants) {
1304
1294
  return createHash("sha256").update(`toolroll:fence:${canonicalJson({ fence: [...fence], grants: grants })}`).digest("hex").slice(0, 32);
1305
1295
  }
1296
+ /**
1297
+ * One stream event's billing observation: the key source and model from the session's own init, plan windows from a
1298
+ * rate-limit event, and whether the main query answered. The session's own events only (stream-scope.ts): a
1299
+ * subagent's init, rate-limit event or result says nothing about this turn's billing.
1300
+ */
1301
+ export function observeClaudeBilling(seen, event) {
1302
+ if (event["type"] === "system" && event["subtype"] === "init" && topLevelEvent(event)) {
1303
+ if (typeof event["apiKeySource"] === "string")
1304
+ seen.keySource = event["apiKeySource"].slice(0, 80);
1305
+ if (typeof event["model"] === "string")
1306
+ seen.model = event["model"].slice(0, 200);
1307
+ }
1308
+ else if (event["type"] === "rate_limit_event" && topLevelEvent(event)) {
1309
+ seen.planWindows = true;
1310
+ }
1311
+ else if (primaryResultEvent(event) && event["is_error"] !== true && event["subtype"] === "success") {
1312
+ seen.answered = true;
1313
+ }
1314
+ }
package/dist/kits-ui.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { kitChecklist, kitInstalled, KITS } from "./kits.js";
2
2
  import { SUBAGENT_TEMPLATES } from "./subagents.js";
3
- import { html, postForm } from "./html.js";
3
+ import { html, postForm, reauth } from "./html.js";
4
4
  const CHECK = html `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`;
5
5
  const RING = html `<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8"/></svg>`;
6
6
  export const KITS_CSS = `.kits{max-width:960px;min-width:0}.kits-lede{color:var(--so-muted);margin:.25rem 0 1.5rem;max-width:40rem}.kit-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}` +
@@ -35,7 +35,9 @@ export function kitPageHtml(store, kit, repo, dir, canSetUp, notice) {
35
35
  const rows = steps.map(step => {
36
36
  const go = step.done ? (step.href === null ? "" : html `<a class="kit-step-go" href="${step.href}">Open</a>`)
37
37
  : step.action === "sample" || step.action === "github"
38
- ? (canSetUp ? postForm(`/kits/${kit.id}/${step.action}`, html `<button${step.action === "sample" ? "" : html ` class="secondary"`}>${step.action === "sample" ? "Try it" : "Bring them in"}</button>`, { hidden: { repo } }) : "")
38
+ ? (canSetUp ? postForm(`/kits/${kit.id}/${step.action}`, step.action === "sample" ? html `<button>Try it</button>`
39
+ // Issues from anyone come in: it takes your password (an ordinary step-up; a session does under Passwordless).
40
+ : html `${reauth(html `<input type="password" name="password" autocomplete="current-password" aria-label="Your password" placeholder="Your password">`)}<button class="secondary">Bring them in</button>`, { hidden: { repo } }) : "")
39
41
  : step.href === null ? "" : html `<a class="kit-step-go" href="${step.href}">${step.action === "connect" ? "Connect" : "Set up"}</a>`;
40
42
  return html `<li data-step="${step.id}" data-done="${String(step.done)}">${step.done ? CHECK : RING}<span class="kit-step-said">${step.said}</span>${go}</li>`;
41
43
  });
package/dist/kits.d.ts CHANGED
@@ -53,9 +53,11 @@ export declare function kitInstalled(store: Store, kit: Kit, repo: string): {
53
53
  * flow, and its button triggers; then its subagent may use whichever of the
54
54
  * kit's tools the project already has (reading free, writing asking first).
55
55
  */
56
- export declare function setUpKit(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null, io?: ToolIo): Promise<Done>;
57
- /** The same, all at once (a chat confirmation can't wait on a tool): its subagent is given the kit's connected tools by the names their last test found, and each is listed properly on its first turn. */
58
- export declare function setUpKitNow(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null, grantByName?: boolean): Done;
56
+ export declare function setUpKit(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null, io?: ToolIo, options?: {
57
+ widening?: "proved";
58
+ }): Promise<Done>;
59
+ /** The same, all at once and without its tools (a chat confirmation is never a grant's proof): its subagent, its flow and its buttons. */
60
+ export declare function setUpKitNow(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null): Done;
59
61
  /** One line of a kit's checklist: done, or the next step with where to take it. */
60
62
  export type KitStep = {
61
63
  id: string;
@@ -76,7 +78,7 @@ export declare function addKitSample(store: Store, kit: Kit, repo: string, by: s
76
78
  said: string;
77
79
  };
78
80
  /** Bring new issues from the project's GitHub repository into the kit's flow (issues from anyone, polled). */
79
- export declare function addKitGithubTrigger(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null): {
81
+ export declare function addKitGithubTrigger(store: Store, kit: Kit, repo: string, by: string, now: Date, dir: string | null, proved?: "proved" | null): {
80
82
  ok: true;
81
83
  said: string;
82
84
  } | {
package/dist/kits.js CHANGED
@@ -17,7 +17,7 @@ import { readEmailSettings } from "./email-settings.js";
17
17
  import { mailboxReady } from "./mailbox.js";
18
18
  import { projectToolsOf } from "./project-tools.js";
19
19
  import { createSubagentFrom, nameOf } from "./subagent-admin.js";
20
- import { grantListed, grantTool } from "./subagent-tools.js";
20
+ import { grantTool } from "./subagent-tools.js";
21
21
  import { SUBAGENT_TEMPLATES } from "./subagents.js";
22
22
  export const KITS = [
23
23
  {
@@ -115,22 +115,23 @@ export function kitInstalled(store, kit, repo) {
115
115
  * flow, and its button triggers; then its subagent may use whichever of the
116
116
  * kit's tools the project already has (reading free, writing asking first).
117
117
  */
118
- export async function setUpKit(store, kit, repo, by, now, dir, io = {}) {
118
+ export async function setUpKit(store, kit, repo, by, now, dir, io = {}, options = {}) {
119
119
  const made = setUpKitNow(store, kit, repo, by, now, dir);
120
- if (!made.ok || made.said.endsWith("already set up here."))
120
+ // Its tools only with the person's proof (chat-management.md §0a.3); without it, the checklist says to let it use each.
121
+ if (!made.ok || made.said.endsWith("already set up here.") || options.widening !== "proved")
121
122
  return made;
122
123
  const mate = store.getSubagent(made.subagent);
123
124
  const present = new Set(projectToolsOf(store, repo).map(one => one.name));
124
125
  for (const tool of kit.tools.filter(one => present.has(one.tool))) {
125
126
  try {
126
- await grantTool(store, mate, tool.tool, by, now, io);
127
+ await grantTool(store, mate, tool.tool, by, now, io, options);
127
128
  }
128
129
  catch { /* it can be given the tool on its page */ }
129
130
  }
130
131
  return made;
131
132
  }
132
- /** The same, all at once (a chat confirmation can't wait on a tool): its subagent is given the kit's connected tools by the names their last test found, and each is listed properly on its first turn. */
133
- export function setUpKitNow(store, kit, repo, by, now, dir, grantByName = false) {
133
+ /** The same, all at once and without its tools (a chat confirmation is never a grant's proof): its subagent, its flow and its buttons. */
134
+ export function setUpKitNow(store, kit, repo, by, now, dir) {
134
135
  const had = kitInstalled(store, kit, repo);
135
136
  if (had !== null)
136
137
  return { ok: true, said: `${kit.name} is already set up here.`, flow: had.flow.id, subagent: had.mate.id };
@@ -155,9 +156,6 @@ export function setUpKitNow(store, kit, repo, by, now, dir, grantByName = false)
155
156
  const flow = store.getFlow(flowId);
156
157
  for (const button of kit.buttons)
157
158
  addFlowTriggerTo(store, flow, { kind: "button", label: button.label, questions: button.questions, zone: definition.start }, by, now, dir);
158
- if (grantByName)
159
- for (const tool of projectToolsOf(store, repo).filter(one => kit.tools.some(each => each.tool === one.name)))
160
- grantListed(store, mate, tool, null, by, now);
161
159
  return { ok: true, said: `${kit.name} is ready: ${nameOf(mate)} works its ${kit.flowName} flow.`, flow: flowId, subagent: mate.id };
162
160
  }
163
161
  /** What's set up and what's left, read fresh each time. */
@@ -205,11 +203,11 @@ export function addKitSample(store, kit, repo, by, now) {
205
203
  return added.ok ? { ok: true, href: flowCardHref(set.flow.id, added.card) } : { ok: false, said: added.message };
206
204
  }
207
205
  /** Bring new issues from the project's GitHub repository into the kit's flow (issues from anyone, polled). */
208
- export function addKitGithubTrigger(store, kit, repo, by, now, dir) {
206
+ export function addKitGithubTrigger(store, kit, repo, by, now, dir, proved = null) {
209
207
  const set = kitInstalled(store, kit, repo);
210
208
  const github = githubRepoOf(repo);
211
209
  if (set === null || github === null)
212
210
  return { ok: false, said: "This project isn't on GitHub." };
213
- const made = addFlowTriggerTo(store, set.flow, { kind: "github", repo: github, watch: "issues", from: "anyone", delivery: "poll", zone: flowDefinitionOf(set.flow)?.start }, by, now, dir);
211
+ const made = addFlowTriggerTo(store, set.flow, { kind: "github", repo: github, watch: "issues", from: "anyone", delivery: "poll", zone: flowDefinitionOf(set.flow)?.start }, by, now, dir, proved === null ? {} : { widening: proved });
214
212
  return made.ok ? { ok: true, said: `New issues on ${github} now come into ${kit.flowName}.` } : { ok: false, said: made.message };
215
213
  }
@@ -1,6 +1,9 @@
1
1
  import { type KnowledgeDraft, type KnowledgeSelection, type KnowledgeView } from './project-knowledge.js';
2
2
  import { type Html } from './html.js';
3
- export declare const KNOWLEDGE_CSS = ".knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span{padding:.15rem .55rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}";
3
+ /** A section as a frame (after ReUI's Frame, MIT): its heading on the raised well, its body one paper panel. The
4
+ * cards inside become hairline-divided rows of that panel, never cards in a card. */
5
+ export declare function framedSection(heading: string, body: Html | string, extra?: string): Html;
6
+ export declare const KNOWLEDGE_CSS = ".knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span+span::before{content:\"\u00B7\";margin-right:.4rem}.knowledge.frame{margin:0 0 16px}.knowledge.frame>.frame-head h2,.knowledge.frame>summary.frame-head{margin:0;font-size:13px;font-weight:600;line-height:1.4}.knowledge.frame>summary.frame-head{min-height:44px;padding:4px 10px}.knowledge.frame>.frame-panel{min-width:0}.knowledge.frame>.frame-panel>:first-child{margin-top:0}.knowledge.frame>.frame-panel>:last-child{margin-bottom:0}.knowledge .frame-panel>details.card,.knowledge .frame-panel .learning .card{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.knowledge .frame-panel>details.card+details.card,.knowledge .frame-panel .learning .card+.card{border-top:1px solid var(--so-line);padding-top:4px}.knowledge.frame>summary.frame-head{justify-content:flex-start}.knowledge form.field-row{grid-template-columns:minmax(0,1fr) auto;align-items:end}.knowledge form.field-row>label,.knowledge form.field-row :is(input,select,button){margin:0}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}";
4
7
  export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string): Html;
5
8
  export declare function knowledgeContextHtml(snapshot: KnowledgeSelection | null): Html | '';
6
9
  import type { Decision, MemoryHit } from './project-memory.js';
@@ -2,7 +2,12 @@ import { STALE_KNOWLEDGE } from './project-knowledge.js';
2
2
  import { html, postForm } from './html.js';
3
3
  // Hidden fields as they always were: a missing value is an empty field.
4
4
  const fields = (data) => Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v ?? '')]));
5
- export const KNOWLEDGE_CSS = `.knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span{padding:.15rem .55rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}`;
5
+ /** A section as a frame (after ReUI's Frame, MIT): its heading on the raised well, its body one paper panel. The
6
+ * cards inside become hairline-divided rows of that panel, never cards in a card. */
7
+ export function framedSection(heading, body, extra = '') {
8
+ return html `<section class="${`knowledge frame${extra ? ` ${extra}` : ''}`}"><div class="frame-head"><h2>${heading}</h2></div><div class="frame-panel">${body}</div></section>`;
9
+ }
10
+ export const KNOWLEDGE_CSS = `.knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span+span::before{content:"·";margin-right:.4rem}.knowledge.frame{margin:0 0 16px}.knowledge.frame>.frame-head h2,.knowledge.frame>summary.frame-head{margin:0;font-size:13px;font-weight:600;line-height:1.4}.knowledge.frame>summary.frame-head{min-height:44px;padding:4px 10px}.knowledge.frame>.frame-panel{min-width:0}.knowledge.frame>.frame-panel>:first-child{margin-top:0}.knowledge.frame>.frame-panel>:last-child{margin-bottom:0}.knowledge .frame-panel>details.card,.knowledge .frame-panel .learning .card{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.knowledge .frame-panel>details.card+details.card,.knowledge .frame-panel .learning .card+.card{border-top:1px solid var(--so-line);padding-top:4px}.knowledge.frame>summary.frame-head{justify-content:flex-start}.knowledge form.field-row{grid-template-columns:minmax(0,1fr) auto;align-items:end}.knowledge form.field-row>label,.knowledge form.field-row :is(input,select,button){margin:0}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}`;
6
11
  export function knowledgeHtml(view, csrf, canManage, draft, error) {
7
12
  const manage = canManage && !!csrf;
8
13
  const base = { repo: view.repo, identity: view.identity, revision: view.revision };
@@ -18,7 +23,7 @@ export function knowledgeHtml(view, csrf, canManage, draft, error) {
18
23
  }
19
24
  const instructions = html `<pre class="knowledge-instructions">${view.knowledge.instructions || 'No project instructions yet.'}</pre>`;
20
25
  const editor = manage ? html `<details class="knowledge-editor"${draft?.instructions !== undefined ? html ` open` : ''}><summary>${view.knowledge.instructions ? 'Edit instructions' : 'Add instructions'}</summary>${form('instructions', html `<textarea aria-label="Project instructions" name="instructions" rows="5" maxlength="4000" placeholder="Goals, preferences, and what good work looks like…">${draft?.instructions ?? view.knowledge.instructions}</textarea><button>Save instructions</button>`)}</details>` : '';
21
- return html `<section class="knowledge knowledge-library">${error ? html `<p class="problem" role="alert">${error}</p>` : ''}<section class="knowledge-intro"><h2>Instructions</h2>${instructions}${editor}</section><section><h2>References</h2>${refs.length ? refs : html `<p class="meta">Add a spec, design guide, or useful project document.</p>`}${manage ? html `<details class="card"${draft?.title && !draft.id ? html ` open` : ''}><summary>Add reference</summary>${form('save', referenceFields(draft?.id ? {} : draft ?? {}))}</details>` : ''}</section>${history.length ? html `<details><summary>Change history</summary><ol>${history}</ol></details>` : ''}</section>`;
26
+ return html `<section class="knowledge knowledge-library">${error ? html `<p class="problem" role="alert">${error}</p>` : ''}${framedSection('Instructions', html `${instructions}${editor}`, 'knowledge-intro')}${framedSection('References', html `${refs.length ? refs : html `<p class="meta">Add a spec, design guide, or useful project document.</p>`}${manage ? html `<details class="card"${draft?.title && !draft.id ? html ` open` : ''}><summary>Add reference</summary>${form('save', referenceFields(draft?.id ? {} : draft ?? {}))}</details>` : ''}`)}${history.length ? html `<details class="knowledge frame"><summary class="frame-head">Change history</summary><div class="frame-panel"><ol>${history}</ol></div></details>` : ''}</section>`;
22
27
  }
23
28
  export function knowledgeContextHtml(snapshot) {
24
29
  if (!snapshot)
@@ -32,17 +37,17 @@ export function decisionsHtml(repo, decisions, csrf, canManage, draft, error) {
32
37
  const form = (action, body, extra = {}) => postForm('/settings/knowledge/decision', body, { hidden: fields({ ...base, action, ...extra }) });
33
38
  const rows = decisions.map(d => html `<details class="card decision" id="decision-${d.id}"><summary><strong>${d.claim}</strong> <span class="meta">#${d.id} · ${d.decidedBy} · ${d.decidedAt.slice(0, 10)}</span></summary><p>${d.why}</p><p class="meta">${d.sourceKind}${d.sourceRef ? ` · ${d.sourceRef}` : ''}${d.supersedes === null ? '' : ` · replaces #${d.supersedes}`}</p>${manage ? form('retire', html `<label>Why it no longer holds<input name="reason" maxlength="500" required></label><button type="submit">Retire</button>`, { decision: d.id }) : ''}</details>`);
34
39
  const add = manage ? html `<details class="knowledge-editor"${draft?.claim !== undefined ? html ` open` : ''}><summary>Record a decision</summary>${form('record', html `<label>The choice, in one sentence<input name="claim" maxlength="240" value="${draft?.claim}" required></label><label>Why<textarea name="why" rows="3" maxlength="2000" required>${draft?.why}</textarea></label><button type="submit">Record</button>`)}</details>` : '';
35
- return html `<section class="knowledge"><h2>Decisions</h2>${error ? html `<p class="problem" role="alert">${error}</p>` : ''}${rows.length ? rows : html `<p class="meta">No decisions yet. The lead suggests one when a conversation settles a choice.</p>`}${add}</section>`;
40
+ return framedSection('Decisions', html `${error ? html `<p class="problem" role="alert">${error}</p>` : ''}${rows.length ? rows : html `<p class="meta">No decisions yet. The lead suggests one when a conversation settles a choice.</p>`}${add}`);
36
41
  }
37
42
  export function memorySearchHtml(repo, query, hits) {
38
43
  const kind = (h) => h.kind === 'decision' ? html `<a href="#decision-${h.ref}">decision #${h.ref}</a>` : h.kind === 'conversation' ? 'conversation' : h.kind;
39
44
  const results = hits === null ? '' : hits.length === 0 ? html `<p class="meta">Nothing in project memory matches.</p>` : html `<ul class="memory-hits">${hits.map(h => html `<li><strong>${kind(h)}</strong> · ${h.title}<br><span class="meta">${h.snippet}</span></li>`)}</ul>`;
40
- return html `<section class="knowledge memory-search"><form method="get" action="/settings/knowledge"><input type="hidden" name="repo" value="${repo}"><label>Search project memory<input name="memory" value="${query}" maxlength="300" placeholder="Search decisions, references, lessons…" type="search" autocomplete="off"></label><button type="submit">Search</button></form>${results}</section>`;
45
+ return framedSection('Search project memory', html `<form class="field-row" method="get" action="/settings/knowledge"><input type="hidden" name="repo" value="${repo}"><label><span class="visually-hidden">Search project memory</span><input name="memory" value="${query}" maxlength="300" placeholder="Search decisions, references, lessons…" type="search" autocomplete="off"></label><button type="submit">Search</button></form>${results}`, 'memory-search');
41
46
  }
42
47
  export function proposalsHtml(repo, proposals, status, csrf, canManage) {
43
48
  const manage = canManage && !!csrf;
44
49
  const line = status === null ? '' : html `<p class="meta memory-stats"><span>${status.sessions.analyzed}/${status.sessions.total} sessions read${status.sessions.failed ? ` · ${status.sessions.failed} failed` : ''}</span><span>${status.openGaps} open gap${status.openGaps === 1 ? '' : 's'}</span><span>Instructions ${status.instructionBytes}/${status.budgetBytes} bytes</span></p><details class="settings-more"><summary>Read new sessions</summary><p class="meta">Run <code>toolroll memory propose --repo ${repo}</code> on this computer.</p></details>`;
45
50
  const decide = (p, decision, label) => postForm('/settings/knowledge/proposal', html `<button type="submit">${label}</button>`, { attrs: { class: 'inline' }, hidden: fields({ repo, proposal: p.id, decision }) });
46
51
  const cards = proposals.map(p => html `<details class="card proposal" open><summary><strong>${p.title}</strong> <span class="meta">#${p.id} · ${p.kind} · ${p.sessions} sessions</span></summary><p>${p.rationale}</p>${p.beforeText ? html `<pre class="knowledge-instructions">- ${p.beforeText}</pre>` : ''}${p.afterText ? html `<pre class="knowledge-instructions">+ ${p.afterText}</pre>` : ''}<ul class="memory-evidence">${p.evidence.map(ev => html `<li>“${ev.quote.slice(0, 200)}” <span class="meta">${ev.session.split(':')[0] ?? ''}</span></li>`)}</ul>${manage ? html `${decide(p, 'accept', 'Accept')} ${decide(p, 'reject', 'Reject')}` : ''}</details>`);
47
- return html `<section class="knowledge"><h2>Proposed from sessions</h2>${line}${cards.length ? cards : html `<p class="meta">Nothing to review. Suggestions appear when the same mistake shows up in two sessions.</p>`}</section>`;
52
+ return framedSection('Proposed from sessions', html `${line}${cards.length ? cards : html `<p class="meta">Nothing to review. Suggestions appear when the same mistake shows up in two sessions.</p>`}`);
48
53
  }
@@ -20,11 +20,14 @@ export type LeadProgress = {
20
20
  step: number;
21
21
  id: string;
22
22
  outcome: LeadToolOutcome;
23
- } | {
23
+ }
24
+ /** `final`: the provider's finished answer for this step, sent once and secret-checked whole. */
25
+ | {
24
26
  kind: "text";
25
27
  turn: number;
26
28
  step: number;
27
29
  text: string;
30
+ final?: true;
28
31
  };
29
32
  export type LeadToolOutcome = {
30
33
  state: "succeeded";
@@ -1,6 +1,7 @@
1
1
  /** Live progress of one lead turn (chat streaming): what the lead is doing
2
2
  * and the reply as it is written. Nothing here is durable or authoritative —
3
3
  * the finished turn's saved message and cards remain the record. */
4
+ import { topLevelEvent } from "./stream-scope.js";
4
5
  /** Plain words for each tool the lead uses, as the person watching reads them. */
5
6
  const TOOL_LABELS = {
6
7
  get_brief: "Catching up on your work",
@@ -127,7 +128,8 @@ export function claudeStreamReader(onText, capBytes = 1_048_576) {
127
128
  catch {
128
129
  continue;
129
130
  }
130
- if (!record(event) || event["type"] !== "stream_event" || !record(event["event"]))
131
+ // A subagent's frames (parent_tool_use_id set) are not the lead's reply: they never start, feed or end it.
132
+ if (!record(event) || !topLevelEvent(event) || event["type"] !== "stream_event" || !record(event["event"]))
131
133
  continue;
132
134
  const inner = event["event"];
133
135
  if (inner["type"] === "content_block_start") {
package/dist/lead.js CHANGED
@@ -486,6 +486,10 @@ export async function runLeadTurn(input) {
486
486
  return fail("malformed-reply", LEAD_FAILURE_COPY.unreadableUnknownCost, true);
487
487
  }
488
488
  const answer = result.answer;
489
+ // The streamed reply's finished text, once, secret-checked whole: only now may its last word show live.
490
+ if (!direct && input.onProgress !== undefined && !controller.signal.aborted && answer.text !== "" && scanForSecrets(answer.text).length === 0) {
491
+ progress({ kind: "text", turn: turnId, step: steps, text: answer.text, final: true });
492
+ }
489
493
  // Usage that cannot be true — more input tokens than bytes sent — is a
490
494
  // malformed reply with unknown cost, never a number to settle by.
491
495
  if (direct && answer.tokensIn > requestBytes) {
@@ -128,6 +128,8 @@ export type LiveRoomOptions = {
128
128
  /** A room whose read is costly reads at most once per this window after a signal; every viewer is still re-proved
129
129
  * the moment a signal arrives (and again at the read). */
130
130
  readSettleMs?: number;
131
+ /** Told when a room's last viewer has left and the room is gone (state kept per room can go with it). */
132
+ onEmpty?: (key: unknown) => void;
131
133
  };
132
134
  /**
133
135
  * Rooms of open pages that hear the bus. `presence` says who else is here, as
package/dist/live-bus.js CHANGED
@@ -309,6 +309,7 @@ export function createLiveRooms(fingerprint, presence, options = {}) {
309
309
  if (room.reading !== null)
310
310
  clearTimeout(room.reading);
311
311
  rooms.delete(key);
312
+ options.onEmpty?.(key);
312
313
  if (rooms.size === 0 && unsubscribe !== null) {
313
314
  unsubscribe();
314
315
  unsubscribe = null;
@@ -0,0 +1,52 @@
1
+ import { type LiveRecord } from "./live-records.js";
2
+ export declare const CURSOR_CAP: number;
3
+ /** Longer than any real key block: past this much of a stream's text with no END marker, its key state is known again. */
4
+ export declare const UNKNOWN_KEY_BYTES: number;
5
+ export type ChildView = {
6
+ k: "child";
7
+ s: number;
8
+ at?: string;
9
+ parent: string | null;
10
+ via?: number;
11
+ desc?: string;
12
+ } | {
13
+ k: "end";
14
+ s: number;
15
+ at?: string;
16
+ ok: boolean | null;
17
+ } | {
18
+ k: "over";
19
+ }
20
+ /** The file stopped at its size cap: nothing more will be written. */
21
+ | {
22
+ k: "cap";
23
+ } | (LiveRecord & {
24
+ s: number;
25
+ });
26
+ export type ChildWindow = {
27
+ ok: true;
28
+ records: ChildView[];
29
+ cursor: string;
30
+ /** A cursor was given but didn't hold (forged, stale, another run's or file's): this read started over. */
31
+ restarted: boolean;
32
+ /** Output before this read's start isn't shown (the read began at a checkpoint, or waits for one). */
33
+ earlier: boolean;
34
+ /** Records that failed a check: counted, never shown. */
35
+ quarantined: number;
36
+ /** Lines too long to show that were skipped. */
37
+ skipped: number;
38
+ /** More is already written: read again. */
39
+ more: boolean;
40
+ } | {
41
+ ok: false;
42
+ reason: "missing" | "unavailable";
43
+ };
44
+ /** Seal a reader's state under the caller's key (the same form for the run's own file and its subagents'). */
45
+ export declare function sealCursor(state: object, key: Buffer): string;
46
+ /** A sealed state's object when the seal holds and it is within the cap; null otherwise (the caller re-checks it). */
47
+ export declare function openCursor(cursor: string, key: Buffer): Record<string, unknown> | null;
48
+ /** One window of a run's subagents' file. `commands` is the run's own captured answer (store.ts runLiveCommands). */
49
+ export declare function readChildWindow(root: string, runId: number, cursor: string | null, options: {
50
+ key: Buffer;
51
+ commands: boolean;
52
+ }): ChildWindow;