@zeph-to/cli 2.15.0 → 2.17.0
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/README.md +48 -4
- package/dist/cli.js +7 -2
- package/dist/gate.d.ts +8 -3
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +6 -5
- package/dist/listener.d.ts +7 -0
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +71 -6
- package/dist/presence.d.ts +14 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +84 -0
- package/dist/remote-agents.d.ts +35 -0
- package/dist/remote-agents.d.ts.map +1 -1
- package/dist/remote-agents.js +60 -1
- package/dist/session-registry.d.ts +8 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +7 -14
- package/dist/templates.d.ts +3 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +132 -6
- package/dist/test-state-dir.d.ts +10 -0
- package/dist/test-state-dir.d.ts.map +1 -0
- package/dist/test-state-dir.js +32 -0
- package/dist/transcript-tail.d.ts +147 -0
- package/dist/transcript-tail.d.ts.map +1 -0
- package/dist/transcript-tail.js +397 -0
- package/dist/turn-ring.d.ts +85 -0
- package/dist/turn-ring.d.ts.map +1 -0
- package/dist/turn-ring.js +280 -0
- package/dist/turn-watch.d.ts +151 -0
- package/dist/turn-watch.d.ts.map +1 -0
- package/dist/turn-watch.js +502 -0
- package/package.json +1 -1
package/dist/remote-agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.matchAgentByPaneCommand = exports.findAgentBySubcommand = exports.REMOTE_AGENTS = exports.detectCodexSessionName = exports.newestVersionedDb = exports.detectHermesSessionName = exports.sqliteJson = exports.pickRowByProcStart = exports.detectClaudeSessionNameByPid = exports.detectClaudeSessionIdByPid = exports.detectClaudeSessionByPid = exports.collectDescendantPids = exports.psStartTimes = exports.parseProcTable = exports.detectClaudeSessionId = void 0;
|
|
3
|
+
exports.matchAgentByPaneCommand = exports.findAgentBySubcommand = exports.REMOTE_AGENTS = exports.detectCodexSessionName = exports.newestVersionedDb = exports.detectHermesSessionName = exports.sqliteJson = exports.pickRowByProcStart = exports.claudeTranscriptPath = exports.detectClaudeSessionNameByPid = exports.detectClaudeSessionIdByPid = exports.detectClaudeSessionByPid = exports.pidSessionRecordsExist = exports.collectDescendantPids = exports.psStartTimes = exports.parseProcTable = exports.detectClaudeSessionId = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Remote-control agent registry — the single table behind every
|
|
6
6
|
* `zeph <agent>` subcommand, the listener's pane matching, and the
|
|
@@ -248,6 +248,16 @@ const collectDescendantPids = (rootPid) => {
|
|
|
248
248
|
return descendantCache.set(rootPid, walkDescendants(rootPid, children));
|
|
249
249
|
};
|
|
250
250
|
exports.collectDescendantPids = collectDescendantPids;
|
|
251
|
+
/**
|
|
252
|
+
* Whether this machine's Claude Code writes `~/.claude/sessions` records at all.
|
|
253
|
+
*
|
|
254
|
+
* The difference between "no records anywhere" (an older CC — the mtime
|
|
255
|
+
* heuristic is the only option) and "records exist, none match this pane" (the
|
|
256
|
+
* pane is not a Claude session) is the difference between a useful fallback and
|
|
257
|
+
* a wrong answer, so callers that care ask it explicitly.
|
|
258
|
+
*/
|
|
259
|
+
const pidSessionRecordsExist = () => cachedPidSessionRecords().length > 0;
|
|
260
|
+
exports.pidSessionRecordsExist = pidSessionRecordsExist;
|
|
251
261
|
/**
|
|
252
262
|
* Exact resolution: the session record whose pid lives in the pane's
|
|
253
263
|
* process tree AND whose cwd matches (guards against OS pid reuse
|
|
@@ -276,6 +286,54 @@ exports.detectClaudeSessionIdByPid = detectClaudeSessionIdByPid;
|
|
|
276
286
|
*/
|
|
277
287
|
const detectClaudeSessionNameByPid = (panePid, paneCwd, deps = {}) => nonBlank((0, exports.detectClaudeSessionByPid)(panePid, paneCwd, deps)?.name);
|
|
278
288
|
exports.detectClaudeSessionNameByPid = detectClaudeSessionNameByPid;
|
|
289
|
+
/**
|
|
290
|
+
* The transcript file for the Claude Code session running in a tmux pane.
|
|
291
|
+
*
|
|
292
|
+
* Pid first, cwd only as a fallback, and the order is the whole point: the cwd
|
|
293
|
+
* heuristic picks the newest `*.jsonl` in the directory, so a second `claude`
|
|
294
|
+
* started in the same repo steals the first one's transcript (the comment above
|
|
295
|
+
* `detectClaudeSessionIdByPid` records that failure). Live-timeline viewers are
|
|
296
|
+
* per tmux session, so two sessions in one directory must resolve to two files
|
|
297
|
+
* or each phone reads the other's work.
|
|
298
|
+
*
|
|
299
|
+
* Returns null when this pane is not running Claude Code at all — a Codex or
|
|
300
|
+
* Gemini pane has no transcript here, and that is an answer, not a failure.
|
|
301
|
+
*/
|
|
302
|
+
const claudeTranscriptPath = (paneCwd, panePid) => {
|
|
303
|
+
if (!paneCwd)
|
|
304
|
+
return null;
|
|
305
|
+
let sessionId = null;
|
|
306
|
+
if (panePid) {
|
|
307
|
+
sessionId = (0, exports.detectClaudeSessionIdByPid)(panePid, paneCwd);
|
|
308
|
+
// Records exist but this pane matched none: the pane is not running
|
|
309
|
+
// Claude Code, or its record has not landed yet. Falling through to the
|
|
310
|
+
// mtime heuristic here is what lets a *second* `claude` in the same
|
|
311
|
+
// directory hand this pane the other session's transcript — and a
|
|
312
|
+
// viewer reading another session's work is worse than one reading none.
|
|
313
|
+
// Only an installation that writes no records at all earns the fallback.
|
|
314
|
+
if (!sessionId && (0, exports.pidSessionRecordsExist)())
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
sessionId ??= (0, exports.detectClaudeSessionId)(paneCwd);
|
|
318
|
+
if (!sessionId)
|
|
319
|
+
return null;
|
|
320
|
+
// Claude Code flattens the cwd into one directory name by replacing every
|
|
321
|
+
// `/` AND `.` with `-`: `/Users/x/.claude-mem/y` becomes
|
|
322
|
+
// `-Users-x--claude-mem-y`. Encoding only the slashes yields a path that
|
|
323
|
+
// never exists, and the watcher would then poll a missing file forever
|
|
324
|
+
// without ever saying so.
|
|
325
|
+
const projectDir = (0, path_1.join)(CLAUDE_PROJECTS_DIR, paneCwd.replace(/[/.]/g, '-'));
|
|
326
|
+
const path = (0, path_1.join)(projectDir, `${sessionId}.jsonl`);
|
|
327
|
+
// Prove it before handing it out. The pid path never touches the filesystem,
|
|
328
|
+
// so without this a mis-encoded directory reads as a healthy silent session.
|
|
329
|
+
try {
|
|
330
|
+
return (0, fs_1.statSync)(path).isFile() ? path : null;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
exports.claudeTranscriptPath = claudeTranscriptPath;
|
|
279
337
|
// ── Agents whose session store keeps no pid ──────────────────────
|
|
280
338
|
//
|
|
281
339
|
// Hermes and Codex both record a session per cwd with a creation timestamp and
|
|
@@ -437,6 +495,7 @@ const REMOTE_AGENT_TABLE = [
|
|
|
437
495
|
resolveSessionId: (paneCwd, panePid) => (panePid !== undefined ? (0, exports.detectClaudeSessionIdByPid)(panePid, paneCwd) : null)
|
|
438
496
|
?? (0, exports.detectClaudeSessionId)(paneCwd),
|
|
439
497
|
resolveSessionName: (paneCwd, panePid) => panePid !== undefined ? (0, exports.detectClaudeSessionNameByPid)(panePid, paneCwd) : null,
|
|
498
|
+
resolveTranscript: (paneCwd, panePid) => (0, exports.claudeTranscriptPath)(paneCwd, panePid),
|
|
440
499
|
},
|
|
441
500
|
{
|
|
442
501
|
kind: 'codex',
|
|
@@ -66,7 +66,14 @@ export declare const rememberSessions: (live: Array<{
|
|
|
66
66
|
* running, so nothing re-adds an entry for a session that has ended. Running
|
|
67
67
|
* that name again is what brings it back, which is also the only way back.
|
|
68
68
|
*/
|
|
69
|
-
export
|
|
69
|
+
export type ForgetOutcome =
|
|
70
|
+
/** This machine never had a record under that name. */
|
|
71
|
+
'unknown'
|
|
72
|
+
/** Registry entry and chat scrollback both gone. */
|
|
73
|
+
| 'forgotten'
|
|
74
|
+
/** Registry entry gone, but the scrollback file would not delete. */
|
|
75
|
+
| 'scrollback_kept';
|
|
76
|
+
export declare const forgetSession: (name: string) => ForgetOutcome;
|
|
70
77
|
/** Whether the registry knows this name — the resume whitelist check. */
|
|
71
78
|
export declare const isKnownSession: (name: string, now?: number) => boolean;
|
|
72
79
|
/** Test seam: the file this module reads and writes. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC;2DAC2D;AAC3D,eAAO,MAAM,oBAAoB,QAA2B,CAAC;AAsC7D,0EAA0E;AAC1E,eAAO,MAAM,aAAa,GAAI,MAAK,MAAmB,KAAG,YAAY,EAGJ,CAAC;AAElE,6EAA6E;AAC7E,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,MAAK,MAAmB,KAAG,YAAY,GAAG,IAC3B,CAAC;AAE5D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GACzB,MAAM,KAAK,CAAC;IACR,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC,EACF,MAAK,MAAmB,KACzB,IAmBF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa;AACrB,uDAAuD;AACrD,SAAS;AACX,oDAAoD;GAClD,WAAW;AACb,qEAAqE;GACnE,iBAAiB,CAAC;AAExB,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,aAU5C,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,MAAK,MAAmB,KAAG,OACnC,CAAC;AAEtC,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,QA1HL,MA0HoB,CAAC;AAE9C,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,GAAI,OAAO,YAAY,KAAG,OAAgC,CAAC"}
|
package/dist/session-registry.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.sessionDirectoryExists = exports.knownSessionsPath = exports.isKnownSession = exports.forgetSession = exports.rememberSessions = exports.recallSession = exports.knownSessions = exports.KNOWN_SESSION_TTL_MS = exports.MAX_KNOWN_SESSIONS = void 0;
|
|
18
18
|
const fs_1 = require("fs");
|
|
19
19
|
const path_1 = require("path");
|
|
20
|
+
const turn_ring_js_1 = require("./turn-ring.js");
|
|
20
21
|
const gate_js_1 = require("./gate.js");
|
|
21
22
|
/**
|
|
22
23
|
* Ceiling on remembered sessions. tmux names come from a small reused pool
|
|
@@ -100,25 +101,17 @@ const rememberSessions = (live, now = Date.now()) => {
|
|
|
100
101
|
writeAll(entries);
|
|
101
102
|
};
|
|
102
103
|
exports.rememberSessions = rememberSessions;
|
|
103
|
-
/**
|
|
104
|
-
* Forget one session, by name. Returns false when this machine never knew it.
|
|
105
|
-
*
|
|
106
|
-
* This is what deleting a past session means on the machine that ran it: the
|
|
107
|
-
* entry leaves the file, so the phone stops being offered the session AND —
|
|
108
|
-
* since this file is the resume whitelist — stops being able to start it.
|
|
109
|
-
* That is the intended pair, not a side effect.
|
|
110
|
-
*
|
|
111
|
-
* It stays forgotten: `rememberSessions` only writes down sessions that are
|
|
112
|
-
* running, so nothing re-adds an entry for a session that has ended. Running
|
|
113
|
-
* that name again is what brings it back, which is also the only way back.
|
|
114
|
-
*/
|
|
115
104
|
const forgetSession = (name) => {
|
|
116
105
|
const entries = readAll();
|
|
117
106
|
const kept = entries.filter((e) => e.name !== name);
|
|
118
107
|
if (kept.length === entries.length)
|
|
119
|
-
return
|
|
108
|
+
return 'unknown';
|
|
120
109
|
writeAll(kept);
|
|
121
|
-
|
|
110
|
+
// The chat's scrollback for that session goes with it. Forgetting a session
|
|
111
|
+
// everywhere except the one file that holds a week of its prompts and tool
|
|
112
|
+
// targets is not forgetting it — and a failure to delete that file has to
|
|
113
|
+
// reach the person who asked, not stay in a swallowed catch.
|
|
114
|
+
return (0, turn_ring_js_1.removeTurnRing)(name) ? 'forgotten' : 'scrollback_kept';
|
|
122
115
|
};
|
|
123
116
|
exports.forgetSession = forgetSession;
|
|
124
117
|
/** Whether the registry knows this name — the resume whitelist check. */
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Grace before the pi extension turns an unanswered prompt into a push. Exported for the tests. */
|
|
2
|
+
export declare const PI_PROMPT_GRACE_MS = 10000;
|
|
1
3
|
/** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). No prompt hook. */
|
|
2
4
|
export declare const CURSOR_RULE: string;
|
|
3
5
|
/** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. No prompt hook. */
|
|
@@ -12,7 +14,7 @@ export declare const COPILOT_RULE: string;
|
|
|
12
14
|
export declare const CLINE_RULE: string;
|
|
13
15
|
/** Aider — standalone conventions file via .aider.conf.yml `read:` (no Stop hook, no prompt hook). */
|
|
14
16
|
export declare const AIDER_RULE: string;
|
|
15
|
-
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook (PI_EXTENSION). */
|
|
17
|
+
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook + waiting-on-you push (PI_EXTENSION). */
|
|
16
18
|
export declare const PI_RULE: string;
|
|
17
19
|
/** OpenCode — managed block in ~/.config/opencode/AGENTS.md. Stop hook via plugin, no prompt hook (v1). */
|
|
18
20
|
export declare const OPENCODE_RULE: string;
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AA+FA,oGAAoG;AACpG,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAoHzC,6FAA6F;AAC7F,eAAO,MAAM,WAAW,QAKtB,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,aAAa,QAIxB,CAAC;AAEH,0FAA0F;AAC1F,eAAO,MAAM,WAAW,QAAyE,CAAC;AAElG,uFAAuF;AACvF,eAAO,MAAM,UAAU,QAAyE,CAAC;AAEjG,oGAAoG;AACpG,eAAO,MAAM,YAAY,QAIvB,CAAC;AAEH,gFAAgF;AAChF,eAAO,MAAM,UAAU,QAIrB,CAAC;AAEH,sGAAsG;AACtG,eAAO,MAAM,UAAU,QAIrB,CAAC;AAEH,mIAAmI;AACnI,eAAO,MAAM,OAAO,QAKlB,CAAC;AAEH,2GAA2G;AAC3G,eAAO,MAAM,aAAa,QAIxB,CAAC;AAIH,eAAO,MAAM,YAAY,QAKd,CAAC;AAEZ,eAAO,MAAM,cAAc,QAOhB,CAAC;AAEZ,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;CAuBxB,CAAC;AAYF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;CASvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,OAUhD,CAAC;AAEF,eAAO,MAAM,aAAa,QASf,CAAC;AAQZ,2FAA2F;AAC3F,eAAO,MAAM,YAAY,QAgJxB,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,eAAe,QAwG3B,CAAC;AASF,eAAO,MAAM,eAAe,oFAA+E,CAAC;AAC5G,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAQjD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAOrD,CAAC"}
|
package/dist/templates.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// Keeping this in one place means a rule change lands everywhere at once
|
|
27
27
|
// and the agents can't drift apart.
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.removeManagedBlock = exports.upsertManagedBlock = exports.ZEPH_MARK_END = exports.ZEPH_MARK_START = exports.OPENCODE_PLUGIN = exports.PI_EXTENSION = exports.COPILOT_HOOKS = exports.isZephHookGroup = exports.CODEX_HOOKS = exports.GEMINI_HOOKS = exports.WINDSURF_HOOKS = exports.CURSOR_HOOKS = exports.OPENCODE_RULE = exports.PI_RULE = exports.AIDER_RULE = exports.CLINE_RULE = exports.COPILOT_RULE = exports.CODEX_RULE = exports.GEMINI_RULE = exports.WINDSURF_RULE = exports.CURSOR_RULE = void 0;
|
|
29
|
+
exports.removeManagedBlock = exports.upsertManagedBlock = exports.ZEPH_MARK_END = exports.ZEPH_MARK_START = exports.OPENCODE_PLUGIN = exports.PI_EXTENSION = exports.COPILOT_HOOKS = exports.isZephHookGroup = exports.CODEX_HOOKS = exports.GEMINI_HOOKS = exports.WINDSURF_HOOKS = exports.CURSOR_HOOKS = exports.OPENCODE_RULE = exports.PI_RULE = exports.AIDER_RULE = exports.CLINE_RULE = exports.COPILOT_RULE = exports.CODEX_RULE = exports.GEMINI_RULE = exports.WINDSURF_RULE = exports.CURSOR_RULE = exports.PI_PROMPT_GRACE_MS = void 0;
|
|
30
30
|
const gate_js_1 = require("./gate.js");
|
|
31
31
|
const zeph_core_generated_js_1 = require("./zeph-core.generated.js");
|
|
32
32
|
// Graceful resolution: prefer the installed `zeph` CLI, but fall back to
|
|
@@ -43,8 +43,9 @@ const zeph_core_generated_js_1 = require("./zeph-core.generated.js");
|
|
|
43
43
|
// `--pushmode-default normal` is what keeps the first half of that true. The
|
|
44
44
|
// built-in default for a project with no dial is quiet, and quiet only lets a
|
|
45
45
|
// `high` Push Signal marker through — a marker these hooks have no way to
|
|
46
|
-
// emit
|
|
47
|
-
//
|
|
46
|
+
// emit — or a turn the user was away for (presence.ts). Without the flag they
|
|
47
|
+
// would install and then stay silent whenever the user is at the terminal.
|
|
48
|
+
// The user's own dial still outranks it, so /zeph-quiet keeps working here.
|
|
48
49
|
//
|
|
49
50
|
// Older installed `zeph` versions parse both flags as unknown booleans and
|
|
50
51
|
// ignore them — graceful backward compatibility, and for `--pushmode-default`
|
|
@@ -72,6 +73,19 @@ const TURN_FACT_FLAGS = ` --${gate_js_1.TOOL_COUNT_FLAG} \${tools} --${gate_js_1
|
|
|
72
73
|
// stderr is discarded; `|| true` keeps a broken install from ever blocking
|
|
73
74
|
// a prompt.
|
|
74
75
|
const remoteHookCmd = (agent) => `$(command -v zeph || echo "npx -y @zeph-to/cli") remote-hook ${agent} 2>/dev/null || true`;
|
|
76
|
+
// One-off `high` push from the pi extension — a prompt left waiting (the twin
|
|
77
|
+
// of the plugin's zeph-ask.sh) or a turn that died on an error. `high` gets
|
|
78
|
+
// through the quiet dial, and plain `notify` (no --auto) means only a mute
|
|
79
|
+
// stops it. Title and body arrive as env vars, never spliced into the string:
|
|
80
|
+
// the body can carry the model's own bash command or a provider error.
|
|
81
|
+
const highNotifyCmd = '$(command -v zeph || echo "npx -y @zeph-to/cli") notify'
|
|
82
|
+
+ ' --title "$ZEPH_PUSH_TITLE" --body "$ZEPH_PUSH_BODY" --priority high 2>/dev/null || true';
|
|
83
|
+
// The pi turn's Push Signal marker, as a JS template placeholder (single quotes:
|
|
84
|
+
// `${marker}` must reach the artifact unexpanded). The artifact only ever
|
|
85
|
+
// assigns it a word the marker regex captured, or "none".
|
|
86
|
+
const PUSH_SIGNAL_FLAG = ' --marker ${marker}';
|
|
87
|
+
/** Grace before the pi extension turns an unanswered prompt into a push. Exported for the tests. */
|
|
88
|
+
exports.PI_PROMPT_GRACE_MS = 10_000;
|
|
75
89
|
// ── Shared behavioral core ───────────────────────────────────────
|
|
76
90
|
//
|
|
77
91
|
// GENERATED from plugin/docs/CORE_RULES.md — see src/zeph-core.generated.ts
|
|
@@ -136,10 +150,31 @@ tool instead — same semantics:
|
|
|
136
150
|
unreachable) is a Done-like outcome — treat it as NORMAL.
|
|
137
151
|
- zeph_notify → \`zeph notify --title "…" --body "…" [--priority high]\`
|
|
138
152
|
- AskUserQuestion → pi's own terminal prompt.`;
|
|
153
|
+
// Push Signal preamble — pi only. The shared core has no Push Signal section:
|
|
154
|
+
// Claude Code gets it from the plugin's SessionStart hook, per push dial. The
|
|
155
|
+
// pi extension reads the same markers (PI_EXTENSION, message_end), so pi's
|
|
156
|
+
// rules carry a dial-independent version here.
|
|
157
|
+
const PI_PUSH_SIGNAL = `## Push Signal — steer the end-of-turn push
|
|
158
|
+
|
|
159
|
+
When a turn ends, the zeph extension decides whether to push by tool volume
|
|
160
|
+
and the user's push dial. Put ONE marker anywhere in your final response to
|
|
161
|
+
override that for this turn. The extension removes it before the message is
|
|
162
|
+
shown or kept:
|
|
163
|
+
|
|
164
|
+
- \`<!-- zeph: high -->\` — push at high priority. The only marker that gets
|
|
165
|
+
through the quiet dial, so use it whenever the user must act: you ended the
|
|
166
|
+
turn with a question for them, or you are blocked on a decision only they
|
|
167
|
+
can make. Never on routine completions.
|
|
168
|
+
- \`<!-- zeph: push -->\` — push a turn the heuristic would skip.
|
|
169
|
+
- \`<!-- zeph: skip -->\` — no push for a turn not worth a ping.
|
|
170
|
+
|
|
171
|
+
No marker → the heuristic: fewer than 2 tool calls, or only read-only ones,
|
|
172
|
+
stays silent. Markers are lowercase and exact.`;
|
|
139
173
|
/** Assemble a full rule document from optional frontmatter + preambles + core. */
|
|
140
174
|
const buildRule = (opts) => {
|
|
141
175
|
const fm = opts.frontmatter ? `${opts.frontmatter}\n\n` : '';
|
|
142
176
|
const tools = opts.toolAccess ? `${opts.toolAccess}\n\n` : '';
|
|
177
|
+
const signal = opts.pushSignal ? `${opts.pushSignal}\n\n` : '';
|
|
143
178
|
const entry = opts.remoteEntry ? `${opts.remoteEntry}\n\n` : '';
|
|
144
179
|
return `${fm}# Zeph — Remote-Control Rules
|
|
145
180
|
|
|
@@ -149,7 +184,7 @@ the user.
|
|
|
149
184
|
|
|
150
185
|
${opts.notify}
|
|
151
186
|
|
|
152
|
-
${tools}${entry}${opts.core}
|
|
187
|
+
${tools}${signal}${entry}${opts.core}
|
|
153
188
|
`;
|
|
154
189
|
};
|
|
155
190
|
// ── Per-agent rule documents ─────────────────────────────────────
|
|
@@ -192,10 +227,11 @@ exports.AIDER_RULE = buildRule({
|
|
|
192
227
|
remoteEntry: REMOTE_ENTRY_NO_HOOK,
|
|
193
228
|
core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY,
|
|
194
229
|
});
|
|
195
|
-
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook (PI_EXTENSION). */
|
|
230
|
+
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook + waiting-on-you push (PI_EXTENSION). */
|
|
196
231
|
exports.PI_RULE = buildRule({
|
|
197
232
|
notify: HOOK_DRIVEN_NOTIFY,
|
|
198
233
|
toolAccess: PI_TOOL_ACCESS,
|
|
234
|
+
pushSignal: PI_PUSH_SIGNAL,
|
|
199
235
|
core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN,
|
|
200
236
|
});
|
|
201
237
|
/** OpenCode — managed block in ~/.config/opencode/AGENTS.md. Stop hook via plugin, no prompt hook (v1). */
|
|
@@ -319,20 +355,108 @@ const sh = (cmd: string, cwd: string, stdin?: string): Promise<string> =>
|
|
|
319
355
|
// an unknown tool errs toward pushing rather than toward silence.
|
|
320
356
|
const READ_ONLY = new Set(["read", "grep", "find", "ls"]);
|
|
321
357
|
|
|
358
|
+
// How long a blocking prompt may stay open before it becomes a push. An answer
|
|
359
|
+
// inside the window means the user is at the terminal, and a guard extension
|
|
360
|
+
// can raise several prompts a turn — pushing each one at once would be noise.
|
|
361
|
+
const PROMPT_GRACE_MS = ${exports.PI_PROMPT_GRACE_MS};
|
|
362
|
+
|
|
363
|
+
// Push Signal markers — the pattern the Claude Code Stop hook reads
|
|
364
|
+
// (plugin/hooks/zeph-stop.sh MARKER_RE). pi's markdown renders an HTML comment
|
|
365
|
+
// as plain text, so message_end also strips it from what the user sees.
|
|
366
|
+
const MARKER_RE = /<!--[ \\t]*zeph:[ \\t]*(skip|push|high)[ \\t]*-->/g;
|
|
367
|
+
|
|
368
|
+
const projectOf = (cwd: string): string => cwd.split("/").pop() || cwd;
|
|
369
|
+
|
|
370
|
+
// Fire-and-forget: never block pi on the notify network call.
|
|
371
|
+
const pushHigh = (cwd: string, title: string, body: string): void => {
|
|
372
|
+
const env = {
|
|
373
|
+
...process.env,
|
|
374
|
+
ZEPH_PUSH_TITLE: title,
|
|
375
|
+
ZEPH_PUSH_BODY: body.length > 200 ? body.slice(0, 199) + "…" : body,
|
|
376
|
+
};
|
|
377
|
+
const child = spawn("sh", ["-c", ${JSON.stringify(highNotifyCmd)}], { cwd, env, stdio: "ignore", detached: true });
|
|
378
|
+
child.on("error", () => {});
|
|
379
|
+
child.unref();
|
|
380
|
+
};
|
|
381
|
+
|
|
322
382
|
export default function (pi: ExtensionAPI) {
|
|
323
383
|
// Turn facts for the push gate. One agent per process, so plain counters
|
|
324
384
|
// suffice — no session keying.
|
|
325
385
|
let tools = 0;
|
|
326
386
|
let nonReadonly = 0;
|
|
387
|
+
// The tool call in flight. tool_execution_start fires before pi runs the
|
|
388
|
+
// tool_call handlers, so a guard's prompt always finds its own call here.
|
|
389
|
+
let running: { toolName: string; args: any } | undefined;
|
|
390
|
+
let promptTimer: ReturnType<typeof setTimeout> | undefined;
|
|
391
|
+
// A prompt only counts inside a turn. Outside one it is a dialog the user
|
|
392
|
+
// opened at the terminal themselves (/caveman config, a settings picker).
|
|
393
|
+
let agentRunning = false;
|
|
394
|
+
// This turn's Push Signal, and why its last assistant message stopped.
|
|
395
|
+
let marker = "none";
|
|
396
|
+
let stop: { reason: string; error?: string } | undefined;
|
|
327
397
|
|
|
398
|
+
pi.on("agent_start", () => {
|
|
399
|
+
agentRunning = true;
|
|
400
|
+
});
|
|
401
|
+
pi.on("message_end", (event) => {
|
|
402
|
+
const message = event.message;
|
|
403
|
+
if (message.role !== "assistant") return;
|
|
404
|
+
stop = { reason: message.stopReason, error: message.errorMessage };
|
|
405
|
+
let found = false;
|
|
406
|
+
const content = message.content.map((part) => {
|
|
407
|
+
if (part.type !== "text") return part;
|
|
408
|
+
const text = part.text.replace(MARKER_RE, (_match: string, word: string) => {
|
|
409
|
+
marker = word;
|
|
410
|
+
found = true;
|
|
411
|
+
return "";
|
|
412
|
+
});
|
|
413
|
+
return text === part.text ? part : { ...part, text: text.trimEnd() };
|
|
414
|
+
});
|
|
415
|
+
if (found) return { message: { ...message, content } };
|
|
416
|
+
});
|
|
417
|
+
pi.on("tool_execution_start", (event) => {
|
|
418
|
+
running = event;
|
|
419
|
+
});
|
|
328
420
|
pi.on("tool_execution_end", (event) => {
|
|
421
|
+
running = undefined;
|
|
329
422
|
tools += 1;
|
|
330
423
|
if (!READ_ONLY.has(event.toolName)) nonReadonly += 1;
|
|
331
424
|
});
|
|
425
|
+
// Waiting-on-you: pi fires ui_prompt_start for every blocking extension
|
|
426
|
+
// dialog (select/confirm/input/editor/custom — bash guards, ask-user tools).
|
|
427
|
+
// A custom dialog carries no title, so name the call that raised it instead.
|
|
428
|
+
pi.on("ui_prompt_start", (event, ctx) => {
|
|
429
|
+
clearTimeout(promptTimer);
|
|
430
|
+
if (!agentRunning) return;
|
|
431
|
+
const command = running?.toolName === "bash" ? running.args?.command : undefined;
|
|
432
|
+
const body = event.title
|
|
433
|
+
?? (typeof command === "string" ? "$ " + command : undefined)
|
|
434
|
+
?? (running ? running.toolName + " is waiting for your answer" : "Waiting for your input");
|
|
435
|
+
promptTimer = setTimeout(() => {
|
|
436
|
+
promptTimer = undefined;
|
|
437
|
+
pushHigh(ctx.cwd, "pi asks: " + projectOf(ctx.cwd), body);
|
|
438
|
+
}, PROMPT_GRACE_MS);
|
|
439
|
+
promptTimer.unref?.();
|
|
440
|
+
});
|
|
441
|
+
pi.on("ui_prompt_end", () => {
|
|
442
|
+
clearTimeout(promptTimer);
|
|
443
|
+
promptTimer = undefined;
|
|
444
|
+
});
|
|
332
445
|
// Stop-equivalent: agent_settled fires once per user turn, after retries/compaction.
|
|
333
446
|
// Fire-and-forget: never block pi's turn-end on the notify network call.
|
|
334
447
|
pi.on("agent_settled", (_event, ctx) => {
|
|
335
|
-
|
|
448
|
+
agentRunning = false;
|
|
449
|
+
clearTimeout(promptTimer);
|
|
450
|
+
promptTimer = undefined;
|
|
451
|
+
// Esc (at the terminal, or a key sent from the phone): whoever stopped the
|
|
452
|
+
// turn is already looking at it.
|
|
453
|
+
if (stop?.reason === "aborted") return;
|
|
454
|
+
// A turn that died on a provider error is a blocker, not a completion.
|
|
455
|
+
if (stop?.reason === "error") {
|
|
456
|
+
pushHigh(ctx.cwd, "pi stopped: " + projectOf(ctx.cwd), stop.error || "The model request failed");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const child = spawn("sh", ["-c", ${notifyCmdLiteral(TURN_FACT_FLAGS + PUSH_SIGNAL_FLAG)}], { cwd: ctx.cwd, stdio: "ignore", detached: true });
|
|
336
460
|
child.on("error", () => {});
|
|
337
461
|
child.unref();
|
|
338
462
|
});
|
|
@@ -342,6 +466,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
342
466
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
343
467
|
tools = 0;
|
|
344
468
|
nonReadonly = 0;
|
|
469
|
+
marker = "none";
|
|
470
|
+
stop = undefined;
|
|
345
471
|
const out = await sh(${JSON.stringify(remoteHookCmd('pi'))}, ctx.cwd,
|
|
346
472
|
JSON.stringify({ prompt: event.prompt, cwd: ctx.cwd }));
|
|
347
473
|
try {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point `stateDir()` at a throwaway directory for the length of one suite.
|
|
3
|
+
*
|
|
4
|
+
* Both files that keep state under `stateDir()` need this and both had rolled
|
|
5
|
+
* it by hand, restore-else-delete included. Modules that read the env at call
|
|
6
|
+
* time (`gate.ts`'s `stateDir`) can be imported normally; only ones that freeze
|
|
7
|
+
* a path at module scope need the dynamic-import dance.
|
|
8
|
+
*/
|
|
9
|
+
export declare const withTmpStateDir: (prefix: string) => (() => string);
|
|
10
|
+
//# sourceMappingURL=test-state-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-state-dir.d.ts","sourceRoot":"","sources":["../src/test-state-dir.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,CAAC,MAAM,MAAM,CAa7D,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withTmpStateDir = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
/**
|
|
9
|
+
* Point `stateDir()` at a throwaway directory for the length of one suite.
|
|
10
|
+
*
|
|
11
|
+
* Both files that keep state under `stateDir()` need this and both had rolled
|
|
12
|
+
* it by hand, restore-else-delete included. Modules that read the env at call
|
|
13
|
+
* time (`gate.ts`'s `stateDir`) can be imported normally; only ones that freeze
|
|
14
|
+
* a path at module scope need the dynamic-import dance.
|
|
15
|
+
*/
|
|
16
|
+
const withTmpStateDir = (prefix) => {
|
|
17
|
+
let dir = '';
|
|
18
|
+
const original = process.env.XDG_STATE_HOME;
|
|
19
|
+
(0, vitest_1.beforeEach)(() => {
|
|
20
|
+
dir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), prefix));
|
|
21
|
+
process.env.XDG_STATE_HOME = dir;
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.afterEach)(() => {
|
|
24
|
+
(0, fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
25
|
+
if (original === undefined)
|
|
26
|
+
delete process.env.XDG_STATE_HOME;
|
|
27
|
+
else
|
|
28
|
+
process.env.XDG_STATE_HOME = original;
|
|
29
|
+
});
|
|
30
|
+
return () => dir;
|
|
31
|
+
};
|
|
32
|
+
exports.withTmpStateDir = withTmpStateDir;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code session transcript (`~/.claude/projects/<hash>/<uuid>.jsonl`) →
|
|
3
|
+
* the small events the agent chat timeline draws.
|
|
4
|
+
*
|
|
5
|
+
* Two pure pieces, deliberately split: reading bytes off a growing file, and
|
|
6
|
+
* deciding what of it is worth putting on the wire. Neither touches the socket,
|
|
7
|
+
* so both are testable without tmux, WebSocket, or a clock.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is sized by what a real transcript actually contains, measured
|
|
10
|
+
* 2026-09-08 over 1443 files in `~/.claude/projects`: the largest file was
|
|
11
|
+
* 75.9 MB (p90 1.1 MB) and the longest single line 915,292 bytes. Those two
|
|
12
|
+
* numbers are why the reader starts near the end and refuses oversized lines
|
|
13
|
+
* before parsing them — a tail that starts at byte 0, or that hands a ~1 MB
|
|
14
|
+
* string to `JSON.parse` and only then decides to drop it, has already paid the
|
|
15
|
+
* cost the cap exists to avoid.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* How far back from the end of the file the first read starts.
|
|
19
|
+
*
|
|
20
|
+
* Not zero: a fresh viewer that sees nothing until the *next* tool call is a
|
|
21
|
+
* blank screen for whoever just picked up their phone mid-turn. Not the whole
|
|
22
|
+
* file either — that is up to 75.9 MB. A fixed window from the end costs the
|
|
23
|
+
* same on an 8 KB transcript and a 76 MB one.
|
|
24
|
+
*/
|
|
25
|
+
export declare const TRANSCRIPT_BACKFILL_BYTES: number;
|
|
26
|
+
/** Ceiling on one tick's read, so a burst of appends cannot be swallowed whole. */
|
|
27
|
+
export declare const MAX_TAIL_BYTES_PER_TICK: number;
|
|
28
|
+
/**
|
|
29
|
+
* A line longer than this is abandoned unparsed, the reader skipping to the next
|
|
30
|
+
* newline. The measured worst case is a 915 KB `tool_result`, whose body this
|
|
31
|
+
* module drops anyway — buffering one to completion would be paying megabytes to
|
|
32
|
+
* learn a tool id.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAX_TRANSCRIPT_LINE_CHARS: number;
|
|
35
|
+
/**
|
|
36
|
+
* Ceiling on a tool's label — its name, and the short string saying what it
|
|
37
|
+
* acted on. A path or a one-line description; anything longer is a runaway
|
|
38
|
+
* input, not a label.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MAX_EVENT_FIELD_CHARS = 512;
|
|
41
|
+
/**
|
|
42
|
+
* Ceiling on prose — what the agent wrote, and what the person asked.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately far above the label cap: 512 characters is a filename, and
|
|
45
|
+
* applying it here cut every reply off mid-sentence. 5000 is what the
|
|
46
|
+
* completion push already carries for the same text (`zeph-stop.sh`), so the
|
|
47
|
+
* live lane and the push that replaces it agree on how much of a reply is
|
|
48
|
+
* worth sending.
|
|
49
|
+
*/
|
|
50
|
+
export declare const MAX_EVENT_TEXT_CHARS = 5000;
|
|
51
|
+
/**
|
|
52
|
+
* Where the reader is in one file.
|
|
53
|
+
*
|
|
54
|
+
* `size`/`mtimeMs` are the skip check: when neither moved there is nothing to
|
|
55
|
+
* read, and the tick returns without allocating. Since idle is this watcher's
|
|
56
|
+
* normal state, that check is what keeps it off the GC's back.
|
|
57
|
+
*/
|
|
58
|
+
export interface TailState {
|
|
59
|
+
readonly offset: number;
|
|
60
|
+
readonly size: number;
|
|
61
|
+
readonly mtimeMs: number;
|
|
62
|
+
/** Which file this offset belongs to. A new inode is a new file at the same path. */
|
|
63
|
+
readonly ino: number;
|
|
64
|
+
/** Bytes after the last newline seen — an incomplete line, held for the next tick. */
|
|
65
|
+
readonly carry: string;
|
|
66
|
+
/** Inside an oversized line: discard bytes until the next newline. */
|
|
67
|
+
readonly resyncing: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface TailRead {
|
|
70
|
+
/** Whole lines, in file order. Never includes a partial line. */
|
|
71
|
+
readonly lines: string[];
|
|
72
|
+
readonly state: TailState;
|
|
73
|
+
readonly bytesRead: number;
|
|
74
|
+
/** Lines abandoned for exceeding MAX_TRANSCRIPT_LINE_CHARS. */
|
|
75
|
+
readonly droppedLines: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* No state yet. `null` rather than a sentinel-filled record: "never read this
|
|
79
|
+
* file" and "read it and got nothing" are different, and an out-of-band value
|
|
80
|
+
* that has to stay below every real offset is a trap for the next `>=`.
|
|
81
|
+
*/
|
|
82
|
+
export declare const initialTailState: () => TailState | null;
|
|
83
|
+
/**
|
|
84
|
+
* Read what was appended since `prev`, or `null` when there is nothing to do —
|
|
85
|
+
* the file is unreadable, or neither its size nor its mtime moved.
|
|
86
|
+
*
|
|
87
|
+
* `null` is the common case (an idle session) and is deliberately allocation
|
|
88
|
+
* free: no buffer, no string, no array.
|
|
89
|
+
*/
|
|
90
|
+
export declare const readTranscriptDelta: (path: string, prev: TailState | null) => TailRead | null;
|
|
91
|
+
/**
|
|
92
|
+
* What the timeline draws. Stateless by design: a `tool_result` arrives as its
|
|
93
|
+
* own event rather than mutating an earlier one, because the call it answers is
|
|
94
|
+
* routinely in a previous tick's batch — merging by `id` is the viewer's job,
|
|
95
|
+
* and it is the only side that holds the whole turn.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* `at` is the transcript entry's own timestamp, carried so the viewer can place
|
|
99
|
+
* a live turn among the pushes in time order instead of parking the whole live
|
|
100
|
+
* block below them — where a message sent while reading lands above the turn it
|
|
101
|
+
* answers.
|
|
102
|
+
*/
|
|
103
|
+
export type TurnEvent = {
|
|
104
|
+
at?: string;
|
|
105
|
+
} & ({
|
|
106
|
+
kind: 'tool';
|
|
107
|
+
id: string;
|
|
108
|
+
name: string;
|
|
109
|
+
target?: string;
|
|
110
|
+
} | {
|
|
111
|
+
kind: 'tool_result';
|
|
112
|
+
id: string;
|
|
113
|
+
ok: boolean;
|
|
114
|
+
} | {
|
|
115
|
+
kind: 'text';
|
|
116
|
+
text: string;
|
|
117
|
+
} | {
|
|
118
|
+
kind: 'prompt';
|
|
119
|
+
text: string;
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* Turn raw JSONL lines into wire events.
|
|
123
|
+
*
|
|
124
|
+
* Nothing a tool read or wrote survives this function: only the tool's name, a
|
|
125
|
+
* short label for what it acted on, and whether it worked. That is the whole
|
|
126
|
+
* privacy story of the feature — the transcript holds file contents and command
|
|
127
|
+
* output, and this is the one place that decides none of it leaves the machine.
|
|
128
|
+
*
|
|
129
|
+
* `sinceLastPrompt` is for the backfill window: finished turns already exist in
|
|
130
|
+
* the chat as their completion pushes, so replaying them would double every
|
|
131
|
+
* message. Only the turn still in flight is new information.
|
|
132
|
+
*
|
|
133
|
+
* The prompt a person typed IS carried, unlike anything a tool read or wrote.
|
|
134
|
+
* It is their own words, it is the same class of content the completion push
|
|
135
|
+
* already sends, and without it the timeline is a list of tool names with no
|
|
136
|
+
* record of what was asked.
|
|
137
|
+
*
|
|
138
|
+
* Everything here — the block shapes, the entry types, `TARGET_KEYS` — is Claude
|
|
139
|
+
* Code's transcript format. Supporting another agent (pi, Codex) means a
|
|
140
|
+
* projector of its own alongside this one, reached the way `REMOTE_AGENTS`
|
|
141
|
+
* already reaches per-agent session resolvers; `turn-watch` takes the reader as
|
|
142
|
+
* a dependency and needs no change for it.
|
|
143
|
+
*/
|
|
144
|
+
export declare const projectTranscriptEntries: (lines: readonly string[], opts?: {
|
|
145
|
+
sinceLastPrompt?: boolean;
|
|
146
|
+
}) => TurnEvent[];
|
|
147
|
+
//# sourceMappingURL=transcript-tail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-tail.d.ts","sourceRoot":"","sources":["../src/transcript-tail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,QAAa,CAAC;AAEpD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAElD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAc,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACrB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CACjC;AAoBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,SAAS,GAAG,IAAY,CAAC;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,MAAM,SAAS,GAAG,IAAI,KAAG,QAAQ,GAAG,IAuHrF,CAAC;AAIF;;;;;GAKG;AACH;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,CACpC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CACrC,CAAC;AA4HF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,wBAAwB,GACjC,OAAO,SAAS,MAAM,EAAE,EACxB,OAAM;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,KACzC,SAAS,EAwDX,CAAC"}
|