@hyperdrive.bot/paseo-server 0.3.10 → 0.3.12
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/dist/server/server/agent/agent-loading.js +24 -3
- package/dist/server/server/agent/agent-manager.d.ts +22 -0
- package/dist/server/server/agent/agent-manager.js +36 -0
- package/dist/server/server/agent/agent-storage.d.ts +9 -0
- package/dist/server/server/agent/blocker-log.d.ts +81 -0
- package/dist/server/server/agent/blocker-log.js +113 -0
- package/dist/server/server/agent/card-move-log.d.ts +66 -0
- package/dist/server/server/agent/card-move-log.js +113 -0
- package/dist/server/server/agent/jarvis-judge.d.ts +121 -0
- package/dist/server/server/agent/jarvis-judge.js +225 -0
- package/dist/server/server/agent/judge-relay-gate.d.ts +70 -0
- package/dist/server/server/agent/judge-relay-gate.js +92 -0
- package/dist/server/server/agent/mcp-server.js +9 -0
- package/dist/server/server/agent/mcp-shared.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +92 -10
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +615 -38
- package/dist/server/server/agent/session-digest-generator.d.ts +27 -0
- package/dist/server/server/agent/session-digest-generator.js +208 -17
- package/dist/server/server/agent/tools/paseo-tools.d.ts +12 -0
- package/dist/server/server/agent/tools/paseo-tools.js +48 -0
- package/dist/server/server/agent/tools/read-only-surface.d.ts +52 -0
- package/dist/server/server/agent/tools/read-only-surface.js +68 -0
- package/dist/server/server/agent/tools/types.d.ts +5 -0
- package/dist/server/server/bootstrap.js +73 -0
- package/dist/server/server/relay-transport.js +36 -5
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-9f03d060da06e8838a4e0fdd5c067757.js → desktop-attachment-bridge-e6e0f92f1a2959279a7c3b2b50bf1df3.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e6e0f92f1a2959279a7c3b2b50bf1df3.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e6e0f92f1a2959279a7c3b2b50bf1df3.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e6e0f92f1a2959279a7c3b2b50bf1df3.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e6e0f92f1a2959279a7c3b2b50bf1df3.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-d0d9dffa612bf12bf551c5e513c28691.js → desktop-attachment-store-d5c93939344c993e52d655ccf8942928.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5c93939344c993e52d655ccf8942928.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5c93939344c993e52d655ccf8942928.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5c93939344c993e52d655ccf8942928.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5c93939344c993e52d655ccf8942928.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-461cf3ab1d54d6debea0e037829768e9.js → index-853ef1a7fc9c9b7a39577b79564e0896.js} +166 -165
- package/dist/server/web-ui/_expo/static/js/web/index-853ef1a7fc9c9b7a39577b79564e0896.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-853ef1a7fc9c9b7a39577b79564e0896.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-853ef1a7fc9c9b7a39577b79564e0896.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-461cf3ab1d54d6debea0e037829768e9.js.map.gz → index-853ef1a7fc9c9b7a39577b79564e0896.js.map.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-54584275ea7a32061741c119074ff13f.js → indexeddb-attachment-store-35fb4ddfe925b1fb6b7a6c1953b21621.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-35fb4ddfe925b1fb6b7a6c1953b21621.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-35fb4ddfe925b1fb6b7a6c1953b21621.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-35fb4ddfe925b1fb6b7a6c1953b21621.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-35fb4ddfe925b1fb6b7a6c1953b21621.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-b53f2ba81a9777f6f6d04a98b0a12c33.js → native-file-attachment-store-8602ccac919e2b56f2e9a252dd06483e.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8602ccac919e2b56f2e9a252dd06483e.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8602ccac919e2b56f2e9a252dd06483e.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8602ccac919e2b56f2e9a252dd06483e.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8602ccac919e2b56f2e9a252dd06483e.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{profiler-7b2011c26cedb58cc00c02a7c25fc0a0.js → profiler-b3dd30ba0a88aece50e7e403785f34ba.js} +11 -11
- package/dist/server/web-ui/_expo/static/js/web/profiler-b3dd30ba0a88aece50e7e403785f34ba.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-b3dd30ba0a88aece50e7e403785f34ba.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-b3dd30ba0a88aece50e7e403785f34ba.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-b3dd30ba0a88aece50e7e403785f34ba.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-1b57837c4c358ef963a1392d9d96354b.js → startRecording-fd9fa11e24a3b8d94e5161aae7f42a5d.js} +60 -60
- package/dist/server/web-ui/_expo/static/js/web/startRecording-fd9fa11e24a3b8d94e5161aae7f42a5d.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-fd9fa11e24a3b8d94e5161aae7f42a5d.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-fd9fa11e24a3b8d94e5161aae7f42a5d.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-1b57837c4c358ef963a1392d9d96354b.js.map.gz → startRecording-fd9fa11e24a3b8d94e5161aae7f42a5d.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9f03d060da06e8838a4e0fdd5c067757.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9f03d060da06e8838a4e0fdd5c067757.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9f03d060da06e8838a4e0fdd5c067757.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9f03d060da06e8838a4e0fdd5c067757.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d0d9dffa612bf12bf551c5e513c28691.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d0d9dffa612bf12bf551c5e513c28691.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d0d9dffa612bf12bf551c5e513c28691.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d0d9dffa612bf12bf551c5e513c28691.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-461cf3ab1d54d6debea0e037829768e9.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-461cf3ab1d54d6debea0e037829768e9.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-461cf3ab1d54d6debea0e037829768e9.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-54584275ea7a32061741c119074ff13f.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-54584275ea7a32061741c119074ff13f.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-54584275ea7a32061741c119074ff13f.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-54584275ea7a32061741c119074ff13f.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b53f2ba81a9777f6f6d04a98b0a12c33.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b53f2ba81a9777f6f6d04a98b0a12c33.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b53f2ba81a9777f6f6d04a98b0a12c33.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b53f2ba81a9777f6f6d04a98b0a12c33.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-7b2011c26cedb58cc00c02a7c25fc0a0.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-7b2011c26cedb58cc00c02a7c25fc0a0.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-7b2011c26cedb58cc00c02a7c25fc0a0.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-7b2011c26cedb58cc00c02a7c25fc0a0.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1b57837c4c358ef963a1392d9d96354b.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1b57837c4c358ef963a1392d9d96354b.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1b57837c4c358ef963a1392d9d96354b.js.map.br +0 -0
|
@@ -26,7 +26,33 @@ export interface SessionDigestRefreshOptions {
|
|
|
26
26
|
};
|
|
27
27
|
logger: Logger;
|
|
28
28
|
deps?: SessionDigestGeneratorDeps;
|
|
29
|
+
/**
|
|
30
|
+
* Called once the derivation has finished, whether it produced a digest,
|
|
31
|
+
* skipped, or failed (JARVIS-V1-SPEC §4).
|
|
32
|
+
*
|
|
33
|
+
* This exists because the Jarvis judge and this refresh both listen to the
|
|
34
|
+
* SAME `finished` attention event. Run in parallel, the judge always wins the
|
|
35
|
+
* race and reads a digest that has not been written yet, so every briefing
|
|
36
|
+
* arrives with no summary, no blockers and no deadline, and the judge
|
|
37
|
+
* correctly answers "quiet" forever. That failure is silent: no error, no log,
|
|
38
|
+
* a feature that looks alive and delivers nothing. Chaining the judge off this
|
|
39
|
+
* callback is what makes the substrate actually reach it.
|
|
40
|
+
*/
|
|
41
|
+
onSettled?: () => void;
|
|
42
|
+
/** Test seam. Defaults to DIGEST_GENERATION_TIMEOUT_MS. */
|
|
43
|
+
timeoutMs?: number;
|
|
29
44
|
}
|
|
45
|
+
/** Observability for a path whose failures are permanent (§3.1, hazard 2). */
|
|
46
|
+
declare const digestCounters: {
|
|
47
|
+
attempted: number;
|
|
48
|
+
succeeded: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
timedOut: number;
|
|
51
|
+
skippedUnchanged: number;
|
|
52
|
+
};
|
|
53
|
+
export declare function getSessionDigestCounters(): Readonly<typeof digestCounters>;
|
|
54
|
+
/** Test seam: the maps are module-level, so tests need a way back to a clean slate. */
|
|
55
|
+
export declare function resetSessionDigestStateForTests(): void;
|
|
30
56
|
export declare function generateAndApplySessionDigest(options: SessionDigestRefreshOptions): Promise<void>;
|
|
31
57
|
/**
|
|
32
58
|
* Fire-and-forget, debounced digest refresh. Safe to call on every running→idle
|
|
@@ -34,4 +60,5 @@ export declare function generateAndApplySessionDigest(options: SessionDigestRefr
|
|
|
34
60
|
* debounce window.
|
|
35
61
|
*/
|
|
36
62
|
export declare function scheduleSessionDigestRefresh(options: SessionDigestRefreshOptions): void;
|
|
63
|
+
export {};
|
|
37
64
|
//# sourceMappingURL=session-digest-generator.d.ts.map
|
|
@@ -4,35 +4,200 @@ import { resolveStructuredGenerationProviders, } from "./structured-generation-p
|
|
|
4
4
|
import { buildDigestSourceText, deriveKeyFilesFromTimeline, mergeDigest, } from "./session-digest.js";
|
|
5
5
|
const SUMMARY_MAX_CHARS = 600;
|
|
6
6
|
const ACCOMPLISHMENT_MAX = 10;
|
|
7
|
+
const NEXT_STEP_MAX_CHARS = 240;
|
|
8
|
+
const BLOCKER_MAX = 5;
|
|
7
9
|
const DIGEST_SUMMARY_SCHEMA = z.object({
|
|
8
10
|
summary: z.string().max(SUMMARY_MAX_CHARS),
|
|
9
11
|
accomplishments: z.array(z.string()).max(ACCOMPLISHMENT_MAX),
|
|
12
|
+
// Jarvis substrate (JARVIS-V1-SPEC §3.1). These land in `derived*` digest keys,
|
|
13
|
+
// never in the self-reported `nextStep`/`blockers`.
|
|
14
|
+
// Nullish rather than required: partial substrate beats a whole generation
|
|
15
|
+
// failing schema validation and costing the summary too. Cheap insurance, and
|
|
16
|
+
// unlike a second generation call it costs no extra tokens.
|
|
17
|
+
nextStep: z.string().max(NEXT_STEP_MAX_CHARS).nullish(),
|
|
18
|
+
blockers: z.array(z.string()).max(BLOCKER_MAX).nullish(),
|
|
19
|
+
urgency: z.enum(["low", "normal", "high"]).nullish(),
|
|
20
|
+
deadline: z.string().nullish(),
|
|
10
21
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Refresh gating (JARVIS-V1-SPEC §3.1, hazard 1).
|
|
24
|
+
*
|
|
25
|
+
* The ORIGINAL gate was a flat 10-minute wall-clock debounce, stamped BEFORE the
|
|
26
|
+
* work ran. Two consequences made it unsafe once the digest became a one-shot,
|
|
27
|
+
* unrecoverable derivation rather than a nice-to-have summary:
|
|
28
|
+
*
|
|
29
|
+
* 1. Back-to-back idles inside the window were silently dropped, and a dropped
|
|
30
|
+
* derivation is not always recoverable (see the readability note below).
|
|
31
|
+
* 2. Stamping before the attempt meant a FAILED generation still burned the
|
|
32
|
+
* window, so the next idle was skipped too. That is "record intent as fact":
|
|
33
|
+
* the timestamp meant "we tried", not "we produced a digest".
|
|
34
|
+
*
|
|
35
|
+
* CORRECTION (measured 2026-07-31). An earlier version of this comment claimed
|
|
36
|
+
* the timeline is "destroyed shortly after a session goes cold". That is WRONG,
|
|
37
|
+
* and it was generalized from two probes at the extremes with nothing in between.
|
|
38
|
+
* What is actually true:
|
|
39
|
+
*
|
|
40
|
+
* - paseo stores NO timeline of its own. Agent records on disk are ~4 KB of
|
|
41
|
+
* pure metadata with no timeline key.
|
|
42
|
+
* - `get_agent_activity` reads THROUGH to the provider's own transcript, for
|
|
43
|
+
* Claude `~/.claude/projects/<slug>/<sid>.jsonl`. Readability is binary and
|
|
44
|
+
* age-independent: a 19.6-day-old session returned 88 activities in full.
|
|
45
|
+
* - Content is lost only when that provider file is pruned. For Claude that is
|
|
46
|
+
* `cleanupPeriodDays`, default 30, which runs at CLI startup on file mtime.
|
|
47
|
+
* - A second gate exists: the read fails outright when the agent's `cwd` no
|
|
48
|
+
* longer exists ("Working directory does not exist"), which affects sessions
|
|
49
|
+
* from removed worktrees or another machine regardless of file retention.
|
|
50
|
+
*
|
|
51
|
+
* So this derivation is NOT primarily insurance against a minutes-later loss.
|
|
52
|
+
* Its real value is (a) surviving the provider's prune window, (b) surviving the
|
|
53
|
+
* cwd gate, (c) normalizing across providers whose stores paseo cannot read, and
|
|
54
|
+
* (d) cost: a few hundred bytes of digest instead of re-reading a transcript that
|
|
55
|
+
* can exceed 40 MB. Still worth doing on every idle, for different reasons than
|
|
56
|
+
* originally written.
|
|
57
|
+
*
|
|
58
|
+
* The replacement gates on CONTENT and nothing else: skip only when the timeline
|
|
59
|
+
* has not grown since the last SUCCESSFUL digest. New content is new information
|
|
60
|
+
* and is always worth deriving.
|
|
61
|
+
*
|
|
62
|
+
* There is deliberately NO wall-clock floor. An earlier draft had one, and it
|
|
63
|
+
* reintroduced exactly the bug being fixed: two idles a minute apart, both with
|
|
64
|
+
* new content, and the second silently dropped. Any clock-based gate re-creates
|
|
65
|
+
* that, because "we ran recently" and "nothing new happened" are different
|
|
66
|
+
* questions and only the second is a safe reason to skip a one-shot derivation.
|
|
67
|
+
*
|
|
68
|
+
* Cost note: on a chatty session this derives roughly once per completed turn
|
|
69
|
+
* instead of once per 10 minutes. That is the intended trade. The spec classifies
|
|
70
|
+
* a lost derivation as a correctness bug and an extra derivation as a cost line.
|
|
71
|
+
* If cost needs tuning later, the knob must stay content-based (for example
|
|
72
|
+
* "only derive when the timeline grew by more than N rows"), never clock-based.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Hard ceiling on one generation (found live 2026-07-31).
|
|
76
|
+
*
|
|
77
|
+
* `generateStructuredAgentResponseWithFallback` takes no signal and no timeout,
|
|
78
|
+
* and it CAN hang indefinitely: an internal generator agent spawned at 17:50:42
|
|
79
|
+
* and never settled, no success and no error, for the rest of the daemon's life.
|
|
80
|
+
*
|
|
81
|
+
* That is far worse than a slow digest. The hung promise never leaves `inFlight`,
|
|
82
|
+
* so the agent can never derive again, AND because the Jarvis judge is chained to
|
|
83
|
+
* `onSettled`, the judge goes permanently silent for that agent too. One stuck
|
|
84
|
+
* generation quietly removes a card from Jarvis forever, with nothing in the log.
|
|
85
|
+
*
|
|
86
|
+
* Racing a timeout turns that into an ordinary failure: counted, logged, mark not
|
|
87
|
+
* stamped so the next idle retries, and onSettled still fires so the judge runs
|
|
88
|
+
* with whatever substrate already exists. The abandoned work may keep running in
|
|
89
|
+
* the background; that is acceptable, recovering the caller is the point.
|
|
90
|
+
*/
|
|
91
|
+
const DIGEST_GENERATION_TIMEOUT_MS = 120000;
|
|
92
|
+
class DigestGenerationTimeoutError extends Error {
|
|
93
|
+
constructor(ms) {
|
|
94
|
+
super(`Session digest generation exceeded ${ms}ms`);
|
|
95
|
+
this.name = "DigestGenerationTimeoutError";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function withTimeout(work, ms) {
|
|
99
|
+
let timer;
|
|
100
|
+
const bomb = new Promise((_resolve, reject) => {
|
|
101
|
+
timer = setTimeout(() => reject(new DigestGenerationTimeoutError(ms)), ms);
|
|
102
|
+
// Never let this timer keep the daemon alive on shutdown.
|
|
103
|
+
timer.unref?.();
|
|
104
|
+
});
|
|
105
|
+
return Promise.race([work, bomb]).finally(() => {
|
|
106
|
+
if (timer) {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
15
111
|
const lastDigestAt = new Map();
|
|
112
|
+
/** agentId -> `${epoch}:${maxSeq}` of the last SUCCESSFUL digest. */
|
|
113
|
+
const lastDigestMark = new Map();
|
|
16
114
|
const inFlight = new Set();
|
|
17
|
-
|
|
115
|
+
/** Observability for a path whose failures are permanent (§3.1, hazard 2). */
|
|
116
|
+
const digestCounters = { attempted: 0, succeeded: 0, failed: 0, timedOut: 0, skippedUnchanged: 0 };
|
|
117
|
+
export function getSessionDigestCounters() {
|
|
118
|
+
return { ...digestCounters };
|
|
119
|
+
}
|
|
120
|
+
/** Test seam: the maps are module-level, so tests need a way back to a clean slate. */
|
|
121
|
+
export function resetSessionDigestStateForTests() {
|
|
122
|
+
lastDigestAt.clear();
|
|
123
|
+
lastDigestMark.clear();
|
|
124
|
+
inFlight.clear();
|
|
125
|
+
digestCounters.attempted = 0;
|
|
126
|
+
digestCounters.succeeded = 0;
|
|
127
|
+
digestCounters.failed = 0;
|
|
128
|
+
digestCounters.timedOut = 0;
|
|
129
|
+
digestCounters.skippedUnchanged = 0;
|
|
130
|
+
}
|
|
131
|
+
function buildDigestPrompt(sourceText, todayIso) {
|
|
18
132
|
return [
|
|
19
133
|
"You are summarizing a coding agent session so an orchestrator can understand it",
|
|
20
|
-
"without reading the full transcript. Return JSON with exactly
|
|
134
|
+
"without reading the full transcript. Return JSON with exactly these fields:",
|
|
21
135
|
`- summary: one paragraph (<= ${SUMMARY_MAX_CHARS} chars) describing what this session`,
|
|
22
136
|
" is doing and its current state.",
|
|
23
137
|
`- accomplishments: up to ${ACCOMPLISHMENT_MAX} short strings, each a concrete thing done`,
|
|
24
138
|
" (file changed, feature added, bug fixed). Be specific and factual; do not invent.",
|
|
139
|
+
`- nextStep: <= ${NEXT_STEP_MAX_CHARS} chars, the single concrete thing this session was`,
|
|
140
|
+
" about to do next, phrased as an action. Null if the work is finished or you cannot tell.",
|
|
141
|
+
`- blockers: up to ${BLOCKER_MAX} short strings, each something STOPPING progress. If the`,
|
|
142
|
+
" session is waiting on a specific person or an external party, name them explicitly",
|
|
143
|
+
' (for example "waiting on Marco to sign the contract"). Empty array if nothing blocks it.',
|
|
144
|
+
"- urgency: one of low, normal, high. Judge from the work itself, not from how recent it is.",
|
|
145
|
+
" high = production is broken, a client is affected, or a dated commitment is at risk.",
|
|
146
|
+
" normal = ordinary in-flight work. low = exploration, cleanup, or already finished.",
|
|
147
|
+
`- deadline: a real-world calendar date named in the session as a commitment, in YYYY-MM-DD`,
|
|
148
|
+
` form. Today is ${todayIso}, so resolve relative phrases like "Friday" against it. Null if`,
|
|
149
|
+
" no date is committed to. Do NOT invent one and do not infer a date from mere urgency.",
|
|
25
150
|
"",
|
|
26
151
|
"Session transcript:",
|
|
27
152
|
sourceText,
|
|
28
153
|
].join("\n");
|
|
29
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* `${epoch}:${maxSeq}` for the agent's current timeline.
|
|
157
|
+
*
|
|
158
|
+
* Row `seq` restarts at 1 whenever the timeline store is re-initialized without
|
|
159
|
+
* seeded rows, so a seq is ONLY comparable within an epoch. Comparing a bare seq
|
|
160
|
+
* across epochs makes a fresh timeline look older than the last digest and skips
|
|
161
|
+
* forever, silently. Found live: turn 2 of a session never re-derived because
|
|
162
|
+
* its post-resume seq was below the pre-resume high-water mark.
|
|
163
|
+
*/
|
|
164
|
+
function resolveDigestMark(agentManager, agentId, rows) {
|
|
165
|
+
const maxSeq = rows.length > 0 ? rows[rows.length - 1].seq : 0;
|
|
166
|
+
try {
|
|
167
|
+
return `${agentManager.getTimelineEpoch(agentId)}:${maxSeq}`;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Agent not resident: use a marker that can never match a stored one, so we
|
|
171
|
+
// derive rather than skip. Skipping is the dangerous direction.
|
|
172
|
+
return `unknown-${Date.now()}:${maxSeq}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function isUnchangedSinceLastDigest(agentId, mark) {
|
|
176
|
+
const seen = lastDigestMark.get(agentId);
|
|
177
|
+
if (seen === undefined) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const sep = seen.lastIndexOf(":");
|
|
181
|
+
const markSep = mark.lastIndexOf(":");
|
|
182
|
+
const sameEpoch = seen.slice(0, sep) === mark.slice(0, markSep);
|
|
183
|
+
return sameEpoch && Number(mark.slice(markSep + 1)) <= Number(seen.slice(sep + 1));
|
|
184
|
+
}
|
|
30
185
|
export async function generateAndApplySessionDigest(options) {
|
|
31
186
|
const { agentManager, agentId, cwd, logger } = options;
|
|
32
187
|
const rows = await agentManager.getTimelineRows(agentId);
|
|
33
188
|
if (rows.length === 0) {
|
|
34
189
|
return;
|
|
35
190
|
}
|
|
191
|
+
// Content-based gate (see the lastDigestMark block comment above). Extracted
|
|
192
|
+
// to a helper to stay under the oxlint complexity ceiling of 20.
|
|
193
|
+
const mark = resolveDigestMark(agentManager, agentId, rows);
|
|
194
|
+
if (isUnchangedSinceLastDigest(agentId, mark)) {
|
|
195
|
+
digestCounters.skippedUnchanged += 1;
|
|
196
|
+
// INFO, not debug. Every bug in this file has been a silent skip; a skip
|
|
197
|
+
// nobody can see is how a dead feature looks healthy.
|
|
198
|
+
logger.info({ agentId, mark }, "Session digest skipped: timeline unchanged");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
36
201
|
// Deterministic, no LLM — always safe to refresh.
|
|
37
202
|
const keyFiles = deriveKeyFilesFromTimeline(rows);
|
|
38
203
|
const existing = agentManager.getAgent(agentId)?.digest;
|
|
@@ -50,10 +215,11 @@ export async function generateAndApplySessionDigest(options) {
|
|
|
50
215
|
currentSelection: options.currentSelection,
|
|
51
216
|
})
|
|
52
217
|
: [];
|
|
53
|
-
|
|
218
|
+
digestCounters.attempted += 1;
|
|
219
|
+
const result = await withTimeout(generator({
|
|
54
220
|
manager: agentManager,
|
|
55
221
|
cwd,
|
|
56
|
-
prompt: buildDigestPrompt(sourceText),
|
|
222
|
+
prompt: buildDigestPrompt(sourceText, new Date().toISOString().slice(0, 10)),
|
|
57
223
|
schema: DIGEST_SUMMARY_SCHEMA,
|
|
58
224
|
schemaName: "SessionDigestSummary",
|
|
59
225
|
maxRetries: 2,
|
|
@@ -64,18 +230,36 @@ export async function generateAndApplySessionDigest(options) {
|
|
|
64
230
|
title: "Session digest generator",
|
|
65
231
|
internal: true,
|
|
66
232
|
},
|
|
67
|
-
});
|
|
233
|
+
}), options.timeoutMs ?? DIGEST_GENERATION_TIMEOUT_MS);
|
|
68
234
|
summaryPatch = {
|
|
69
235
|
summary: result.summary,
|
|
70
236
|
accomplishments: result.accomplishments,
|
|
237
|
+
// Derived fields go in their own keys. Writing `nextStep`/`blockers`
|
|
238
|
+
// directly would clobber whatever the session self-reported, because
|
|
239
|
+
// mergeDigest overwrites any key present in the patch.
|
|
240
|
+
...(result.nextStep ? { derivedNextStep: result.nextStep } : {}),
|
|
241
|
+
...(result.blockers ? { derivedBlockers: result.blockers } : {}),
|
|
242
|
+
...(result.urgency ? { urgency: result.urgency } : {}),
|
|
243
|
+
...(result.deadline ? { derivedDeadline: result.deadline } : {}),
|
|
71
244
|
};
|
|
245
|
+
// Stamp ONLY on success. A failure must leave the window open so the next
|
|
246
|
+
// idle retries: there is no catch-up job, and the source transcript is
|
|
247
|
+
// subject to the provider's own prune window and the cwd gate, so a
|
|
248
|
+
// derivation missed today may not be reproducible later.
|
|
249
|
+
lastDigestAt.set(agentId, Date.now());
|
|
250
|
+
lastDigestMark.set(agentId, mark);
|
|
251
|
+
digestCounters.succeeded += 1;
|
|
72
252
|
}
|
|
73
253
|
catch (error) {
|
|
254
|
+
digestCounters.failed += 1;
|
|
255
|
+
if (error instanceof DigestGenerationTimeoutError) {
|
|
256
|
+
digestCounters.timedOut += 1;
|
|
257
|
+
}
|
|
74
258
|
const attempts = error instanceof StructuredAgentFallbackError ? error.attempts : undefined;
|
|
75
|
-
logger.error({ err: error, agentId, attempts }, error instanceof StructuredAgentResponseError ||
|
|
259
|
+
logger.error({ err: error, agentId, attempts, failedTotal: digestCounters.failed }, error instanceof StructuredAgentResponseError ||
|
|
76
260
|
error instanceof StructuredAgentFallbackError
|
|
77
|
-
? "Session digest summary generation failed"
|
|
78
|
-
: "Session digest generation failed");
|
|
261
|
+
? "Session digest summary generation failed (derivation lost for this idle; will retry on next idle)"
|
|
262
|
+
: "Session digest generation failed (derivation lost for this idle; will retry on next idle)");
|
|
79
263
|
// Fall through — still persist the deterministic keyFiles + timestamp.
|
|
80
264
|
}
|
|
81
265
|
}
|
|
@@ -96,11 +280,9 @@ export function scheduleSessionDigestRefresh(options) {
|
|
|
96
280
|
if (inFlight.has(agentId)) {
|
|
97
281
|
return;
|
|
98
282
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
lastDigestAt.set(agentId, now);
|
|
283
|
+
// The gate itself moved into generateAndApplySessionDigest: it needs the
|
|
284
|
+
// timeline to tell "nothing new happened" apart from "we ran recently", and
|
|
285
|
+
// only the former is a safe reason to skip a one-shot derivation.
|
|
104
286
|
inFlight.add(agentId);
|
|
105
287
|
queueMicrotask(() => {
|
|
106
288
|
void generateAndApplySessionDigest(options)
|
|
@@ -109,6 +291,15 @@ export function scheduleSessionDigestRefresh(options) {
|
|
|
109
291
|
})
|
|
110
292
|
.finally(() => {
|
|
111
293
|
inFlight.delete(agentId);
|
|
294
|
+
// Fires even on skip or failure: a judge that only ran on the happy path
|
|
295
|
+
// would go silent exactly when something went wrong, which is when the
|
|
296
|
+
// user most needs to hear about it.
|
|
297
|
+
try {
|
|
298
|
+
options.onSettled?.();
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
options.logger.error({ err: error, agentId }, "Session digest onSettled hook threw");
|
|
302
|
+
}
|
|
112
303
|
});
|
|
113
304
|
});
|
|
114
305
|
}
|
|
@@ -6,6 +6,7 @@ import type { VoiceCallerContext, VoiceSpeakHandler } from "../../voice-types.js
|
|
|
6
6
|
import type { FirstAgentContext } from "../../messages.js";
|
|
7
7
|
import type { TerminalManager } from "../../../terminal/terminal-manager.js";
|
|
8
8
|
import type { CreatePaseoWorktreeWorkflowFn } from "../../worktree-session.js";
|
|
9
|
+
import type { JudgeRelayGate } from "../judge-relay-gate.js";
|
|
9
10
|
import type { ScheduleService } from "../../schedule/service.js";
|
|
10
11
|
import type { ProviderSnapshotManager } from "../provider-snapshot-manager.js";
|
|
11
12
|
import type { GitHubService } from "../../../services/github-service.js";
|
|
@@ -48,6 +49,17 @@ export interface PaseoToolHostDependencies {
|
|
|
48
49
|
resolveCallerContext?: (callerAgentId: string) => VoiceCallerContext | null;
|
|
49
50
|
enableVoiceTools?: boolean;
|
|
50
51
|
voiceOnly?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Restrict the surface to READ_ONLY_PASEO_TOOLS (JARVIS-V1-SPEC §6).
|
|
54
|
+
* Enforced by not registering the tool at all, never by prompt wording.
|
|
55
|
+
*/
|
|
56
|
+
readOnly?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Present only for the Jarvis judge. Enables `relay_to_agent`, the single
|
|
59
|
+
* mutating capability it may hold, gated at execution time on a human turn
|
|
60
|
+
* and on the target having been briefed. See judge-relay-gate.ts.
|
|
61
|
+
*/
|
|
62
|
+
judgeRelayGate?: JudgeRelayGate;
|
|
51
63
|
logger: Logger;
|
|
52
64
|
}
|
|
53
65
|
export declare function createPaseoToolCatalog(options: PaseoToolHostDependencies): PaseoToolCatalog;
|
|
@@ -10,6 +10,7 @@ import { killTerminalsForWorkspace, } from "../../workspace-archive-service.js";
|
|
|
10
10
|
import { WaitForAgentTracker } from "../wait-for-agent-tracker.js";
|
|
11
11
|
import { createAgentCommand } from "../create-agent/create.js";
|
|
12
12
|
import { expandUserPath, isSameOrDescendantPath, resolvePathFromBase } from "../../path-utils.js";
|
|
13
|
+
import { isReadOnlyPaseoTool } from "./read-only-surface.js";
|
|
13
14
|
import { ScheduleRunSchema, ScheduleSummarySchema, StoredScheduleSchema, } from "@hyperdrive.bot/paseo-protocol/schedule/types";
|
|
14
15
|
import { AgentModelSchema, AgentProviderEnum, AgentStatusEnum, ProviderModeSchema, ProviderSummarySchema, parseDurationString, resolveRequiredProviderModel, sanitizePermissionRequest, serializeSnapshotWithMetadata, toScheduleSummary, waitForAgentWithTimeout, } from "../mcp-shared.js";
|
|
15
16
|
import { sendPromptToAgent, setupFinishNotification } from "../agent-prompt.js";
|
|
@@ -249,6 +250,11 @@ export function createPaseoToolCatalog(options) {
|
|
|
249
250
|
const registerTool = (name, config,
|
|
250
251
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool handlers are schema-validated at registration boundaries.
|
|
251
252
|
handler) => {
|
|
253
|
+
// Read-only gate (JARVIS-V1-SPEC §6). Allowlist, so a mutating tool added
|
|
254
|
+
// later fails closed instead of silently joining a read-only surface.
|
|
255
|
+
if (options.readOnly && !isReadOnlyPaseoTool(name)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
252
258
|
tools.set(name, {
|
|
253
259
|
name,
|
|
254
260
|
title: config.title,
|
|
@@ -743,6 +749,48 @@ export function createPaseoToolCatalog(options) {
|
|
|
743
749
|
};
|
|
744
750
|
});
|
|
745
751
|
}
|
|
752
|
+
// The judge's one mutating capability. Registered through `tools.set` rather
|
|
753
|
+
// than `registerTool` on purpose: `registerTool` applies the read-only
|
|
754
|
+
// allowlist, and this tool must be available to a read-only agent precisely
|
|
755
|
+
// BECAUSE its own gate is stricter than the allowlist. Keeping it out of
|
|
756
|
+
// READ_ONLY_PASEO_TOOLS also keeps that list honestly "reads only".
|
|
757
|
+
if (options.judgeRelayGate) {
|
|
758
|
+
const gate = options.judgeRelayGate;
|
|
759
|
+
tools.set("relay_to_agent", {
|
|
760
|
+
name: "relay_to_agent",
|
|
761
|
+
title: "Relay the user's decision to an agent",
|
|
762
|
+
description: "Pass the user's instruction to an agent you were briefed about. Only usable when the user has just told you something in this conversation; it is refused while you are processing an event. You do not act yourself, the target agent acts with its own context and permissions.",
|
|
763
|
+
inputSchema: {
|
|
764
|
+
agentId: z.string().describe("The agent to relay to. Must be one you were briefed about."),
|
|
765
|
+
message: z
|
|
766
|
+
.string()
|
|
767
|
+
.trim()
|
|
768
|
+
.min(1)
|
|
769
|
+
.max(4000)
|
|
770
|
+
.describe("The user's instruction, in your words. Do not invent instructions."),
|
|
771
|
+
},
|
|
772
|
+
outputSchema: { ok: z.boolean(), agentId: z.string() },
|
|
773
|
+
handler: (async (args) => {
|
|
774
|
+
const decision = gate.evaluate(args.agentId);
|
|
775
|
+
if (!decision.allowed) {
|
|
776
|
+
throw new Error(decision.detail ?? "Relay refused");
|
|
777
|
+
}
|
|
778
|
+
const target = await ensureAgentLoaded(args.agentId, {
|
|
779
|
+
agentManager,
|
|
780
|
+
agentStorage,
|
|
781
|
+
logger: options.logger,
|
|
782
|
+
});
|
|
783
|
+
if (agentManager.hasInFlightRun(target.id)) {
|
|
784
|
+
throw new Error(`Agent ${args.agentId} is mid-run; try again once it settles.`);
|
|
785
|
+
}
|
|
786
|
+
await agentManager.runAgent(target.id, args.message);
|
|
787
|
+
return {
|
|
788
|
+
content: [],
|
|
789
|
+
structuredContent: ensureValidJson({ ok: true, agentId: args.agentId }),
|
|
790
|
+
};
|
|
791
|
+
}),
|
|
792
|
+
});
|
|
793
|
+
}
|
|
746
794
|
if (options.voiceOnly) {
|
|
747
795
|
return toCatalog();
|
|
748
796
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only paseo tool surface (JARVIS-V1-SPEC §6, HARD REQUIREMENT).
|
|
3
|
+
*
|
|
4
|
+
* The Jarvis judge reads other sessions' contents in order to decide whether to
|
|
5
|
+
* interrupt the user. Those transcripts are arbitrary untrusted text by design:
|
|
6
|
+
* fetched web pages, repository files, tool output, error messages. Handing a
|
|
7
|
+
* reader of untrusted text a permission-granting tool creates a direct path from
|
|
8
|
+
* "text somebody else wrote" to "an approved action on the user's machine". No
|
|
9
|
+
* malice is required, only a transcript containing a sentence shaped like an
|
|
10
|
+
* instruction.
|
|
11
|
+
*
|
|
12
|
+
* A prompt saying "do not do this" is a suggestion to a model that is actively
|
|
13
|
+
* reading adversarial input. Absence of the tool is a wall. This module is the
|
|
14
|
+
* wall.
|
|
15
|
+
*
|
|
16
|
+
* ## Why an ALLOWLIST and not a denylist
|
|
17
|
+
*
|
|
18
|
+
* A denylist fails OPEN: the day somebody adds a new mutating tool, the judge
|
|
19
|
+
* silently gains it and nobody notices until it is used. An allowlist fails
|
|
20
|
+
* CLOSED: the new tool is simply unavailable until a human deliberately adds it
|
|
21
|
+
* here. The benign failure (a new read tool the judge cannot see yet) is worth
|
|
22
|
+
* far more than the dangerous one.
|
|
23
|
+
*
|
|
24
|
+
* ## Two registration paths, both gated
|
|
25
|
+
*
|
|
26
|
+
* Tools reach an agent through TWO places, and gating only one leaves a hole:
|
|
27
|
+
* 1. `paseo-tools.ts` `registerTool` (35 tools) -> filtered by this allowlist.
|
|
28
|
+
* 2. `mcp-server.ts`, which re-exposes the catalog and then registers 10 MORE
|
|
29
|
+
* of its own, including `set_session_digest`, `set_session_tags`,
|
|
30
|
+
* `open_url`, `workflow_start` and `workflow_cancel`. That path takes an
|
|
31
|
+
* early return in read-only mode, so none of its extras are exposed.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Every paseo tool a read-only agent may hold. Anything absent is denied.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately excluded and why:
|
|
37
|
+
* - `respond_to_permission` grants permissions. The core injection target.
|
|
38
|
+
* - `send_agent_prompt` drives another agent. Action by proxy.
|
|
39
|
+
* - `update_agent`, `archive_agent`, `kill_agent`, `cancel_agent` mutate sessions.
|
|
40
|
+
* - `set_agent_mode` could lift the agent OUT of read-only. Privilege escalation.
|
|
41
|
+
* - `create_agent`, `create_worktree`, `archive_worktree` create or destroy.
|
|
42
|
+
* - `create_terminal`, `kill_terminal`, `send_terminal_keys` arbitrary command execution.
|
|
43
|
+
* - `create_schedule`, `update_schedule`, `delete_schedule`,
|
|
44
|
+
* `pause_schedule`, `resume_schedule`, `create_heartbeat` persistent side effects.
|
|
45
|
+
* - `rename_workspace` mutates.
|
|
46
|
+
* - `speak` an outward side effect, and voice is out of v1 scope.
|
|
47
|
+
* - `wait_for_agent` not mutating, but it BLOCKS. An event-woken judge
|
|
48
|
+
* that blocks holds its run open indefinitely.
|
|
49
|
+
*/
|
|
50
|
+
export declare const READ_ONLY_PASEO_TOOLS: ReadonlySet<string>;
|
|
51
|
+
export declare function isReadOnlyPaseoTool(name: string): boolean;
|
|
52
|
+
//# sourceMappingURL=read-only-surface.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only paseo tool surface (JARVIS-V1-SPEC §6, HARD REQUIREMENT).
|
|
3
|
+
*
|
|
4
|
+
* The Jarvis judge reads other sessions' contents in order to decide whether to
|
|
5
|
+
* interrupt the user. Those transcripts are arbitrary untrusted text by design:
|
|
6
|
+
* fetched web pages, repository files, tool output, error messages. Handing a
|
|
7
|
+
* reader of untrusted text a permission-granting tool creates a direct path from
|
|
8
|
+
* "text somebody else wrote" to "an approved action on the user's machine". No
|
|
9
|
+
* malice is required, only a transcript containing a sentence shaped like an
|
|
10
|
+
* instruction.
|
|
11
|
+
*
|
|
12
|
+
* A prompt saying "do not do this" is a suggestion to a model that is actively
|
|
13
|
+
* reading adversarial input. Absence of the tool is a wall. This module is the
|
|
14
|
+
* wall.
|
|
15
|
+
*
|
|
16
|
+
* ## Why an ALLOWLIST and not a denylist
|
|
17
|
+
*
|
|
18
|
+
* A denylist fails OPEN: the day somebody adds a new mutating tool, the judge
|
|
19
|
+
* silently gains it and nobody notices until it is used. An allowlist fails
|
|
20
|
+
* CLOSED: the new tool is simply unavailable until a human deliberately adds it
|
|
21
|
+
* here. The benign failure (a new read tool the judge cannot see yet) is worth
|
|
22
|
+
* far more than the dangerous one.
|
|
23
|
+
*
|
|
24
|
+
* ## Two registration paths, both gated
|
|
25
|
+
*
|
|
26
|
+
* Tools reach an agent through TWO places, and gating only one leaves a hole:
|
|
27
|
+
* 1. `paseo-tools.ts` `registerTool` (35 tools) -> filtered by this allowlist.
|
|
28
|
+
* 2. `mcp-server.ts`, which re-exposes the catalog and then registers 10 MORE
|
|
29
|
+
* of its own, including `set_session_digest`, `set_session_tags`,
|
|
30
|
+
* `open_url`, `workflow_start` and `workflow_cancel`. That path takes an
|
|
31
|
+
* early return in read-only mode, so none of its extras are exposed.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Every paseo tool a read-only agent may hold. Anything absent is denied.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately excluded and why:
|
|
37
|
+
* - `respond_to_permission` grants permissions. The core injection target.
|
|
38
|
+
* - `send_agent_prompt` drives another agent. Action by proxy.
|
|
39
|
+
* - `update_agent`, `archive_agent`, `kill_agent`, `cancel_agent` mutate sessions.
|
|
40
|
+
* - `set_agent_mode` could lift the agent OUT of read-only. Privilege escalation.
|
|
41
|
+
* - `create_agent`, `create_worktree`, `archive_worktree` create or destroy.
|
|
42
|
+
* - `create_terminal`, `kill_terminal`, `send_terminal_keys` arbitrary command execution.
|
|
43
|
+
* - `create_schedule`, `update_schedule`, `delete_schedule`,
|
|
44
|
+
* `pause_schedule`, `resume_schedule`, `create_heartbeat` persistent side effects.
|
|
45
|
+
* - `rename_workspace` mutates.
|
|
46
|
+
* - `speak` an outward side effect, and voice is out of v1 scope.
|
|
47
|
+
* - `wait_for_agent` not mutating, but it BLOCKS. An event-woken judge
|
|
48
|
+
* that blocks holds its run open indefinitely.
|
|
49
|
+
*/
|
|
50
|
+
export const READ_ONLY_PASEO_TOOLS = new Set([
|
|
51
|
+
"capture_terminal",
|
|
52
|
+
"get_agent_activity",
|
|
53
|
+
"get_agent_status",
|
|
54
|
+
"inspect_provider",
|
|
55
|
+
"inspect_schedule",
|
|
56
|
+
"list_agents",
|
|
57
|
+
"list_models",
|
|
58
|
+
"list_pending_permissions",
|
|
59
|
+
"list_providers",
|
|
60
|
+
"list_schedules",
|
|
61
|
+
"list_terminals",
|
|
62
|
+
"list_worktrees",
|
|
63
|
+
"schedule_logs",
|
|
64
|
+
]);
|
|
65
|
+
export function isReadOnlyPaseoTool(name) {
|
|
66
|
+
return READ_ONLY_PASEO_TOOLS.has(name);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=read-only-surface.js.map
|
|
@@ -31,6 +31,11 @@ export interface PaseoToolRuntimeContext {
|
|
|
31
31
|
callerAgentId?: string;
|
|
32
32
|
enableVoiceTools?: boolean;
|
|
33
33
|
voiceOnly?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Restrict this session's surface to READ_ONLY_PASEO_TOOLS
|
|
36
|
+
* (JARVIS-V1-SPEC §6). Set by bootstrap for the Jarvis judge's agent id.
|
|
37
|
+
*/
|
|
38
|
+
readOnly?: boolean;
|
|
34
39
|
}
|
|
35
40
|
export type PaseoToolCatalogFactory = (context: PaseoToolRuntimeContext) => PaseoToolCatalog | Promise<PaseoToolCatalog>;
|
|
36
41
|
//# sourceMappingURL=types.d.ts.map
|