runtape 0.1.1 → 0.3.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 +38 -7
- package/dist/{chunk-Q4RSBPGN.js → chunk-PXZACW6S.js} +21 -3
- package/dist/chunk-PXZACW6S.js.map +1 -0
- package/dist/index.js +383 -15
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +283 -1
- package/dist/types.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-Q4RSBPGN.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
v0.
|
|
9
|
+
v0.3 — Adds subagent (Task tool) capture. The full internal session of every spawned agent — its user prompt, assistant turns with model + token usage, and tool calls — is now recorded as nested children of the parent `Agent` step.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -44,11 +44,12 @@ If you prefer the granular commands instead of the wizard, `runtape login` and `
|
|
|
44
44
|
|
|
45
45
|
```
|
|
46
46
|
~/.runtape/
|
|
47
|
-
config.json
|
|
48
|
-
buffer/<session_id>.ndjson
|
|
49
|
-
seq/<session_id>
|
|
50
|
-
|
|
51
|
-
flusher.
|
|
47
|
+
config.json # api_key (chmod 600), server_url
|
|
48
|
+
buffer/<session_id>.ndjson # pending events
|
|
49
|
+
seq/<session_id> # monotonic sequence counter
|
|
50
|
+
transcript/<session_id> # uuids of assistant turns already emitted (v0.2+)
|
|
51
|
+
flusher.pid # daemon PID
|
|
52
|
+
flusher.log # daemon log (append-only)
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
Override the home dir with `RUNTAPE_HOME` (useful for tests). Override the server with `RUNTAPE_API_URL` or `runtape login --server-url <url>`.
|
|
@@ -63,6 +64,36 @@ import { RuntapeEvent, IngestionRequest } from "runtape/types";
|
|
|
63
64
|
|
|
64
65
|
The schemas live in `src/types.ts`. The package's `exports` map points TypeScript at the source file (no build step required for type consumers) and Node at the compiled `dist/types.js`.
|
|
65
66
|
|
|
67
|
+
## Changelog
|
|
68
|
+
|
|
69
|
+
### 0.3.0 — 2026-05-15
|
|
70
|
+
|
|
71
|
+
- **Subagent capture.** On `SubagentStop`, the CLI now reads the subagent's `agent_transcript_path` and synthesizes the full internal event stream — user prompt, assistant turns (with model + usage), tool attempts and calls. Every synthesized event carries `agent_tool_use_id` so the server resolves it as a child of the parent `Agent` step. The dashboard renders these inline as nested rows under the Agent.
|
|
72
|
+
- **`agent_tool_use_id` envelope field.** Optional on every event type. Server-side, when set, it overrides the open-Agent temporal heuristic and resolves the parent step directly.
|
|
73
|
+
|
|
74
|
+
### 0.2.1 — 2026-05-15
|
|
75
|
+
|
|
76
|
+
- **Skip `<synthetic>` turns.** Claude Code emits placeholder assistant messages with `model: "<synthetic>"` (compaction markers, internal state) that always have zero usage. The transcript scanner now drops them so the dashboard doesn't show empty no-cost turns.
|
|
77
|
+
|
|
78
|
+
### 0.2.0 — 2026-05-15
|
|
79
|
+
|
|
80
|
+
- **Model + token usage per turn.** The CLI now reads the Claude Code transcript JSONL on `PostToolUse` / `Stop` / `SubagentStop` and emits a new `assistant_turn` event for every assistant message, carrying `model`, `input_tokens`, `output_tokens`, `cache_read_tokens`, and `cache_creation_tokens`. The dashboard surfaces a per-turn model + cost pill and a run-level total.
|
|
81
|
+
- **Tool error surfacing.** `tool_call` events now carry `is_error` + `error_message` (derived from the tool response shape — Bash exits, Edit/Write rejections, `is_error` content blocks, interrupts). Errored tools are visible in the run timeline without expanding the step.
|
|
82
|
+
- **State.** Adds `~/.runtape/transcript/<session_id>` to track which assistant message uuids have already been emitted (idempotent scans across hook fires).
|
|
83
|
+
|
|
84
|
+
To upgrade:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install -g runtape@latest
|
|
88
|
+
runtape install # safe to re-run; refreshes the hook entries
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
No changes to the existing hook commands or config file format.
|
|
92
|
+
|
|
93
|
+
### 0.1.x
|
|
94
|
+
|
|
95
|
+
Initial MVP. Hook-based capture of Claude Code sessions (`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`, `SubagentStop`), buffered to disk and flushed by a detached daemon.
|
|
96
|
+
|
|
66
97
|
## Open source
|
|
67
98
|
|
|
68
99
|
MIT licensed. Audit exactly what's captured. The Runtape backend (dashboard, ingestion API) is closed-source SaaS at [runtape.dev](https://runtape.dev).
|
|
@@ -70,7 +101,7 @@ MIT licensed. Audit exactly what's captured. The Runtape backend (dashboard, ing
|
|
|
70
101
|
## Repos
|
|
71
102
|
|
|
72
103
|
- This repo (`runtape`) — the open-source CLI
|
|
73
|
-
- `runtape-mcp` — MCP server for Runtape (
|
|
104
|
+
- `runtape-mcp` — MCP server for Runtape (planned)
|
|
74
105
|
|
|
75
106
|
## License
|
|
76
107
|
|
|
@@ -9,7 +9,8 @@ var ClaudeHookBase = z.object({
|
|
|
9
9
|
});
|
|
10
10
|
var CliAugment = z.object({
|
|
11
11
|
wall_ts: z.string().datetime(),
|
|
12
|
-
sequence: z.number().int().nonnegative()
|
|
12
|
+
sequence: z.number().int().nonnegative(),
|
|
13
|
+
agent_tool_use_id: z.string().min(1).optional()
|
|
13
14
|
});
|
|
14
15
|
var SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
15
16
|
type: z.literal("session_start"),
|
|
@@ -31,7 +32,22 @@ var ToolCallEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
|
31
32
|
tool_input: z.unknown(),
|
|
32
33
|
tool_response: z.unknown(),
|
|
33
34
|
tool_use_id: z.string().min(1),
|
|
34
|
-
duration_ms: z.number().nonnegative()
|
|
35
|
+
duration_ms: z.number().nonnegative(),
|
|
36
|
+
// Set by the CLI when the tool_response signals an error (Bash non-zero
|
|
37
|
+
// exit, Edit/Write rejection, tool_response.is_error, etc.). Optional so
|
|
38
|
+
// older CLI versions stay forward-compatible.
|
|
39
|
+
is_error: z.boolean().optional(),
|
|
40
|
+
error_message: z.string().optional()
|
|
41
|
+
});
|
|
42
|
+
var AssistantTurnEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
43
|
+
type: z.literal("assistant_turn"),
|
|
44
|
+
message_uuid: z.string().min(1),
|
|
45
|
+
model: z.string().min(1),
|
|
46
|
+
input_tokens: z.number().int().nonnegative(),
|
|
47
|
+
output_tokens: z.number().int().nonnegative(),
|
|
48
|
+
cache_read_tokens: z.number().int().nonnegative().default(0),
|
|
49
|
+
cache_creation_tokens: z.number().int().nonnegative().default(0),
|
|
50
|
+
text: z.string().optional()
|
|
35
51
|
});
|
|
36
52
|
var SubagentEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
37
53
|
type: z.literal("subagent_end"),
|
|
@@ -51,6 +67,7 @@ var RuntapeEvent = z.discriminatedUnion("type", [
|
|
|
51
67
|
UserPromptEvent,
|
|
52
68
|
ToolAttemptEvent,
|
|
53
69
|
ToolCallEvent,
|
|
70
|
+
AssistantTurnEvent,
|
|
54
71
|
SubagentEndEvent,
|
|
55
72
|
SessionEndEvent
|
|
56
73
|
]);
|
|
@@ -72,10 +89,11 @@ export {
|
|
|
72
89
|
UserPromptEvent,
|
|
73
90
|
ToolAttemptEvent,
|
|
74
91
|
ToolCallEvent,
|
|
92
|
+
AssistantTurnEvent,
|
|
75
93
|
SubagentEndEvent,
|
|
76
94
|
SessionEndEvent,
|
|
77
95
|
RuntapeEvent,
|
|
78
96
|
IngestionRequest,
|
|
79
97
|
IngestionResponse
|
|
80
98
|
};
|
|
81
|
-
//# sourceMappingURL=chunk-
|
|
99
|
+
//# sourceMappingURL=chunk-PXZACW6S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { z } from 'zod';\n\n// Every Claude Code hook event carries these envelope fields.\nconst ClaudeHookBase = z.object({\n session_id: z.string().min(1),\n transcript_path: z.string().min(1),\n cwd: z.string().min(1),\n hook_event_name: z.string().min(1),\n permission_mode: z.string().optional(),\n});\n\n// CLI-augmented envelope adds monotonic ordering + wall-clock timestamp.\n// agent_tool_use_id is set when the CLI is synthesizing events from a\n// subagent transcript — it carries the parent Agent step's tool_use_id so\n// the server can resolve parent_step_id deterministically instead of\n// relying on the temporal open-stack heuristic. Optional so the field is\n// absent for normal top-level events.\nconst CliAugment = z.object({\n wall_ts: z.string().datetime(),\n sequence: z.number().int().nonnegative(),\n agent_tool_use_id: z.string().min(1).optional(),\n});\n\nexport const SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_start'),\n source: z.string(),\n});\n\nexport const UserPromptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('user_prompt'),\n prompt: z.string(),\n});\n\nexport const ToolAttemptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_attempt'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_use_id: z.string().min(1),\n});\n\nexport const ToolCallEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_call'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_response: z.unknown(),\n tool_use_id: z.string().min(1),\n duration_ms: z.number().nonnegative(),\n // Set by the CLI when the tool_response signals an error (Bash non-zero\n // exit, Edit/Write rejection, tool_response.is_error, etc.). Optional so\n // older CLI versions stay forward-compatible.\n is_error: z.boolean().optional(),\n error_message: z.string().optional(),\n});\n\n// Emitted by the CLI on Stop / PostToolUse after scanning the Claude Code\n// transcript JSONL. One event per assistant message we haven't seen yet,\n// keyed by message_uuid so re-deliveries dedupe at the server. Carries the\n// model identifier and token usage for that turn — the only place either\n// datum is available in Claude Code's emit chain.\nexport const AssistantTurnEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('assistant_turn'),\n message_uuid: z.string().min(1),\n model: z.string().min(1),\n input_tokens: z.number().int().nonnegative(),\n output_tokens: z.number().int().nonnegative(),\n cache_read_tokens: z.number().int().nonnegative().default(0),\n cache_creation_tokens: z.number().int().nonnegative().default(0),\n text: z.string().optional(),\n});\n\nexport const SubagentEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('subagent_end'),\n agent_id: z.string().min(1),\n agent_type: z.string().min(1),\n agent_transcript_path: z.string().min(1),\n last_assistant_message: z.string(),\n stop_hook_active: z.boolean().optional(),\n});\n\nexport const SessionEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_end'),\n last_assistant_message: z.string().optional(),\n stop_hook_active: z.boolean().optional(),\n});\n\nexport const RuntapeEvent = z.discriminatedUnion('type', [\n SessionStartEvent,\n UserPromptEvent,\n ToolAttemptEvent,\n ToolCallEvent,\n AssistantTurnEvent,\n SubagentEndEvent,\n SessionEndEvent,\n]);\n\nexport type RuntapeEvent = z.infer<typeof RuntapeEvent>;\n\n// POST /v1/events body — a batch of up to 100 events.\nexport const IngestionRequest = z.object({\n events: z.array(RuntapeEvent).min(1).max(100),\n});\n\nexport type IngestionRequest = z.infer<typeof IngestionRequest>;\n\n// Response shape (server returns 200 on accepted, 400 on Zod failure, 401 on bad auth).\nexport const IngestionResponse = z.object({\n accepted: z.number().int().nonnegative(),\n errors: z\n .array(\n z.object({\n index: z.number().int().nonnegative(),\n reason: z.string(),\n }),\n )\n .default([]),\n});\n\nexport type IngestionResponse = z.infer<typeof IngestionResponse>;\n"],"mappings":";AAAA,SAAS,SAAS;AAGlB,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACjC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACrB,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACjC,iBAAiB,EAAE,OAAO,EAAE,SAAS;AACvC,CAAC;AAQD,IAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACvC,mBAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAChD,CAAC;AAEM,IAAM,oBAAoB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC9E,MAAM,EAAE,QAAQ,eAAe;AAAA,EAC/B,QAAQ,EAAE,OAAO;AACnB,CAAC;AAEM,IAAM,kBAAkB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC5E,MAAM,EAAE,QAAQ,aAAa;AAAA,EAC7B,QAAQ,EAAE,OAAO;AACnB,CAAC;AAEM,IAAM,mBAAmB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC7E,MAAM,EAAE,QAAQ,cAAc;AAAA,EAC9B,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,YAAY,EAAE,QAAQ;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAC/B,CAAC;AAEM,IAAM,gBAAgB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC1E,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,YAAY,EAAE,QAAQ;AAAA,EACtB,eAAe,EAAE,QAAQ;AAAA,EACzB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,aAAa,EAAE,OAAO,EAAE,YAAY;AAAA;AAAA;AAAA;AAAA,EAIpC,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC/B,eAAe,EAAE,OAAO,EAAE,SAAS;AACrC,CAAC;AAOM,IAAM,qBAAqB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC/E,MAAM,EAAE,QAAQ,gBAAgB;AAAA,EAChC,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC5C,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC;AAAA,EAC3D,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC;AAAA,EAC/D,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;AAEM,IAAM,mBAAmB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC7E,MAAM,EAAE,QAAQ,cAAc;AAAA,EAC9B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvC,wBAAwB,EAAE,OAAO;AAAA,EACjC,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AACzC,CAAC;AAEM,IAAM,kBAAkB,eAAe,OAAO,WAAW,KAAK,EAAE,OAAO;AAAA,EAC5E,MAAM,EAAE,QAAQ,aAAa;AAAA,EAC7B,wBAAwB,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5C,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AACzC,CAAC;AAEM,IAAM,eAAe,EAAE,mBAAmB,QAAQ;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,QAAQ,EAAE,MAAM,YAAY,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAC9C,CAAC;AAKM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACvC,QAAQ,EACL;AAAA,IACC,EAAE,OAAO;AAAA,MACP,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,MACpC,QAAQ,EAAE,OAAO;AAAA,IACnB,CAAC;AAAA,EACH,EACC,QAAQ,CAAC,CAAC;AACf,CAAC;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RuntapeEvent
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PXZACW6S.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
|
-
import { dirname as
|
|
8
|
+
import { dirname as dirname8, join as join2 } from "path";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
|
|
11
11
|
// src/commands/login.ts
|
|
@@ -30,6 +30,11 @@ var paths = {
|
|
|
30
30
|
flusherLog: join(RUNTAPE_HOME, "flusher.log"),
|
|
31
31
|
bufferFile: (sessionId) => join(RUNTAPE_HOME, "buffer", `${sessionId}.ndjson`),
|
|
32
32
|
seqFile: (sessionId) => join(RUNTAPE_HOME, "seq", sessionId),
|
|
33
|
+
// Per-session marker recording the last assistant message uuid we've
|
|
34
|
+
// emitted from the transcript. Lets transcript scans be idempotent across
|
|
35
|
+
// hook invocations without re-reading + re-emitting everything.
|
|
36
|
+
transcriptCursorFile: (sessionId) => join(RUNTAPE_HOME, "transcript", sessionId),
|
|
37
|
+
transcriptDir: join(RUNTAPE_HOME, "transcript"),
|
|
33
38
|
claudeSettings: (scope) => scope === "user" ? join(homedir(), ".claude", "settings.json") : join(process.cwd(), ".claude", "settings.json"),
|
|
34
39
|
claudeSettingsBackup: (scope) => scope === "user" ? join(homedir(), ".claude", "settings.json.runtape-backup") : join(process.cwd(), ".claude", "settings.json.runtape-backup")
|
|
35
40
|
};
|
|
@@ -182,6 +187,30 @@ var SUPPORTED_HOOKS = [
|
|
|
182
187
|
"Stop",
|
|
183
188
|
"SubagentStop"
|
|
184
189
|
];
|
|
190
|
+
function inspectToolResponse(tool_response) {
|
|
191
|
+
if (!tool_response || typeof tool_response !== "object") return { is_error: false };
|
|
192
|
+
const r = tool_response;
|
|
193
|
+
if (r.is_error === true) {
|
|
194
|
+
const content = r.content;
|
|
195
|
+
if (Array.isArray(content)) {
|
|
196
|
+
for (const block of content) {
|
|
197
|
+
if (block && typeof block === "object" && "text" in block) {
|
|
198
|
+
const t = block.text;
|
|
199
|
+
if (typeof t === "string") return { is_error: true, error_message: t };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (typeof r.message === "string") return { is_error: true, error_message: r.message };
|
|
204
|
+
return { is_error: true };
|
|
205
|
+
}
|
|
206
|
+
if (typeof r.error === "string" && r.error.trim() !== "") {
|
|
207
|
+
return { is_error: true, error_message: r.error };
|
|
208
|
+
}
|
|
209
|
+
if (r.interrupted === true) {
|
|
210
|
+
return { is_error: true, error_message: "Interrupted" };
|
|
211
|
+
}
|
|
212
|
+
return { is_error: false };
|
|
213
|
+
}
|
|
185
214
|
function mapHookPayload(hookName, payload, augment) {
|
|
186
215
|
const base = {
|
|
187
216
|
session_id: payload.session_id,
|
|
@@ -209,7 +238,8 @@ function mapHookPayload(hookName, payload, augment) {
|
|
|
209
238
|
tool_use_id: payload.tool_use_id
|
|
210
239
|
};
|
|
211
240
|
break;
|
|
212
|
-
case "PostToolUse":
|
|
241
|
+
case "PostToolUse": {
|
|
242
|
+
const err = inspectToolResponse(payload.tool_response);
|
|
213
243
|
candidate = {
|
|
214
244
|
...base,
|
|
215
245
|
type: "tool_call",
|
|
@@ -217,9 +247,12 @@ function mapHookPayload(hookName, payload, augment) {
|
|
|
217
247
|
tool_input: payload.tool_input,
|
|
218
248
|
tool_response: payload.tool_response,
|
|
219
249
|
tool_use_id: payload.tool_use_id,
|
|
220
|
-
duration_ms: payload.duration_ms
|
|
250
|
+
duration_ms: payload.duration_ms,
|
|
251
|
+
is_error: err.is_error,
|
|
252
|
+
error_message: err.error_message
|
|
221
253
|
};
|
|
222
254
|
break;
|
|
255
|
+
}
|
|
223
256
|
case "Stop":
|
|
224
257
|
candidate = {
|
|
225
258
|
...base,
|
|
@@ -474,6 +507,233 @@ async function bufferMtimeMs(sessionId) {
|
|
|
474
507
|
}
|
|
475
508
|
}
|
|
476
509
|
|
|
510
|
+
// src/lib/transcript.ts
|
|
511
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
512
|
+
import { dirname as dirname5 } from "path";
|
|
513
|
+
function asInt(x) {
|
|
514
|
+
if (typeof x === "number" && Number.isFinite(x) && x >= 0) return Math.trunc(x);
|
|
515
|
+
return 0;
|
|
516
|
+
}
|
|
517
|
+
function extractAssistantText(content) {
|
|
518
|
+
if (!Array.isArray(content)) return void 0;
|
|
519
|
+
const parts = [];
|
|
520
|
+
for (const block of content) {
|
|
521
|
+
if (block && typeof block === "object" && "type" in block) {
|
|
522
|
+
const b = block;
|
|
523
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const joined = parts.join("\n").trim();
|
|
527
|
+
return joined === "" ? void 0 : joined;
|
|
528
|
+
}
|
|
529
|
+
async function readNewAssistantTurns(sessionId, transcriptPath) {
|
|
530
|
+
const seen = await readCursor(sessionId);
|
|
531
|
+
let raw;
|
|
532
|
+
try {
|
|
533
|
+
raw = await readFile5(transcriptPath, "utf8");
|
|
534
|
+
} catch {
|
|
535
|
+
return { turns: [], seen };
|
|
536
|
+
}
|
|
537
|
+
const turns = [];
|
|
538
|
+
for (const line of raw.split("\n")) {
|
|
539
|
+
const trimmed = line.trim();
|
|
540
|
+
if (trimmed === "") continue;
|
|
541
|
+
let parsed;
|
|
542
|
+
try {
|
|
543
|
+
parsed = JSON.parse(trimmed);
|
|
544
|
+
} catch {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (parsed.type !== "assistant") continue;
|
|
548
|
+
const uuid = typeof parsed.uuid === "string" ? parsed.uuid : "";
|
|
549
|
+
if (uuid === "" || seen.has(uuid)) continue;
|
|
550
|
+
const msg = parsed.message ?? {};
|
|
551
|
+
const model = typeof msg.model === "string" ? msg.model : "";
|
|
552
|
+
if (model === "" || model === "<synthetic>") continue;
|
|
553
|
+
const usage = msg.usage ?? {};
|
|
554
|
+
turns.push({
|
|
555
|
+
message_uuid: uuid,
|
|
556
|
+
model,
|
|
557
|
+
input_tokens: asInt(usage.input_tokens),
|
|
558
|
+
output_tokens: asInt(usage.output_tokens),
|
|
559
|
+
cache_read_tokens: asInt(usage.cache_read_input_tokens),
|
|
560
|
+
cache_creation_tokens: asInt(usage.cache_creation_input_tokens),
|
|
561
|
+
text: extractAssistantText(msg.content)
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
return { turns, seen };
|
|
565
|
+
}
|
|
566
|
+
var CURSOR_KEEP = 200;
|
|
567
|
+
async function readCursor(sessionId) {
|
|
568
|
+
try {
|
|
569
|
+
const raw = await readFile5(paths.transcriptCursorFile(sessionId), "utf8");
|
|
570
|
+
return new Set(
|
|
571
|
+
raw.split("\n").map((s) => s.trim()).filter((s) => s !== "")
|
|
572
|
+
);
|
|
573
|
+
} catch {
|
|
574
|
+
return /* @__PURE__ */ new Set();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function persistCursor(sessionId, seen, newlyEmitted) {
|
|
578
|
+
for (const u of newlyEmitted) seen.add(u);
|
|
579
|
+
const file = paths.transcriptCursorFile(sessionId);
|
|
580
|
+
await mkdir5(dirname5(file), { recursive: true });
|
|
581
|
+
const arr = [...seen];
|
|
582
|
+
const trimmed = arr.length > CURSOR_KEEP ? arr.slice(arr.length - CURSOR_KEEP) : arr;
|
|
583
|
+
await writeFile5(file, trimmed.join("\n") + "\n");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/lib/subagent-transcript.ts
|
|
587
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
588
|
+
import { dirname as dirname6 } from "path";
|
|
589
|
+
function asInt2(x) {
|
|
590
|
+
if (typeof x === "number" && Number.isFinite(x) && x >= 0) return Math.trunc(x);
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
593
|
+
function textFromBlocks(content) {
|
|
594
|
+
if (!Array.isArray(content)) return typeof content === "string" ? content : void 0;
|
|
595
|
+
const parts = [];
|
|
596
|
+
for (const block of content) {
|
|
597
|
+
if (block && typeof block === "object" && "type" in block) {
|
|
598
|
+
const b = block;
|
|
599
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const joined = parts.join("\n").trim();
|
|
603
|
+
return joined === "" ? void 0 : joined;
|
|
604
|
+
}
|
|
605
|
+
function summarizeToolResult(content) {
|
|
606
|
+
if (!Array.isArray(content)) {
|
|
607
|
+
if (typeof content === "string") return { is_error: false };
|
|
608
|
+
return { is_error: false };
|
|
609
|
+
}
|
|
610
|
+
let isError = false;
|
|
611
|
+
const texts = [];
|
|
612
|
+
for (const block of content) {
|
|
613
|
+
if (block && typeof block === "object") {
|
|
614
|
+
const b = block;
|
|
615
|
+
if (b.is_error === true) isError = true;
|
|
616
|
+
if (typeof b.text === "string") texts.push(b.text);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (isError && texts.length > 0) return { is_error: true, error_message: texts.join("\n").slice(0, 500) };
|
|
620
|
+
if (isError) return { is_error: true };
|
|
621
|
+
return { is_error: false };
|
|
622
|
+
}
|
|
623
|
+
async function readNewSubagentEvents(parentSessionId, agentToolUseId, transcriptPath) {
|
|
624
|
+
const seen = await readCursor2(parentSessionId, agentToolUseId);
|
|
625
|
+
let raw;
|
|
626
|
+
try {
|
|
627
|
+
raw = await readFile6(transcriptPath, "utf8");
|
|
628
|
+
} catch {
|
|
629
|
+
return { emits: [], seen };
|
|
630
|
+
}
|
|
631
|
+
const emits = [];
|
|
632
|
+
const toolNameByUseId = /* @__PURE__ */ new Map();
|
|
633
|
+
for (const line of raw.split("\n")) {
|
|
634
|
+
const trimmed = line.trim();
|
|
635
|
+
if (trimmed === "") continue;
|
|
636
|
+
let parsed;
|
|
637
|
+
try {
|
|
638
|
+
parsed = JSON.parse(trimmed);
|
|
639
|
+
} catch {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const uuid = typeof parsed.uuid === "string" ? parsed.uuid : "";
|
|
643
|
+
if (uuid === "" || seen.has(uuid)) continue;
|
|
644
|
+
const msg = parsed.message ?? {};
|
|
645
|
+
if (parsed.type === "user") {
|
|
646
|
+
const content = msg.content;
|
|
647
|
+
const hasToolResults = Array.isArray(content) && content.some(
|
|
648
|
+
(b) => b && typeof b === "object" && b.type === "tool_result"
|
|
649
|
+
);
|
|
650
|
+
if (hasToolResults) {
|
|
651
|
+
for (const block of content) {
|
|
652
|
+
if (!block || typeof block !== "object") continue;
|
|
653
|
+
const b = block;
|
|
654
|
+
if (b.type !== "tool_result") continue;
|
|
655
|
+
const tuid = typeof b.tool_use_id === "string" ? b.tool_use_id : "";
|
|
656
|
+
if (tuid === "") continue;
|
|
657
|
+
const { is_error, error_message } = summarizeToolResult(b.content);
|
|
658
|
+
emits.push({
|
|
659
|
+
kind: "tool_call",
|
|
660
|
+
uuid: `${uuid}:${tuid}`,
|
|
661
|
+
tool_use_id: tuid,
|
|
662
|
+
tool_name: toolNameByUseId.get(tuid) ?? "unknown",
|
|
663
|
+
tool_response: b.content,
|
|
664
|
+
is_error,
|
|
665
|
+
error_message
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
const text = textFromBlocks(content);
|
|
670
|
+
if (text !== void 0) {
|
|
671
|
+
emits.push({ kind: "user_prompt", uuid, prompt: text });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (parsed.type === "assistant") {
|
|
677
|
+
const model = typeof msg.model === "string" ? msg.model : "";
|
|
678
|
+
if (model === "" || model === "<synthetic>") continue;
|
|
679
|
+
const usage = msg.usage ?? {};
|
|
680
|
+
emits.push({
|
|
681
|
+
kind: "assistant_turn",
|
|
682
|
+
uuid,
|
|
683
|
+
message_uuid: uuid,
|
|
684
|
+
model,
|
|
685
|
+
input_tokens: asInt2(usage.input_tokens),
|
|
686
|
+
output_tokens: asInt2(usage.output_tokens),
|
|
687
|
+
cache_read_tokens: asInt2(usage.cache_read_input_tokens),
|
|
688
|
+
cache_creation_tokens: asInt2(usage.cache_creation_input_tokens),
|
|
689
|
+
text: textFromBlocks(msg.content)
|
|
690
|
+
});
|
|
691
|
+
if (Array.isArray(msg.content)) {
|
|
692
|
+
for (const block of msg.content) {
|
|
693
|
+
if (!block || typeof block !== "object") continue;
|
|
694
|
+
const b = block;
|
|
695
|
+
if (b.type !== "tool_use") continue;
|
|
696
|
+
const tuid = typeof b.id === "string" ? b.id : "";
|
|
697
|
+
const tname = typeof b.name === "string" ? b.name : "";
|
|
698
|
+
if (tuid === "" || tname === "") continue;
|
|
699
|
+
toolNameByUseId.set(tuid, tname);
|
|
700
|
+
emits.push({
|
|
701
|
+
kind: "tool_attempt",
|
|
702
|
+
uuid: `${uuid}:${tuid}`,
|
|
703
|
+
tool_use_id: tuid,
|
|
704
|
+
tool_name: tname,
|
|
705
|
+
tool_input: b.input
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return { emits, seen };
|
|
713
|
+
}
|
|
714
|
+
var CURSOR_KEEP2 = 500;
|
|
715
|
+
async function readCursor2(parentSessionId, agentToolUseId) {
|
|
716
|
+
try {
|
|
717
|
+
const raw = await readFile6(cursorFile(parentSessionId, agentToolUseId), "utf8");
|
|
718
|
+
return new Set(
|
|
719
|
+
raw.split("\n").map((s) => s.trim()).filter((s) => s !== "")
|
|
720
|
+
);
|
|
721
|
+
} catch {
|
|
722
|
+
return /* @__PURE__ */ new Set();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function cursorFile(parentSessionId, agentToolUseId) {
|
|
726
|
+
return `${paths.transcriptDir}/subagent-${parentSessionId}-${agentToolUseId}`;
|
|
727
|
+
}
|
|
728
|
+
async function persistSubagentCursor(parentSessionId, agentToolUseId, seen, newlyEmitted) {
|
|
729
|
+
for (const u of newlyEmitted) seen.add(u);
|
|
730
|
+
const file = cursorFile(parentSessionId, agentToolUseId);
|
|
731
|
+
await mkdir6(dirname6(file), { recursive: true });
|
|
732
|
+
const arr = [...seen];
|
|
733
|
+
const trimmed = arr.length > CURSOR_KEEP2 ? arr.slice(arr.length - CURSOR_KEEP2) : arr;
|
|
734
|
+
await writeFile6(file, trimmed.join("\n") + "\n");
|
|
735
|
+
}
|
|
736
|
+
|
|
477
737
|
// src/commands/push.ts
|
|
478
738
|
async function readStdin() {
|
|
479
739
|
if (process.stdin.isTTY) return "";
|
|
@@ -526,6 +786,113 @@ async function pushCommand(opts) {
|
|
|
526
786
|
return 0;
|
|
527
787
|
}
|
|
528
788
|
await appendEvent(sessionId, result.event);
|
|
789
|
+
if (opts.event === "PostToolUse" || opts.event === "Stop" || opts.event === "SubagentStop") {
|
|
790
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
791
|
+
if (transcriptPath !== "") {
|
|
792
|
+
try {
|
|
793
|
+
const { turns, seen } = await readNewAssistantTurns(sessionId, transcriptPath);
|
|
794
|
+
const newlyEmitted = [];
|
|
795
|
+
for (const t of turns) {
|
|
796
|
+
const seq = await nextSequence(sessionId);
|
|
797
|
+
const ev = {
|
|
798
|
+
type: "assistant_turn",
|
|
799
|
+
session_id: sessionId,
|
|
800
|
+
transcript_path: transcriptPath,
|
|
801
|
+
cwd: typeof payload.cwd === "string" ? payload.cwd : "",
|
|
802
|
+
hook_event_name: opts.event,
|
|
803
|
+
permission_mode: typeof payload.permission_mode === "string" ? payload.permission_mode : void 0,
|
|
804
|
+
wall_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
805
|
+
sequence: seq,
|
|
806
|
+
message_uuid: t.message_uuid,
|
|
807
|
+
model: t.model,
|
|
808
|
+
input_tokens: t.input_tokens,
|
|
809
|
+
output_tokens: t.output_tokens,
|
|
810
|
+
cache_read_tokens: t.cache_read_tokens,
|
|
811
|
+
cache_creation_tokens: t.cache_creation_tokens,
|
|
812
|
+
text: t.text
|
|
813
|
+
};
|
|
814
|
+
await appendEvent(sessionId, ev);
|
|
815
|
+
newlyEmitted.push(t.message_uuid);
|
|
816
|
+
}
|
|
817
|
+
if (newlyEmitted.length > 0) {
|
|
818
|
+
await persistCursor(sessionId, seen, newlyEmitted);
|
|
819
|
+
}
|
|
820
|
+
} catch (err) {
|
|
821
|
+
process.stderr.write(`runtape: transcript scan failed: ${err instanceof Error ? err.message : String(err)}
|
|
822
|
+
`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (opts.event === "SubagentStop") {
|
|
827
|
+
const subTranscript = typeof payload.agent_transcript_path === "string" ? payload.agent_transcript_path : "";
|
|
828
|
+
const agentToolUseId = typeof payload.agent_id === "string" ? payload.agent_id : "";
|
|
829
|
+
if (subTranscript !== "" && agentToolUseId !== "") {
|
|
830
|
+
try {
|
|
831
|
+
const { emits, seen } = await readNewSubagentEvents(sessionId, agentToolUseId, subTranscript);
|
|
832
|
+
const newlyEmitted = [];
|
|
833
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : "";
|
|
834
|
+
for (const e of emits) {
|
|
835
|
+
const seq = await nextSequence(sessionId);
|
|
836
|
+
const baseEnvelope = {
|
|
837
|
+
session_id: sessionId,
|
|
838
|
+
transcript_path: subTranscript,
|
|
839
|
+
cwd,
|
|
840
|
+
hook_event_name: opts.event,
|
|
841
|
+
permission_mode: typeof payload.permission_mode === "string" ? payload.permission_mode : void 0,
|
|
842
|
+
wall_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
843
|
+
sequence: seq,
|
|
844
|
+
agent_tool_use_id: agentToolUseId
|
|
845
|
+
};
|
|
846
|
+
let ev = null;
|
|
847
|
+
if (e.kind === "user_prompt") {
|
|
848
|
+
ev = { ...baseEnvelope, type: "user_prompt", prompt: e.prompt };
|
|
849
|
+
} else if (e.kind === "assistant_turn") {
|
|
850
|
+
ev = {
|
|
851
|
+
...baseEnvelope,
|
|
852
|
+
type: "assistant_turn",
|
|
853
|
+
message_uuid: e.message_uuid,
|
|
854
|
+
model: e.model,
|
|
855
|
+
input_tokens: e.input_tokens,
|
|
856
|
+
output_tokens: e.output_tokens,
|
|
857
|
+
cache_read_tokens: e.cache_read_tokens,
|
|
858
|
+
cache_creation_tokens: e.cache_creation_tokens,
|
|
859
|
+
text: e.text
|
|
860
|
+
};
|
|
861
|
+
} else if (e.kind === "tool_attempt") {
|
|
862
|
+
ev = {
|
|
863
|
+
...baseEnvelope,
|
|
864
|
+
type: "tool_attempt",
|
|
865
|
+
tool_name: e.tool_name,
|
|
866
|
+
tool_input: e.tool_input,
|
|
867
|
+
tool_use_id: e.tool_use_id
|
|
868
|
+
};
|
|
869
|
+
} else if (e.kind === "tool_call") {
|
|
870
|
+
ev = {
|
|
871
|
+
...baseEnvelope,
|
|
872
|
+
type: "tool_call",
|
|
873
|
+
tool_name: e.tool_name,
|
|
874
|
+
tool_input: null,
|
|
875
|
+
tool_response: e.tool_response,
|
|
876
|
+
tool_use_id: e.tool_use_id,
|
|
877
|
+
duration_ms: 0,
|
|
878
|
+
is_error: e.is_error,
|
|
879
|
+
error_message: e.error_message
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
if (ev) {
|
|
883
|
+
await appendEvent(sessionId, ev);
|
|
884
|
+
newlyEmitted.push(e.uuid);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (newlyEmitted.length > 0) {
|
|
888
|
+
await persistSubagentCursor(sessionId, agentToolUseId, seen, newlyEmitted);
|
|
889
|
+
}
|
|
890
|
+
} catch (err) {
|
|
891
|
+
process.stderr.write(`runtape: subagent transcript scan failed: ${err instanceof Error ? err.message : String(err)}
|
|
892
|
+
`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
529
896
|
spawnFlusher(resolveCliBinPath());
|
|
530
897
|
return 0;
|
|
531
898
|
} catch (err) {
|
|
@@ -536,10 +903,10 @@ async function pushCommand(opts) {
|
|
|
536
903
|
}
|
|
537
904
|
|
|
538
905
|
// src/commands/status.ts
|
|
539
|
-
import { readFile as
|
|
906
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
540
907
|
async function readFlusherPid() {
|
|
541
908
|
try {
|
|
542
|
-
const raw = await
|
|
909
|
+
const raw = await readFile7(paths.flusherPid, "utf8");
|
|
543
910
|
const n = Number.parseInt(raw.trim(), 10);
|
|
544
911
|
return Number.isFinite(n) ? n : null;
|
|
545
912
|
} catch (err) {
|
|
@@ -639,7 +1006,8 @@ async function setupCommand(opts) {
|
|
|
639
1006
|
const rl = createInterface3({ input: input3, output: output3 });
|
|
640
1007
|
try {
|
|
641
1008
|
process.stdout.write("\nRuntape setup\n");
|
|
642
|
-
process.stdout.write("Let's get your Claude Code runs captured.\n
|
|
1009
|
+
process.stdout.write("Let's get your Claude Code runs captured.\n");
|
|
1010
|
+
process.stdout.write("(Press Enter at any prompt to accept the default shown in [brackets].)\n\n");
|
|
643
1011
|
const existing = await readConfig();
|
|
644
1012
|
if (existing) {
|
|
645
1013
|
process.stdout.write(`Already logged in to ${existing.server_url}
|
|
@@ -655,7 +1023,7 @@ async function setupCommand(opts) {
|
|
|
655
1023
|
const suggestedUrl = defaultServerUrl();
|
|
656
1024
|
process.stdout.write(`Step 1/3 \u2014 Backend
|
|
657
1025
|
`);
|
|
658
|
-
const urlInput = (await rl.question(`Server URL [${suggestedUrl}]: `)).trim();
|
|
1026
|
+
const urlInput = (await rl.question(`Server URL [${suggestedUrl}] (Enter to use this): `)).trim();
|
|
659
1027
|
const serverUrl = urlInput === "" ? suggestedUrl : urlInput;
|
|
660
1028
|
const dashboardUrl = `${serverUrl.replace(/\/$/, "")}/dashboard`;
|
|
661
1029
|
process.stdout.write(`
|
|
@@ -726,15 +1094,15 @@ Step 3/3 \u2014 Claude Code hooks
|
|
|
726
1094
|
}
|
|
727
1095
|
|
|
728
1096
|
// src/lib/flusher.ts
|
|
729
|
-
import { appendFile as appendFile2, mkdir as
|
|
730
|
-
import { dirname as
|
|
1097
|
+
import { appendFile as appendFile2, mkdir as mkdir7, readFile as readFile8, unlink as unlink3, writeFile as writeFile7 } from "fs/promises";
|
|
1098
|
+
import { dirname as dirname7 } from "path";
|
|
731
1099
|
var POLL_INTERVAL_MS = 1500;
|
|
732
1100
|
var IDLE_EXIT_MS = 3e4;
|
|
733
1101
|
var BATCH_MAX = 100;
|
|
734
1102
|
var BACKOFF_STEPS_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
|
|
735
1103
|
async function log(line) {
|
|
736
1104
|
try {
|
|
737
|
-
await
|
|
1105
|
+
await mkdir7(dirname7(paths.flusherLog), { recursive: true });
|
|
738
1106
|
await appendFile2(paths.flusherLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
739
1107
|
`);
|
|
740
1108
|
} catch {
|
|
@@ -751,9 +1119,9 @@ async function isProcessAlive(pid) {
|
|
|
751
1119
|
}
|
|
752
1120
|
}
|
|
753
1121
|
async function acquirePidLock() {
|
|
754
|
-
await
|
|
1122
|
+
await mkdir7(dirname7(paths.flusherPid), { recursive: true });
|
|
755
1123
|
try {
|
|
756
|
-
const existing = await
|
|
1124
|
+
const existing = await readFile8(paths.flusherPid, "utf8");
|
|
757
1125
|
const pid = Number.parseInt(existing.trim(), 10);
|
|
758
1126
|
if (Number.isFinite(pid) && await isProcessAlive(pid)) {
|
|
759
1127
|
return false;
|
|
@@ -761,7 +1129,7 @@ async function acquirePidLock() {
|
|
|
761
1129
|
} catch (err) {
|
|
762
1130
|
if (err.code !== "ENOENT") throw err;
|
|
763
1131
|
}
|
|
764
|
-
await
|
|
1132
|
+
await writeFile7(paths.flusherPid, String(process.pid));
|
|
765
1133
|
return true;
|
|
766
1134
|
}
|
|
767
1135
|
async function releasePidLock() {
|
|
@@ -846,7 +1214,7 @@ async function runFlusher() {
|
|
|
846
1214
|
}
|
|
847
1215
|
|
|
848
1216
|
// src/index.ts
|
|
849
|
-
var pkgPath = join2(
|
|
1217
|
+
var pkgPath = join2(dirname8(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
850
1218
|
var PKG_VERSION = JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
851
1219
|
if (process.argv.includes("--internal-flusher")) {
|
|
852
1220
|
void runFlusher().then(
|