@stevezhou/sisu 0.3.5 → 0.3.6
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 +1 -1
- package/dist/runtime/transcriptEvents.js +195 -0
- package/dist/tui.js +18 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ sisu login
|
|
|
13
13
|
sisu
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`npm install -g` is a small JS package. postinstall fetches the stamped SiSu TUI pager for **this package version** into `~/.sisu/bin` when a prebuilt exists
|
|
16
|
+
`npm install -g` is a small JS package. postinstall fetches the stamped SiSu TUI pager for **this package version** into `~/.sisu/bin` when a prebuilt exists. GitHub Release tags ship `darwin-arm64`, `linux-x64`, and `linux-arm64`. `darwin-x64` is opt-in (`workflow_dispatch` with `platforms` containing `darwin-x64`) and often missing; platforms without a binary, or a missing GitHub Release asset, keep the Node TUI.
|
|
17
17
|
|
|
18
18
|
Requires Node.js 20 or newer. `npx sisu` works without a global install.
|
|
19
19
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startCompactionCheckpointWatch = void 0;
|
|
7
|
+
exports.transcriptEventFromCheckpoint = transcriptEventFromCheckpoint;
|
|
8
|
+
exports.transcriptEventFromToolLog = transcriptEventFromToolLog;
|
|
9
|
+
exports.listCompactionCheckpointFiles = listCompactionCheckpointFiles;
|
|
10
|
+
exports.rememberExistingCheckpoints = rememberExistingCheckpoints;
|
|
11
|
+
exports.listTerminalLogFiles = listTerminalLogFiles;
|
|
12
|
+
exports.rememberExistingTerminalLogs = rememberExistingTerminalLogs;
|
|
13
|
+
exports.flushNewTerminalLogs = flushNewTerminalLogs;
|
|
14
|
+
exports.postTranscriptEvent = postTranscriptEvent;
|
|
15
|
+
exports.flushNewCompactionCheckpoints = flushNewCompactionCheckpoints;
|
|
16
|
+
exports.startTranscriptWatch = startTranscriptWatch;
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const http_1 = require("../http");
|
|
20
|
+
function transcriptEventFromCheckpoint(raw, conversationId, fallbackId) {
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(parsed.compacted_history))
|
|
29
|
+
return null;
|
|
30
|
+
const checkpointId = String(parsed.checkpoint_id || '').trim() || String(fallbackId || '').trim();
|
|
31
|
+
if (!checkpointId)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
kind: 'compaction',
|
|
35
|
+
conversation_id: conversationId,
|
|
36
|
+
client_request_id: checkpointId,
|
|
37
|
+
messages: parsed.compacted_history,
|
|
38
|
+
payload: {
|
|
39
|
+
checkpoint_id: checkpointId,
|
|
40
|
+
schema_version: parsed.schema_version,
|
|
41
|
+
prompt_index_at_compaction: parsed.prompt_index_at_compaction,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function transcriptEventFromToolLog(content, conversationId, toolCallId) {
|
|
46
|
+
return {
|
|
47
|
+
kind: 'tool_result_full',
|
|
48
|
+
conversation_id: conversationId,
|
|
49
|
+
client_request_id: toolCallId,
|
|
50
|
+
payload: { tool_call_id: toolCallId, content },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function listCompactionCheckpointFiles(engineHome) {
|
|
54
|
+
const sessions = path_1.default.join(engineHome, 'sessions');
|
|
55
|
+
if (!fs_1.default.existsSync(sessions))
|
|
56
|
+
return [];
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const sessionName of fs_1.default.readdirSync(sessions)) {
|
|
59
|
+
const dir = path_1.default.join(sessions, sessionName, 'compaction_checkpoints');
|
|
60
|
+
if (!fs_1.default.existsSync(dir) || !fs_1.default.statSync(dir).isDirectory())
|
|
61
|
+
continue;
|
|
62
|
+
for (const name of fs_1.default.readdirSync(dir)) {
|
|
63
|
+
if (name.endsWith('.json'))
|
|
64
|
+
out.push(path_1.default.join(dir, name));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out.sort();
|
|
68
|
+
}
|
|
69
|
+
function rememberExistingCheckpoints(engineHome, posted) {
|
|
70
|
+
for (const file of listCompactionCheckpointFiles(engineHome))
|
|
71
|
+
posted.add(file);
|
|
72
|
+
}
|
|
73
|
+
function listTerminalLogFiles(engineHome) {
|
|
74
|
+
const sessions = path_1.default.join(engineHome, 'sessions');
|
|
75
|
+
if (!fs_1.default.existsSync(sessions))
|
|
76
|
+
return [];
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const sessionName of fs_1.default.readdirSync(sessions)) {
|
|
79
|
+
const dir = path_1.default.join(sessions, sessionName, 'terminal');
|
|
80
|
+
if (!fs_1.default.existsSync(dir) || !fs_1.default.statSync(dir).isDirectory())
|
|
81
|
+
continue;
|
|
82
|
+
for (const name of fs_1.default.readdirSync(dir)) {
|
|
83
|
+
if (name.endsWith('.log'))
|
|
84
|
+
out.push(path_1.default.join(dir, name));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out.sort();
|
|
88
|
+
}
|
|
89
|
+
function rememberExistingTerminalLogs(engineHome, posted) {
|
|
90
|
+
for (const file of listTerminalLogFiles(engineHome))
|
|
91
|
+
posted.add(file);
|
|
92
|
+
}
|
|
93
|
+
const TOOL_LOG_MAX_BYTES = 8 * 1024 * 1024;
|
|
94
|
+
function readToolLog(file) {
|
|
95
|
+
const size = fs_1.default.statSync(file).size;
|
|
96
|
+
if (size <= 0)
|
|
97
|
+
return '';
|
|
98
|
+
if (size <= TOOL_LOG_MAX_BYTES)
|
|
99
|
+
return fs_1.default.readFileSync(file, 'utf8');
|
|
100
|
+
const fd = fs_1.default.openSync(file, 'r');
|
|
101
|
+
try {
|
|
102
|
+
const buf = Buffer.alloc(TOOL_LOG_MAX_BYTES);
|
|
103
|
+
fs_1.default.readSync(fd, buf, 0, TOOL_LOG_MAX_BYTES, 0);
|
|
104
|
+
return buf.toString('utf8');
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
fs_1.default.closeSync(fd);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function flushNewTerminalLogs(options) {
|
|
111
|
+
let sent = 0;
|
|
112
|
+
for (const file of listTerminalLogFiles(options.engineHome)) {
|
|
113
|
+
if (options.posted.has(file))
|
|
114
|
+
continue;
|
|
115
|
+
let content = '';
|
|
116
|
+
try {
|
|
117
|
+
content = readToolLog(file);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!content)
|
|
123
|
+
continue;
|
|
124
|
+
const event = transcriptEventFromToolLog(content, options.conversationId, path_1.default.parse(file).name);
|
|
125
|
+
const ok = await options.post(event);
|
|
126
|
+
if (!ok)
|
|
127
|
+
continue;
|
|
128
|
+
options.posted.add(file);
|
|
129
|
+
sent += 1;
|
|
130
|
+
}
|
|
131
|
+
return sent;
|
|
132
|
+
}
|
|
133
|
+
async function postTranscriptEvent(http, apiBase, token, event) {
|
|
134
|
+
const base = apiBase.replace(/\/+$/, '');
|
|
135
|
+
const headers = { ...(0, http_1.authHeaders)(token) };
|
|
136
|
+
if (event.conversation_id)
|
|
137
|
+
headers['x-sisu-conversation-id'] = event.conversation_id;
|
|
138
|
+
const response = await http(`${base}/api/runtime/v1/transcript/events`, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers,
|
|
141
|
+
body: JSON.stringify(event),
|
|
142
|
+
});
|
|
143
|
+
return Boolean(response?.ok);
|
|
144
|
+
}
|
|
145
|
+
async function flushNewCompactionCheckpoints(options) {
|
|
146
|
+
let sent = 0;
|
|
147
|
+
for (const file of listCompactionCheckpointFiles(options.engineHome)) {
|
|
148
|
+
if (options.posted.has(file))
|
|
149
|
+
continue;
|
|
150
|
+
let raw = '';
|
|
151
|
+
try {
|
|
152
|
+
raw = fs_1.default.readFileSync(file, 'utf8');
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const event = transcriptEventFromCheckpoint(raw, options.conversationId, path_1.default.parse(file).name);
|
|
158
|
+
if (!event)
|
|
159
|
+
continue;
|
|
160
|
+
const ok = await options.post(event);
|
|
161
|
+
if (!ok)
|
|
162
|
+
continue;
|
|
163
|
+
options.posted.add(file);
|
|
164
|
+
sent += 1;
|
|
165
|
+
}
|
|
166
|
+
return sent;
|
|
167
|
+
}
|
|
168
|
+
function startTranscriptWatch(options) {
|
|
169
|
+
const posted = new Set();
|
|
170
|
+
rememberExistingCheckpoints(options.engineHome, posted);
|
|
171
|
+
rememberExistingTerminalLogs(options.engineHome, posted);
|
|
172
|
+
const tick = () => Promise.all([
|
|
173
|
+
flushNewCompactionCheckpoints({
|
|
174
|
+
engineHome: options.engineHome,
|
|
175
|
+
conversationId: options.conversationId,
|
|
176
|
+
posted,
|
|
177
|
+
post: options.post,
|
|
178
|
+
}),
|
|
179
|
+
flushNewTerminalLogs({
|
|
180
|
+
engineHome: options.engineHome,
|
|
181
|
+
conversationId: options.conversationId,
|
|
182
|
+
posted,
|
|
183
|
+
post: options.post,
|
|
184
|
+
}),
|
|
185
|
+
]).catch(() => 0);
|
|
186
|
+
const timer = setInterval(() => {
|
|
187
|
+
void tick();
|
|
188
|
+
}, options.intervalMs ?? 2000);
|
|
189
|
+
void tick();
|
|
190
|
+
return async () => {
|
|
191
|
+
clearInterval(timer);
|
|
192
|
+
await tick();
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
exports.startCompactionCheckpointWatch = startTranscriptWatch;
|
package/dist/tui.js
CHANGED
|
@@ -20,6 +20,7 @@ const stdio_1 = require("./pager/stdio");
|
|
|
20
20
|
const store_1 = require("./store");
|
|
21
21
|
const launch_1 = require("./runtime/launch");
|
|
22
22
|
const transport_1 = require("./runtime/transport");
|
|
23
|
+
const transcriptEvents_1 = require("./runtime/transcriptEvents");
|
|
23
24
|
const child_process_1 = require("child_process");
|
|
24
25
|
/** Pager exits with this code so the host runs `sisu login` and respawns. */
|
|
25
26
|
exports.SISU_LOGIN_EXIT_CODE = 10;
|
|
@@ -273,14 +274,28 @@ async function runTui(io, deps = {}) {
|
|
|
273
274
|
(0, launch_1.purgeChangelogCache)(home, engine);
|
|
274
275
|
(0, launch_1.writeSisuGrokConfig)();
|
|
275
276
|
io.close?.();
|
|
277
|
+
const env = (0, launch_1.sisuGrokBuildEnv)();
|
|
278
|
+
const stopWatch = (0, transcriptEvents_1.startTranscriptWatch)({
|
|
279
|
+
engineHome: engine,
|
|
280
|
+
conversationId: String(env.SISU_CONVERSATION_ID || ''),
|
|
281
|
+
post: async (event) => {
|
|
282
|
+
const current = auth();
|
|
283
|
+
if (!current?.token)
|
|
284
|
+
return false;
|
|
285
|
+
return (0, transcriptEvents_1.postTranscriptEvent)(http, current.api_base || store_1.DEFAULT_API_BASE, current.token, event);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
276
288
|
const child = (0, child_process_1.spawn)(grokBin, [], {
|
|
277
289
|
stdio: 'inherit',
|
|
278
|
-
env
|
|
290
|
+
env,
|
|
279
291
|
cwd: process.cwd(),
|
|
280
292
|
});
|
|
281
293
|
return new Promise((resolve) => {
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
const finish = (code) => {
|
|
295
|
+
void stopWatch().finally(() => resolve(code));
|
|
296
|
+
};
|
|
297
|
+
child.on('exit', (code) => finish(code ?? 1));
|
|
298
|
+
child.on('error', () => finish(1));
|
|
284
299
|
});
|
|
285
300
|
});
|
|
286
301
|
if (deps.spawnGrokPager || ((0, launch_1.findGrokBuildBinary)() && process.stdout.isTTY)) {
|