toolroll 0.9.63 → 0.9.67
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 +108 -0
- package/README.md +33 -5
- package/dist/acp-agents.d.ts +43 -0
- package/dist/acp-agents.js +62 -0
- package/dist/acp-attest.d.ts +44 -0
- package/dist/acp-attest.js +269 -0
- package/dist/acp-check.d.ts +28 -0
- package/dist/acp-check.js +410 -0
- package/dist/acp-client.d.ts +163 -0
- package/dist/acp-client.js +738 -0
- package/dist/acp-inspect.d.ts +23 -0
- package/dist/acp-inspect.js +109 -0
- package/dist/agent-board.d.ts +19 -6
- package/dist/agent-board.js +30 -9
- package/dist/agent-fence.d.ts +117 -2
- package/dist/agent-fence.js +439 -12
- package/dist/agent-pulse.js +9 -2
- package/dist/agent-state-words.d.ts +19 -0
- package/dist/agent-state-words.js +20 -0
- package/dist/agent-state.d.ts +65 -0
- package/dist/agent-state.js +229 -0
- package/dist/agentconfig.js +39 -13
- package/dist/agents-board.d.ts +34 -2
- package/dist/agents-board.js +110 -1
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-delivery.d.ts +14 -0
- package/dist/assignment-delivery.js +13 -0
- package/dist/assignment.js +13 -0
- package/dist/attest.js +7 -0
- package/dist/backup-ui.js +2 -2
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +94 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +228 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +42 -38
- package/dist/browser-shell.js +6 -3
- package/dist/browser-workspace.d.ts +235 -1
- package/dist/browser-workspace.js +60 -0
- package/dist/builder.d.ts +10 -1
- package/dist/builder.js +87 -24
- package/dist/chat-actions.d.ts +13 -1
- package/dist/chat-actions.js +197 -15
- package/dist/chat-ask.d.ts +4 -0
- package/dist/chat-ask.js +14 -0
- package/dist/chat-channel.js +20 -0
- package/dist/chat-continuity.js +5 -4
- package/dist/chat-decide.d.ts +0 -14
- package/dist/chat-decide.js +4 -1
- package/dist/chat-delivery.d.ts +9 -0
- package/dist/chat-delivery.js +45 -6
- package/dist/chat-messages.d.ts +8 -0
- package/dist/chat-messages.js +36 -4
- package/dist/check-levels-ui.js +2 -2
- package/dist/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +19 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +4 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +21 -2
- package/dist/coding-ui.js +6 -4
- package/dist/console-instance.d.ts +124 -0
- package/dist/console-instance.js +337 -0
- package/dist/contracts/chat-actions.d.ts +16 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/chat-content.d.ts +19 -0
- package/dist/contracts/chat-content.js +6 -0
- package/dist/contracts/console-api.d.ts +13 -4
- package/dist/contracts/console-api.js +13 -4
- package/dist/contracts/flow-card.d.ts +4 -0
- package/dist/contracts/flow-card.js +3 -0
- package/dist/contracts/flow.d.ts +46 -0
- package/dist/contracts/flow.js +11 -2
- package/dist/contracts/lead-tools.d.ts +92 -0
- package/dist/contracts/lead-tools.js +13 -0
- package/dist/contracts/provider.d.ts +11 -1
- package/dist/contracts/provider.js +1 -1
- package/dist/contracts/route.d.ts +76 -1
- package/dist/contracts/route.js +1 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/control-setup.d.ts +1 -1
- package/dist/control-ui.js +2 -2
- package/dist/converse.d.ts +16 -0
- package/dist/converse.js +32 -4
- package/dist/demo-chat.js +4 -4
- package/dist/demo.js +2 -2
- package/dist/desktop-update-gate.js +6 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +4 -1
- package/dist/doctor.js +1 -1
- package/dist/egress-bridge.d.ts +17 -0
- package/dist/egress-bridge.js +111 -0
- package/dist/egress.d.ts +119 -0
- package/dist/egress.js +423 -0
- package/dist/exec.d.ts +78 -0
- package/dist/exec.js +367 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-engine.js +24 -5
- package/dist/flow-gallery-ui.js +2 -2
- package/dist/flow-gallery.js +27 -8
- package/dist/flow-share.js +16 -5
- package/dist/flow-triggers.d.ts +77 -8
- package/dist/flow-triggers.js +517 -70
- package/dist/flows-cli.js +8 -2
- package/dist/flows-ui.d.ts +0 -2
- package/dist/flows-ui.js +52 -4
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/invoke.d.ts +35 -0
- package/dist/invoke.js +347 -28
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/lead-capabilities.js +3 -2
- package/dist/lead-contract.js +2 -0
- package/dist/lead-doors.d.ts +2 -0
- package/dist/lead-doors.js +7 -0
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +58 -2
- package/dist/lead-progress.js +1 -0
- package/dist/lead-queue-runtime.d.ts +46 -0
- package/dist/lead-queue-runtime.js +191 -0
- package/dist/lead-queue.d.ts +104 -0
- package/dist/lead-queue.js +187 -0
- package/dist/lead-sweep.d.ts +67 -0
- package/dist/lead-sweep.js +99 -0
- package/dist/lead-tools.d.ts +22 -0
- package/dist/lead-tools.js +152 -1
- package/dist/lead.d.ts +13 -5
- package/dist/lead.js +89 -29
- package/dist/limits-ui.d.ts +8 -1
- package/dist/limits-ui.js +26 -0
- package/dist/live-bus.d.ts +8 -0
- package/dist/live-bus.js +40 -4
- package/dist/live-detail.d.ts +27 -0
- package/dist/live-detail.js +57 -0
- package/dist/live-notes.d.ts +20 -0
- package/dist/live-notes.js +47 -0
- package/dist/live-records.d.ts +140 -0
- package/dist/live-records.js +935 -0
- package/dist/live-terminal.d.ts +269 -0
- package/dist/live-terminal.js +686 -0
- package/dist/live.d.ts +38 -27
- package/dist/live.js +142 -124
- package/dist/mcp-oauth.d.ts +6 -3
- package/dist/mcp-oauth.js +8 -4
- package/dist/memory-pass.js +2 -2
- package/dist/mobile-viewport.d.ts +5 -1
- package/dist/mobile-viewport.js +18 -2
- package/dist/model-catalog.js +4 -0
- package/dist/monitoring-ui.js +2 -2
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +11 -4
- package/dist/operate.d.ts +4 -0
- package/dist/operate.js +335 -19
- package/dist/passwordless.d.ts +23 -0
- package/dist/passwordless.js +26 -0
- package/dist/peek-cli.d.ts +12 -4
- package/dist/peek-cli.js +19 -11
- package/dist/phase-routing.js +10 -3
- package/dist/planner-source.d.ts +3 -0
- package/dist/planner-source.js +14 -0
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +7 -1
- package/dist/policy-ui.js +2 -2
- package/dist/policy.d.ts +1 -20
- package/dist/policy.js +29 -2
- package/dist/project-delete-ui.d.ts +18 -0
- package/dist/project-delete-ui.js +60 -9
- package/dist/project-delete.js +2 -2
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +5 -3
- package/dist/project-tools.js +12 -9
- package/dist/provider-auth.d.ts +4 -3
- package/dist/provider-auth.js +5 -4
- package/dist/provider-connection.d.ts +2 -1
- package/dist/provider-connection.js +5 -0
- package/dist/provider.d.ts +25 -2
- package/dist/provider.js +63 -9
- package/dist/pull-request-ui.js +2 -2
- package/dist/push.js +1 -1
- package/dist/quick-start.d.ts +24 -0
- package/dist/quick-start.js +127 -0
- package/dist/release-adapters.d.ts +6 -0
- package/dist/release-adapters.js +70 -3
- package/dist/release.d.ts +10 -1
- package/dist/release.js +34 -15
- package/dist/request-budget-ui.js +2 -2
- package/dist/result-follow-ups.js +3 -2
- package/dist/result-review.d.ts +1 -1
- package/dist/result-review.js +9 -3
- package/dist/retention-ui.js +2 -2
- package/dist/retention.js +10 -1
- package/dist/run-efficiency.d.ts +73 -0
- package/dist/run-efficiency.js +228 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.js +22 -1
- package/dist/scope.d.ts +183 -5
- package/dist/scope.js +256 -31
- package/dist/scout-net.d.ts +4 -1
- package/dist/scout-net.js +37 -12
- package/dist/scout.d.ts +20 -6
- package/dist/scout.js +27 -3
- package/dist/serve.js +248 -40
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +16 -2
- package/dist/server/chrome.js +63 -11
- package/dist/server/console.css +107 -42
- package/dist/server/flows.js +38 -8
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +23 -9
- package/dist/server/http.d.ts +7 -0
- package/dist/server/http.js +3 -2
- package/dist/server/live.d.ts +34 -1
- package/dist/server/live.js +39 -17
- package/dist/server/pages-assets.js +5 -0
- package/dist/server/pages.js +90 -13
- package/dist/server/people-tokens.d.ts +3 -0
- package/dist/server/people-tokens.js +55 -17
- package/dist/server/remote-hooks.js +12 -6
- package/dist/server/remote.d.ts +2 -1
- package/dist/server/remote.js +14 -5
- package/dist/server/render-chat.d.ts +12 -3
- package/dist/server/render-chat.js +47 -16
- package/dist/server/render-pages.d.ts +32 -4
- package/dist/server/render-pages.js +83 -13
- package/dist/server/render-people.d.ts +3 -2
- package/dist/server/render-people.js +3 -2
- package/dist/server/render-results.d.ts +18 -2
- package/dist/server/render-results.js +37 -28
- package/dist/server/render-settings.js +6 -5
- package/dist/server/render-tasks.d.ts +35 -10
- package/dist/server/render-tasks.js +111 -27
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +2 -1
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +47 -5
- package/dist/server/runtime.d.ts +33 -2
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +142 -10
- package/dist/server/sweeps.d.ts +4 -0
- package/dist/server/sweeps.js +58 -0
- package/dist/server/task-views.d.ts +2 -1
- package/dist/server/task-views.js +96 -6
- package/dist/server/tasks.js +327 -78
- package/dist/server/terminals.d.ts +31 -0
- package/dist/server/terminals.js +365 -0
- package/dist/setup-guide.js +3 -2
- package/dist/slack-chat.js +4 -1
- package/dist/spend-ui.js +3 -3
- package/dist/spend.js +2 -1
- package/dist/steer-capabilities.d.ts +32 -0
- package/dist/steer-capabilities.js +60 -0
- package/dist/steer-controls.d.ts +14 -0
- package/dist/steer-controls.js +36 -0
- package/dist/steer-status.js +2 -2
- package/dist/storage-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +440 -15
- package/dist/store.js +1072 -52
- package/dist/subagent-desk.js +1 -1
- package/dist/summary.js +3 -0
- package/dist/task-source.d.ts +58 -0
- package/dist/task-source.js +93 -0
- package/dist/team-leads.js +1 -1
- package/dist/teams-chat.js +4 -1
- package/dist/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- package/dist/telegram-lead.d.ts +3 -0
- package/dist/telegram-lead.js +44 -7
- package/dist/telegram.d.ts +11 -2
- package/dist/telegram.js +51 -17
- package/dist/terminal-catalog.d.ts +11 -0
- package/dist/terminal-catalog.js +129 -0
- package/dist/text-limits.d.ts +2 -0
- package/dist/text-limits.js +2 -0
- package/dist/tool-broker.d.ts +14 -0
- package/dist/tool-broker.js +67 -2
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- package/dist/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- package/dist/webhook-limits.d.ts +23 -0
- package/dist/webhook-limits.js +30 -0
- package/dist/webhook-readers.d.ts +56 -0
- package/dist/webhook-readers.js +138 -0
- package/dist/webhook-signing.d.ts +27 -0
- package/dist/webhook-signing.js +119 -0
- package/dist/work-index.d.ts +29 -0
- package/dist/work-index.js +16 -2
- package/dist/workspace-revision.js +3 -1
- package/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +15 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a run went (docs/plans/run-efficiency.md): derived from the run's own records, display only. It never changes a
|
|
3
|
+
* completion, an approval, a charge or a check verdict, and it claims nothing its sources don't hold — an unreported
|
|
4
|
+
* number is unknown (null, shown as a dash), never zero.
|
|
5
|
+
*
|
|
6
|
+
* Sources, and what each can say: the run row (wall times, tokens, the provider's raw usage with its cache counts, the
|
|
7
|
+
* agent's turns where reported) is kept with the run; the live records (`<evidence>/live/<run>.jsonl`, display-only,
|
|
8
|
+
* kept 24 hours) are read once, bounded, through the descriptor-safe live reader, for step counts and observed step
|
|
9
|
+
* times. No command text is ever stored here: repeats are counted, never quoted.
|
|
10
|
+
*/
|
|
11
|
+
import type { Run, Store } from "./store.js";
|
|
12
|
+
import type { LiveActivityKind, LiveRecord } from "./live-records.js";
|
|
13
|
+
export declare const RUN_EFFICIENCY_VERSION = 3;
|
|
14
|
+
/** Budgets for one derivation: never more than the live file's own cap, and a bounded record count. */
|
|
15
|
+
export declare const EFFICIENCY_READ_BYTES: number;
|
|
16
|
+
export declare const EFFICIENCY_RECORDS = 20000;
|
|
17
|
+
export type StepsState = "complete" | "truncated" | "expired" | "absent";
|
|
18
|
+
export type RunEfficiency = {
|
|
19
|
+
version: number;
|
|
20
|
+
/** Waiting to start (claimed → provider started) and the agent (provider started → finished); null when unrecorded. */
|
|
21
|
+
time: {
|
|
22
|
+
waitMs: number | null;
|
|
23
|
+
agentMs: number | null;
|
|
24
|
+
};
|
|
25
|
+
turns: number | null;
|
|
26
|
+
/** Tokens in counts everything the model read (cached included); `cacheShare` is read-from-cache ÷ that, 0–1. */
|
|
27
|
+
usage: {
|
|
28
|
+
in: number | null;
|
|
29
|
+
out: number | null;
|
|
30
|
+
cacheRead: number | null;
|
|
31
|
+
cacheWrite: number | null;
|
|
32
|
+
cacheShare: number | null;
|
|
33
|
+
provider: string;
|
|
34
|
+
model: string | null;
|
|
35
|
+
};
|
|
36
|
+
steps: {
|
|
37
|
+
state: StepsState;
|
|
38
|
+
reason: string | null;
|
|
39
|
+
total: number | null;
|
|
40
|
+
failed: number | null;
|
|
41
|
+
unknownDuration: number | null;
|
|
42
|
+
byKind: Partial<Record<LiveActivityKind, number>>;
|
|
43
|
+
longest: {
|
|
44
|
+
kind: LiveActivityKind;
|
|
45
|
+
ms: number;
|
|
46
|
+
}[];
|
|
47
|
+
/** Repeated activity: the same step (kind and its full recorded input) at least three times. Null when the inputs
|
|
48
|
+
* can't be compared (command detail off, or a target hidden or shortened) — said, never guessed. */
|
|
49
|
+
repeats: {
|
|
50
|
+
kind: LiveActivityKind;
|
|
51
|
+
count: number;
|
|
52
|
+
}[] | null;
|
|
53
|
+
repeatsReason: string | null;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/** Tokens and cache from the provider's own usage report, read per provider (their counts mean different things). */
|
|
57
|
+
export declare function usageOf(run: Pick<Run, "provider" | "model" | "tokensIn" | "tokensOut"> & {
|
|
58
|
+
usageJson?: string | null;
|
|
59
|
+
}): RunEfficiency["usage"];
|
|
60
|
+
/** The live records' steps, bounded: counts by kind, failures, observed times and repeats (never their words). */
|
|
61
|
+
/** `measured`: the recorder saw this provider's steps start and end as they happened (Claude's stream does); for the
|
|
62
|
+
* others their times are partly made up (Codex reports a step when it completes, ACP agents may report late), so no
|
|
63
|
+
* duration is claimed for them. */
|
|
64
|
+
export declare function stepsOf(records: Iterable<LiveRecord>, truncated: string | null, measured?: boolean): RunEfficiency["steps"];
|
|
65
|
+
/** Derive a finished run's How it ran from its records. */
|
|
66
|
+
export declare function deriveRunEfficiency(run: Run & {
|
|
67
|
+
usageJson?: string | null;
|
|
68
|
+
}, evidenceRoot: string, now: Date): RunEfficiency;
|
|
69
|
+
/**
|
|
70
|
+
* A finished run's How it ran: the saved one when it was derived by this version, else derived now and saved (once:
|
|
71
|
+
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved.
|
|
72
|
+
*/
|
|
73
|
+
export declare function runEfficiencyOf(store: Store, evidenceRoot: string, runId: number, now: Date): RunEfficiency | null;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { LIVE_RETAIN_MS, legacyLiveLogPath, liveLogPath, liveWindowRecords } from "./live.js";
|
|
4
|
+
export const RUN_EFFICIENCY_VERSION = 3;
|
|
5
|
+
/** Budgets for one derivation: never more than the live file's own cap, and a bounded record count. */
|
|
6
|
+
export const EFFICIENCY_READ_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
export const EFFICIENCY_RECORDS = 20_000;
|
|
8
|
+
const LONGEST = 5;
|
|
9
|
+
const num = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
10
|
+
const msBetween = (from, to) => {
|
|
11
|
+
if (from === null || to === null)
|
|
12
|
+
return null;
|
|
13
|
+
const ms = Date.parse(to) - Date.parse(from);
|
|
14
|
+
return Number.isFinite(ms) && ms >= 0 ? ms : null;
|
|
15
|
+
};
|
|
16
|
+
/** Tokens and cache from the provider's own usage report, read per provider (their counts mean different things). */
|
|
17
|
+
export function usageOf(run) {
|
|
18
|
+
let raw = {};
|
|
19
|
+
try {
|
|
20
|
+
const parsed = run.usageJson == null ? null : JSON.parse(run.usageJson);
|
|
21
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
|
|
22
|
+
raw = parsed;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
raw = {};
|
|
26
|
+
}
|
|
27
|
+
const base = { provider: run.provider, model: run.model ?? null, out: num(raw["output_tokens"]) ?? run.tokensOut ?? null };
|
|
28
|
+
if (run.provider === "claude") {
|
|
29
|
+
// Claude's input_tokens leave out what was read from or written to the cache: the three add up to what it read, so
|
|
30
|
+
// all three must be reported before a total or a share is claimed (a missing one is unknown, never zero).
|
|
31
|
+
const input = num(raw["input_tokens"]), read = num(raw["cache_read_input_tokens"]), write = num(raw["cache_creation_input_tokens"]);
|
|
32
|
+
const total = input === null || read === null || write === null ? null : input + read + write;
|
|
33
|
+
return { ...base, in: total, cacheRead: read, cacheWrite: write, cacheShare: total === null || total === 0 ? null : read / total };
|
|
34
|
+
}
|
|
35
|
+
if (run.provider === "codex") {
|
|
36
|
+
// Codex's input_tokens already include what came from its cache: both reported, or no share.
|
|
37
|
+
const input = num(raw["input_tokens"]), read = num(raw["cached_input_tokens"]);
|
|
38
|
+
return { ...base, in: input ?? run.tokensIn ?? null, cacheRead: read, cacheWrite: null,
|
|
39
|
+
cacheShare: read === null || input === null || input === 0 || read > input ? null : read / input };
|
|
40
|
+
}
|
|
41
|
+
return { ...base, in: run.tokensIn ?? null, cacheRead: null, cacheWrite: null, cacheShare: null };
|
|
42
|
+
}
|
|
43
|
+
/** The live records' steps, bounded: counts by kind, failures, observed times and repeats (never their words). */
|
|
44
|
+
/** `measured`: the recorder saw this provider's steps start and end as they happened (Claude's stream does); for the
|
|
45
|
+
* others their times are partly made up (Codex reports a step when it completes, ACP agents may report late), so no
|
|
46
|
+
* duration is claimed for them. */
|
|
47
|
+
export function stepsOf(records, truncated, measured = true) {
|
|
48
|
+
let detail = "base";
|
|
49
|
+
const open = new Map();
|
|
50
|
+
const byKind = {};
|
|
51
|
+
const longest = [];
|
|
52
|
+
let total = 0, failed = 0, unknown = 0;
|
|
53
|
+
for (const record of records) {
|
|
54
|
+
if (record.k === "open")
|
|
55
|
+
detail = record.detail;
|
|
56
|
+
else if (record.k === "step") {
|
|
57
|
+
if (record.kind === "session" || record.kind === "message")
|
|
58
|
+
continue;
|
|
59
|
+
total++;
|
|
60
|
+
byKind[record.kind] = (byKind[record.kind] ?? 0) + 1;
|
|
61
|
+
open.set(record.id, { kind: record.kind, at: record.at, once: record.once === true });
|
|
62
|
+
}
|
|
63
|
+
else if (record.k === "done") {
|
|
64
|
+
const started = open.get(record.id);
|
|
65
|
+
if (started === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
open.delete(record.id);
|
|
68
|
+
if (record.ok === false)
|
|
69
|
+
failed++;
|
|
70
|
+
// Not a measured time: a provider whose times the recorder can't see as they happen, a start and end made up
|
|
71
|
+
// together (`once`), a step the recorder closed itself when the run ended (outcome unknown), or no time between.
|
|
72
|
+
const ms = !measured || started.once || record.ok === null ? null : msBetween(started.at || null, record.at || null);
|
|
73
|
+
if (ms === null || ms === 0) {
|
|
74
|
+
unknown++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
longest.push({ kind: started.kind, ms });
|
|
78
|
+
longest.sort((a, b) => b.ms - a.ms);
|
|
79
|
+
if (longest.length > LONGEST)
|
|
80
|
+
longest.pop();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Steps that never closed (the run ended, or the file was cut) have no measured time.
|
|
84
|
+
unknown += open.size;
|
|
85
|
+
// Repeats are never claimed: the step records keep a display form of each step (a file name, a host, a command with
|
|
86
|
+
// its shell wrapper taken off), so two different steps can read alike. Said, not guessed.
|
|
87
|
+
const repeatsReason = detail === "commands"
|
|
88
|
+
? "Repeats can't be told apart: the step records keep a short form of each step, not its whole input."
|
|
89
|
+
: "Repeats can't be told apart: this project doesn't record step details.";
|
|
90
|
+
const repeats = null;
|
|
91
|
+
return { state: truncated === null ? "complete" : "truncated", reason: truncated, total, failed, unknownDuration: unknown, byKind, longest, repeats, repeatsReason };
|
|
92
|
+
}
|
|
93
|
+
const noSteps = (state, reason) => ({ state, reason, total: null, failed: null, unknownDuration: null, byKind: {}, longest: [], repeats: null, repeatsReason: null });
|
|
94
|
+
/** Read the run's live records once, strictly bounded: its own descriptor (no link followed, in a real live
|
|
95
|
+
* directory), measured once, and never more than the budget read whatever the file does meanwhile; then parsed line by
|
|
96
|
+
* line. A file over the budget, in the older text format, with lines that don't read, or cut or capped while the agent
|
|
97
|
+
* ran, says its steps are incomplete. Null when there is no step file at all. */
|
|
98
|
+
function readRecords(evidenceRoot, runId) {
|
|
99
|
+
try {
|
|
100
|
+
const dir = lstatSync(join(evidenceRoot, "live"));
|
|
101
|
+
if (!dir.isDirectory() || dir.isSymbolicLink())
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
let fd;
|
|
108
|
+
try {
|
|
109
|
+
fd = openSync(liveLogPath(evidenceRoot, runId), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code !== "ENOENT")
|
|
113
|
+
return { unreadable: "The step records couldn't be read." };
|
|
114
|
+
// No records file: an older run's plain text file has no step detail; with neither, there is nothing.
|
|
115
|
+
try {
|
|
116
|
+
lstatSync(legacyLiveLogPath(evidenceRoot, runId));
|
|
117
|
+
return { unreadable: "This run's steps were recorded in an older format that has no step detail." };
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let text, short = false;
|
|
124
|
+
try {
|
|
125
|
+
const stat = fstatSync(fd);
|
|
126
|
+
if (!stat.isFile())
|
|
127
|
+
return { unreadable: "The step records aren't a plain file, so they weren't read." };
|
|
128
|
+
if (stat.size > EFFICIENCY_READ_BYTES)
|
|
129
|
+
return { unreadable: "The step records are larger than they can be read here." };
|
|
130
|
+
// At most what it measured, and never past the budget: growth after the measure is not read.
|
|
131
|
+
const buffer = Buffer.alloc(stat.size);
|
|
132
|
+
let at = 0;
|
|
133
|
+
while (at < buffer.length) {
|
|
134
|
+
const got = readSync(fd, buffer, at, buffer.length - at, at);
|
|
135
|
+
if (got <= 0)
|
|
136
|
+
break;
|
|
137
|
+
at += got;
|
|
138
|
+
}
|
|
139
|
+
// Less than it measured: the file changed under the read.
|
|
140
|
+
short = at < buffer.length;
|
|
141
|
+
text = buffer.subarray(0, at).toString("utf8");
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return { unreadable: "The step records couldn't be read." };
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
closeSync(fd);
|
|
148
|
+
}
|
|
149
|
+
const lines = text.split("\n");
|
|
150
|
+
// The last line may be unfinished (the agent's final write): it is read when it parses, never counted as a loss.
|
|
151
|
+
const unfinished = lines.at(-1) ?? "";
|
|
152
|
+
const whole = lines.slice(0, -1).filter(line => line.trim() !== "");
|
|
153
|
+
if (whole.length > EFFICIENCY_RECORDS)
|
|
154
|
+
whole.length = EFFICIENCY_RECORDS;
|
|
155
|
+
const keys = { inKey: false };
|
|
156
|
+
// One record per line: a whole line that doesn't read is a record lost.
|
|
157
|
+
const read = liveWindowRecords({ text: whole.length === 0 ? "" : `${whole.join("\n")}\n`, format: "records" }, keys);
|
|
158
|
+
const lost = whole.length - read.length;
|
|
159
|
+
// A finished run's last line without its newline is read like any other; one that doesn't read is a record lost.
|
|
160
|
+
const tail = unfinished.trim() === "" ? [] : liveWindowRecords({ text: `${unfinished}\n`, format: "records" }, keys);
|
|
161
|
+
const records = [...read, ...tail];
|
|
162
|
+
const lostAll = lost + (unfinished.trim() !== "" && tail.length === 0 ? 1 : 0);
|
|
163
|
+
if (records.length === 0 && text.trim() !== "")
|
|
164
|
+
return { unreadable: "The step records couldn't be read." };
|
|
165
|
+
const truncated = short ? "the step records changed while they were read"
|
|
166
|
+
: lines.length - 1 > EFFICIENCY_RECORDS ? `only the first ${EFFICIENCY_RECORDS.toLocaleString("en-US")} records were read`
|
|
167
|
+
: records.some(one => one.k === "cap") ? "the step records reached their size limit while the agent ran"
|
|
168
|
+
: records.some(one => one.k === "cut") ? "a step record was too long to keep"
|
|
169
|
+
: lostAll > 0 ? `${lostAll} step record${lostAll === 1 ? "" : "s"} couldn't be read`
|
|
170
|
+
: null;
|
|
171
|
+
return { records, truncated };
|
|
172
|
+
}
|
|
173
|
+
/** Derive a finished run's How it ran from its records. */
|
|
174
|
+
export function deriveRunEfficiency(run, evidenceRoot, now) {
|
|
175
|
+
const finished = run.finishedAt;
|
|
176
|
+
const read = readRecords(evidenceRoot, run.id);
|
|
177
|
+
const steps = read !== null && "unreadable" in read ? noSteps("truncated", read.unreadable)
|
|
178
|
+
: read !== null ? stepsOf(read.records, read.truncated, run.provider === "claude")
|
|
179
|
+
: finished !== null && now.getTime() - Date.parse(finished) > LIVE_RETAIN_MS ? noSteps("expired", "Step details are kept for a day after a run; this one's have expired.")
|
|
180
|
+
: noSteps("absent", "No step records were kept for this run.");
|
|
181
|
+
return {
|
|
182
|
+
version: RUN_EFFICIENCY_VERSION,
|
|
183
|
+
time: { waitMs: msBetween(run.startedAt, run.providerStartedAt), agentMs: msBetween(run.providerStartedAt, finished) },
|
|
184
|
+
turns: run.agentTurns ?? null,
|
|
185
|
+
usage: usageOf(run),
|
|
186
|
+
steps,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A finished run's How it ran: the saved one when it was derived by this version, else derived now and saved (once:
|
|
191
|
+
* a row whose steps were complete or have expired is final). An unfinished run is derived but never saved.
|
|
192
|
+
*/
|
|
193
|
+
export function runEfficiencyOf(store, evidenceRoot, runId, now) {
|
|
194
|
+
const run = store.getRun(runId);
|
|
195
|
+
if (run === null)
|
|
196
|
+
return null;
|
|
197
|
+
const usageJson = store.handle.prepare("SELECT usage_json FROM run WHERE id = ?").get(runId)?.["usage_json"];
|
|
198
|
+
const full = { ...run, usageJson: typeof usageJson === "string" ? usageJson : null };
|
|
199
|
+
if (run.finishedAt === null)
|
|
200
|
+
return deriveRunEfficiency(full, evidenceRoot, now);
|
|
201
|
+
let saved;
|
|
202
|
+
try {
|
|
203
|
+
saved = store.handle.prepare("SELECT parser_version, payload_json FROM run_efficiency WHERE run = ?").get(runId);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
saved = undefined;
|
|
207
|
+
}
|
|
208
|
+
if (saved !== undefined && Number(saved["parser_version"]) === RUN_EFFICIENCY_VERSION) {
|
|
209
|
+
try {
|
|
210
|
+
return JSON.parse(String(saved["payload_json"]));
|
|
211
|
+
}
|
|
212
|
+
catch { /* derived again below */ }
|
|
213
|
+
}
|
|
214
|
+
const derived = deriveRunEfficiency(full, evidenceRoot, now);
|
|
215
|
+
const payload = JSON.stringify(derived);
|
|
216
|
+
// A row too large for its column (it can't be: the counts are bounded) is shown, never saved.
|
|
217
|
+
// Saving is a convenience: a store that can't take the row (read-only, busy) still shows what was derived.
|
|
218
|
+
if (payload.length <= 16_384) {
|
|
219
|
+
try {
|
|
220
|
+
store.handle.prepare(`INSERT INTO run_efficiency (run, parser_version, steps, steps_reason, payload_json, derived_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
221
|
+
ON CONFLICT(run) DO UPDATE SET parser_version = excluded.parser_version, steps = excluded.steps, steps_reason = excluded.steps_reason,
|
|
222
|
+
payload_json = excluded.payload_json, derived_at = excluded.derived_at`)
|
|
223
|
+
.run(runId, RUN_EFFICIENCY_VERSION, derived.steps.state, derived.steps.reason, payload, now.toISOString());
|
|
224
|
+
}
|
|
225
|
+
catch { /* shown, not saved: derived again next time */ }
|
|
226
|
+
}
|
|
227
|
+
return derived;
|
|
228
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* again. Reaping on the second tells you a *machine* is gone, and everything
|
|
20
20
|
* it was holding — claims and worktrees alike — needs recovering together.
|
|
21
21
|
*/
|
|
22
|
+
import { type AcpProviderId } from "./acp-agents.js";
|
|
23
|
+
import type { AcpAttestation } from "./store.js";
|
|
22
24
|
import type { Store, Mutation } from "./store.js";
|
|
23
25
|
import { type ProviderId } from "./provider.js";
|
|
24
26
|
import type { ReadinessObservation } from "./phase-routing.js";
|
|
@@ -264,7 +266,9 @@ export type ReadinessProbe = (file: string, args: readonly string[], options?: {
|
|
|
264
266
|
notFound: boolean;
|
|
265
267
|
timedOut: boolean;
|
|
266
268
|
}>;
|
|
267
|
-
export declare function observeProviderReadiness(probe: ReadinessProbe, env?: Record<string, string | undefined>, providers?: readonly ProviderId[]
|
|
269
|
+
export declare function observeProviderReadiness(probe: ReadinessProbe, env?: Record<string, string | undefined>, providers?: readonly ProviderId[],
|
|
270
|
+
/** An ACP agent's live check on this runner (store.acpAttestationFor), when the caller can read one. */
|
|
271
|
+
acpChecked?: (provider: AcpProviderId) => AcpAttestation | null): Promise<Omit<ReadinessObservation, "runner" | "observedAt">[]>;
|
|
268
272
|
/**
|
|
269
273
|
* Record a runner's observations under its OWN authenticated name: the
|
|
270
274
|
* credential is verified and the rows written in one transaction, so a
|
package/dist/runner.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
* again. Reaping on the second tells you a *machine* is gone, and everything
|
|
20
20
|
* it was holding — claims and worktrees alike — needs recovering together.
|
|
21
21
|
*/
|
|
22
|
+
import { isAcpProvider } from "./acp-agents.js";
|
|
23
|
+
import { verifyLaunch } from "./acp-attest.js";
|
|
24
|
+
import { inspectAcpAgent, inspectionWords } from "./acp-inspect.js";
|
|
22
25
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
23
26
|
import { resolve } from "node:path";
|
|
24
27
|
import { canonicalProject } from "./project.js";
|
|
@@ -363,8 +366,26 @@ function sameDigest(left, right) {
|
|
|
363
366
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
364
367
|
}
|
|
365
368
|
const READINESS_PROBE_TIMEOUT_MS = 5_000;
|
|
366
|
-
export async function observeProviderReadiness(probe, env = process.env, providers = PROVIDER_IDS
|
|
369
|
+
export async function observeProviderReadiness(probe, env = process.env, providers = PROVIDER_IDS,
|
|
370
|
+
/** An ACP agent's live check on this runner (store.acpAttestationFor), when the caller can read one. */
|
|
371
|
+
acpChecked) {
|
|
367
372
|
return Promise.all(providers.map(async (provider) => {
|
|
373
|
+
// An ACP agent is looked at, never run, outside the gateway (acp-inspect.ts). It is ready only once its
|
|
374
|
+
// conformance check passed on this computer; until then it says so.
|
|
375
|
+
if (isAcpProvider(provider)) {
|
|
376
|
+
const checked = acpChecked?.(provider) ?? null;
|
|
377
|
+
if (checked !== null) {
|
|
378
|
+
// Ready only while every file its check bound is still the one that passed.
|
|
379
|
+
const verified = verifyLaunch(checked.launch);
|
|
380
|
+
return verified.ok
|
|
381
|
+
? { provider, state: "ready", reason: `${checked.agent.name} ${checked.agent.version}, checked ${checked.recordedAt.slice(0, 10)}`, probe: "version" }
|
|
382
|
+
: { provider, state: "unavailable", reason: `${verified.problem}`, probe: "version" };
|
|
383
|
+
}
|
|
384
|
+
const seen = inspectAcpAgent(provider, env);
|
|
385
|
+
return seen.found
|
|
386
|
+
? { provider, state: "unavailable", reason: `${inspectionWords(seen)}; not checked on this computer — run \`toolroll agents check ${provider}\``, probe: "version" }
|
|
387
|
+
: { provider, state: "unavailable", reason: inspectionWords(seen), probe: "version" };
|
|
388
|
+
}
|
|
368
389
|
const facts = inspectionOf(provider);
|
|
369
390
|
const version = await probe(facts.binary, ["--version"], { timeoutMs: READINESS_PROBE_TIMEOUT_MS, omitEnv: ALL_CREDENTIAL_ENV });
|
|
370
391
|
if (version.notFound) {
|
package/dist/scope.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { ACCEPTANCE_LIMITS, EVIDENCE_KINDS, type EvidenceKind } from "./contracts/acceptance-terms.js";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { isAcpProfile, type AcpProviderId } from "./acp-agents.js";
|
|
4
|
+
export { isAcpProfile };
|
|
5
|
+
import { type NetworkProfile } from "./egress.js";
|
|
6
|
+
import { type WritesMode } from "./writes-mode.js";
|
|
3
7
|
import { type EvidenceKind } from "./contracts/acceptance-terms.js";
|
|
4
8
|
import { type AcceptanceCriterion, type AcceptanceProblem, type ScopeTerms } from "./contracts/scope.js";
|
|
5
9
|
import type { Store, Mutation } from "./store.js";
|
|
@@ -42,6 +46,13 @@ export declare const GEMINI_LIMITS: {
|
|
|
42
46
|
readonly timeoutSeconds: 1200;
|
|
43
47
|
readonly repairTimeoutSeconds: 300;
|
|
44
48
|
};
|
|
49
|
+
/** An ACP agent runs under two clocks at once: the whole attempt, and silence (no line from the agent). */
|
|
50
|
+
export declare const ACP_LIMITS: {
|
|
51
|
+
readonly timeoutSeconds: 3600;
|
|
52
|
+
readonly idleTimeoutSeconds: 900;
|
|
53
|
+
readonly repairTimeoutSeconds: 900;
|
|
54
|
+
readonly repairIdleTimeoutSeconds: 300;
|
|
55
|
+
};
|
|
45
56
|
export type ClaudeProfile = {
|
|
46
57
|
provider: "claude";
|
|
47
58
|
/** The exact model string the argv carries. Never empty, never "default". */
|
|
@@ -61,6 +72,12 @@ export type ClaudeProfile = {
|
|
|
61
72
|
/** Exact model for repairs, or the stable literal "inherit" (= the
|
|
62
73
|
* build model, which is itself exact). */
|
|
63
74
|
repairModel: string;
|
|
75
|
+
/** What the build may reach (egress.ts), resolved when the scope was written; absent on approvals sealed before
|
|
76
|
+
* network policies, which run narrowed (egress.ts legacyNetwork). */
|
|
77
|
+
network?: NetworkProfile;
|
|
78
|
+
/** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
|
|
79
|
+
* write allowlists, which keep writing anywhere their fence allows. */
|
|
80
|
+
writes?: WritesMode;
|
|
64
81
|
};
|
|
65
82
|
export type CodexShapedProfile = {
|
|
66
83
|
provider: "codex" | "openrouter";
|
|
@@ -77,6 +94,10 @@ export type CodexShapedProfile = {
|
|
|
77
94
|
timeoutKind?: "idle";
|
|
78
95
|
repairTimeoutSeconds: number;
|
|
79
96
|
repairModel: string;
|
|
97
|
+
network?: NetworkProfile;
|
|
98
|
+
/** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
|
|
99
|
+
* write allowlists, which keep writing anywhere their fence allows. */
|
|
100
|
+
writes?: WritesMode;
|
|
80
101
|
};
|
|
81
102
|
export type GeminiProfile = {
|
|
82
103
|
provider: "gemini";
|
|
@@ -95,8 +116,46 @@ export type GeminiProfile = {
|
|
|
95
116
|
timeoutKind?: "idle";
|
|
96
117
|
repairTimeoutSeconds: number;
|
|
97
118
|
repairModel: string;
|
|
119
|
+
network?: NetworkProfile;
|
|
120
|
+
/** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
|
|
121
|
+
* write allowlists, which keep writing anywhere their fence allows. */
|
|
122
|
+
writes?: WritesMode;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* An agent over the Agent Client Protocol (acp-agents.ts, docs/plans/acp-adapter.md).
|
|
126
|
+
* Full access inside the OS fence is the only posture: nothing else could be
|
|
127
|
+
* made true (an agent may run commands without asking). There is no turn
|
|
128
|
+
* limit; both clocks bound every attempt, and a repair has its own pair.
|
|
129
|
+
*/
|
|
130
|
+
export type AcpProfile = {
|
|
131
|
+
provider: AcpProviderId;
|
|
132
|
+
/** Exact, and one the agent offered when it was checked. */
|
|
133
|
+
model: string;
|
|
134
|
+
permission: "full";
|
|
135
|
+
maxTurns: "unsupported";
|
|
136
|
+
repairMaxTurns: "unsupported";
|
|
137
|
+
timeoutSeconds: number;
|
|
138
|
+
idleTimeoutSeconds: number;
|
|
139
|
+
repairTimeoutSeconds: number;
|
|
140
|
+
repairIdleTimeoutSeconds: number;
|
|
141
|
+
repairModel: string;
|
|
142
|
+
network?: NetworkProfile;
|
|
143
|
+
/** Where the build may write (writes-mode.ts), resolved when the scope was written; absent on approvals sealed before
|
|
144
|
+
* write allowlists, which keep writing anywhere their fence allows. */
|
|
145
|
+
writes?: WritesMode;
|
|
146
|
+
};
|
|
147
|
+
export type ExecutionProfile = ClaudeProfile | CodexShapedProfile | GeminiProfile | AcpProfile;
|
|
148
|
+
/** Whether a profile's clock is a no-progress window (an ACP profile has both clocks; its overall one is a deadline). */
|
|
149
|
+
export declare function idleClock(profile: ExecutionProfile): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* The watchdogs an attempt runs under, from its sealed profile and the overall bound the caller resolved: an ACP
|
|
152
|
+
* profile gets both clocks at once (the deadline and its silence window — a repair its own pair); the others one,
|
|
153
|
+
* a no-progress window when the profile says idle, else a deadline.
|
|
154
|
+
*/
|
|
155
|
+
export declare function watchdogsOf(profile: ExecutionProfile, timeoutMs: number, repair?: boolean): {
|
|
156
|
+
timeoutMs?: number;
|
|
157
|
+
idleTimeoutMs?: number;
|
|
98
158
|
};
|
|
99
|
-
export type ExecutionProfile = ClaudeProfile | CodexShapedProfile | GeminiProfile;
|
|
100
159
|
/** The durable unattended permission choices exposed by the console. The
|
|
101
160
|
* provider-specific argv is still what gets sealed into an approval; this
|
|
102
161
|
* small cross-provider type is only the operator-facing policy. */
|
|
@@ -461,6 +520,11 @@ export type ApproveResult = {
|
|
|
461
520
|
ok: false;
|
|
462
521
|
reason: "policy";
|
|
463
522
|
message: string;
|
|
523
|
+
}
|
|
524
|
+
/** Cockpit B2: a remembered password can't approve these terms (protected, escalated, or a quarantined filing). */
|
|
525
|
+
| {
|
|
526
|
+
ok: false;
|
|
527
|
+
reason: "password-now";
|
|
464
528
|
};
|
|
465
529
|
/**
|
|
466
530
|
* Whether this name-and-token pair is a person the store knows. Shared by
|
|
@@ -494,12 +558,114 @@ export declare function authenticateAccount(store: Store, by: string, secret: st
|
|
|
494
558
|
* approval, chat-action review, the console's own ceremonies). A typed
|
|
495
559
|
* password is always checked, and nothing else stands in.
|
|
496
560
|
*/
|
|
497
|
-
|
|
498
|
-
|
|
561
|
+
/**
|
|
562
|
+
* PASSWORD FRESHNESS (docs/plans/agents-cockpit.md B2), and the one place an empty password field is ever honoured.
|
|
563
|
+
*
|
|
564
|
+
* - An identity-provider re-check made **on request** (a step-up's re-authentication, never an ordinary sign-in) stands
|
|
565
|
+
* in for one password typed now, for ONE step-up of any kind, within five minutes: it is consumed by the act it
|
|
566
|
+
* proves (`sso-now`). Never reused across acts.
|
|
567
|
+
* - A password verified on this session, or that explicit re-check, stands in for fifteen minutes — but only inside an
|
|
568
|
+
* allowlisted act (`withFreshness`), only for this person, only on a direct local request (`fresh-password`,
|
|
569
|
+
* `fresh-sso`). Never extended by use.
|
|
570
|
+
* Every fact is read at the moment of the act (`FreshFacts` are live readers, not a snapshot), so a sign-out, a session
|
|
571
|
+
* revoked or rotated, a credential change or Forget made while a request was in flight is seen by it.
|
|
572
|
+
*/
|
|
573
|
+
export type FreshOp = "approve-standard" | "chat-send" | "reply-question";
|
|
574
|
+
/** "session": Passwordless is on and this is the person's own live browser session (docs/plans/passwordless.md). */
|
|
575
|
+
/** "remote-token" / "lead-token": a machine credential stood in for the person (a remote run's API token, a lead's
|
|
576
|
+
* token) — fine for ordinary acts it is allowed, never for a credential act (it would mint a way in with no password). */
|
|
577
|
+
export type AuthMethod = "password-now" | "sso-now" | "fresh-password" | "fresh-sso" | "session" | "cli-login" | "remote-token" | "lead-token";
|
|
578
|
+
/**
|
|
579
|
+
* The local CLI's remembered login, marked for the one command that read it (operate.ts credentialsFrom): with
|
|
580
|
+
* Passwordless on, a proof made with it records "cli-login" — never a typed password — and no credential act accepts it.
|
|
581
|
+
* Scoped to that command's async chain; a browser request in the console's own process never carries it.
|
|
582
|
+
*/
|
|
583
|
+
export declare const rememberedLogin: AsyncLocalStorage<{
|
|
584
|
+
login: {
|
|
585
|
+
name: string;
|
|
586
|
+
token: string;
|
|
587
|
+
} | null;
|
|
499
588
|
}>;
|
|
500
|
-
export declare const
|
|
501
|
-
|
|
589
|
+
export declare const FRESH_WINDOW_MS: number;
|
|
590
|
+
/** How long an explicit provider re-check stands in for one password typed now. */
|
|
591
|
+
export declare const SSO_NOW_MS: number;
|
|
592
|
+
export type FreshFacts = {
|
|
593
|
+
actor: string;
|
|
594
|
+
/** Straight from this computer: a loopback peer naming a loopback host, with no forwarding headers of any kind. */
|
|
595
|
+
local: boolean;
|
|
596
|
+
/** The request's route is on the freshness allowlist (route-table.ts `freshness: "allowlist"`): the table decides. */
|
|
597
|
+
allowlisted: boolean;
|
|
598
|
+
/** The session is still signed in, under the account's current credential generation, not revoked. */
|
|
599
|
+
live: () => boolean;
|
|
600
|
+
/** When a password was last verified on this session; when the provider last re-checked on request (window start);
|
|
601
|
+
* and an unconsumed re-check for one step-up. */
|
|
602
|
+
passwordAt: () => number | null;
|
|
603
|
+
ssoReauthAt: () => number | null;
|
|
604
|
+
ssoNow: () => number | null;
|
|
605
|
+
/** Spend the re-check; the answer puts it back (a refund) when the act it proved doesn't happen. */
|
|
606
|
+
consumeSsoNow: () => () => void;
|
|
607
|
+
/** Nothing before this counts (Forget). */
|
|
608
|
+
floor: () => number;
|
|
609
|
+
/** Record a verified password entry on this session (the console decides whether this request may start one). */
|
|
610
|
+
stamp: (at: number) => void;
|
|
611
|
+
};
|
|
612
|
+
export declare const freshFacts: AsyncLocalStorage<FreshFacts | null>;
|
|
613
|
+
/**
|
|
614
|
+
* Run an act a re-check may prove, spending the re-check only if the act happens: when it throws (a rolled-back
|
|
615
|
+
* transaction) or `failed` says it was refused, every re-check it spent is put back. ONLY for acts whose every effect
|
|
616
|
+
* is in one database transaction (an approval, a chat approval proposal, a chat turn opening): there, "refused" and
|
|
617
|
+
* "thrown" prove nothing happened. Every other step-up spends its re-check whatever comes of it (fail-closed: at worst
|
|
618
|
+
* the person confirms with their provider again). Synchronous acts only.
|
|
619
|
+
*/
|
|
620
|
+
export declare function spendingOnlyOnSuccess<T>(act: () => T, failed: (result: T) => boolean): T;
|
|
621
|
+
/** Run an allowlisted act: within it, a fresh password or re-check may stand in for one typed now. */
|
|
622
|
+
export declare function withFreshness<T>(op: FreshOp, act: () => T): T;
|
|
623
|
+
/** The freshness this act may stand on now, or null: only inside an allowlisted act on an allowlisted route, for this
|
|
624
|
+
* person, on a direct local request, on a live session, within the window, after any Forget. `preview` asks it from a
|
|
625
|
+
* page (a GET) about the allowlisted act its form posts to — for words only, never to authorise. */
|
|
626
|
+
export declare function freshnessFor(by: string, now?: number, preview?: boolean): {
|
|
627
|
+
method: "fresh-password" | "fresh-sso";
|
|
628
|
+
at: number;
|
|
629
|
+
} | null;
|
|
630
|
+
/** Whether an explicit provider re-check is waiting to stand in for one step-up (the page says "Confirmed with …"). */
|
|
631
|
+
export declare function ssoNowAvailable(by: string, now?: number): boolean;
|
|
632
|
+
/**
|
|
633
|
+
* Proof that this person is here now, for any account step-up (an approver's or not): a password typed now, or an
|
|
634
|
+
* explicit provider re-check (consumed). The core of every console step-up; `authenticateApprover` adds standing.
|
|
635
|
+
*/
|
|
636
|
+
export declare function proveAccountNow(store: Store, by: string, token: string, options?: {
|
|
637
|
+
credential?: boolean;
|
|
638
|
+
}): {
|
|
639
|
+
ok: true;
|
|
640
|
+
method: AuthMethod;
|
|
641
|
+
at: number;
|
|
642
|
+
} | {
|
|
643
|
+
ok: false;
|
|
644
|
+
reason: "no-approvers" | "not-an-approver";
|
|
645
|
+
};
|
|
646
|
+
/**
|
|
647
|
+
* A credential act (docs/plans/passwordless.md, the closed list: one that creates or widens a way in): only a password
|
|
648
|
+
* typed now, or a one-use identity-provider re-check, proves it — never a session, never a remembered password.
|
|
649
|
+
*/
|
|
650
|
+
/** Whether an empty password would pass for `by` right now because Passwordless is on and this is their live browser
|
|
651
|
+
* session. Read only — the act itself proves it again inside `proveAccountNow`. */
|
|
652
|
+
export declare function passwordlessSessionFor(store: Store, by: string): boolean;
|
|
653
|
+
/** Whether a proof is one a credential act accepts: a password typed now, or a one-use identity-provider re-check. */
|
|
654
|
+
export declare const credentialProof: (method: AuthMethod) => boolean;
|
|
655
|
+
export declare function proveCredential(store: Store, by: string, token: string, repo?: string | null): {
|
|
502
656
|
ok: true;
|
|
657
|
+
method: AuthMethod;
|
|
658
|
+
at: number;
|
|
659
|
+
} | {
|
|
660
|
+
ok: false;
|
|
661
|
+
reason: "no-approvers" | "not-an-approver";
|
|
662
|
+
};
|
|
663
|
+
export declare function authenticateApprover(store: Store, by: string, token: string, repo?: string | null, options?: {
|
|
664
|
+
credential?: boolean;
|
|
665
|
+
}): {
|
|
666
|
+
ok: true;
|
|
667
|
+
method: AuthMethod;
|
|
668
|
+
at: number;
|
|
503
669
|
} | {
|
|
504
670
|
ok: false;
|
|
505
671
|
reason: "no-approvers" | "not-an-approver";
|
|
@@ -548,6 +714,18 @@ export declare function fileAndSealUnderMode(store: Store, input: ScopeInput & {
|
|
|
548
714
|
detail?: string;
|
|
549
715
|
};
|
|
550
716
|
export declare function approve(store: Store, taskId: string, by: string, now: Date, sawDigest: string, token: string, mutation?: Mutation): ApproveResult;
|
|
717
|
+
/** The ledger act that records how an approval's person was proved (password-now, sso-now, fresh-password, fresh-sso). */
|
|
718
|
+
export declare const APPROVAL_PROOF = "approval proved";
|
|
719
|
+
/** The proof row's detail: when the person was proved, the generation, and the exact digest they said yes to. */
|
|
720
|
+
export declare const approvalProofDetail: (at: number, generation: number, digest: string) => string;
|
|
721
|
+
/** The proof of THIS approval: the approver's own row for these exact bytes (never another approval's words). */
|
|
722
|
+
export declare function approvalProofOf(store: Store, taskId: string, by: string, digest: string): {
|
|
723
|
+
method: string;
|
|
724
|
+
at: string;
|
|
725
|
+
} | null;
|
|
726
|
+
/** Whether a remembered password may approve these terms: one approver (no protected work), standard permissions
|
|
727
|
+
* (never escalated), and not filed by a coordinator, a lead or a scout (those keep their own password ceremony). */
|
|
728
|
+
export declare function freshlyApprovable(store: Store, taskId: string, scope: Scope, singleApprover: boolean): boolean;
|
|
551
729
|
/**
|
|
552
730
|
* Whether this task may be built.
|
|
553
731
|
*
|