perstack 0.0.80 → 0.0.81
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.
|
@@ -1,19 +1,188 @@
|
|
|
1
1
|
import { createId } from '@paralleldrive/cuid2';
|
|
2
|
-
import { parseWithFriendlyError, startCommandInputSchema, defaultMaxRetries, defaultTimeout, checkpointSchema, perstackConfigSchema } from '@perstack/core';
|
|
3
|
-
import {
|
|
2
|
+
import { parseWithFriendlyError, startCommandInputSchema, defaultMaxRetries, defaultTimeout, checkpointSchema, jobSchema, perstackConfigSchema, runSettingSchema } from '@perstack/core';
|
|
3
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
5
|
+
import path5 from 'path';
|
|
4
6
|
import { findLockfile, loadLockfile, runtimeVersion, run } from '@perstack/runtime';
|
|
5
7
|
import { Command } from 'commander';
|
|
6
8
|
import dotenv from 'dotenv';
|
|
7
|
-
import { readFile } from 'fs/promises';
|
|
8
|
-
import path from 'path';
|
|
9
9
|
import TOML from 'smol-toml';
|
|
10
|
-
import { existsSync, readFileSync } from 'fs';
|
|
11
10
|
import { render, useApp, useInput, Box, Text } from 'ink';
|
|
12
11
|
import { createContext, useMemo, useReducer, useState, useEffect, useCallback, useRef, useInsertionEffect, useContext } from 'react';
|
|
13
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
13
|
import { useRun } from '@perstack/react';
|
|
15
14
|
|
|
16
15
|
// src/start.ts
|
|
16
|
+
function getJobsDir() {
|
|
17
|
+
return `${process.cwd()}/perstack/jobs`;
|
|
18
|
+
}
|
|
19
|
+
function getJobDir(jobId) {
|
|
20
|
+
return `${getJobsDir()}/${jobId}`;
|
|
21
|
+
}
|
|
22
|
+
function storeJob(job) {
|
|
23
|
+
const jobDir = getJobDir(job.id);
|
|
24
|
+
if (!existsSync(jobDir)) {
|
|
25
|
+
mkdirSync(jobDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
const jobPath = path5.resolve(jobDir, "job.json");
|
|
28
|
+
writeFileSync(jobPath, JSON.stringify(job, null, 2));
|
|
29
|
+
}
|
|
30
|
+
function retrieveJob(jobId) {
|
|
31
|
+
const jobDir = getJobDir(jobId);
|
|
32
|
+
const jobPath = path5.resolve(jobDir, "job.json");
|
|
33
|
+
if (!existsSync(jobPath)) {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
const content = readFileSync(jobPath, "utf-8");
|
|
37
|
+
return jobSchema.parse(JSON.parse(content));
|
|
38
|
+
}
|
|
39
|
+
function getAllJobs() {
|
|
40
|
+
const jobsDir = getJobsDir();
|
|
41
|
+
if (!existsSync(jobsDir)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
|
|
45
|
+
if (jobDirNames.length === 0) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const jobs = [];
|
|
49
|
+
for (const jobDirName of jobDirNames) {
|
|
50
|
+
const jobPath = path5.resolve(jobsDir, jobDirName, "job.json");
|
|
51
|
+
if (!existsSync(jobPath)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const content = readFileSync(jobPath, "utf-8");
|
|
56
|
+
jobs.push(jobSchema.parse(JSON.parse(content)));
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return jobs.sort((a, b) => b.startedAt - a.startedAt);
|
|
61
|
+
}
|
|
62
|
+
function createInitialJob(jobId, expertKey, maxSteps) {
|
|
63
|
+
return {
|
|
64
|
+
id: jobId,
|
|
65
|
+
status: "running",
|
|
66
|
+
coordinatorExpertKey: expertKey,
|
|
67
|
+
runtimeVersion: "v1.0",
|
|
68
|
+
totalSteps: 0,
|
|
69
|
+
maxSteps,
|
|
70
|
+
usage: {
|
|
71
|
+
inputTokens: 0,
|
|
72
|
+
outputTokens: 0,
|
|
73
|
+
reasoningTokens: 0,
|
|
74
|
+
totalTokens: 0,
|
|
75
|
+
cachedInputTokens: 0
|
|
76
|
+
},
|
|
77
|
+
startedAt: Date.now()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ../../packages/filesystem/src/checkpoint.ts
|
|
82
|
+
function getCheckpointDir(jobId) {
|
|
83
|
+
return `${getJobDir(jobId)}/checkpoints`;
|
|
84
|
+
}
|
|
85
|
+
function getCheckpointPath(jobId, checkpointId) {
|
|
86
|
+
return `${getCheckpointDir(jobId)}/${checkpointId}.json`;
|
|
87
|
+
}
|
|
88
|
+
async function defaultRetrieveCheckpoint(jobId, checkpointId) {
|
|
89
|
+
const checkpointPath = getCheckpointPath(jobId, checkpointId);
|
|
90
|
+
if (!existsSync(checkpointPath)) {
|
|
91
|
+
throw new Error(`checkpoint not found: ${checkpointId}`);
|
|
92
|
+
}
|
|
93
|
+
const checkpoint = await readFile(checkpointPath, "utf8");
|
|
94
|
+
return checkpointSchema.parse(JSON.parse(checkpoint));
|
|
95
|
+
}
|
|
96
|
+
async function defaultStoreCheckpoint(checkpoint) {
|
|
97
|
+
const { id, jobId } = checkpoint;
|
|
98
|
+
const checkpointDir = getCheckpointDir(jobId);
|
|
99
|
+
await mkdir(checkpointDir, { recursive: true });
|
|
100
|
+
await writeFile(getCheckpointPath(jobId, id), JSON.stringify(checkpoint));
|
|
101
|
+
}
|
|
102
|
+
function getCheckpointsByJobId(jobId) {
|
|
103
|
+
const checkpointDir = getCheckpointDir(jobId);
|
|
104
|
+
if (!existsSync(checkpointDir)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const files = readdirSync(checkpointDir).filter((file) => file.endsWith(".json"));
|
|
108
|
+
const checkpoints = [];
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
try {
|
|
111
|
+
const content = readFileSync(path5.resolve(checkpointDir, file), "utf-8");
|
|
112
|
+
checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return checkpoints.sort((a, b) => a.stepNumber - b.stepNumber);
|
|
117
|
+
}
|
|
118
|
+
function defaultGetRunDir(jobId, runId) {
|
|
119
|
+
return `${process.cwd()}/perstack/jobs/${jobId}/runs/${runId}`;
|
|
120
|
+
}
|
|
121
|
+
function getAllRuns() {
|
|
122
|
+
const jobsDir = getJobsDir();
|
|
123
|
+
if (!existsSync(jobsDir)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
|
|
127
|
+
if (jobDirNames.length === 0) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const runs = [];
|
|
131
|
+
for (const jobDirName of jobDirNames) {
|
|
132
|
+
const runsDir = path5.resolve(jobsDir, jobDirName, "runs");
|
|
133
|
+
if (!existsSync(runsDir)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const runDirNames = readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
|
|
137
|
+
for (const runDirName of runDirNames) {
|
|
138
|
+
const runSettingPath = path5.resolve(runsDir, runDirName, "run-setting.json");
|
|
139
|
+
if (!existsSync(runSettingPath)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const content = readFileSync(runSettingPath, "utf-8");
|
|
144
|
+
runs.push(runSettingSchema.parse(JSON.parse(content)));
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return runs.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ../../packages/filesystem/src/event.ts
|
|
153
|
+
async function defaultStoreEvent(event) {
|
|
154
|
+
const { timestamp, jobId, runId, stepNumber, type } = event;
|
|
155
|
+
const runDir = defaultGetRunDir(jobId, runId);
|
|
156
|
+
const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
|
|
157
|
+
await mkdir(runDir, { recursive: true });
|
|
158
|
+
await writeFile(eventPath, JSON.stringify(event));
|
|
159
|
+
}
|
|
160
|
+
function getEventContents(jobId, runId, maxStepNumber) {
|
|
161
|
+
const runDir = defaultGetRunDir(jobId, runId);
|
|
162
|
+
if (!existsSync(runDir)) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const eventFiles = readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
|
|
166
|
+
const [_, timestamp, step, type] = file.split(".")[0].split("-");
|
|
167
|
+
return { file, timestamp: Number(timestamp), stepNumber: Number(step), type };
|
|
168
|
+
}).filter((e) => maxStepNumber === void 0 || e.stepNumber <= maxStepNumber).sort((a, b) => a.timestamp - b.timestamp);
|
|
169
|
+
const events = [];
|
|
170
|
+
for (const { file } of eventFiles) {
|
|
171
|
+
try {
|
|
172
|
+
const content = readFileSync(path5.resolve(runDir, file), "utf-8");
|
|
173
|
+
events.push(JSON.parse(content));
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return events;
|
|
178
|
+
}
|
|
179
|
+
function getRunIdsByJobId(jobId) {
|
|
180
|
+
const runsDir = path5.resolve(getJobDir(jobId), "runs");
|
|
181
|
+
if (!existsSync(runsDir)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
|
|
185
|
+
}
|
|
17
186
|
function getEnv(envPath) {
|
|
18
187
|
const env = Object.fromEntries(
|
|
19
188
|
Object.entries(process.env).filter(([_, value]) => !!value).map(([key, value]) => [key, value])
|
|
@@ -63,23 +232,23 @@ async function findPerstackConfigString(configPath) {
|
|
|
63
232
|
return await fetchRemoteConfig(configPath);
|
|
64
233
|
}
|
|
65
234
|
try {
|
|
66
|
-
const tomlString = await readFile(
|
|
235
|
+
const tomlString = await readFile(path5.resolve(process.cwd(), configPath), "utf-8");
|
|
67
236
|
return tomlString;
|
|
68
237
|
} catch {
|
|
69
238
|
throw new Error(`Given config path "${configPath}" is not found`);
|
|
70
239
|
}
|
|
71
240
|
}
|
|
72
|
-
return await findPerstackConfigStringRecursively(
|
|
241
|
+
return await findPerstackConfigStringRecursively(path5.resolve(process.cwd()));
|
|
73
242
|
}
|
|
74
243
|
async function findPerstackConfigStringRecursively(cwd) {
|
|
75
244
|
try {
|
|
76
|
-
const tomlString = await readFile(
|
|
245
|
+
const tomlString = await readFile(path5.resolve(cwd, "perstack.toml"), "utf-8");
|
|
77
246
|
return tomlString;
|
|
78
247
|
} catch {
|
|
79
|
-
if (cwd ===
|
|
248
|
+
if (cwd === path5.parse(cwd).root) {
|
|
80
249
|
return null;
|
|
81
250
|
}
|
|
82
|
-
return await findPerstackConfigStringRecursively(
|
|
251
|
+
return await findPerstackConfigStringRecursively(path5.dirname(cwd));
|
|
83
252
|
}
|
|
84
253
|
}
|
|
85
254
|
async function parsePerstackConfig(config) {
|
|
@@ -184,32 +353,32 @@ function getProviderConfig(provider, env, providerTable) {
|
|
|
184
353
|
}
|
|
185
354
|
}
|
|
186
355
|
}
|
|
187
|
-
function
|
|
188
|
-
return getAllJobs
|
|
356
|
+
function getAllJobs2() {
|
|
357
|
+
return getAllJobs();
|
|
189
358
|
}
|
|
190
|
-
function
|
|
191
|
-
return getAllRuns
|
|
359
|
+
function getAllRuns2() {
|
|
360
|
+
return getAllRuns();
|
|
192
361
|
}
|
|
193
362
|
function getMostRecentRun() {
|
|
194
|
-
const runs =
|
|
363
|
+
const runs = getAllRuns2();
|
|
195
364
|
if (runs.length === 0) {
|
|
196
365
|
throw new Error("No runs found");
|
|
197
366
|
}
|
|
198
367
|
return runs[0];
|
|
199
368
|
}
|
|
200
|
-
function
|
|
201
|
-
return getCheckpointsByJobId
|
|
369
|
+
function getCheckpointsByJobId2(jobId) {
|
|
370
|
+
return getCheckpointsByJobId(jobId);
|
|
202
371
|
}
|
|
203
372
|
function getMostRecentCheckpoint(jobId) {
|
|
204
373
|
const targetJobId = jobId ?? getMostRecentRun().jobId;
|
|
205
|
-
const checkpoints =
|
|
374
|
+
const checkpoints = getCheckpointsByJobId2(targetJobId);
|
|
206
375
|
if (checkpoints.length === 0) {
|
|
207
376
|
throw new Error(`No checkpoints found for job ${targetJobId}`);
|
|
208
377
|
}
|
|
209
378
|
return checkpoints[checkpoints.length - 1];
|
|
210
379
|
}
|
|
211
380
|
function getRecentExperts(limit) {
|
|
212
|
-
const runs =
|
|
381
|
+
const runs = getAllRuns2();
|
|
213
382
|
const expertMap = /* @__PURE__ */ new Map();
|
|
214
383
|
for (const setting of runs) {
|
|
215
384
|
const expertKey = setting.expertKey;
|
|
@@ -232,7 +401,7 @@ function getCheckpointById(jobId, checkpointId) {
|
|
|
232
401
|
return checkpointSchema.parse(JSON.parse(checkpoint));
|
|
233
402
|
}
|
|
234
403
|
function getCheckpointsWithDetails(jobId) {
|
|
235
|
-
return
|
|
404
|
+
return getCheckpointsByJobId2(jobId).map((cp) => ({
|
|
236
405
|
id: cp.id,
|
|
237
406
|
runId: cp.runId,
|
|
238
407
|
stepNumber: cp.stepNumber,
|
|
@@ -1153,7 +1322,7 @@ function renderTodo(action, color) {
|
|
|
1153
1322
|
] }) });
|
|
1154
1323
|
}
|
|
1155
1324
|
function renderReadTextFile(action, color) {
|
|
1156
|
-
const { path:
|
|
1325
|
+
const { path: path6, content, from, to } = action;
|
|
1157
1326
|
const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
|
|
1158
1327
|
const lines = content?.split("\n") ?? [];
|
|
1159
1328
|
return /* @__PURE__ */ jsx(
|
|
@@ -1161,24 +1330,24 @@ function renderReadTextFile(action, color) {
|
|
|
1161
1330
|
{
|
|
1162
1331
|
indicatorColor: color,
|
|
1163
1332
|
label: "Read Text File",
|
|
1164
|
-
summary: `${shortenPath(
|
|
1333
|
+
summary: `${shortenPath(path6)}${lineRange}`,
|
|
1165
1334
|
children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 1, children: /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line }) }, `read-${idx}`)) })
|
|
1166
1335
|
}
|
|
1167
1336
|
);
|
|
1168
1337
|
}
|
|
1169
1338
|
function renderWriteTextFile(action, color) {
|
|
1170
|
-
const { path:
|
|
1339
|
+
const { path: path6, text } = action;
|
|
1171
1340
|
const lines = text.split("\n");
|
|
1172
|
-
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(
|
|
1341
|
+
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1173
1342
|
/* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
|
|
1174
1343
|
/* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
|
|
1175
1344
|
] }, `write-${idx}`)) }) });
|
|
1176
1345
|
}
|
|
1177
1346
|
function renderEditTextFile(action, color) {
|
|
1178
|
-
const { path:
|
|
1347
|
+
const { path: path6, oldText, newText } = action;
|
|
1179
1348
|
const oldLines = oldText.split("\n");
|
|
1180
1349
|
const newLines = newText.split("\n");
|
|
1181
|
-
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(
|
|
1350
|
+
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1182
1351
|
oldLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1183
1352
|
/* @__PURE__ */ jsx(Text, { color: "red", dimColor: true, children: "-" }),
|
|
1184
1353
|
/* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
|
|
@@ -1190,18 +1359,18 @@ function renderEditTextFile(action, color) {
|
|
|
1190
1359
|
] }) });
|
|
1191
1360
|
}
|
|
1192
1361
|
function renderAppendTextFile(action, color) {
|
|
1193
|
-
const { path:
|
|
1362
|
+
const { path: path6, text } = action;
|
|
1194
1363
|
const lines = text.split("\n");
|
|
1195
|
-
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(
|
|
1364
|
+
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1196
1365
|
/* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
|
|
1197
1366
|
/* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
|
|
1198
1367
|
] }, `append-${idx}`)) }) });
|
|
1199
1368
|
}
|
|
1200
1369
|
function renderListDirectory(action, color) {
|
|
1201
|
-
const { path:
|
|
1370
|
+
const { path: path6, items } = action;
|
|
1202
1371
|
const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
|
|
1203
1372
|
const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
|
|
1204
|
-
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(
|
|
1373
|
+
return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path6), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1205
1374
|
visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
|
|
1206
1375
|
remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1207
1376
|
"... +",
|
|
@@ -1849,7 +2018,7 @@ async function startHandler(expertKey, query, options, handlerOptions) {
|
|
|
1849
2018
|
}));
|
|
1850
2019
|
const recentExperts = getRecentExperts(10);
|
|
1851
2020
|
const showHistory = !input.expertKey && !input.query && !checkpoint;
|
|
1852
|
-
const historyJobs = showHistory ?
|
|
2021
|
+
const historyJobs = showHistory ? getAllJobs2().map((j) => ({
|
|
1853
2022
|
jobId: j.id,
|
|
1854
2023
|
status: j.status,
|
|
1855
2024
|
expertKey: j.coordinatorExpertKey,
|
|
@@ -1987,6 +2156,6 @@ var startCommand = new Command().command("start").description("Start Perstack wi
|
|
|
1987
2156
|
"Resume from a specific checkpoint (requires --continue or --continue-job)"
|
|
1988
2157
|
).option("-i, --interactive-tool-call-result", "Query is interactive tool call result").action((expertKey, query, options) => startHandler(expertKey, query, options));
|
|
1989
2158
|
|
|
1990
|
-
export { getEnv, getPerstackConfig, parseInteractiveToolCallResult, parseInteractiveToolCallResultJson, resolveRunContext, startCommand, startHandler };
|
|
1991
|
-
//# sourceMappingURL=chunk-
|
|
1992
|
-
//# sourceMappingURL=chunk-
|
|
2159
|
+
export { createInitialJob, defaultRetrieveCheckpoint, defaultStoreCheckpoint, defaultStoreEvent, getAllJobs, getAllRuns, getCheckpointsByJobId, getEnv, getEventContents, getPerstackConfig, parseInteractiveToolCallResult, parseInteractiveToolCallResultJson, resolveRunContext, retrieveJob, startCommand, startHandler, storeJob };
|
|
2160
|
+
//# sourceMappingURL=chunk-FOHDMSVR.js.map
|
|
2161
|
+
//# sourceMappingURL=chunk-FOHDMSVR.js.map
|