toolroll 0.9.63 → 0.9.65
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 +51 -0
- package/README.md +15 -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 +50 -2
- package/dist/agent-fence.js +157 -10
- 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 +36 -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/browser/THIRD_PARTY_NOTICES.txt +48 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +117 -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 +6 -1
- package/dist/builder.js +34 -13
- 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 +12 -4
- package/dist/contracts/console-api.js +12 -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 +17 -0
- package/dist/invoke.js +217 -21
- 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 +258 -0
- package/dist/live-terminal.js +649 -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 +2 -0
- package/dist/operate.js +275 -17
- 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 +12 -0
- package/dist/project-delete-ui.js +41 -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.d.ts +3 -0
- package/dist/release.js +14 -8
- package/dist/request-budget-ui.js +2 -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 +170 -5
- package/dist/scope.js +251 -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 +247 -40
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +6 -1
- package/dist/server/chrome.js +18 -7
- package/dist/server/console.css +52 -38
- 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 +4 -1
- package/dist/server/render-pages.js +5 -4
- 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 +110 -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 +46 -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 +121 -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 +320 -74
- 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 +430 -15
- package/dist/store.js +1041 -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/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 +2 -0
- package/dist/tool-broker.js +1 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- 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/package.json +11 -2
package/dist/live.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* Fail-soft everywhere: a live view that cannot be written must never
|
|
14
14
|
* break, slow, or reclassify a build.
|
|
15
15
|
*/
|
|
16
|
+
import { type KeyState, type LiveActivityKind, type LiveRecord, type LiveRecorderOptions } from "./live-records.js";
|
|
16
17
|
export declare const LIVE_LINE_CAP: number;
|
|
17
18
|
export declare const LIVE_FILE_CAP: number;
|
|
18
19
|
export declare const LIVE_CAP_MARKER = "[the live view stopped at its size cap \u2014 the run record continues]";
|
|
@@ -23,29 +24,18 @@ export declare const LIVE_RETAIN_MS: number;
|
|
|
23
24
|
export declare const LIVE_FILE_BOUND = 500;
|
|
24
25
|
export declare function liveDir(root: string): string;
|
|
25
26
|
/** The exact filename, from the numeric id and nothing else — no user
|
|
26
|
-
* input is ever parsed into this path.
|
|
27
|
+
* input is ever parsed into this path. Records (`.jsonl`) are what every
|
|
28
|
+
* run writes now; the name is the format. */
|
|
27
29
|
export declare function liveLogPath(root: string, runId: number): string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* name, arguments, and metadata never reach the file: a tool named after
|
|
32
|
-
* a secret stays unspoken.
|
|
33
|
-
*/
|
|
34
|
-
export type LiveActivityKind = "session" | "message" | "edit" | "command" | "read" | "search" | "lookup" | "plan" | "delegate" | "tool";
|
|
35
|
-
export declare const LIVE_ACTIVITY_KINDS: readonly LiveActivityKind[];
|
|
30
|
+
/** The plain-text file runs wrote before the live conversation; read until it ages out. */
|
|
31
|
+
export declare function legacyLiveLogPath(root: string, runId: number): string;
|
|
32
|
+
export { LIVE_ACTIVITY_KINDS, type LiveActivityKind } from "./live-records.js";
|
|
36
33
|
/**
|
|
37
34
|
* What one stream event says the agent just did, as one fixed kind: the
|
|
38
35
|
* last-activity line's only input. Never text, names or arguments; an
|
|
39
36
|
* event that says nothing (results, deltas, subagent chatter) is null.
|
|
40
37
|
*/
|
|
41
38
|
export declare function liveActivityKind(event: Record<string, unknown>): LiveActivityKind | null;
|
|
42
|
-
/**
|
|
43
|
-
* One stream event → zero or more display lines, in the fixed vocabulary.
|
|
44
|
-
* Assistant TEXT is the agent's own words (sanitized below); tool use is a
|
|
45
|
-
* headline; everything else — tool results, deltas, results, subagent
|
|
46
|
-
* chatter — renders nothing in v1.
|
|
47
|
-
*/
|
|
48
|
-
export declare function renderTranscriptLines(event: Record<string, unknown>): string[];
|
|
49
39
|
/** Sanitize one display line: strip controls and bidi overrides, redact whole on any secret hit, cap. */
|
|
50
40
|
export declare function sanitizeTranscriptLine(line: string): string;
|
|
51
41
|
export type LiveLog = {
|
|
@@ -53,31 +43,52 @@ export type LiveLog = {
|
|
|
53
43
|
close(): void;
|
|
54
44
|
};
|
|
55
45
|
/**
|
|
56
|
-
* Open the run's live file for appending. The directory is 0700,
|
|
57
|
-
* 0600 and CREATED HERE (O_EXCL: a name that already exists —
|
|
58
|
-
* it there — is refused, not appended to), opened O_NOFOLLOW
|
|
59
|
-
* descriptor retained for the run's whole duration.
|
|
60
|
-
*
|
|
46
|
+
* Open the run's live file for appending records. The directory is 0700,
|
|
47
|
+
* the file 0600 and CREATED HERE (O_EXCL: a name that already exists —
|
|
48
|
+
* whatever put it there — is refused, not appended to), opened O_NOFOLLOW
|
|
49
|
+
* with the descriptor retained for the run's whole duration. `options` is
|
|
50
|
+
* the run's trusted context: its checkout, and whether its project shows
|
|
51
|
+
* commands (read once, here). Returns null on any failure: no live view is
|
|
52
|
+
* ever worth a build.
|
|
61
53
|
*/
|
|
62
|
-
export declare function openLiveLog(root: string, runId: number): LiveLog | null;
|
|
54
|
+
export declare function openLiveLog(root: string, runId: number, options?: Partial<LiveRecorderOptions>): LiveLog | null;
|
|
55
|
+
export type LiveFormat = "records" | "text";
|
|
63
56
|
export type LiveWindowRead = {
|
|
64
57
|
ok: true;
|
|
58
|
+
/** Complete lines read from `from`: records (JSON lines) or legacy text. */
|
|
65
59
|
text: string;
|
|
66
60
|
nextOffset: number;
|
|
67
61
|
eof: boolean;
|
|
62
|
+
format: LiveFormat;
|
|
63
|
+
/** Which file this offset belongs to: a client holding another resets. */
|
|
64
|
+
gen: string;
|
|
68
65
|
} | {
|
|
69
66
|
ok: false;
|
|
70
67
|
reason: "missing" | "replaced" | "unreadable";
|
|
71
68
|
};
|
|
72
69
|
/**
|
|
73
70
|
* Read the live file from a byte offset, returning only COMPLETE lines
|
|
74
|
-
* (through the last LF in the window). The
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
71
|
+
* (through the last LF in the window). The records file is read when it
|
|
72
|
+
* exists, else the legacy text file. The descriptor is the authority:
|
|
73
|
+
* opened O_NOFOLLOW | O_NONBLOCK inside a real live directory, proved a
|
|
74
|
+
* regular file, measured and read through itself. A file smaller than the
|
|
75
|
+
* offset was replaced — the caller resets, visibly, and `gen` names the file
|
|
76
|
+
* so an offset is never reused against another one. A line longer than the
|
|
77
|
+
* whole window is skipped through its end, so a reader never stalls.
|
|
78
|
+
* `allowTail` (the final drain) returns the unterminated tail after
|
|
79
|
+
* finalization. Legacy text is cleaned again on the way out.
|
|
79
80
|
*/
|
|
80
81
|
export declare function readLiveWindow(root: string, runId: number, from: number, allowTail?: boolean): LiveWindowRead;
|
|
82
|
+
/** A window's complete lines as the plain words the run page and `toolroll peek` show, whatever its format. */
|
|
83
|
+
export declare function liveWindowLines(read: {
|
|
84
|
+
text: string;
|
|
85
|
+
format: LiveFormat;
|
|
86
|
+
}, keys?: KeyState): string[];
|
|
87
|
+
/** A window as records, whatever its format: an older plain file's lines become the agent's words. */
|
|
88
|
+
export declare function liveWindowRecords(read: {
|
|
89
|
+
text: string;
|
|
90
|
+
format: LiveFormat;
|
|
91
|
+
}, keys?: KeyState): LiveRecord[];
|
|
81
92
|
export type LiveSweepVerdict = {
|
|
82
93
|
/** run id, or null when the name is not a run's file. */
|
|
83
94
|
runId: number | null;
|
package/dist/live.js
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
* Fail-soft everywhere: a live view that cannot be written must never
|
|
14
14
|
* break, slow, or reclassify a build.
|
|
15
15
|
*/
|
|
16
|
-
import { closeSync, constants, fstatSync, mkdirSync, openSync, readSync, readdirSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
16
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readSync, readdirSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { scanForSecrets } from "./evidence.js";
|
|
19
|
+
import { createLiveRecorder, LIVE_CAP_WORDS, parseLiveRecords, redactLiveLines, projectLiveText, serializeLiveRecord } from "./live-records.js";
|
|
19
20
|
export const LIVE_LINE_CAP = 2 * 1024;
|
|
20
|
-
export const LIVE_FILE_CAP =
|
|
21
|
-
export const LIVE_CAP_MARKER =
|
|
21
|
+
export const LIVE_FILE_CAP = 4 * 1024 * 1024;
|
|
22
|
+
export const LIVE_CAP_MARKER = LIVE_CAP_WORDS;
|
|
22
23
|
export const LIVE_REDACTED_LINE = "[a credential-shaped line was redacted]";
|
|
23
24
|
/** Finalized runs keep their live file this long for the final drain. */
|
|
24
25
|
export const LIVE_RETAIN_MS = 24 * 60 * 60 * 1000;
|
|
@@ -28,11 +29,17 @@ export function liveDir(root) {
|
|
|
28
29
|
return join(root, "live");
|
|
29
30
|
}
|
|
30
31
|
/** The exact filename, from the numeric id and nothing else — no user
|
|
31
|
-
* input is ever parsed into this path.
|
|
32
|
+
* input is ever parsed into this path. Records (`.jsonl`) are what every
|
|
33
|
+
* run writes now; the name is the format. */
|
|
32
34
|
export function liveLogPath(root, runId) {
|
|
35
|
+
return join(liveDir(root), `${Math.trunc(runId)}.jsonl`);
|
|
36
|
+
}
|
|
37
|
+
/** The plain-text file runs wrote before the live conversation; read until it ages out. */
|
|
38
|
+
export function legacyLiveLogPath(root, runId) {
|
|
33
39
|
return join(liveDir(root), `${Math.trunc(runId)}.log`);
|
|
34
40
|
}
|
|
35
|
-
export
|
|
41
|
+
export { LIVE_ACTIVITY_KINDS } from "./live-records.js";
|
|
42
|
+
/** The activity line's kinds for known Claude tools (task-activity.ts). */
|
|
36
43
|
const TOOL_KINDS = new Map([
|
|
37
44
|
["Edit", "edit"],
|
|
38
45
|
["Write", "edit"],
|
|
@@ -48,18 +55,6 @@ const TOOL_KINDS = new Map([
|
|
|
48
55
|
["TodoWrite", "plan"],
|
|
49
56
|
["Task", "delegate"],
|
|
50
57
|
]);
|
|
51
|
-
const KIND_HEADLINES = {
|
|
52
|
-
session: "the agent session started",
|
|
53
|
-
message: "",
|
|
54
|
-
edit: "→ editing files",
|
|
55
|
-
command: "→ running a command",
|
|
56
|
-
read: "→ reading the code",
|
|
57
|
-
search: "→ searching the code",
|
|
58
|
-
lookup: "→ looking something up",
|
|
59
|
-
plan: "→ organizing its plan",
|
|
60
|
-
delegate: "→ delegating to a helper",
|
|
61
|
-
tool: "→ using a tool",
|
|
62
|
-
};
|
|
63
58
|
const toolKind = (name) => TOOL_KINDS.get(typeof name === "string" ? name : "") ?? "tool";
|
|
64
59
|
/**
|
|
65
60
|
* What one stream event says the agent just did, as one fixed kind: the
|
|
@@ -119,77 +114,6 @@ function capBytes(text, cap) {
|
|
|
119
114
|
// A cut mid-character decodes with a trailing replacement char — drop it.
|
|
120
115
|
return decoded.endsWith("�") ? decoded.slice(0, -1) : decoded;
|
|
121
116
|
}
|
|
122
|
-
/**
|
|
123
|
-
* One stream event → zero or more display lines, in the fixed vocabulary.
|
|
124
|
-
* Assistant TEXT is the agent's own words (sanitized below); tool use is a
|
|
125
|
-
* headline; everything else — tool results, deltas, results, subagent
|
|
126
|
-
* chatter — renders nothing in v1.
|
|
127
|
-
*/
|
|
128
|
-
export function renderTranscriptLines(event) {
|
|
129
|
-
const type = String(event["type"] ?? "");
|
|
130
|
-
if (type === "system" && String(event["subtype"] ?? "") === "init") {
|
|
131
|
-
return ["the agent session started"];
|
|
132
|
-
}
|
|
133
|
-
// Codex-shaped streams (peek): a thread starts, items complete.
|
|
134
|
-
if (type === "thread.started")
|
|
135
|
-
return ["the agent session started"];
|
|
136
|
-
if (type === "item.completed") {
|
|
137
|
-
const item = event["item"];
|
|
138
|
-
const kind = item === undefined ? "" : String(item["type"] ?? "");
|
|
139
|
-
if (kind === "agent_message") {
|
|
140
|
-
const text = item?.["text"];
|
|
141
|
-
return typeof text === "string" ? text.split("\n").filter(one => one.trim() !== "") : [];
|
|
142
|
-
}
|
|
143
|
-
if (kind === "command_execution")
|
|
144
|
-
return ["→ running a command"];
|
|
145
|
-
if (kind === "file_change")
|
|
146
|
-
return ["→ editing files"];
|
|
147
|
-
if (kind === "web_search")
|
|
148
|
-
return ["→ looking something up"];
|
|
149
|
-
return [];
|
|
150
|
-
}
|
|
151
|
-
// Gemini-shaped streams (peek): whole assistant messages, never deltas.
|
|
152
|
-
if (type === "init")
|
|
153
|
-
return ["the agent session started"];
|
|
154
|
-
if (type === "message" && String(event["role"] ?? "") === "assistant" && event["delta"] !== true) {
|
|
155
|
-
const content = event["content"];
|
|
156
|
-
return typeof content === "string" ? content.split("\n").filter(one => one.trim() !== "") : [];
|
|
157
|
-
}
|
|
158
|
-
if (type === "tool_call" || type === "tool_use") {
|
|
159
|
-
const name = event["name"] ?? event["tool"]?.["name"];
|
|
160
|
-
return [KIND_HEADLINES[toolKind(name)]];
|
|
161
|
-
}
|
|
162
|
-
if (type !== "assistant")
|
|
163
|
-
return [];
|
|
164
|
-
// Top-level only: a subagent's words are not this run's transcript.
|
|
165
|
-
const parent = event["parent_tool_use_id"];
|
|
166
|
-
if (parent !== undefined && parent !== null)
|
|
167
|
-
return [];
|
|
168
|
-
const message = event["message"];
|
|
169
|
-
const content = message?.["content"];
|
|
170
|
-
if (!Array.isArray(content))
|
|
171
|
-
return [];
|
|
172
|
-
const lines = [];
|
|
173
|
-
for (const block of content) {
|
|
174
|
-
if (block === null || typeof block !== "object")
|
|
175
|
-
continue;
|
|
176
|
-
const kind = String(block["type"] ?? "");
|
|
177
|
-
if (kind === "text") {
|
|
178
|
-
const text = block["text"];
|
|
179
|
-
if (typeof text === "string" && text.trim() !== "") {
|
|
180
|
-
for (const one of text.split("\n")) {
|
|
181
|
-
if (one.trim() !== "")
|
|
182
|
-
lines.push(one);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
else if (kind === "tool_use") {
|
|
187
|
-
const name = block["name"];
|
|
188
|
-
lines.push(KIND_HEADLINES[toolKind(name)]);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return lines;
|
|
192
|
-
}
|
|
193
117
|
/** Bidirectional overrides and isolates (U+202A–U+202E, U+2066–U+2069): they can make shown text read differently from what was written. */
|
|
194
118
|
const BIDI_CONTROLS = /[\u202a-\u202e\u2066-\u2069]/g;
|
|
195
119
|
/** Sanitize one display line: strip controls and bidi overrides, redact whole on any secret hit, cap. */
|
|
@@ -200,13 +124,15 @@ export function sanitizeTranscriptLine(line) {
|
|
|
200
124
|
return capBytes(stripped, LIVE_LINE_CAP);
|
|
201
125
|
}
|
|
202
126
|
/**
|
|
203
|
-
* Open the run's live file for appending. The directory is 0700,
|
|
204
|
-
* 0600 and CREATED HERE (O_EXCL: a name that already exists —
|
|
205
|
-
* it there — is refused, not appended to), opened O_NOFOLLOW
|
|
206
|
-
* descriptor retained for the run's whole duration.
|
|
207
|
-
*
|
|
127
|
+
* Open the run's live file for appending records. The directory is 0700,
|
|
128
|
+
* the file 0600 and CREATED HERE (O_EXCL: a name that already exists —
|
|
129
|
+
* whatever put it there — is refused, not appended to), opened O_NOFOLLOW
|
|
130
|
+
* with the descriptor retained for the run's whole duration. `options` is
|
|
131
|
+
* the run's trusted context: its checkout, and whether its project shows
|
|
132
|
+
* commands (read once, here). Returns null on any failure: no live view is
|
|
133
|
+
* ever worth a build.
|
|
208
134
|
*/
|
|
209
|
-
export function openLiveLog(root, runId) {
|
|
135
|
+
export function openLiveLog(root, runId, options = {}) {
|
|
210
136
|
let fd;
|
|
211
137
|
try {
|
|
212
138
|
mkdirSync(liveDir(root), { recursive: true, mode: 0o700 });
|
|
@@ -215,19 +141,20 @@ export function openLiveLog(root, runId) {
|
|
|
215
141
|
catch {
|
|
216
142
|
return null;
|
|
217
143
|
}
|
|
144
|
+
const recorder = createLiveRecorder({ base: options.base ?? null, commands: options.commands === true, ...(options.clock === undefined ? {} : { clock: options.clock }) });
|
|
218
145
|
let written = 0;
|
|
219
146
|
let capped = false;
|
|
220
147
|
let closed = false;
|
|
221
|
-
const append = (
|
|
148
|
+
const append = (record) => {
|
|
222
149
|
if (closed || capped)
|
|
223
150
|
return;
|
|
224
|
-
const payload = Buffer.from(`${
|
|
225
|
-
// The
|
|
226
|
-
const
|
|
227
|
-
if (written + payload.length +
|
|
151
|
+
const payload = Buffer.from(`${serializeLiveRecord(record)}\n`, "utf8");
|
|
152
|
+
// The cap record is RESERVED inside the cap: whatever remains must still fit it.
|
|
153
|
+
const capLine = `${serializeLiveRecord({ k: "cap", at: record.at })}\n`;
|
|
154
|
+
if (written + payload.length + Buffer.byteLength(capLine, "utf8") > LIVE_FILE_CAP) {
|
|
228
155
|
capped = true;
|
|
229
156
|
try {
|
|
230
|
-
writeSync(fd,
|
|
157
|
+
writeSync(fd, capLine);
|
|
231
158
|
}
|
|
232
159
|
catch {
|
|
233
160
|
// Nothing to do: the file simply ends early.
|
|
@@ -248,20 +175,27 @@ export function openLiveLog(root, runId) {
|
|
|
248
175
|
}
|
|
249
176
|
}
|
|
250
177
|
};
|
|
178
|
+
append({ k: "open", at: (options.clock ?? (() => new Date()))().toISOString(), detail: options.commands === true ? "commands" : "base" });
|
|
251
179
|
return {
|
|
252
180
|
observe(event) {
|
|
253
181
|
try {
|
|
254
|
-
for (const
|
|
255
|
-
append(
|
|
256
|
-
}
|
|
182
|
+
for (const record of recorder.observe(event))
|
|
183
|
+
append(record);
|
|
257
184
|
}
|
|
258
185
|
catch {
|
|
259
|
-
//
|
|
186
|
+
// Recording is observational; a malformed event records nothing.
|
|
260
187
|
}
|
|
261
188
|
},
|
|
262
189
|
close() {
|
|
263
190
|
if (closed)
|
|
264
191
|
return;
|
|
192
|
+
try {
|
|
193
|
+
for (const record of recorder.end())
|
|
194
|
+
append(record);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// the steps simply stay open in the file
|
|
198
|
+
}
|
|
265
199
|
closed = true;
|
|
266
200
|
try {
|
|
267
201
|
closeSync(fd);
|
|
@@ -274,28 +208,64 @@ export function openLiveLog(root, runId) {
|
|
|
274
208
|
}
|
|
275
209
|
const READ_WINDOW = 64 * 1024;
|
|
276
210
|
const READ_LOOKAHEAD = 2 * 1024;
|
|
211
|
+
/** How far past an over-long line one read looks for its end; past it, the line is skipped up to there. */
|
|
212
|
+
const SKIP_SCAN = 64 * 1024 * 1024;
|
|
213
|
+
/** The live directory itself: a real directory, never a link to one. */
|
|
214
|
+
function liveDirIsReal(root) {
|
|
215
|
+
try {
|
|
216
|
+
const stat = lstatSync(liveDir(root));
|
|
217
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function openForRead(path) {
|
|
224
|
+
try {
|
|
225
|
+
return { fd: openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK) };
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return error.code === "ENOENT" ? { missing: true } : { unreadable: true };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** The line that stands in for one too long to read, in the file's own format. */
|
|
232
|
+
function cutLine(format) {
|
|
233
|
+
return format === "records" ? `${serializeLiveRecord({ k: "cut", at: "" })}\n` : "[a line was too long to show]\n";
|
|
234
|
+
}
|
|
277
235
|
/**
|
|
278
236
|
* Read the live file from a byte offset, returning only COMPLETE lines
|
|
279
|
-
* (through the last LF in the window). The
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
237
|
+
* (through the last LF in the window). The records file is read when it
|
|
238
|
+
* exists, else the legacy text file. The descriptor is the authority:
|
|
239
|
+
* opened O_NOFOLLOW | O_NONBLOCK inside a real live directory, proved a
|
|
240
|
+
* regular file, measured and read through itself. A file smaller than the
|
|
241
|
+
* offset was replaced — the caller resets, visibly, and `gen` names the file
|
|
242
|
+
* so an offset is never reused against another one. A line longer than the
|
|
243
|
+
* whole window is skipped through its end, so a reader never stalls.
|
|
244
|
+
* `allowTail` (the final drain) returns the unterminated tail after
|
|
245
|
+
* finalization. Legacy text is cleaned again on the way out.
|
|
284
246
|
*/
|
|
285
247
|
export function readLiveWindow(root, runId, from, allowTail = false) {
|
|
286
248
|
if (!Number.isSafeInteger(from) || from < 0)
|
|
287
249
|
return { ok: false, reason: "unreadable" };
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
250
|
+
if (!liveDirIsReal(root))
|
|
251
|
+
return { ok: false, reason: "missing" };
|
|
252
|
+
let format = "records";
|
|
253
|
+
let opened = openForRead(liveLogPath(root, runId));
|
|
254
|
+
if ("missing" in opened) {
|
|
255
|
+
format = "text";
|
|
256
|
+
opened = openForRead(legacyLiveLogPath(root, runId));
|
|
294
257
|
}
|
|
258
|
+
if ("missing" in opened)
|
|
259
|
+
return { ok: false, reason: "missing" };
|
|
260
|
+
if ("unreadable" in opened)
|
|
261
|
+
return { ok: false, reason: "unreadable" };
|
|
262
|
+
const fd = opened.fd;
|
|
295
263
|
try {
|
|
296
|
-
const stat = fstatSync(fd);
|
|
264
|
+
const stat = fstatSync(fd, { bigint: true });
|
|
297
265
|
if (!stat.isFile())
|
|
298
266
|
return { ok: false, reason: "unreadable" };
|
|
267
|
+
// Linux hands a removed file's inode straight to the next one made, so the birth time (ns) tells them apart too.
|
|
268
|
+
const gen = `${format}:${stat.dev}:${stat.ino}:${stat.birthtimeNs}`;
|
|
299
269
|
const size = Number(stat.size);
|
|
300
270
|
if (size < from)
|
|
301
271
|
return { ok: false, reason: "replaced" };
|
|
@@ -311,17 +281,44 @@ export function readLiveWindow(root, runId, from, allowTail = false) {
|
|
|
311
281
|
const chunk = buffer.subarray(0, offset);
|
|
312
282
|
const lastLf = chunk.lastIndexOf(0x0a);
|
|
313
283
|
const eofReached = from + offset >= size;
|
|
284
|
+
const out = (text, next, eof) => ({
|
|
285
|
+
ok: true,
|
|
286
|
+
// Raw: every reader cleans and redacts through liveWindowLines / liveWindowRecords, carrying the key state.
|
|
287
|
+
text,
|
|
288
|
+
nextOffset: next,
|
|
289
|
+
eof,
|
|
290
|
+
format,
|
|
291
|
+
gen,
|
|
292
|
+
});
|
|
314
293
|
if (lastLf === -1) {
|
|
315
|
-
if (allowTail && eofReached && offset > 0)
|
|
316
|
-
return
|
|
294
|
+
if (allowTail && eofReached && offset > 0)
|
|
295
|
+
return out(chunk.toString("utf8"), from + offset, true);
|
|
296
|
+
// A whole window without an end of line: skip through its end (bounded), never stall on it.
|
|
297
|
+
if (offset === READ_WINDOW + READ_LOOKAHEAD) {
|
|
298
|
+
const scan = Buffer.alloc(64 * 1024);
|
|
299
|
+
const limit = Math.min(size, from + SKIP_SCAN);
|
|
300
|
+
let at = from + offset;
|
|
301
|
+
while (at < limit) {
|
|
302
|
+
const read = readSync(fd, scan, 0, Math.min(scan.length, limit - at), at);
|
|
303
|
+
if (read <= 0)
|
|
304
|
+
break;
|
|
305
|
+
const lf = scan.subarray(0, read).indexOf(0x0a);
|
|
306
|
+
if (lf !== -1)
|
|
307
|
+
return { ok: true, text: cutLine(format), nextOffset: at + lf + 1, eof: at + lf + 1 >= size, format, gen };
|
|
308
|
+
at += read;
|
|
309
|
+
}
|
|
310
|
+
// No end within reach. A finished run's drain, or a line longer than any read looks, is cut where the
|
|
311
|
+
// look stopped, so reading always moves on; a live run's line may still be being written, so it waits.
|
|
312
|
+
if (allowTail || at >= from + SKIP_SCAN)
|
|
313
|
+
return { ok: true, text: cutLine(format), nextOffset: at, eof: at >= size, format, gen };
|
|
314
|
+
return { ok: true, text: "", nextOffset: from, eof: false, format, gen };
|
|
317
315
|
}
|
|
318
|
-
return
|
|
319
|
-
}
|
|
320
|
-
if (allowTail && eofReached && lastLf + 1 < offset) {
|
|
321
|
-
return { ok: true, text: chunk.toString("utf8"), nextOffset: from + offset, eof: true };
|
|
316
|
+
return out("", from, eofReached && offset === 0);
|
|
322
317
|
}
|
|
318
|
+
if (allowTail && eofReached && lastLf + 1 < offset)
|
|
319
|
+
return out(chunk.toString("utf8"), from + offset, true);
|
|
323
320
|
const complete = chunk.subarray(0, lastLf + 1);
|
|
324
|
-
return
|
|
321
|
+
return out(complete.toString("utf8"), from + lastLf + 1, from + lastLf + 1 >= size);
|
|
325
322
|
}
|
|
326
323
|
catch {
|
|
327
324
|
return { ok: false, reason: "unreadable" };
|
|
@@ -330,6 +327,27 @@ export function readLiveWindow(root, runId, from, allowTail = false) {
|
|
|
330
327
|
closeSync(fd);
|
|
331
328
|
}
|
|
332
329
|
}
|
|
330
|
+
/** A window's complete lines as the plain words the run page and `toolroll peek` show, whatever its format. */
|
|
331
|
+
export function liveWindowLines(read, keys = { inKey: false }) {
|
|
332
|
+
if (read.format === "records")
|
|
333
|
+
return projectLiveText(parseLiveRecords(read.text, keys));
|
|
334
|
+
return cleanLegacy(read.text, keys).split("\n").filter(one => one !== "");
|
|
335
|
+
}
|
|
336
|
+
/** A window as records, whatever its format: an older plain file's lines become the agent's words. */
|
|
337
|
+
export function liveWindowRecords(read, keys = { inKey: false }) {
|
|
338
|
+
if (read.format === "records")
|
|
339
|
+
return parseLiveRecords(read.text, keys);
|
|
340
|
+
return cleanLegacy(read.text, keys).split("\n").filter(one => one !== "").map(line => ({ k: "say", at: "", t: [line] }));
|
|
341
|
+
}
|
|
342
|
+
/** Legacy text lines, cleaned again on read: the file may have been altered after it was written. */
|
|
343
|
+
function cleanLegacy(text, keys) {
|
|
344
|
+
const lines = text.split("\n");
|
|
345
|
+
const ended = lines.length > 0 && lines[lines.length - 1] === "";
|
|
346
|
+
if (ended)
|
|
347
|
+
lines.pop();
|
|
348
|
+
const shown = redactLiveLines(lines, keys);
|
|
349
|
+
return ended ? `${shown.join("\n")}\n` : shown.join("\n");
|
|
350
|
+
}
|
|
333
351
|
/**
|
|
334
352
|
* Enumerate the live directory for the reconcile sweep. Pure listing — the
|
|
335
353
|
* CALLER decides which runs are finalized and when; this module only
|
|
@@ -353,7 +371,7 @@ export function listLiveFiles(root) {
|
|
|
353
371
|
catch {
|
|
354
372
|
continue;
|
|
355
373
|
}
|
|
356
|
-
const match = /^(\d{1,15})\.log$/.exec(name);
|
|
374
|
+
const match = /^(\d{1,15})\.(?:jsonl|log)$/.exec(name);
|
|
357
375
|
out.push({ runId: match === null ? null : Number(match[1]), fileName: name, mtimeMs });
|
|
358
376
|
}
|
|
359
377
|
return out;
|
package/dist/mcp-oauth.d.ts
CHANGED
|
@@ -51,7 +51,9 @@ export type OAuthSession = {
|
|
|
51
51
|
name: string;
|
|
52
52
|
role: "approver" | "viewer";
|
|
53
53
|
csrf: string;
|
|
54
|
-
/**
|
|
54
|
+
/** The account's credential generation this session signed in under: a code is never issued under another. */
|
|
55
|
+
generation: number;
|
|
56
|
+
/** Signed in with an identity provider: its name, and whether an explicit re-check made on request awaits one step-up. */
|
|
55
57
|
sso: {
|
|
56
58
|
label: string;
|
|
57
59
|
fresh: boolean;
|
|
@@ -68,8 +70,9 @@ export type OAuthHttpOptions = {
|
|
|
68
70
|
session: (request: IncomingMessage) => OAuthSession | null;
|
|
69
71
|
/** A posted form came from a page of this console (its Origin, or Referer, names an allowed host). */
|
|
70
72
|
sameSite: (request: IncomingMessage) => boolean;
|
|
71
|
-
/** The step-up: the person's password, or (empty)
|
|
72
|
-
|
|
73
|
+
/** The step-up, proved at the moment of consent on this request's live session: the person's password, or (empty)
|
|
74
|
+
* an explicit identity-provider re-check, spent by this consent. */
|
|
75
|
+
confirm: (request: IncomingMessage, session: OAuthSession, typed: string) => boolean;
|
|
73
76
|
/** The projects this person may choose: each a project the server knows that they can use now. */
|
|
74
77
|
projectsFor: (account: string) => string[];
|
|
75
78
|
/** The token exchange's budget by source (request-budget.ts): charged once per request, before its body is read or any code or refresh secret is spent. */
|
package/dist/mcp-oauth.js
CHANGED
|
@@ -308,8 +308,8 @@ export function createOAuthHttp(options) {
|
|
|
308
308
|
return backToClient(response, origin, one.redirectUri, one.state, { error: "access_denied", error_description: "the person declined" }, "Cancelled. Returning to your MCP client…");
|
|
309
309
|
}
|
|
310
310
|
const account = store.accountOf(session.name);
|
|
311
|
-
if (account === null || account.revokedAt !== null)
|
|
312
|
-
return problemPage(response, 403, "Sign in first", "Your
|
|
311
|
+
if (account === null || account.revokedAt !== null || account.generation !== session.generation)
|
|
312
|
+
return problemPage(response, 403, "Sign in first", "Your access changed. Sign in again; nothing was allowed.");
|
|
313
313
|
const access = form.get("access") === "act" ? "act" : "read";
|
|
314
314
|
const chosen = [...new Set(form.getAll("project"))];
|
|
315
315
|
const again = (status, problem) => page(response, status, consentHtml(session, id, one, problem, { access, projects: chosen }));
|
|
@@ -323,11 +323,15 @@ export function createOAuthHttp(options) {
|
|
|
323
323
|
return again(403, "Choose from your own projects.");
|
|
324
324
|
if (!adapterPolicy({ caller: "cookie", capability: account.role === "approver" ? "act" : "read", viewer: account.role === "viewer" }, "step-up", source => source === "none" && chosen.every(path => mine.includes(path))).ok)
|
|
325
325
|
return again(403, "Choose from your own projects.");
|
|
326
|
-
if (!options.confirm(session, form.get("password") ?? ""))
|
|
326
|
+
if (!options.confirm(request, session, form.get("password") ?? ""))
|
|
327
327
|
return again(403, "Enter your Toolroll password to allow access.");
|
|
328
|
+
// Re-proved after the step-up: the code carries the generation the person consented under, never a newer one.
|
|
329
|
+
const current = store.accountOf(session.name);
|
|
330
|
+
if (current === null || current.revokedAt !== null || current.generation !== session.generation)
|
|
331
|
+
return problemPage(response, 403, "Sign in first", "Your access changed. Sign in again; nothing was allowed.");
|
|
328
332
|
waiting.delete(id);
|
|
329
333
|
const code = randomBytes(32).toString("base64url");
|
|
330
|
-
store.saveOAuthCode(sha256(code), { client: client.id, account: session.name, generation:
|
|
334
|
+
store.saveOAuthCode(sha256(code), { client: client.id, account: session.name, generation: session.generation, access, projects: chosen, redirectUri: one.redirectUri, resource: one.resource,
|
|
331
335
|
challenge: one.challenge, expiresAt: new Date(now.getTime() + OAUTH_TIMES.codeMs).toISOString() });
|
|
332
336
|
return backToClient(response, origin, one.redirectUri, one.state, { code }, "Allowed. Returning to your MCP client…");
|
|
333
337
|
}
|
package/dist/memory-pass.js
CHANGED
|
@@ -138,11 +138,11 @@ function fileSources(repo, kind, home) {
|
|
|
138
138
|
/** The plane's own sessions: lead threads that touched the project, and finished crew runs with their handoff and check output. */
|
|
139
139
|
function planeSources(store, repo, evidenceRoot) {
|
|
140
140
|
const out = [];
|
|
141
|
-
const threads = store.handle.prepare(`SELECT c.thread AS thread, c.id AS conversation, MAX(m.id) AS last, MAX(m.created_at) AS at FROM team_conversation c JOIN lead_message m ON m.thread=c.thread
|
|
141
|
+
const threads = store.handle.prepare(`SELECT c.thread AS thread, c.id AS conversation, MAX(m.id) AS last, MAX(m.created_at) AS at FROM team_conversation c JOIN lead_message m ON m.thread=c.thread AND m.pending=0
|
|
142
142
|
WHERE EXISTS (SELECT 1 FROM json_each(c.projects_json) WHERE value=?) GROUP BY c.thread`).all(repo);
|
|
143
143
|
for (const row of threads) {
|
|
144
144
|
const thread = Number(row['thread']);
|
|
145
|
-
out.push({ id: `lead:${thread}:${Number(row['last'])}`, repo, kind: 'lead', source: `conversation:${String(row['conversation'])}`, at: String(row['at']), trace: () => redact(store.handle.prepare('SELECT role,text FROM lead_message WHERE thread=? ORDER BY id LIMIT 400').all(thread).map(m => `${m['role']}: ${clip(String(m['text']), 4000)}`).join('\n')) });
|
|
145
|
+
out.push({ id: `lead:${thread}:${Number(row['last'])}`, repo, kind: 'lead', source: `conversation:${String(row['conversation'])}`, at: String(row['at']), trace: () => redact(store.handle.prepare('SELECT role,text FROM lead_message WHERE thread=? AND pending=0 ORDER BY id LIMIT 400').all(thread).map(m => `${m['role']}: ${clip(String(m['text']), 4000)}`).join('\n')) });
|
|
146
146
|
}
|
|
147
147
|
const runs = store.handle.prepare(`SELECT r.id, r.role, r.finished_at, r.handoff, t.external_id FROM run r JOIN task_ref t ON t.id=r.task_ref WHERE t.repo=? AND r.role IN ('builder','repair') AND r.outcome IS NOT NULL ORDER BY r.id DESC LIMIT 100`).all(repo);
|
|
148
148
|
for (const row of runs) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
/** Presentation only: no fields are read, no requests are sent, and no
|
|
2
|
-
* keyboard events are captured. A focused input alone is NOT a keyboard.
|
|
2
|
+
* keyboard events are captured. A focused input alone is NOT a keyboard.
|
|
3
|
+
*
|
|
4
|
+
* It publishes the visible area (--viewport-height, --viewport-top): a phone's
|
|
5
|
+
* keyboard shrinks only the visual viewport on iOS (100dvh never moves), so a
|
|
6
|
+
* full-height app shell sized to it is what keeps the composer above the keys. */
|
|
3
7
|
export declare const MOBILE_VIEWPORT_SCRIPT: string;
|
package/dist/mobile-viewport.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/** Presentation only: no fields are read, no requests are sent, and no
|
|
2
|
-
* keyboard events are captured. A focused input alone is NOT a keyboard.
|
|
2
|
+
* keyboard events are captured. A focused input alone is NOT a keyboard.
|
|
3
|
+
*
|
|
4
|
+
* It publishes the visible area (--viewport-height, --viewport-top): a phone's
|
|
5
|
+
* keyboard shrinks only the visual viewport on iOS (100dvh never moves), so a
|
|
6
|
+
* full-height app shell sized to it is what keeps the composer above the keys. */
|
|
3
7
|
export const MOBILE_VIEWPORT_SCRIPT = String.raw `
|
|
4
8
|
(function(){
|
|
5
9
|
var root=document.documentElement,viewport=window.visualViewport;
|
|
6
10
|
if(!viewport)return;
|
|
7
|
-
var phone=window.matchMedia('(max-width:
|
|
11
|
+
var phone=window.matchMedia('(max-width: 900px)'),baseline=window.innerHeight,frame=0;
|
|
8
12
|
function update(){
|
|
9
13
|
frame=0;
|
|
10
14
|
var active=document.activeElement;
|
|
@@ -18,8 +22,20 @@ export const MOBILE_VIEWPORT_SCRIPT = String.raw `
|
|
|
18
22
|
var opening=open&&!root.hasAttribute('data-mobile-keyboard');
|
|
19
23
|
var atEnd=opening&&window.innerHeight+window.scrollY>=root.scrollHeight-120;
|
|
20
24
|
root.toggleAttribute('data-mobile-keyboard',Boolean(open));
|
|
25
|
+
// A pinch zoom shrinks the visual viewport too, and then a keyboard can't be told from the zoom: zoomed, the shell
|
|
26
|
+
// is the whole screen (the reader pans to the field), so a keyboard closed while zoomed never leaves it short.
|
|
27
|
+
if(Math.abs(viewport.scale-1)<0.01){
|
|
28
|
+
root.style.setProperty('--viewport-height',Math.round(viewport.height)+'px');
|
|
29
|
+
root.style.setProperty('--viewport-top',Math.max(0,Math.round(viewport.offsetTop))+'px');
|
|
30
|
+
}else{
|
|
31
|
+
root.style.removeProperty('--viewport-height');
|
|
32
|
+
root.style.removeProperty('--viewport-top');
|
|
33
|
+
}
|
|
21
34
|
root.style.setProperty('--keyboard-inset',open?Math.round(gap)+'px':'0px');
|
|
22
35
|
if(atEnd)window.scrollTo({top:root.scrollHeight,behavior:'instant'});
|
|
36
|
+
// In the app shell the field sits in its own scrolling panel, which the shell's new size just cut short:
|
|
37
|
+
// bring it back into that panel's view once the smaller layout is in.
|
|
38
|
+
if(opening&&active.closest('.so-workspace'))requestAnimationFrame(function(){active.scrollIntoView({block:'nearest',behavior:'instant'});});
|
|
23
39
|
}
|
|
24
40
|
function schedule(){if(!frame)frame=requestAnimationFrame(update);}
|
|
25
41
|
viewport.addEventListener('resize',schedule);
|
package/dist/model-catalog.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* installed CLIs are current. Everything lands in orders.db so pickers,
|
|
7
7
|
* spend pins and "new model" notices read one saved snapshot.
|
|
8
8
|
*/
|
|
9
|
+
import { isAcpProvider } from "./acp-agents.js";
|
|
9
10
|
import { execFile } from "node:child_process";
|
|
10
11
|
import { accessSync, constants, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
11
12
|
import { homedir } from "node:os";
|
|
@@ -197,6 +198,9 @@ export function modelOptions(store, provider, now, home = homedir()) {
|
|
|
197
198
|
}
|
|
198
199
|
if (provider === "gemini")
|
|
199
200
|
return seenModels(store, "gemini").slice(0, 20).map(option);
|
|
201
|
+
// An ACP agent offers only the models its check recorded, on the runner that checked it — read there, never guessed.
|
|
202
|
+
if (isAcpProvider(provider))
|
|
203
|
+
return [];
|
|
200
204
|
return seenModels(store, "openrouter").filter(model => model.tools).sort((a, b) => a.name.localeCompare(b.name)).map(option);
|
|
201
205
|
}
|
|
202
206
|
/** Chat reserves worst-case spend: price per token in whole micro-dollars,
|