naiad-cli 0.2.6 → 0.2.8
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/dist/api/client.d.ts +64 -0
- package/dist/api/client.js +72 -0
- package/dist/api/client.js.map +1 -1
- package/dist/commands/continue.d.ts +2 -0
- package/dist/commands/continue.js +402 -0
- package/dist/commands/continue.js.map +1 -0
- package/dist/commands/exec.js +34 -8
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/interactive.js +47 -13
- package/dist/commands/interactive.js.map +1 -1
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.js +2 -1
- package/dist/config/config.js.map +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/setup.js +46 -4
- package/dist/lifecycle/setup.js.map +1 -1
- package/dist/lifecycle/teardown.js +2 -1
- package/dist/lifecycle/teardown.js.map +1 -1
- package/dist/lifecycle/types.d.ts +5 -0
- package/dist/lifecycle/workers.js +4 -0
- package/dist/lifecycle/workers.js.map +1 -1
- package/dist/sync/checkpoint.d.ts +29 -0
- package/dist/sync/checkpoint.js +240 -0
- package/dist/sync/checkpoint.js.map +1 -0
- package/dist/sync/command-listener.d.ts +25 -0
- package/dist/sync/command-listener.js +152 -0
- package/dist/sync/command-listener.js.map +1 -0
- package/extensions/naiad-extension.ts +121 -1
- package/package.json +1 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, unlinkSync, rmSync, statSync, openSync, readSync, closeSync, createReadStream } from "fs";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
const THROTTLE_MS = 30_000;
|
|
4
|
+
const JSONL_MAX_BYTES = 200 * 1024 * 1024;
|
|
5
|
+
const MARKER_LINE = '{"type":"system","message":"[Naiad] Session log truncated — earlier entries exceeded 200MB limit."}\n';
|
|
6
|
+
export class CheckpointUploader {
|
|
7
|
+
client;
|
|
8
|
+
sessionId;
|
|
9
|
+
sessionDir;
|
|
10
|
+
attempt;
|
|
11
|
+
lastUploadAt = 0;
|
|
12
|
+
pendingCheckpoint = null;
|
|
13
|
+
constructor(client, sessionId, sessionDir, attempt) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.sessionId = sessionId;
|
|
16
|
+
this.sessionDir = sessionDir;
|
|
17
|
+
this.attempt = attempt;
|
|
18
|
+
}
|
|
19
|
+
async handleCheckpoint(msg, forceUpload = false) {
|
|
20
|
+
this.pendingCheckpoint = msg;
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (!forceUpload && now - this.lastUploadAt < THROTTLE_MS) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await this.uploadCheckpoint();
|
|
26
|
+
}
|
|
27
|
+
async flushOnExit() {
|
|
28
|
+
if (this.pendingCheckpoint) {
|
|
29
|
+
await this.uploadCheckpoint();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async uploadCheckpoint() {
|
|
33
|
+
const msg = this.pendingCheckpoint;
|
|
34
|
+
if (!msg)
|
|
35
|
+
return;
|
|
36
|
+
this.pendingCheckpoint = null;
|
|
37
|
+
try {
|
|
38
|
+
const policies = await this.client.requestCheckpointPolicies(this.sessionId);
|
|
39
|
+
const skippedArtifacts = [...msg.skippedArtifacts];
|
|
40
|
+
const uploadedKeys = {};
|
|
41
|
+
// Incremental checksum: UTF8(base_sha) + 0x0A + UTF8(head_sha)
|
|
42
|
+
const hash = createHash("sha256");
|
|
43
|
+
hash.update(msg.base_sha, "utf8");
|
|
44
|
+
hash.update("\n");
|
|
45
|
+
hash.update(msg.head_sha, "utf8");
|
|
46
|
+
// Upload patch
|
|
47
|
+
if (msg.patchPath && !msg.skippedArtifacts.includes("patch")) {
|
|
48
|
+
const patchData = readFileSync(msg.patchPath);
|
|
49
|
+
hash.update("\n");
|
|
50
|
+
hash.update(patchData);
|
|
51
|
+
await this.uploadArtifact(policies.artifacts["patch.bin"], patchData);
|
|
52
|
+
uploadedKeys.patch_key = policies.artifacts["patch.bin"].fields["key"];
|
|
53
|
+
}
|
|
54
|
+
// Upload untracked
|
|
55
|
+
if (msg.untrackedPath && !msg.skippedArtifacts.includes("untracked")) {
|
|
56
|
+
hash.update("\n");
|
|
57
|
+
const untrackedData = await this.readAndHash(msg.untrackedPath, hash);
|
|
58
|
+
await this.uploadArtifact(policies.artifacts["untracked.tar.gz"], untrackedData);
|
|
59
|
+
uploadedKeys.untracked_key = policies.artifacts["untracked.tar.gz"].fields["key"];
|
|
60
|
+
}
|
|
61
|
+
// Upload session JSONL (possibly truncated)
|
|
62
|
+
const sessionJsonl = this.findSessionJsonl();
|
|
63
|
+
if (sessionJsonl) {
|
|
64
|
+
const sessionResult = this.prepareSessionJsonl(sessionJsonl);
|
|
65
|
+
if (sessionResult === null) {
|
|
66
|
+
skippedArtifacts.push("session");
|
|
67
|
+
}
|
|
68
|
+
else if (sessionResult === "stream") {
|
|
69
|
+
hash.update("\n");
|
|
70
|
+
const sessionData = await this.readAndHash(sessionJsonl, hash);
|
|
71
|
+
await this.uploadArtifact(policies.artifacts["session.jsonl"], sessionData);
|
|
72
|
+
uploadedKeys.session_key = policies.artifacts["session.jsonl"].fields["key"];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
hash.update("\n");
|
|
76
|
+
hash.update(sessionResult);
|
|
77
|
+
await this.uploadArtifact(policies.artifacts["session.jsonl"], sessionResult);
|
|
78
|
+
uploadedKeys.session_key = policies.artifacts["session.jsonl"].fields["key"];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
skippedArtifacts.push("session");
|
|
83
|
+
}
|
|
84
|
+
const checksum = "sha256:" + hash.digest("hex");
|
|
85
|
+
// Build manifest
|
|
86
|
+
const manifest = {
|
|
87
|
+
version: 1,
|
|
88
|
+
attempt: this.attempt,
|
|
89
|
+
base_sha: msg.base_sha,
|
|
90
|
+
head_sha: msg.head_sha,
|
|
91
|
+
checksum,
|
|
92
|
+
patch_key: uploadedKeys.patch_key ?? null,
|
|
93
|
+
patch_bytes: msg.patchBytes,
|
|
94
|
+
untracked_key: uploadedKeys.untracked_key ?? null,
|
|
95
|
+
untracked_bytes: msg.untrackedPath ? statSync(msg.untrackedPath).size : 0,
|
|
96
|
+
untracked_files: msg.untrackedFiles,
|
|
97
|
+
session_key: uploadedKeys.session_key ?? null,
|
|
98
|
+
session_bytes: sessionJsonl ? statSync(sessionJsonl).size : 0,
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
skipped_artifacts: skippedArtifacts,
|
|
101
|
+
};
|
|
102
|
+
// Upload manifest LAST
|
|
103
|
+
const manifestData = Buffer.from(JSON.stringify(manifest, null, 2));
|
|
104
|
+
await this.uploadArtifact(policies.artifacts["manifest.json"], manifestData);
|
|
105
|
+
// Update session metadata
|
|
106
|
+
const patchUpdate = {};
|
|
107
|
+
if (uploadedKeys.patch_key)
|
|
108
|
+
patchUpdate.git_patch_key = uploadedKeys.patch_key;
|
|
109
|
+
if (uploadedKeys.session_key)
|
|
110
|
+
patchUpdate.session_jsonl_key = uploadedKeys.session_key;
|
|
111
|
+
if (Object.keys(patchUpdate).length > 0) {
|
|
112
|
+
await this.client.patchSession(this.sessionId, patchUpdate);
|
|
113
|
+
}
|
|
114
|
+
this.lastUploadAt = Date.now();
|
|
115
|
+
// Clean up temp files
|
|
116
|
+
this.cleanupTempFiles(msg);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error("[checkpoint] Upload failed:", err.message);
|
|
120
|
+
// Re-queue for next attempt
|
|
121
|
+
if (!this.pendingCheckpoint) {
|
|
122
|
+
this.pendingCheckpoint = msg;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async uploadArtifact(policy, data) {
|
|
127
|
+
const formData = new FormData();
|
|
128
|
+
for (const [key, value] of Object.entries(policy.fields)) {
|
|
129
|
+
formData.append(key, value);
|
|
130
|
+
}
|
|
131
|
+
formData.append("file", new Blob([new Uint8Array(data)]));
|
|
132
|
+
const res = await fetch(policy.url, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
body: formData,
|
|
135
|
+
});
|
|
136
|
+
if (!res.ok && res.status !== 204) {
|
|
137
|
+
const text = await res.text().catch(() => "");
|
|
138
|
+
throw new Error(`Artifact upload failed (${res.status}): ${text}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
findSessionJsonl() {
|
|
142
|
+
try {
|
|
143
|
+
const files = readdirSync(this.sessionDir).filter((f) => f.endsWith(".jsonl"));
|
|
144
|
+
if (files.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
// Use the most recently modified
|
|
147
|
+
let best = null;
|
|
148
|
+
let bestMtime = 0;
|
|
149
|
+
for (const f of files) {
|
|
150
|
+
const full = `${this.sessionDir}/${f}`;
|
|
151
|
+
const st = statSync(full);
|
|
152
|
+
if (st.mtimeMs > bestMtime) {
|
|
153
|
+
bestMtime = st.mtimeMs;
|
|
154
|
+
best = full;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return best;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
prepareSessionJsonl(filePath) {
|
|
164
|
+
try {
|
|
165
|
+
const st = statSync(filePath);
|
|
166
|
+
if (st.size === 0)
|
|
167
|
+
return null;
|
|
168
|
+
if (st.size <= JSONL_MAX_BYTES) {
|
|
169
|
+
return "stream";
|
|
170
|
+
}
|
|
171
|
+
// Truncation: keep last ~200MB on newline boundary
|
|
172
|
+
const markerBytes = Buffer.byteLength(MARKER_LINE, "utf8");
|
|
173
|
+
const maxPayload = JSONL_MAX_BYTES - markerBytes;
|
|
174
|
+
const seekPos = st.size - maxPayload;
|
|
175
|
+
const fd = openSync(filePath, "r");
|
|
176
|
+
try {
|
|
177
|
+
// Read a small chunk to find the next newline
|
|
178
|
+
const scanBuf = Buffer.alloc(Math.min(1024 * 1024, maxPayload));
|
|
179
|
+
const bytesRead = readSync(fd, scanBuf, 0, scanBuf.length, seekPos);
|
|
180
|
+
let newlineOffset = -1;
|
|
181
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
182
|
+
if (scanBuf[i] === 0x0a) {
|
|
183
|
+
newlineOffset = i;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const startPos = newlineOffset >= 0 ? seekPos + newlineOffset + 1 : seekPos + scanBuf.length;
|
|
188
|
+
const tailSize = st.size - startPos;
|
|
189
|
+
const tailBuf = Buffer.alloc(tailSize);
|
|
190
|
+
readSync(fd, tailBuf, 0, tailSize, startPos);
|
|
191
|
+
const marker = Buffer.from(MARKER_LINE, "utf8");
|
|
192
|
+
return Buffer.concat([marker, tailBuf]);
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
closeSync(fd);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error("[checkpoint] Failed to read session JSONL:", err.message);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
readAndHash(filePath, hash) {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const chunks = [];
|
|
206
|
+
const stream = createReadStream(filePath);
|
|
207
|
+
stream.on("data", (chunk) => {
|
|
208
|
+
if (typeof chunk === "string")
|
|
209
|
+
chunk = Buffer.from(chunk);
|
|
210
|
+
hash.update(chunk);
|
|
211
|
+
chunks.push(chunk);
|
|
212
|
+
});
|
|
213
|
+
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
214
|
+
stream.on("error", reject);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
cleanupTempFiles(msg) {
|
|
218
|
+
try {
|
|
219
|
+
if (msg.patchPath)
|
|
220
|
+
unlinkSync(msg.patchPath);
|
|
221
|
+
}
|
|
222
|
+
catch { }
|
|
223
|
+
try {
|
|
224
|
+
if (msg.untrackedPath)
|
|
225
|
+
unlinkSync(msg.untrackedPath);
|
|
226
|
+
}
|
|
227
|
+
catch { }
|
|
228
|
+
// Try to remove the temp directory
|
|
229
|
+
if (msg.patchPath || msg.untrackedPath) {
|
|
230
|
+
const dir = msg.patchPath
|
|
231
|
+
? msg.patchPath.replace(/\/[^/]+$/, "")
|
|
232
|
+
: msg.untrackedPath.replace(/\/[^/]+$/, "");
|
|
233
|
+
try {
|
|
234
|
+
rmSync(dir, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
catch { }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../src/sync/checkpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { APIClient } from "../api/client.js";
|
|
2
|
+
import type { ChildProcess } from "child_process";
|
|
3
|
+
export interface CommandListenerOptions {
|
|
4
|
+
client: APIClient;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
sessionDir: string;
|
|
7
|
+
piProcess: ChildProcess;
|
|
8
|
+
onJsonlUpload?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare class CommandListener {
|
|
11
|
+
private client;
|
|
12
|
+
private sessionId;
|
|
13
|
+
private sessionDir;
|
|
14
|
+
private piProcess;
|
|
15
|
+
private onJsonlUpload?;
|
|
16
|
+
private abortController;
|
|
17
|
+
private lastAckedSeq;
|
|
18
|
+
private running;
|
|
19
|
+
constructor(opts: CommandListenerOptions);
|
|
20
|
+
start(): void;
|
|
21
|
+
stop(): void;
|
|
22
|
+
private connectLoop;
|
|
23
|
+
private streamCommands;
|
|
24
|
+
private handleCommand;
|
|
25
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { writeFileSync, openSync, closeSync, fsyncSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
export class CommandListener {
|
|
4
|
+
client;
|
|
5
|
+
sessionId;
|
|
6
|
+
sessionDir;
|
|
7
|
+
piProcess;
|
|
8
|
+
onJsonlUpload;
|
|
9
|
+
abortController = null;
|
|
10
|
+
lastAckedSeq = 0;
|
|
11
|
+
running = false;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.client = opts.client;
|
|
14
|
+
this.sessionId = opts.sessionId;
|
|
15
|
+
this.sessionDir = opts.sessionDir;
|
|
16
|
+
this.piProcess = opts.piProcess;
|
|
17
|
+
this.onJsonlUpload = opts.onJsonlUpload;
|
|
18
|
+
}
|
|
19
|
+
start() {
|
|
20
|
+
if (this.running)
|
|
21
|
+
return;
|
|
22
|
+
this.running = true;
|
|
23
|
+
this.connectLoop();
|
|
24
|
+
}
|
|
25
|
+
stop() {
|
|
26
|
+
this.running = false;
|
|
27
|
+
this.abortController?.abort();
|
|
28
|
+
this.abortController = null;
|
|
29
|
+
}
|
|
30
|
+
async connectLoop() {
|
|
31
|
+
while (this.running) {
|
|
32
|
+
try {
|
|
33
|
+
await this.streamCommands();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (!this.running)
|
|
37
|
+
return;
|
|
38
|
+
console.error("[command-listener] Connection lost, reconnecting in 3s:", err);
|
|
39
|
+
await sleep(3000);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async streamCommands() {
|
|
44
|
+
this.abortController = new AbortController();
|
|
45
|
+
const url = this.client.getCommandStreamUrl(this.sessionId, this.lastAckedSeq);
|
|
46
|
+
const res = await fetch(url, {
|
|
47
|
+
headers: {
|
|
48
|
+
...this.client.getAuthHeaders(),
|
|
49
|
+
Accept: "text/event-stream",
|
|
50
|
+
},
|
|
51
|
+
signal: this.abortController.signal,
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
throw new Error(`Command stream failed: ${res.status}`);
|
|
55
|
+
}
|
|
56
|
+
const reader = res.body?.getReader();
|
|
57
|
+
if (!reader)
|
|
58
|
+
throw new Error("No response body");
|
|
59
|
+
const decoder = new TextDecoder();
|
|
60
|
+
let buffer = "";
|
|
61
|
+
while (this.running) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done)
|
|
64
|
+
break;
|
|
65
|
+
buffer += decoder.decode(value, { stream: true });
|
|
66
|
+
const lines = buffer.split("\n");
|
|
67
|
+
buffer = lines.pop() ?? "";
|
|
68
|
+
let currentEvent = "";
|
|
69
|
+
let currentData = "";
|
|
70
|
+
let currentId = "";
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
if (line.startsWith("event: ")) {
|
|
73
|
+
currentEvent = line.slice(7).trim();
|
|
74
|
+
}
|
|
75
|
+
else if (line.startsWith("data: ")) {
|
|
76
|
+
currentData = line.slice(6);
|
|
77
|
+
}
|
|
78
|
+
else if (line.startsWith("id: ")) {
|
|
79
|
+
currentId = line.slice(4).trim();
|
|
80
|
+
}
|
|
81
|
+
else if (line === "" && currentEvent) {
|
|
82
|
+
// End of SSE message
|
|
83
|
+
if (currentEvent === "session_ended") {
|
|
84
|
+
console.error("[command-listener] Session ended, stopping");
|
|
85
|
+
this.stop();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (currentEvent === "user_message" && currentData) {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(currentData);
|
|
91
|
+
await this.handleCommand(parsed.id, parsed.text, parseInt(currentId, 10));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error("[command-listener] Failed to handle command:", err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
currentEvent = "";
|
|
98
|
+
currentData = "";
|
|
99
|
+
currentId = "";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async handleCommand(commandId, text, seq) {
|
|
105
|
+
// Step a: Append to local JSONL (fsync)
|
|
106
|
+
const jsonlPath = join(this.sessionDir, "injected-messages.jsonl");
|
|
107
|
+
const entry = JSON.stringify({
|
|
108
|
+
type: "user_injected_message",
|
|
109
|
+
text,
|
|
110
|
+
command_id: commandId,
|
|
111
|
+
seq,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
}) + "\n";
|
|
114
|
+
const fd = openSync(jsonlPath, "a");
|
|
115
|
+
try {
|
|
116
|
+
writeFileSync(fd, entry);
|
|
117
|
+
fsyncSync(fd);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
closeSync(fd);
|
|
121
|
+
}
|
|
122
|
+
// Step b: Trigger JSONL upload (best-effort, non-blocking)
|
|
123
|
+
if (this.onJsonlUpload) {
|
|
124
|
+
try {
|
|
125
|
+
this.onJsonlUpload();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// best-effort
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Step c: Deliver to pi via follow_up on stdin
|
|
132
|
+
const followUp = JSON.stringify({ type: "follow_up", message: text });
|
|
133
|
+
try {
|
|
134
|
+
this.piProcess.stdin?.write(followUp + "\n");
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.error("[command-listener] Failed to write to pi stdin:", err);
|
|
138
|
+
}
|
|
139
|
+
// Step d: Ack only after steps a-c
|
|
140
|
+
try {
|
|
141
|
+
await this.client.ackCommand(this.sessionId, commandId);
|
|
142
|
+
this.lastAckedSeq = seq;
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
console.error("[command-listener] Failed to ack command:", err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function sleep(ms) {
|
|
150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=command-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-listener.js","sourceRoot":"","sources":["../../src/sync/command-listener.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
type ToolCall,
|
|
9
9
|
createAssistantMessageEventStream,
|
|
10
10
|
} from "@mariozechner/pi-ai";
|
|
11
|
+
import { execSync, execFileSync } from "child_process";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import * as os from "os";
|
|
11
15
|
|
|
12
16
|
interface NaiadModel {
|
|
13
17
|
id: string;
|
|
@@ -27,6 +31,115 @@ export default function (pi: ExtensionAPI) {
|
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
// --- Git checkpoint on turn_end ---
|
|
35
|
+
const callbackUrl = process.env.NAIAD_CALLBACK_URL;
|
|
36
|
+
const reporter = {
|
|
37
|
+
send(type: string, data: unknown) {
|
|
38
|
+
if (callbackUrl) {
|
|
39
|
+
fetch(callbackUrl, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body: JSON.stringify({ type, data }),
|
|
43
|
+
}).catch(() => {});
|
|
44
|
+
} else if (process.send) {
|
|
45
|
+
process.send({ type, data });
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (typeof (pi as any).on === "function") {
|
|
51
|
+
(pi as any).on("turn_end", () => {
|
|
52
|
+
try {
|
|
53
|
+
const baseSha = process.env.NAIAD_BASE_SHA;
|
|
54
|
+
if (!baseSha) return;
|
|
55
|
+
|
|
56
|
+
const headSha = execFileSync("git", ["rev-parse", "HEAD"]).toString().trim();
|
|
57
|
+
|
|
58
|
+
let patch: Buffer;
|
|
59
|
+
let patchExceededMaxBuffer = false;
|
|
60
|
+
try {
|
|
61
|
+
patch = execFileSync("git", ["diff", "--binary", baseSha], {
|
|
62
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
63
|
+
encoding: "buffer",
|
|
64
|
+
});
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
if (err.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
67
|
+
patchExceededMaxBuffer = true;
|
|
68
|
+
patch = Buffer.alloc(0);
|
|
69
|
+
} else {
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const untrackedList = execFileSync("git", ["ls-files", "-oz", "--exclude-standard"])
|
|
75
|
+
.toString().split("\0").filter(Boolean)
|
|
76
|
+
.filter(f => !f.startsWith("/") && !f.includes("..") && !f.startsWith("~"));
|
|
77
|
+
|
|
78
|
+
const sensitivePatterns = [".env", ".npmrc", ".netrc", ".pgpass", "credentials",
|
|
79
|
+
"id_rsa", "id_ed25519", ".pem", ".key", ".p12", ".keystore"];
|
|
80
|
+
const safeUntrackedList = untrackedList.filter(f => {
|
|
81
|
+
const basename = f.split("/").pop()!.toLowerCase();
|
|
82
|
+
return !sensitivePatterns.some(p => basename === p || basename.startsWith(p + "."));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let untrackedTarball: Buffer | null = null;
|
|
86
|
+
let untrackedExceededMaxBuffer = false;
|
|
87
|
+
if (safeUntrackedList.length > 0) {
|
|
88
|
+
const fileListBuf = Buffer.from(safeUntrackedList.join("\0") + "\0");
|
|
89
|
+
try {
|
|
90
|
+
untrackedTarball = execFileSync("tar", ["czf", "-", "--null", "-T", "-"], {
|
|
91
|
+
input: fileListBuf,
|
|
92
|
+
maxBuffer: 250 * 1024 * 1024,
|
|
93
|
+
});
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
if (err.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
96
|
+
untrackedExceededMaxBuffer = true;
|
|
97
|
+
untrackedTarball = null;
|
|
98
|
+
} else {
|
|
99
|
+
console.error("[naiad] tar failed, skipping untracked archive:", err.message);
|
|
100
|
+
untrackedTarball = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const patchOverCap = patchExceededMaxBuffer || patch.length > 50 * 1024 * 1024;
|
|
106
|
+
const untrackedOverCap = untrackedExceededMaxBuffer || (untrackedTarball !== null && untrackedTarball.length > 200 * 1024 * 1024);
|
|
107
|
+
const skippedArtifacts: string[] = [];
|
|
108
|
+
if (patchOverCap) skippedArtifacts.push("patch");
|
|
109
|
+
if (untrackedOverCap) skippedArtifacts.push("untracked");
|
|
110
|
+
|
|
111
|
+
const tmpDir = path.join(os.tmpdir(), `naiad-checkpoint-${Date.now()}`);
|
|
112
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
113
|
+
|
|
114
|
+
let patchPath: string | null = null;
|
|
115
|
+
if (!patchOverCap && patch.length > 0) {
|
|
116
|
+
patchPath = path.join(tmpDir, "patch.bin");
|
|
117
|
+
fs.writeFileSync(patchPath, patch);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let untrackedPath: string | null = null;
|
|
121
|
+
if (!untrackedOverCap && untrackedTarball) {
|
|
122
|
+
untrackedPath = path.join(tmpDir, "untracked.tar.gz");
|
|
123
|
+
fs.writeFileSync(untrackedPath, untrackedTarball);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
reporter.send("checkpoint", {
|
|
127
|
+
base_sha: baseSha,
|
|
128
|
+
head_sha: headSha,
|
|
129
|
+
patchPath,
|
|
130
|
+
untrackedPath,
|
|
131
|
+
untrackedFiles: safeUntrackedList,
|
|
132
|
+
patchBytes: patch.length,
|
|
133
|
+
patchExceededMaxBuffer,
|
|
134
|
+
skippedArtifacts,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
});
|
|
137
|
+
} catch (err: any) {
|
|
138
|
+
console.error("[naiad] checkpoint generation failed:", err.message);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
30
143
|
// Parse available models from env; fall back to a single placeholder if unset
|
|
31
144
|
let naiadModels: NaiadModel[] = [];
|
|
32
145
|
try {
|
|
@@ -39,7 +152,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
39
152
|
}
|
|
40
153
|
|
|
41
154
|
// Event forwarding via HTTP callback (for interactive mode)
|
|
42
|
-
const callbackUrl = process.env.NAIAD_CALLBACK_URL;
|
|
43
155
|
if (callbackUrl && typeof (pi as any).on === "function") {
|
|
44
156
|
const postEvent = (type: string, data: unknown) => {
|
|
45
157
|
fetch(callbackUrl, {
|
|
@@ -184,6 +296,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
184
296
|
if (tools) {
|
|
185
297
|
body.tools = tools;
|
|
186
298
|
}
|
|
299
|
+
// Map pi's thinking level to OpenRouter's reasoning_effort.
|
|
300
|
+
// "xhigh" is clamped to "high" (OpenRouter only supports low/medium/high).
|
|
301
|
+
if (options?.reasoning) {
|
|
302
|
+
const effort = options.reasoning === "xhigh" ? "high"
|
|
303
|
+
: options.reasoning === "minimal" ? "low"
|
|
304
|
+
: options.reasoning; // low | medium | high pass through
|
|
305
|
+
body.reasoning_effort = effort;
|
|
306
|
+
}
|
|
187
307
|
|
|
188
308
|
const res = await fetch(`${inferenceUrl}/v1/chat/completions`, {
|
|
189
309
|
method: "POST",
|