pi-remote-control 1.0.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 +46 -0
- package/docs/adr/0001-package-extension-as-control-shim.md +19 -0
- package/docs/adr/0002-use-sqlite-for-daemon-state.md +19 -0
- package/docs/adr/0003-use-lock-file-as-process-state.md +19 -0
- package/docs/adr/0004-allow-loopback-pair-code-without-token.md +19 -0
- package/docs/adr/0005-defer-os-service-installation.md +19 -0
- package/docs/adr/0006-use-tui-activated-remote-control-sessions.md +24 -0
- package/docs/adr/0007-require-tui-originated-pairing.md +19 -0
- package/docs/adr/0008-use-qr-pairing-links.md +21 -0
- package/docs/adr/0009-rename-package-to-remote-control.md +19 -0
- package/docs/adr/0010-clean-stale-lock-on-status.md +19 -0
- package/docs/adr/0011-use-loopback-tui-control.md +19 -0
- package/docs/adr/0012-use-paginated-session-transcript-loading.md +37 -0
- package/docs/adr/0013-require-manual-reactivation-after-tui-entry.md +31 -0
- package/docs/adr/0014-read-transcripts-from-session-files.md +33 -0
- package/docs/adr/0015-normalize-transcript-messages-and-stream-events.md +35 -0
- package/docs/adr/0016-expose-turn-lifecycle-events.md +31 -0
- package/docs/adr/0017-bound-initial-websocket-session-state.md +31 -0
- package/docs/adr/0018-reregister-active-tui-session-on-heartbeat-miss.md +33 -0
- package/docs/adr/0019-display-only-pairing-qr-and-expiry.md +25 -0
- package/docs/adr/0020-expose-session-status-snapshots.md +31 -0
- package/docs/adr/0021-support-remote-compact-action.md +31 -0
- package/docs/adr/0022-rename-session-status-to-runtime-status.md +27 -0
- package/docs/adr/0023-return-remote-compact-results.md +29 -0
- package/docs/architecture.md +96 -0
- package/docs/data-model.md +284 -0
- package/docs/interfaces.md +470 -0
- package/package.json +37 -0
- package/scripts/http-smoke-test.sh +100 -0
- package/src/active-session-registry.ts +205 -0
- package/src/auth/pairing.ts +30 -0
- package/src/auth/tokens.ts +30 -0
- package/src/cli-runner.cjs +15 -0
- package/src/cli.ts +254 -0
- package/src/config.ts +26 -0
- package/src/extension/index.ts +422 -0
- package/src/index.ts +16 -0
- package/src/lock.ts +26 -0
- package/src/pairing-link.ts +15 -0
- package/src/paths.ts +21 -0
- package/src/persistence/daemon-store.ts +56 -0
- package/src/persistence/schema.ts +21 -0
- package/src/qr.ts +23 -0
- package/src/runtime-status.ts +116 -0
- package/src/server/http.ts +529 -0
- package/src/session-index.ts +9 -0
- package/src/session-transcript.ts +34 -0
- package/src/transcript-message.ts +76 -0
- package/src/transcript-pagination.ts +68 -0
- package/src/transcript-preview.ts +102 -0
- package/src/transcript-stream.ts +89 -0
- package/src/types.ts +116 -0
- package/tests/active-session-registry.test.ts +170 -0
- package/tests/auth.test.ts +18 -0
- package/tests/cli.test.ts +361 -0
- package/tests/config.test.ts +35 -0
- package/tests/daemon-store.test.ts +54 -0
- package/tests/extension.test.ts +617 -0
- package/tests/lock.test.ts +36 -0
- package/tests/pairing-link.test.ts +26 -0
- package/tests/pairing.test.ts +26 -0
- package/tests/paths.test.ts +29 -0
- package/tests/qr.test.ts +25 -0
- package/tests/schema.test.ts +18 -0
- package/tests/server-http.test.ts +932 -0
- package/tests/session-index.test.ts +10 -0
- package/tests/session-transcript.test.ts +75 -0
- package/tests/transcript-pagination.test.ts +54 -0
- package/tests/transcript-preview.test.ts +64 -0
- package/tests/transcript-stream.test.ts +103 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { projectIdForPath } from "../src/session-index.js";
|
|
3
|
+
|
|
4
|
+
describe("session identifiers", () => {
|
|
5
|
+
it("creates stable project ids from paths", () => {
|
|
6
|
+
expect(projectIdForPath("/repo/example")).toBe(projectIdForPath("/repo/example"));
|
|
7
|
+
expect(projectIdForPath("/repo/example")).toMatch(/^proj_[0-9a-f]{16}$/);
|
|
8
|
+
expect(projectIdForPath("/repo/example")).not.toBe(projectIdForPath("/repo/other"));
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { readSessionTranscriptMessages } from "../src/session-transcript.js";
|
|
6
|
+
|
|
7
|
+
let root: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
root = await mkdtemp(join(tmpdir(), "pi-remote-control-transcript-"));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await rm(root, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("session transcript files", () => {
|
|
18
|
+
it("reads message entries from Pi JSONL session files", async () => {
|
|
19
|
+
const sessionFile = join(root, "session.jsonl");
|
|
20
|
+
await writeFile(sessionFile, [
|
|
21
|
+
JSON.stringify({ type: "message", id: "msg_1", timestamp: "2026-05-09T00:00:01.000Z", message: { role: "user", content: "hello" } }),
|
|
22
|
+
JSON.stringify({ type: "tool_execution_start", toolCallId: "call_1" }),
|
|
23
|
+
JSON.stringify({ type: "message", id: "msg_2", timestamp: "2026-05-09T00:00:02.000Z", message: { role: "assistant", content: [{ type: "text", text: "hi" }] } }),
|
|
24
|
+
"not-json",
|
|
25
|
+
"",
|
|
26
|
+
].join("\n"));
|
|
27
|
+
|
|
28
|
+
expect(readSessionTranscriptMessages(sessionFile)).toEqual([
|
|
29
|
+
{ id: "msg_1", role: "user", content: [{ type: "text", text: "hello" }], text: "hello", createdAt: "2026-05-09T00:00:01.000Z", isStreaming: false },
|
|
30
|
+
{ id: "msg_2", role: "assistant", content: [{ type: "text", text: "hi" }], text: "hi", createdAt: "2026-05-09T00:00:02.000Z", isStreaming: false },
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("preserves thinking, tool calls, and tool result metadata", async () => {
|
|
35
|
+
const sessionFile = join(root, "structured-session.jsonl");
|
|
36
|
+
await writeFile(sessionFile, [
|
|
37
|
+
JSON.stringify({ type: "message", id: "msg_1", timestamp: "2026-05-09T00:00:01.000Z", message: { role: "assistant", content: [
|
|
38
|
+
{ type: "thinking", thinking: "checking" },
|
|
39
|
+
{ type: "toolCall", id: "call_1", name: "bash", arguments: { command: "ls" } },
|
|
40
|
+
{ type: "text", text: "done" },
|
|
41
|
+
] } }),
|
|
42
|
+
JSON.stringify({ type: "message", id: "msg_2", timestamp: "2026-05-09T00:00:02.000Z", message: { role: "toolResult", toolCallId: "call_1", toolName: "bash", isError: false, content: [{ type: "text", text: "file.txt" }] } }),
|
|
43
|
+
].join("\n"));
|
|
44
|
+
|
|
45
|
+
expect(readSessionTranscriptMessages(sessionFile)).toEqual([
|
|
46
|
+
{
|
|
47
|
+
id: "msg_1",
|
|
48
|
+
role: "assistant",
|
|
49
|
+
content: [
|
|
50
|
+
{ type: "thinking", thinking: "checking" },
|
|
51
|
+
{ type: "toolCall", id: "call_1", name: "bash", arguments: { command: "ls" } },
|
|
52
|
+
{ type: "text", text: "done" },
|
|
53
|
+
],
|
|
54
|
+
text: "done",
|
|
55
|
+
createdAt: "2026-05-09T00:00:01.000Z",
|
|
56
|
+
isStreaming: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "msg_2",
|
|
60
|
+
role: "toolResult",
|
|
61
|
+
content: [{ type: "text", text: "file.txt" }],
|
|
62
|
+
text: "file.txt",
|
|
63
|
+
createdAt: "2026-05-09T00:00:02.000Z",
|
|
64
|
+
toolCallId: "call_1",
|
|
65
|
+
toolName: "bash",
|
|
66
|
+
isError: false,
|
|
67
|
+
isStreaming: false,
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns an empty transcript when the session file is missing", () => {
|
|
73
|
+
expect(readSessionTranscriptMessages(join(root, "missing.jsonl"))).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { TranscriptMessage } from "../src/types.js";
|
|
3
|
+
import { decodeTranscriptCursor, encodeTranscriptCursor, olderTranscriptPage, recentTranscriptWindow } from "../src/transcript-pagination.js";
|
|
4
|
+
|
|
5
|
+
function message(id: string, createdAt: string): TranscriptMessage {
|
|
6
|
+
return { id, role: "user", content: [{ type: "text", text: id }], text: id, createdAt, isStreaming: false };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("transcript pagination", () => {
|
|
10
|
+
const messages = [
|
|
11
|
+
message("msg_1", "2026-05-09T00:00:01.000Z"),
|
|
12
|
+
message("msg_2", "2026-05-09T00:00:02.000Z"),
|
|
13
|
+
message("msg_2", "2026-05-09T00:00:02.000Z"),
|
|
14
|
+
message("msg_3", "2026-05-09T00:00:03.000Z"),
|
|
15
|
+
message("msg_4", "2026-05-09T00:00:04.000Z"),
|
|
16
|
+
message("msg_5", "2026-05-09T00:00:05.000Z"),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
it("returns a bounded recent window with a timestamp-only older cursor", () => {
|
|
20
|
+
const page = recentTranscriptWindow(messages, 2);
|
|
21
|
+
|
|
22
|
+
expect(page.messages.map((item) => item.id)).toEqual(["msg_4", "msg_5"]);
|
|
23
|
+
expect(page.hasOlderMessages).toBe(true);
|
|
24
|
+
expect(decodeTranscriptCursor(page.olderMessagesCursor ?? "")).toBe("2026-05-09T00:00:04.000Z");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns older pages before an exclusive timestamp cursor", () => {
|
|
28
|
+
const first = recentTranscriptWindow(messages, 2);
|
|
29
|
+
const older = olderTranscriptPage(messages, first.olderMessagesCursor ?? "", 2);
|
|
30
|
+
const oldest = olderTranscriptPage(messages, older.olderMessagesCursor ?? "", 2);
|
|
31
|
+
|
|
32
|
+
expect(older.messages.map((item) => item.id)).toEqual(["msg_2", "msg_3"]);
|
|
33
|
+
expect(older.hasOlderMessages).toBe(true);
|
|
34
|
+
expect(decodeTranscriptCursor(older.olderMessagesCursor ?? "")).toBe("2026-05-09T00:00:02.000Z");
|
|
35
|
+
expect(oldest.messages.map((item) => item.id)).toEqual(["msg_1"]);
|
|
36
|
+
expect(oldest.hasOlderMessages).toBe(false);
|
|
37
|
+
expect(oldest.olderMessagesCursor).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("rejects malformed cursors", () => {
|
|
41
|
+
expect(() => olderTranscriptPage(messages, "not-a-cursor", 2)).toThrow("invalid_cursor");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("rejects timestamp cursors that do not match the transcript", () => {
|
|
45
|
+
expect(() => olderTranscriptPage(messages, encodeTranscriptCursor("2026-05-09T00:00:30.000Z"), 2)).toThrow("invalid_cursor");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("encodes cursors without message ids", () => {
|
|
49
|
+
const cursor = encodeTranscriptCursor("2026-05-09T00:00:04.000Z");
|
|
50
|
+
|
|
51
|
+
expect(decodeTranscriptCursor(cursor)).toBe("2026-05-09T00:00:04.000Z");
|
|
52
|
+
expect(Buffer.from(cursor, "base64url").toString("utf8")).toBe("2026-05-09T00:00:04.000Z");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { INITIAL_WEBSOCKET_SESSION_MESSAGE_LIMIT, INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES, previewInitialSessionState } from "../src/transcript-preview.js";
|
|
3
|
+
import type { TranscriptMessage } from "../src/types.js";
|
|
4
|
+
|
|
5
|
+
function message(id: string, text = id): TranscriptMessage {
|
|
6
|
+
return {
|
|
7
|
+
id,
|
|
8
|
+
role: "assistant",
|
|
9
|
+
content: [{ type: "text", text }],
|
|
10
|
+
text,
|
|
11
|
+
createdAt: `2026-05-09T00:00:${id.padStart(2, "0")}.000Z`,
|
|
12
|
+
isStreaming: false,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("initial WebSocket session state preview", () => {
|
|
17
|
+
it("keeps at most the newest 20 messages", () => {
|
|
18
|
+
const state = { messages: Array.from({ length: 25 }, (_, index) => message(String(index + 1))) };
|
|
19
|
+
|
|
20
|
+
expect(previewInitialSessionState(state).messages.map((item) => item.id)).toEqual(
|
|
21
|
+
Array.from({ length: INITIAL_WEBSOCKET_SESSION_MESSAGE_LIMIT }, (_, index) => String(index + 6)),
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("truncates oversized transcript message strings to the first 10 KiB", () => {
|
|
26
|
+
const oversized = "a".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES + 5);
|
|
27
|
+
const state = { messages: [{ ...message("1", oversized), content: [{ type: "text", text: oversized }, { type: "thinking", thinking: oversized }] }] };
|
|
28
|
+
|
|
29
|
+
expect(previewInitialSessionState(state).messages[0]).toEqual({
|
|
30
|
+
id: "1",
|
|
31
|
+
role: "assistant",
|
|
32
|
+
content: [
|
|
33
|
+
{ type: "text", text: "a".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES), truncated: true, originalBytes: INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES + 5 },
|
|
34
|
+
{ type: "thinking", thinking: "a".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES), truncated: true, originalBytes: INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES + 5 },
|
|
35
|
+
],
|
|
36
|
+
text: "a".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES),
|
|
37
|
+
textTruncated: true,
|
|
38
|
+
textOriginalBytes: INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES + 5,
|
|
39
|
+
createdAt: "2026-05-09T00:00:01.000Z",
|
|
40
|
+
isStreaming: false,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("truncates oversized tool call argument strings and marks argument previews", () => {
|
|
45
|
+
const oversized = "x".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES + 1);
|
|
46
|
+
const state = {
|
|
47
|
+
messages: [{
|
|
48
|
+
...message("1", ""),
|
|
49
|
+
content: [{ type: "toolCall", id: "call_1", name: "write", arguments: { path: "file.ts", content: oversized } }],
|
|
50
|
+
}],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = previewInitialSessionState(state).messages[0]?.content[0];
|
|
54
|
+
|
|
55
|
+
expect(result).toEqual({
|
|
56
|
+
type: "toolCall",
|
|
57
|
+
id: "call_1",
|
|
58
|
+
name: "write",
|
|
59
|
+
arguments: { path: "file.ts", content: "x".repeat(INITIAL_WEBSOCKET_STRING_PREVIEW_BYTES) },
|
|
60
|
+
argumentsTruncated: true,
|
|
61
|
+
argumentsOriginalBytes: Buffer.byteLength(JSON.stringify({ path: "file.ts", content: oversized }), "utf8"),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { normalizeTuiEvent } from "../src/transcript-stream.js";
|
|
3
|
+
|
|
4
|
+
describe("TUI event transcript stream normalization", () => {
|
|
5
|
+
it("normalizes message lifecycle events to transcript message events", () => {
|
|
6
|
+
expect(normalizeTuiEvent({
|
|
7
|
+
type: "message_start",
|
|
8
|
+
message: { id: "msg_1", role: "assistant", timestamp: 1778284801000, content: [] },
|
|
9
|
+
})).toEqual([{
|
|
10
|
+
type: "transcript_message_start",
|
|
11
|
+
message: {
|
|
12
|
+
id: "msg_1",
|
|
13
|
+
role: "assistant",
|
|
14
|
+
content: [],
|
|
15
|
+
text: "",
|
|
16
|
+
createdAt: "2026-05-09T00:00:01.000Z",
|
|
17
|
+
isStreaming: true,
|
|
18
|
+
},
|
|
19
|
+
}]);
|
|
20
|
+
|
|
21
|
+
expect(normalizeTuiEvent({
|
|
22
|
+
type: "message_end",
|
|
23
|
+
message: { id: "msg_1", role: "assistant", timestamp: 1778284802000, content: [{ type: "text", text: "done" }] },
|
|
24
|
+
})).toEqual([{
|
|
25
|
+
type: "transcript_message_end",
|
|
26
|
+
message: {
|
|
27
|
+
id: "msg_1",
|
|
28
|
+
role: "assistant",
|
|
29
|
+
content: [{ type: "text", text: "done" }],
|
|
30
|
+
text: "done",
|
|
31
|
+
createdAt: "2026-05-09T00:00:02.000Z",
|
|
32
|
+
isStreaming: false,
|
|
33
|
+
},
|
|
34
|
+
}]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("does not emit message_start for non-streaming user messages", () => {
|
|
38
|
+
expect(normalizeTuiEvent({
|
|
39
|
+
type: "message_start",
|
|
40
|
+
message: { id: "msg_user_1", role: "user", timestamp: 1778284801000, content: "hello" },
|
|
41
|
+
})).toEqual([]);
|
|
42
|
+
|
|
43
|
+
expect(normalizeTuiEvent({
|
|
44
|
+
type: "message_end",
|
|
45
|
+
message: { id: "msg_user_1", role: "user", timestamp: 1778284801000, content: "hello" },
|
|
46
|
+
})).toEqual([{
|
|
47
|
+
type: "transcript_message_end",
|
|
48
|
+
message: {
|
|
49
|
+
id: "msg_user_1",
|
|
50
|
+
role: "user",
|
|
51
|
+
content: [{ type: "text", text: "hello" }],
|
|
52
|
+
text: "hello",
|
|
53
|
+
createdAt: "2026-05-09T00:00:01.000Z",
|
|
54
|
+
isStreaming: false,
|
|
55
|
+
},
|
|
56
|
+
}]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("normalizes assistant message deltas to transcript message patches", () => {
|
|
60
|
+
expect(normalizeTuiEvent({
|
|
61
|
+
type: "message_update",
|
|
62
|
+
message: { id: "msg_1", role: "assistant" },
|
|
63
|
+
assistantMessageEvent: { type: "thinking_delta", contentIndex: 0, delta: "checking" },
|
|
64
|
+
})).toEqual([{ type: "transcript_message_patch", messageId: "msg_1", contentIndex: 0, patch: { type: "thinking_delta", delta: "checking" } }]);
|
|
65
|
+
|
|
66
|
+
expect(normalizeTuiEvent({
|
|
67
|
+
type: "message_update",
|
|
68
|
+
message: { id: "msg_1", role: "assistant" },
|
|
69
|
+
assistantMessageEvent: { type: "text_delta", contentIndex: 1, delta: "hello" },
|
|
70
|
+
})).toEqual([{ type: "transcript_message_patch", messageId: "msg_1", contentIndex: 1, patch: { type: "text_delta", delta: "hello" } }]);
|
|
71
|
+
|
|
72
|
+
expect(normalizeTuiEvent({
|
|
73
|
+
type: "message_update",
|
|
74
|
+
message: { id: "msg_1", role: "assistant" },
|
|
75
|
+
assistantMessageEvent: { type: "toolcall_end", contentIndex: 2, toolCall: { type: "toolCall", id: "call_1", name: "bash", arguments: { command: "ls" } } },
|
|
76
|
+
})).toEqual([{ type: "transcript_message_patch", messageId: "msg_1", contentIndex: 2, patch: { type: "toolCall", toolCall: { type: "toolCall", id: "call_1", name: "bash", arguments: { command: "ls" } } } }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("normalizes turn lifecycle events", () => {
|
|
80
|
+
expect(normalizeTuiEvent({ type: "turn_start", turnIndex: 2, timestamp: 1778284801000 })).toEqual([
|
|
81
|
+
{ type: "turn_start", turnIndex: 2, createdAt: "2026-05-09T00:00:01.000Z" },
|
|
82
|
+
]);
|
|
83
|
+
expect(normalizeTuiEvent({ type: "turn_end", turnIndex: 2, message: { role: "assistant", content: [] }, toolResults: [] })).toEqual([
|
|
84
|
+
{ type: "turn_end", turnIndex: 2 },
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("normalizes tool execution events", () => {
|
|
89
|
+
expect(normalizeTuiEvent({ type: "tool_execution_start", toolCallId: "call_1", toolName: "bash", args: { command: "ls" } })).toEqual([
|
|
90
|
+
{ type: "tool_execution_start", toolCallId: "call_1", toolName: "bash", args: { command: "ls" } },
|
|
91
|
+
]);
|
|
92
|
+
expect(normalizeTuiEvent({ type: "tool_execution_update", toolCallId: "call_1", toolName: "bash", partialResult: { content: [] } })).toEqual([
|
|
93
|
+
{ type: "tool_execution_update", toolCallId: "call_1", toolName: "bash", partialResult: { content: [] } },
|
|
94
|
+
]);
|
|
95
|
+
expect(normalizeTuiEvent({ type: "tool_execution_end", toolCallId: "call_1", toolName: "bash", result: { content: [] }, isError: false })).toEqual([
|
|
96
|
+
{ type: "tool_execution_end", toolCallId: "call_1", toolName: "bash", result: { content: [] }, isError: false },
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("ignores events without public stream representation", () => {
|
|
101
|
+
expect(normalizeTuiEvent({ type: "agent_start" })).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"],
|
|
16
|
+
"exclude": ["dist", "node_modules", "tests"]
|
|
17
|
+
}
|