@gigaflow/gmux 0.15.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/CHANGELOG.md +654 -0
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/dist/adapters/claude-code.d.ts +52 -0
- package/dist/adapters/claude-code.js +315 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +39 -0
- package/dist/adapters/codex.js +233 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/jsonl.d.ts +40 -0
- package/dist/adapters/jsonl.js +92 -0
- package/dist/adapters/jsonl.js.map +1 -0
- package/dist/adapters/registry.d.ts +11 -0
- package/dist/adapters/registry.js +21 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +43 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli/border-client.d.ts +8 -0
- package/dist/cli/border-client.js +11 -0
- package/dist/cli/border-client.js.map +1 -0
- package/dist/cli/commands/__ask-cancel.d.ts +35 -0
- package/dist/cli/commands/__ask-cancel.js +91 -0
- package/dist/cli/commands/__ask-cancel.js.map +1 -0
- package/dist/cli/commands/__ask-refresh.d.ts +40 -0
- package/dist/cli/commands/__ask-refresh.js +54 -0
- package/dist/cli/commands/__ask-refresh.js.map +1 -0
- package/dist/cli/commands/__ask-run.d.ts +43 -0
- package/dist/cli/commands/__ask-run.js +126 -0
- package/dist/cli/commands/__ask-run.js.map +1 -0
- package/dist/cli/commands/__ask-send.d.ts +50 -0
- package/dist/cli/commands/__ask-send.js +138 -0
- package/dist/cli/commands/__ask-send.js.map +1 -0
- package/dist/cli/commands/__preview-card.d.ts +31 -0
- package/dist/cli/commands/__preview-card.js +72 -0
- package/dist/cli/commands/__preview-card.js.map +1 -0
- package/dist/cli/commands/ask.d.ts +50 -0
- package/dist/cli/commands/ask.js +240 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/auto.d.ts +12 -0
- package/dist/cli/commands/auto.js +28 -0
- package/dist/cli/commands/auto.js.map +1 -0
- package/dist/cli/commands/cockpit.d.ts +14 -0
- package/dist/cli/commands/cockpit.js +52 -0
- package/dist/cli/commands/cockpit.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +75 -0
- package/dist/cli/commands/daemon.js +258 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +159 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/grep.d.ts +2 -0
- package/dist/cli/commands/grep.js +52 -0
- package/dist/cli/commands/grep.js.map +1 -0
- package/dist/cli/commands/index-cmd.d.ts +2 -0
- package/dist/cli/commands/index-cmd.js +29 -0
- package/dist/cli/commands/index-cmd.js.map +1 -0
- package/dist/cli/commands/ls.d.ts +30 -0
- package/dist/cli/commands/ls.js +95 -0
- package/dist/cli/commands/ls.js.map +1 -0
- package/dist/cli/commands/overlay.d.ts +26 -0
- package/dist/cli/commands/overlay.js +216 -0
- package/dist/cli/commands/overlay.js.map +1 -0
- package/dist/cli/commands/pick.d.ts +59 -0
- package/dist/cli/commands/pick.js +254 -0
- package/dist/cli/commands/pick.js.map +1 -0
- package/dist/cli/commands/picker-rows.d.ts +17 -0
- package/dist/cli/commands/picker-rows.js +47 -0
- package/dist/cli/commands/picker-rows.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +25 -0
- package/dist/cli/commands/resume.js +88 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/run.d.ts +13 -0
- package/dist/cli/commands/run.js +78 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +37 -0
- package/dist/cli/commands/setup.js +200 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/show.d.ts +2 -0
- package/dist/cli/commands/show.js +24 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/summarize.d.ts +2 -0
- package/dist/cli/commands/summarize.js +59 -0
- package/dist/cli/commands/summarize.js.map +1 -0
- package/dist/cli/commands/tmux.d.ts +11 -0
- package/dist/cli/commands/tmux.js +90 -0
- package/dist/cli/commands/tmux.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +2 -0
- package/dist/cli/commands/watch.js +76 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/format.d.ts +74 -0
- package/dist/cli/format.js +242 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/gmux-render.d.ts +11 -0
- package/dist/cli/gmux-render.js +60 -0
- package/dist/cli/gmux-render.js.map +1 -0
- package/dist/cli/main.d.ts +8 -0
- package/dist/cli/main.js +145 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/overlay-ask.d.ts +13 -0
- package/dist/cli/overlay-ask.js +32 -0
- package/dist/cli/overlay-ask.js.map +1 -0
- package/dist/cli/overlay.d.ts +26 -0
- package/dist/cli/overlay.js +149 -0
- package/dist/cli/overlay.js.map +1 -0
- package/dist/cli/picker.d.ts +308 -0
- package/dist/cli/picker.js +642 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/preview.d.ts +87 -0
- package/dist/cli/preview.js +292 -0
- package/dist/cli/preview.js.map +1 -0
- package/dist/cli/tmux-label.d.ts +50 -0
- package/dist/cli/tmux-label.js +146 -0
- package/dist/cli/tmux-label.js.map +1 -0
- package/dist/core/errors.d.ts +54 -0
- package/dist/core/errors.js +91 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fingerprint.d.ts +21 -0
- package/dist/core/fingerprint.js +59 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/gmux-types.d.ts +102 -0
- package/dist/core/gmux-types.js +13 -0
- package/dist/core/gmux-types.js.map +1 -0
- package/dist/core/pane-state.d.ts +5 -0
- package/dist/core/pane-state.js +15 -0
- package/dist/core/pane-state.js.map +1 -0
- package/dist/core/paths.d.ts +68 -0
- package/dist/core/paths.js +106 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/proc-tree.d.ts +9 -0
- package/dist/core/proc-tree.js +33 -0
- package/dist/core/proc-tree.js.map +1 -0
- package/dist/core/text.d.ts +34 -0
- package/dist/core/text.js +126 -0
- package/dist/core/text.js.map +1 -0
- package/dist/core/types.d.ts +322 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -0
- package/dist/services/ask-transcript.d.ts +256 -0
- package/dist/services/ask-transcript.js +544 -0
- package/dist/services/ask-transcript.js.map +1 -0
- package/dist/services/ask.d.ts +54 -0
- package/dist/services/ask.js +161 -0
- package/dist/services/ask.js.map +1 -0
- package/dist/services/auto-summarize.d.ts +188 -0
- package/dist/services/auto-summarize.js +415 -0
- package/dist/services/auto-summarize.js.map +1 -0
- package/dist/services/concurrency.d.ts +2 -0
- package/dist/services/concurrency.js +16 -0
- package/dist/services/concurrency.js.map +1 -0
- package/dist/services/config.d.ts +131 -0
- package/dist/services/config.js +325 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/daemon-client.d.ts +9 -0
- package/dist/services/daemon-client.js +34 -0
- package/dist/services/daemon-client.js.map +1 -0
- package/dist/services/daemon-socket.d.ts +30 -0
- package/dist/services/daemon-socket.js +76 -0
- package/dist/services/daemon-socket.js.map +1 -0
- package/dist/services/daemon.d.ts +43 -0
- package/dist/services/daemon.js +106 -0
- package/dist/services/daemon.js.map +1 -0
- package/dist/services/distill.d.ts +39 -0
- package/dist/services/distill.js +93 -0
- package/dist/services/distill.js.map +1 -0
- package/dist/services/guardian.d.ts +26 -0
- package/dist/services/guardian.js +71 -0
- package/dist/services/guardian.js.map +1 -0
- package/dist/services/index-store.d.ts +36 -0
- package/dist/services/index-store.js +125 -0
- package/dist/services/index-store.js.map +1 -0
- package/dist/services/log-rotation.d.ts +3 -0
- package/dist/services/log-rotation.js +28 -0
- package/dist/services/log-rotation.js.map +1 -0
- package/dist/services/pane-links.d.ts +10 -0
- package/dist/services/pane-links.js +47 -0
- package/dist/services/pane-links.js.map +1 -0
- package/dist/services/pane-process.d.ts +95 -0
- package/dist/services/pane-process.js +183 -0
- package/dist/services/pane-process.js.map +1 -0
- package/dist/services/pane-registry.d.ts +24 -0
- package/dist/services/pane-registry.js +40 -0
- package/dist/services/pane-registry.js.map +1 -0
- package/dist/services/provider-process.d.ts +37 -0
- package/dist/services/provider-process.js +105 -0
- package/dist/services/provider-process.js.map +1 -0
- package/dist/services/providers.d.ts +76 -0
- package/dist/services/providers.js +104 -0
- package/dist/services/providers.js.map +1 -0
- package/dist/services/resolve.d.ts +3 -0
- package/dist/services/resolve.js +18 -0
- package/dist/services/resolve.js.map +1 -0
- package/dist/services/resources.d.ts +17 -0
- package/dist/services/resources.js +113 -0
- package/dist/services/resources.js.map +1 -0
- package/dist/services/search.d.ts +29 -0
- package/dist/services/search.js +135 -0
- package/dist/services/search.js.map +1 -0
- package/dist/services/semantic-gate.d.ts +13 -0
- package/dist/services/semantic-gate.js +23 -0
- package/dist/services/semantic-gate.js.map +1 -0
- package/dist/services/semantic.d.ts +113 -0
- package/dist/services/semantic.js +216 -0
- package/dist/services/semantic.js.map +1 -0
- package/dist/services/sensors.d.ts +92 -0
- package/dist/services/sensors.js +246 -0
- package/dist/services/sensors.js.map +1 -0
- package/dist/services/summarize.d.ts +84 -0
- package/dist/services/summarize.js +230 -0
- package/dist/services/summarize.js.map +1 -0
- package/dist/services/tmux-gateway.d.ts +17 -0
- package/dist/services/tmux-gateway.js +10 -0
- package/dist/services/tmux-gateway.js.map +1 -0
- package/dist/services/tmux-resolve.d.ts +54 -0
- package/dist/services/tmux-resolve.js +0 -0
- package/dist/services/tmux-resolve.js.map +1 -0
- package/dist/services/tmux.d.ts +32 -0
- package/dist/services/tmux.js +107 -0
- package/dist/services/tmux.js.map +1 -0
- package/dist/services/views.d.ts +7 -0
- package/dist/services/views.js +18 -0
- package/dist/services/views.js.map +1 -0
- package/dist/services/watch.d.ts +36 -0
- package/dist/services/watch.js +98 -0
- package/dist/services/watch.js.map +1 -0
- package/dist/services/workspace.d.ts +19 -0
- package/dist/services/workspace.js +88 -0
- package/dist/services/workspace.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../src/services/semantic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensors: the two ways gmux learns what a pane is doing, unified behind one
|
|
3
|
+
* shape.
|
|
4
|
+
*
|
|
5
|
+
* `TerminalSensor` watches a plain shell (or any harness we can't identify a
|
|
6
|
+
* transcript for) via tmux `capture-pane`, and keeps a `pipe-pane` log running
|
|
7
|
+
* so a later "show me the raw tail" view has more than 200 lines to work with.
|
|
8
|
+
* `AgentSensor` watches a known harness's transcript file directly — richer
|
|
9
|
+
* and cheaper than scraping a terminal, but only available once the pane is
|
|
10
|
+
* linked to a session.
|
|
11
|
+
*
|
|
12
|
+
* Both report the same `Observation` shape (`gmux-types.ts`) so the daemon's
|
|
13
|
+
* state-derivation layer never needs to know which kind of pane it is looking
|
|
14
|
+
* at.
|
|
15
|
+
*/
|
|
16
|
+
import type { Observation, PaneIdentity } from "../core/gmux-types.js";
|
|
17
|
+
import type { TmuxGateway } from "./tmux-gateway.js";
|
|
18
|
+
export interface Sensor {
|
|
19
|
+
readonly kind: "agent" | "terminal";
|
|
20
|
+
observe(now: number): Promise<Observation>;
|
|
21
|
+
teardown(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Watches a pane via tmux capture, for shells and unidentified harnesses.
|
|
25
|
+
*
|
|
26
|
+
* `pipe-pane` is started lazily on first observe (not the constructor) so
|
|
27
|
+
* constructing a sensor is never itself a side effect — only observing one
|
|
28
|
+
* is.
|
|
29
|
+
*/
|
|
30
|
+
export declare class TerminalSensor implements Sensor {
|
|
31
|
+
private readonly identity;
|
|
32
|
+
private readonly gateway;
|
|
33
|
+
readonly kind: "terminal";
|
|
34
|
+
private lastHash;
|
|
35
|
+
private lastActivityTs;
|
|
36
|
+
private piping;
|
|
37
|
+
constructor(identity: PaneIdentity, gateway: TmuxGateway);
|
|
38
|
+
observe(now: number): Promise<Observation>;
|
|
39
|
+
teardown(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/** `timestamp` is the one field both `claude-code.ts` and `codex.ts` key on. */
|
|
42
|
+
export declare function tsOf(row: Record<string, unknown> | undefined): number | null;
|
|
43
|
+
/** Last known speaker was the assistant, with no human turn after it yet. */
|
|
44
|
+
export declare function isAssistantWaiting(rows: Record<string, unknown>[]): boolean | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* A tool/command failure in the tail: Claude Code's `tool_result` blocks with
|
|
47
|
+
* `is_error: true` (see `hasToolError` in `adapters/claude-code.ts`), or a
|
|
48
|
+
* Codex `function_call_output` reporting a nonzero exit (see `exitFailure` in
|
|
49
|
+
* `adapters/codex.ts`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function hasToolError(rows: Record<string, unknown>[]): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* A completion marker in the tail. Only Codex's `task_complete` event is a
|
|
54
|
+
* per-row signal we can trust here — Claude Code's transcripts carry no
|
|
55
|
+
* equivalent single-line marker (its own adapter infers `endedMidTask` from
|
|
56
|
+
* proximity to the end of the *whole* file, which a 40-row tail can't
|
|
57
|
+
* reproduce), so `ended` stays `false` for Claude Code panes at the sensor
|
|
58
|
+
* layer.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isEnded(rows: Record<string, unknown>[]): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Watches a known harness's transcript file.
|
|
63
|
+
*
|
|
64
|
+
* Takes only `identity` — never a `filePath` — so `makeSensor` stays
|
|
65
|
+
* synchronous: the file is resolved lazily, on first `observe()`, via the
|
|
66
|
+
* cached session index, and cached on the instance from then on. If no
|
|
67
|
+
* matching record (or file) is found, `observe` degrades gracefully to an
|
|
68
|
+
* empty observation rather than throwing.
|
|
69
|
+
*/
|
|
70
|
+
export declare class AgentSensor implements Sensor {
|
|
71
|
+
private readonly identity;
|
|
72
|
+
/** Test-only escape hatch: skip index lookup and read this file directly. */
|
|
73
|
+
private readonly filePathOverride?;
|
|
74
|
+
/** Test-only escape hatch: replace the cachedRecords()-based lookup. */
|
|
75
|
+
private readonly lookupOverride?;
|
|
76
|
+
readonly kind: "agent";
|
|
77
|
+
private resolvedPath;
|
|
78
|
+
constructor(identity: PaneIdentity,
|
|
79
|
+
/** Test-only escape hatch: skip index lookup and read this file directly. */
|
|
80
|
+
filePathOverride?: string | undefined,
|
|
81
|
+
/** Test-only escape hatch: replace the cachedRecords()-based lookup. */
|
|
82
|
+
lookupOverride?: (() => Promise<string | null>) | undefined);
|
|
83
|
+
private lookupFromIndex;
|
|
84
|
+
private resolvePath;
|
|
85
|
+
observe(now: number): Promise<Observation>;
|
|
86
|
+
teardown(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Picks the richer sensor whenever the pane is resolvable to a known
|
|
90
|
+
* harness session, else falls back to the terminal sensor.
|
|
91
|
+
*/
|
|
92
|
+
export declare function makeSensor(identity: PaneIdentity, gateway: TmuxGateway): Sensor;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensors: the two ways gmux learns what a pane is doing, unified behind one
|
|
3
|
+
* shape.
|
|
4
|
+
*
|
|
5
|
+
* `TerminalSensor` watches a plain shell (or any harness we can't identify a
|
|
6
|
+
* transcript for) via tmux `capture-pane`, and keeps a `pipe-pane` log running
|
|
7
|
+
* so a later "show me the raw tail" view has more than 200 lines to work with.
|
|
8
|
+
* `AgentSensor` watches a known harness's transcript file directly — richer
|
|
9
|
+
* and cheaper than scraping a terminal, but only available once the pane is
|
|
10
|
+
* linked to a session.
|
|
11
|
+
*
|
|
12
|
+
* Both report the same `Observation` shape (`gmux-types.ts`) so the daemon's
|
|
13
|
+
* state-derivation layer never needs to know which kind of pane it is looking
|
|
14
|
+
* at.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { paneLogPath } from "../core/paths.js";
|
|
18
|
+
import { readJsonl, RingBuffer } from "../adapters/jsonl.js";
|
|
19
|
+
import { cachedRecords } from "./index-store.js";
|
|
20
|
+
import { rotateIfLarge, pruneLog, MAX_PANE_LOG_BYTES } from "./log-rotation.js";
|
|
21
|
+
/** How many transcript rows AgentSensor keeps in view per observe(). */
|
|
22
|
+
const AGENT_TAIL_CAPACITY = 40;
|
|
23
|
+
const hash = (s) => createHash("sha1").update(s).digest("hex");
|
|
24
|
+
/**
|
|
25
|
+
* Watches a pane via tmux capture, for shells and unidentified harnesses.
|
|
26
|
+
*
|
|
27
|
+
* `pipe-pane` is started lazily on first observe (not the constructor) so
|
|
28
|
+
* constructing a sensor is never itself a side effect — only observing one
|
|
29
|
+
* is.
|
|
30
|
+
*/
|
|
31
|
+
export class TerminalSensor {
|
|
32
|
+
identity;
|
|
33
|
+
gateway;
|
|
34
|
+
kind = "terminal";
|
|
35
|
+
lastHash = "";
|
|
36
|
+
lastActivityTs = 0;
|
|
37
|
+
piping = false;
|
|
38
|
+
constructor(identity, gateway) {
|
|
39
|
+
this.identity = identity;
|
|
40
|
+
this.gateway = gateway;
|
|
41
|
+
}
|
|
42
|
+
async observe(now) {
|
|
43
|
+
if (!this.piping) {
|
|
44
|
+
await this.gateway.startPipe(this.identity.paneId, paneLogPath(this.identity.paneId)).catch(() => { });
|
|
45
|
+
this.piping = true;
|
|
46
|
+
}
|
|
47
|
+
const text = await this.gateway.capture(this.identity.paneId, 200);
|
|
48
|
+
const h = hash(text);
|
|
49
|
+
if (h !== this.lastHash) {
|
|
50
|
+
this.lastActivityTs = now;
|
|
51
|
+
this.lastHash = h;
|
|
52
|
+
}
|
|
53
|
+
await rotateIfLarge(paneLogPath(this.identity.paneId), MAX_PANE_LOG_BYTES).catch(() => { });
|
|
54
|
+
const tailLines = text.split("\n").filter((l) => l.length > 0);
|
|
55
|
+
return {
|
|
56
|
+
paneId: this.identity.paneId,
|
|
57
|
+
kind: "terminal",
|
|
58
|
+
ts: now,
|
|
59
|
+
tailLines,
|
|
60
|
+
lastActivityTs: this.lastActivityTs,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async teardown() {
|
|
64
|
+
if (this.piping)
|
|
65
|
+
await this.gateway.stopPipe(this.identity.paneId).catch(() => { });
|
|
66
|
+
await pruneLog(paneLogPath(this.identity.paneId)).catch(() => { });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// --- Agent-signal derivation -----------------------------------------------
|
|
70
|
+
//
|
|
71
|
+
// These read the two shapes gmux already knows how to parse in full
|
|
72
|
+
// (`adapters/claude-code.ts`, `adapters/codex.ts`), but here we only ever see
|
|
73
|
+
// the last ~40 rows of a possibly-still-growing file, so each helper looks
|
|
74
|
+
// for one narrow, well-grounded marker rather than replaying the adapter's
|
|
75
|
+
// whole-file logic. Any row shape we don't recognize is simply not a match —
|
|
76
|
+
// never a throw.
|
|
77
|
+
function record(value) {
|
|
78
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
79
|
+
}
|
|
80
|
+
function blocks(value) {
|
|
81
|
+
return Array.isArray(value) ? value.filter((b) => record(b) !== null) : [];
|
|
82
|
+
}
|
|
83
|
+
/** `timestamp` is the one field both `claude-code.ts` and `codex.ts` key on. */
|
|
84
|
+
export function tsOf(row) {
|
|
85
|
+
if (!row)
|
|
86
|
+
return null;
|
|
87
|
+
const ts = row["timestamp"];
|
|
88
|
+
if (typeof ts !== "string")
|
|
89
|
+
return null;
|
|
90
|
+
const ms = Date.parse(ts);
|
|
91
|
+
return Number.isFinite(ms) ? ms : null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Which side spoke, for the two shapes we recognize: Claude Code's
|
|
95
|
+
* `type: "user" | "assistant"` rows, and Codex's `event_msg` rows tagged
|
|
96
|
+
* `user_message` / `agent_message` in `payload.type`.
|
|
97
|
+
*/
|
|
98
|
+
function roleOf(row) {
|
|
99
|
+
const type = row["type"];
|
|
100
|
+
if (type === "user")
|
|
101
|
+
return "user";
|
|
102
|
+
if (type === "assistant")
|
|
103
|
+
return "assistant";
|
|
104
|
+
if (type === "event_msg") {
|
|
105
|
+
const payload = record(row["payload"]);
|
|
106
|
+
const kind = payload?.["type"];
|
|
107
|
+
if (kind === "user_message")
|
|
108
|
+
return "user";
|
|
109
|
+
if (kind === "agent_message")
|
|
110
|
+
return "assistant";
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Last known speaker was the assistant, with no human turn after it yet. */
|
|
115
|
+
export function isAssistantWaiting(rows) {
|
|
116
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
117
|
+
const role = roleOf(rows[i]);
|
|
118
|
+
if (role === "assistant")
|
|
119
|
+
return true;
|
|
120
|
+
if (role === "user")
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A tool/command failure in the tail: Claude Code's `tool_result` blocks with
|
|
127
|
+
* `is_error: true` (see `hasToolError` in `adapters/claude-code.ts`), or a
|
|
128
|
+
* Codex `function_call_output` reporting a nonzero exit (see `exitFailure` in
|
|
129
|
+
* `adapters/codex.ts`).
|
|
130
|
+
*/
|
|
131
|
+
export function hasToolError(rows) {
|
|
132
|
+
return rows.some((row) => {
|
|
133
|
+
if (row["type"] === "user") {
|
|
134
|
+
const message = record(row["message"]);
|
|
135
|
+
const content = message?.["content"];
|
|
136
|
+
return blocks(content).some((b) => b["type"] === "tool_result" && b["is_error"] === true);
|
|
137
|
+
}
|
|
138
|
+
if (row["type"] === "response_item") {
|
|
139
|
+
const payload = record(row["payload"]);
|
|
140
|
+
if (payload?.["type"] === "function_call_output" && typeof payload["output"] === "string") {
|
|
141
|
+
const match = /Process exited with code (\d+)/.exec(payload["output"]);
|
|
142
|
+
return match !== null && match[1] !== "0";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A completion marker in the tail. Only Codex's `task_complete` event is a
|
|
150
|
+
* per-row signal we can trust here — Claude Code's transcripts carry no
|
|
151
|
+
* equivalent single-line marker (its own adapter infers `endedMidTask` from
|
|
152
|
+
* proximity to the end of the *whole* file, which a 40-row tail can't
|
|
153
|
+
* reproduce), so `ended` stays `false` for Claude Code panes at the sensor
|
|
154
|
+
* layer.
|
|
155
|
+
*/
|
|
156
|
+
export function isEnded(rows) {
|
|
157
|
+
return rows.some((row) => {
|
|
158
|
+
if (row["type"] !== "event_msg")
|
|
159
|
+
return false;
|
|
160
|
+
const payload = record(row["payload"]);
|
|
161
|
+
return payload?.["type"] === "task_complete";
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Watches a known harness's transcript file.
|
|
166
|
+
*
|
|
167
|
+
* Takes only `identity` — never a `filePath` — so `makeSensor` stays
|
|
168
|
+
* synchronous: the file is resolved lazily, on first `observe()`, via the
|
|
169
|
+
* cached session index, and cached on the instance from then on. If no
|
|
170
|
+
* matching record (or file) is found, `observe` degrades gracefully to an
|
|
171
|
+
* empty observation rather than throwing.
|
|
172
|
+
*/
|
|
173
|
+
export class AgentSensor {
|
|
174
|
+
identity;
|
|
175
|
+
filePathOverride;
|
|
176
|
+
lookupOverride;
|
|
177
|
+
kind = "agent";
|
|
178
|
+
resolvedPath;
|
|
179
|
+
constructor(identity,
|
|
180
|
+
/** Test-only escape hatch: skip index lookup and read this file directly. */
|
|
181
|
+
filePathOverride,
|
|
182
|
+
/** Test-only escape hatch: replace the cachedRecords()-based lookup. */
|
|
183
|
+
lookupOverride) {
|
|
184
|
+
this.identity = identity;
|
|
185
|
+
this.filePathOverride = filePathOverride;
|
|
186
|
+
this.lookupOverride = lookupOverride;
|
|
187
|
+
}
|
|
188
|
+
async lookupFromIndex() {
|
|
189
|
+
const records = await cachedRecords().catch(() => []);
|
|
190
|
+
const match = records.find((r) => r.harness === this.identity.harness && r.sessionId === this.identity.sessionId);
|
|
191
|
+
return match?.filePath ?? null;
|
|
192
|
+
}
|
|
193
|
+
async resolvePath() {
|
|
194
|
+
if (this.filePathOverride)
|
|
195
|
+
return this.filePathOverride;
|
|
196
|
+
if (this.resolvedPath !== undefined)
|
|
197
|
+
return this.resolvedPath;
|
|
198
|
+
const filePath = this.lookupOverride ? await this.lookupOverride() : await this.lookupFromIndex();
|
|
199
|
+
// Only memoize a HIT. The daemon keeps one AgentSensor alive for a pane's
|
|
200
|
+
// whole lifetime, so caching a miss would degrade to empty observations
|
|
201
|
+
// forever, even after the index later picks up the transcript. A miss is
|
|
202
|
+
// cheap to retry: cachedRecords() is an in-memory cache read, not a rescan.
|
|
203
|
+
if (filePath)
|
|
204
|
+
this.resolvedPath = filePath;
|
|
205
|
+
return filePath;
|
|
206
|
+
}
|
|
207
|
+
async observe(now) {
|
|
208
|
+
const filePath = await this.resolvePath();
|
|
209
|
+
if (!filePath) {
|
|
210
|
+
return { paneId: this.identity.paneId, kind: "agent", ts: now, tailLines: [], lastActivityTs: now };
|
|
211
|
+
}
|
|
212
|
+
const tail = new RingBuffer(AGENT_TAIL_CAPACITY);
|
|
213
|
+
try {
|
|
214
|
+
for await (const row of readJsonl(filePath))
|
|
215
|
+
tail.push(row);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// File vanished or became unreadable mid-tick; degrade rather than throw.
|
|
219
|
+
return { paneId: this.identity.paneId, kind: "agent", ts: now, tailLines: [], lastActivityTs: now };
|
|
220
|
+
}
|
|
221
|
+
const rows = tail.toArray();
|
|
222
|
+
const tailLines = rows.map((r) => JSON.stringify(r));
|
|
223
|
+
const lastActivityTs = tsOf(rows[rows.length - 1]) ?? now;
|
|
224
|
+
return {
|
|
225
|
+
paneId: this.identity.paneId,
|
|
226
|
+
kind: "agent",
|
|
227
|
+
ts: now,
|
|
228
|
+
tailLines,
|
|
229
|
+
lastActivityTs,
|
|
230
|
+
awaitingInput: isAssistantWaiting(rows),
|
|
231
|
+
sawError: hasToolError(rows),
|
|
232
|
+
ended: isEnded(rows),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async teardown() { }
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Picks the richer sensor whenever the pane is resolvable to a known
|
|
239
|
+
* harness session, else falls back to the terminal sensor.
|
|
240
|
+
*/
|
|
241
|
+
export function makeSensor(identity, gateway) {
|
|
242
|
+
if (identity.harness && identity.sessionId)
|
|
243
|
+
return new AgentSensor(identity);
|
|
244
|
+
return new TerminalSensor(identity, gateway);
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=sensors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensors.js","sourceRoot":"","sources":["../../src/services/sensors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summaries: the layer that answers "where did this land, and what's next?".
|
|
3
|
+
*
|
|
4
|
+
* The provider is a plain CLI invoked with the distilled prompt on stdin. That
|
|
5
|
+
* keeps gmux harness-agnostic — it ships pointing at `claude -p`, but
|
|
6
|
+
* GMUX_SUMMARY_CMD can point it at `codex exec` or anything else that
|
|
7
|
+
* reads a prompt and writes text.
|
|
8
|
+
*
|
|
9
|
+
* Summaries are cached by content hash and regenerated only when the session
|
|
10
|
+
* itself changes, so each one is written once and re-read forever.
|
|
11
|
+
*/
|
|
12
|
+
import type { SessionRecord, SessionSummary, SummaryFields, SummaryInput, SummaryProvider } from "../core/types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Default provider command, ignoring config.
|
|
15
|
+
*
|
|
16
|
+
* Kept for the synchronous callers that predate config and for tests. Prefer
|
|
17
|
+
* `defaultSummaryProvider()`, which honors what the user chose in `gmux setup`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function defaultProviderCommand(): string[];
|
|
20
|
+
export declare class CliSummaryProvider implements SummaryProvider {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
private readonly argv;
|
|
23
|
+
constructor(argv?: string[]);
|
|
24
|
+
isAvailable(): Promise<boolean>;
|
|
25
|
+
generate(input: SummaryInput): Promise<SummaryFields>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The summary provider for the current config, or null when the user has
|
|
29
|
+
* configured gmux to make no model calls.
|
|
30
|
+
*
|
|
31
|
+
* Null is a choice being honored, not a failure. Callers render it as such.
|
|
32
|
+
*/
|
|
33
|
+
export declare function defaultSummaryProvider(): Promise<CliSummaryProvider | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Pull the JSON object out of a model's reply.
|
|
36
|
+
*
|
|
37
|
+
* Models fence their JSON, or preface it, more often than they should — so we
|
|
38
|
+
* take the outermost brace pair rather than trusting the whole reply to parse.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseSummaryFields(raw: string, provider: string): SummaryFields;
|
|
41
|
+
export declare function readSummary(record: SessionRecord): Promise<SessionSummary | null>;
|
|
42
|
+
/** A cached summary is stale once the session it describes has moved on. */
|
|
43
|
+
export declare function isStale(summary: SessionSummary | null, record: SessionRecord): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* How many of the 64 SimHash bits must differ before a session is worth
|
|
46
|
+
* re-summarising. Tunable via `GMUX_REFRESH_DISTANCE`; the default is
|
|
47
|
+
* conservative — refresh on real progress, not on chatter.
|
|
48
|
+
*/
|
|
49
|
+
export declare const REFRESH_DISTANCE: number;
|
|
50
|
+
export declare function contentFingerprint(input: SummaryInput): string;
|
|
51
|
+
/** The low-churn significant signals — any change here always forces a refresh. */
|
|
52
|
+
export declare function signalHash(input: SummaryInput): string;
|
|
53
|
+
/**
|
|
54
|
+
* Should this session's summary be re-generated? The divergence-gated successor
|
|
55
|
+
* to `isStale`, for the background/watch path:
|
|
56
|
+
*
|
|
57
|
+
* - no summary → yes;
|
|
58
|
+
* - a pre-0.9.0 summary with no fingerprint → fall back to the exact hash, so
|
|
59
|
+
* there is no forced re-summarise burst on upgrade;
|
|
60
|
+
* - a significant signal changed (prompt version, a new tool failure, the
|
|
61
|
+
* mid-task flag, the files touched) → yes, even at SimHash distance 0;
|
|
62
|
+
* - otherwise → only when the narrative SimHash has diverged by ≥ REFRESH_DISTANCE.
|
|
63
|
+
*/
|
|
64
|
+
export declare function shouldRefresh(summary: SessionSummary | null, record: SessionRecord): boolean;
|
|
65
|
+
export declare function writeSummary(summary: SessionSummary): Promise<void>;
|
|
66
|
+
/** Generate a summary for one session and cache it. */
|
|
67
|
+
export declare function summarizeSession(record: SessionRecord, provider: SummaryProvider, now?: () => Date): Promise<SessionSummary>;
|
|
68
|
+
export interface SummarizeBatchResult {
|
|
69
|
+
generated: number;
|
|
70
|
+
skipped: number;
|
|
71
|
+
failed: {
|
|
72
|
+
sessionId: string;
|
|
73
|
+
reason: string;
|
|
74
|
+
}[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Summarize many sessions, skipping those whose cached summary is still fresh.
|
|
78
|
+
* Failures are collected rather than thrown: one bad session must not abort a
|
|
79
|
+
* batch of fifty.
|
|
80
|
+
*/
|
|
81
|
+
export declare function summarizeBatch(records: readonly SessionRecord[], provider: SummaryProvider, options?: {
|
|
82
|
+
force?: boolean;
|
|
83
|
+
onProgress?: (done: number, total: number) => void;
|
|
84
|
+
}): Promise<SummarizeBatchResult>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summaries: the layer that answers "where did this land, and what's next?".
|
|
3
|
+
*
|
|
4
|
+
* The provider is a plain CLI invoked with the distilled prompt on stdin. That
|
|
5
|
+
* keeps gmux harness-agnostic — it ships pointing at `claude -p`, but
|
|
6
|
+
* GMUX_SUMMARY_CMD can point it at `codex exec` or anything else that
|
|
7
|
+
* reads a prompt and writes text.
|
|
8
|
+
*
|
|
9
|
+
* Summaries are cached by content hash and regenerated only when the session
|
|
10
|
+
* itself changes, so each one is written once and re-read forever.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
|
+
import { dirname } from "node:path";
|
|
14
|
+
import { SummaryProviderError } from "../core/errors.js";
|
|
15
|
+
import { hammingDistance, simhash64 } from "../core/fingerprint.js";
|
|
16
|
+
import { summaryPath } from "../core/paths.js";
|
|
17
|
+
import { hash } from "../core/text.js";
|
|
18
|
+
import { FALLBACK_COMMAND, readConfig, resolveSummaryCommand } from "./config.js";
|
|
19
|
+
import { buildPrompt, distill } from "./distill.js";
|
|
20
|
+
import { mapLimit } from "./concurrency.js";
|
|
21
|
+
import { runProviderCommand } from "./provider-process.js";
|
|
22
|
+
import { onPath } from "./providers.js";
|
|
23
|
+
/**
|
|
24
|
+
* How many summaries are written at once.
|
|
25
|
+
*
|
|
26
|
+
* Each is an independent `claude -p`; they do not contend. Eight keeps a
|
|
27
|
+
* twenty-session backfill down to a few wall-clock minutes instead of twenty.
|
|
28
|
+
*/
|
|
29
|
+
const SUMMARY_CONCURRENCY = Number(process.env["GMUX_SUMMARY_CONCURRENCY"]) || 8;
|
|
30
|
+
const PROVIDER_TIMEOUT_MS = 120_000;
|
|
31
|
+
/**
|
|
32
|
+
* Default provider command, ignoring config.
|
|
33
|
+
*
|
|
34
|
+
* Kept for the synchronous callers that predate config and for tests. Prefer
|
|
35
|
+
* `defaultSummaryProvider()`, which honors what the user chose in `gmux setup`.
|
|
36
|
+
*/
|
|
37
|
+
export function defaultProviderCommand() {
|
|
38
|
+
const override = process.env.GMUX_SUMMARY_CMD;
|
|
39
|
+
if (override && override.trim() !== "")
|
|
40
|
+
return override.trim().split(/\s+/);
|
|
41
|
+
return [...FALLBACK_COMMAND];
|
|
42
|
+
}
|
|
43
|
+
export class CliSummaryProvider {
|
|
44
|
+
name;
|
|
45
|
+
argv;
|
|
46
|
+
constructor(argv = defaultProviderCommand()) {
|
|
47
|
+
this.argv = argv;
|
|
48
|
+
this.name = argv.join(" ");
|
|
49
|
+
}
|
|
50
|
+
async isAvailable() {
|
|
51
|
+
const binary = this.argv[0];
|
|
52
|
+
if (!binary)
|
|
53
|
+
return false;
|
|
54
|
+
return onPath(binary);
|
|
55
|
+
}
|
|
56
|
+
async generate(input) {
|
|
57
|
+
const prompt = buildPrompt(input);
|
|
58
|
+
try {
|
|
59
|
+
const output = await runProviderCommand(this.argv, prompt, {
|
|
60
|
+
timeoutMs: PROVIDER_TIMEOUT_MS,
|
|
61
|
+
});
|
|
62
|
+
return parseSummaryFields(output, this.name);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof SummaryProviderError)
|
|
66
|
+
throw error;
|
|
67
|
+
throw new SummaryProviderError(this.name, error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The summary provider for the current config, or null when the user has
|
|
73
|
+
* configured gmux to make no model calls.
|
|
74
|
+
*
|
|
75
|
+
* Null is a choice being honored, not a failure. Callers render it as such.
|
|
76
|
+
*/
|
|
77
|
+
export async function defaultSummaryProvider() {
|
|
78
|
+
const command = resolveSummaryCommand(await readConfig());
|
|
79
|
+
return command ? new CliSummaryProvider(command) : null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Pull the JSON object out of a model's reply.
|
|
83
|
+
*
|
|
84
|
+
* Models fence their JSON, or preface it, more often than they should — so we
|
|
85
|
+
* take the outermost brace pair rather than trusting the whole reply to parse.
|
|
86
|
+
*/
|
|
87
|
+
export function parseSummaryFields(raw, provider) {
|
|
88
|
+
const start = raw.indexOf("{");
|
|
89
|
+
const end = raw.lastIndexOf("}");
|
|
90
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
91
|
+
throw new SummaryProviderError(provider, "reply contained no JSON object");
|
|
92
|
+
}
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(raw.slice(start, end + 1));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw new SummaryProviderError(provider, `reply was not valid JSON: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
const object = parsed;
|
|
101
|
+
const field = (key) => {
|
|
102
|
+
const value = object[key];
|
|
103
|
+
return typeof value === "string" ? value.trim() : "";
|
|
104
|
+
};
|
|
105
|
+
const headline = field("headline");
|
|
106
|
+
if (headline === "")
|
|
107
|
+
throw new SummaryProviderError(provider, "reply had no `headline`");
|
|
108
|
+
return {
|
|
109
|
+
headline,
|
|
110
|
+
overview: field("overview"),
|
|
111
|
+
summary: field("summary"),
|
|
112
|
+
landed: field("landed"),
|
|
113
|
+
open: field("open"),
|
|
114
|
+
nextStep: field("nextStep"),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export async function readSummary(record) {
|
|
118
|
+
try {
|
|
119
|
+
const raw = await readFile(summaryPath(record.harness, record.sessionId), "utf8");
|
|
120
|
+
return JSON.parse(raw);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** A cached summary is stale once the session it describes has moved on. */
|
|
127
|
+
export function isStale(summary, record) {
|
|
128
|
+
if (!summary)
|
|
129
|
+
return true;
|
|
130
|
+
return summary.sourceHash !== distill(record).hash;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* How many of the 64 SimHash bits must differ before a session is worth
|
|
134
|
+
* re-summarising. Tunable via `GMUX_REFRESH_DISTANCE`; the default is
|
|
135
|
+
* conservative — refresh on real progress, not on chatter.
|
|
136
|
+
*/
|
|
137
|
+
export const REFRESH_DISTANCE = (() => {
|
|
138
|
+
const raw = Number(process.env.GMUX_REFRESH_DISTANCE);
|
|
139
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 8;
|
|
140
|
+
})();
|
|
141
|
+
/** The narrative fields — what the SimHash tracks for gradual divergence. */
|
|
142
|
+
function narrativeText(input) {
|
|
143
|
+
return [...input.arcPrompts, ...input.recentUserPrompts, input.lastAssistantText ?? ""].join("\n");
|
|
144
|
+
}
|
|
145
|
+
export function contentFingerprint(input) {
|
|
146
|
+
return simhash64(narrativeText(input));
|
|
147
|
+
}
|
|
148
|
+
/** The low-churn significant signals — any change here always forces a refresh. */
|
|
149
|
+
export function signalHash(input) {
|
|
150
|
+
return hash(JSON.stringify({
|
|
151
|
+
promptVersion: input.promptVersion,
|
|
152
|
+
lastToolFailure: input.lastToolFailure,
|
|
153
|
+
endedMidTask: input.endedMidTask,
|
|
154
|
+
files: [...input.filesTouched].sort(),
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Should this session's summary be re-generated? The divergence-gated successor
|
|
159
|
+
* to `isStale`, for the background/watch path:
|
|
160
|
+
*
|
|
161
|
+
* - no summary → yes;
|
|
162
|
+
* - a pre-0.9.0 summary with no fingerprint → fall back to the exact hash, so
|
|
163
|
+
* there is no forced re-summarise burst on upgrade;
|
|
164
|
+
* - a significant signal changed (prompt version, a new tool failure, the
|
|
165
|
+
* mid-task flag, the files touched) → yes, even at SimHash distance 0;
|
|
166
|
+
* - otherwise → only when the narrative SimHash has diverged by ≥ REFRESH_DISTANCE.
|
|
167
|
+
*/
|
|
168
|
+
export function shouldRefresh(summary, record) {
|
|
169
|
+
if (!summary)
|
|
170
|
+
return true;
|
|
171
|
+
const input = distill(record);
|
|
172
|
+
if (!summary.fingerprint)
|
|
173
|
+
return summary.sourceHash !== input.hash;
|
|
174
|
+
if (summary.signalHash !== signalHash(input))
|
|
175
|
+
return true;
|
|
176
|
+
return hammingDistance(summary.fingerprint, contentFingerprint(input)) >= REFRESH_DISTANCE;
|
|
177
|
+
}
|
|
178
|
+
export async function writeSummary(summary) {
|
|
179
|
+
const path = summaryPath(summary.harness, summary.sessionId);
|
|
180
|
+
await mkdir(dirname(path), { recursive: true });
|
|
181
|
+
await writeFile(path, JSON.stringify(summary, null, 2), "utf8");
|
|
182
|
+
}
|
|
183
|
+
/** Generate a summary for one session and cache it. */
|
|
184
|
+
export async function summarizeSession(record, provider, now = () => new Date()) {
|
|
185
|
+
const input = distill(record);
|
|
186
|
+
const fields = await provider.generate(input);
|
|
187
|
+
const summary = {
|
|
188
|
+
harness: record.harness,
|
|
189
|
+
sessionId: record.sessionId,
|
|
190
|
+
sourceHash: input.hash,
|
|
191
|
+
fingerprint: contentFingerprint(input),
|
|
192
|
+
signalHash: signalHash(input),
|
|
193
|
+
generatedAt: now().toISOString(),
|
|
194
|
+
provider: provider.name,
|
|
195
|
+
...fields,
|
|
196
|
+
};
|
|
197
|
+
await writeSummary(summary);
|
|
198
|
+
return summary;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Summarize many sessions, skipping those whose cached summary is still fresh.
|
|
202
|
+
* Failures are collected rather than thrown: one bad session must not abort a
|
|
203
|
+
* batch of fifty.
|
|
204
|
+
*/
|
|
205
|
+
export async function summarizeBatch(records, provider, options = {}) {
|
|
206
|
+
const result = { generated: 0, skipped: 0, failed: [] };
|
|
207
|
+
let done = 0;
|
|
208
|
+
await mapLimit(records, SUMMARY_CONCURRENCY, async (record) => {
|
|
209
|
+
try {
|
|
210
|
+
if (!options.force) {
|
|
211
|
+
const existing = await readSummary(record);
|
|
212
|
+
if (!isStale(existing, record)) {
|
|
213
|
+
result.skipped += 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await summarizeSession(record, provider);
|
|
218
|
+
result.generated += 1;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
result.failed.push({ sessionId: record.sessionId, reason: error.message });
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
done += 1;
|
|
225
|
+
options.onProgress?.(done, records.length);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=summarize.js.map
|