@thxgg/steward 0.1.16 → 0.1.18
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{CxHOXVf6.js → -z_Gr0GN.js} +1 -1
- package/.output/public/_nuxt/5LlyHjkF.js +60 -0
- package/.output/public/_nuxt/BA0u_CRT.js +1 -0
- package/.output/public/_nuxt/{Bibm_IDv.js → BA4e9-N5.js} +2 -2
- package/.output/public/_nuxt/{BSA0RJ-H.js → BO8EM227.js} +1 -1
- package/.output/public/_nuxt/C0XT5P3Q.js +1 -0
- package/.output/public/_nuxt/{BWVTacYj.js → CGzrvVc6.js} +1 -1
- package/.output/public/_nuxt/{Dum5qplW.js → CJlXUkTg.js} +1 -1
- package/.output/public/_nuxt/CZsXZugv.js +1 -0
- package/.output/public/_nuxt/{ynmyrfyT.js → C_HVaH3B.js} +1 -1
- package/.output/public/_nuxt/{wbjFvimm.js → DAnnHVQP.js} +1 -1
- package/.output/public/_nuxt/DEr8q68O.js +141 -0
- package/.output/public/_nuxt/Detail.DSyVQNdr.css +1 -0
- package/.output/public/_nuxt/DrXxYwWw.js +30 -0
- package/.output/public/_nuxt/QAzsKGuP.js +1 -0
- package/.output/public/_nuxt/TSsR_oCL.js +1 -0
- package/.output/public/_nuxt/WUF6Thhn.js +13 -0
- package/.output/public/_nuxt/_prd_.BkpxMFSV.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/19e0e040-a531-4c25-b46d-a6ca54a1ae3e.json +1 -0
- package/.output/public/_nuxt/i9wn3hS7.js +1 -0
- package/.output/server/chunks/_/git-api.mjs +101 -8
- package/.output/server/chunks/_/git-api.mjs.map +1 -1
- package/.output/server/chunks/_/git.mjs +3 -10
- package/.output/server/chunks/_/git.mjs.map +1 -1
- package/.output/server/chunks/_/prd-service.mjs +366 -0
- package/.output/server/chunks/_/prd-service.mjs.map +1 -0
- package/.output/server/chunks/_/repos.mjs +448 -0
- package/.output/server/chunks/_/repos.mjs.map +1 -0
- package/.output/server/chunks/_/task-graph.mjs +13 -14
- package/.output/server/chunks/_/task-graph.mjs.map +1 -1
- package/.output/server/chunks/_/watcher.mjs +54 -68
- package/.output/server/chunks/_/watcher.mjs.map +1 -1
- package/.output/server/chunks/build/{Detail-CUfU85GY.mjs → Detail-BQSkP9Zm.mjs} +170 -74
- package/.output/server/chunks/build/Detail-BQSkP9Zm.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-BFsE2PCW.mjs +4 -0
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-BFsE2PCW.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles.D2bqX3nK.mjs +8 -0
- package/.output/server/chunks/build/DiffViewer-styles.D2bqX3nK.mjs.map +1 -0
- package/.output/server/chunks/build/DiffViewer-styles.FoV36wuV.mjs +10 -0
- package/.output/server/chunks/build/DiffViewer-styles.FoV36wuV.mjs.map +1 -0
- package/.output/server/chunks/build/Viewer-styles.D6wYWFb1.mjs +8 -0
- package/.output/server/chunks/build/Viewer-styles.D6wYWFb1.mjs.map +1 -0
- package/.output/server/chunks/build/{_prd_-CeVnQzOV.mjs → _prd_-CBR_wm9i.mjs} +73 -33
- package/.output/server/chunks/build/_prd_-CBR_wm9i.mjs.map +1 -0
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/{default-DWCOHHTE.mjs → default-Cao5eO80.mjs} +2 -3
- package/.output/server/chunks/build/default-Cao5eO80.mjs.map +1 -0
- package/.output/server/chunks/build/error-404-Bf6kdO80.mjs +0 -1
- package/.output/server/chunks/build/error-500-D_bcARXN.mjs +0 -1
- package/.output/server/chunks/build/{index-CckL_NBD.mjs → index-ByZO4Bvq.mjs} +2 -2
- package/.output/server/chunks/build/index-ByZO4Bvq.mjs.map +1 -0
- package/.output/server/chunks/build/{index-QVeSHT3L.mjs → index-ljj9uTXI.mjs} +6 -5
- package/.output/server/chunks/build/index-ljj9uTXI.mjs.map +1 -0
- package/.output/server/chunks/build/nuxt-link-SvT1nf8Z.mjs +1 -1
- package/.output/server/chunks/build/{repo-graph-CHNl58mY.mjs → repo-graph-CVnkmn8i.mjs} +23 -10
- package/.output/server/chunks/build/repo-graph-CVnkmn8i.mjs.map +1 -0
- package/.output/server/chunks/build/server.mjs +5 -6
- package/.output/server/chunks/build/styles.mjs +4 -4
- package/.output/server/chunks/build/{usePrd-SqcxGyFU.mjs → usePrd-f7ylhIqs.mjs} +10 -34
- package/.output/server/chunks/build/usePrd-f7ylhIqs.mjs.map +1 -0
- package/.output/server/chunks/nitro/nitro.mjs +614 -1211
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/browse.get.mjs +34 -10
- package/.output/server/chunks/routes/api/browse.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get.mjs +3 -2
- package/.output/server/chunks/routes/api/index.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.post.mjs +22 -7
- package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs +29 -23
- package/.output/server/chunks/routes/api/repos/_repoId/git/commits.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs +12 -7
- package/.output/server/chunks/routes/api/repos/_repoId/git/diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs +32 -13
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-content.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs +14 -14
- package/.output/server/chunks/routes/api/repos/_repoId/git/file-diff.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs +7 -2
- package/.output/server/chunks/routes/api/repos/_repoId/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs +16 -2
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/graph.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs +21 -9
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/progress.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs +21 -85
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks/_taskId/commits.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs +21 -9
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug/tasks.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs +31 -50
- package/.output/server/chunks/routes/api/repos/_repoId/prd/_prdSlug_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs +20 -49
- package/.output/server/chunks/routes/api/repos/_repoId/prds.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId/refresh-git-repos.post.mjs +6 -13
- package/.output/server/chunks/routes/api/repos/_repoId/refresh-git-repos.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs +3 -2
- package/.output/server/chunks/routes/api/repos/_repoId_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/runtime.get.mjs +1 -2
- package/.output/server/chunks/routes/api/runtime.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/watch.get.mjs +5 -4
- package/.output/server/chunks/routes/api/watch.get.mjs.map +1 -1
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/index.mjs +1 -2
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/node_modules/zod/index.js +4 -0
- package/.output/server/node_modules/zod/package.json +118 -0
- package/.output/server/node_modules/zod/v3/ZodError.js +133 -0
- package/.output/server/node_modules/zod/v3/errors.js +9 -0
- package/.output/server/node_modules/zod/v3/external.js +6 -0
- package/.output/server/node_modules/zod/v3/helpers/errorUtil.js +6 -0
- package/.output/server/node_modules/zod/v3/helpers/parseUtil.js +109 -0
- package/.output/server/node_modules/zod/v3/helpers/typeAliases.js +1 -0
- package/.output/server/node_modules/zod/v3/helpers/util.js +133 -0
- package/.output/server/node_modules/zod/v3/locales/en.js +109 -0
- package/.output/server/node_modules/zod/v3/types.js +3693 -0
- package/.output/server/package.json +2 -1
- package/README.md +2 -2
- package/dist/host/src/api/prds.js +6 -172
- package/dist/host/src/api/repos.js +3 -18
- package/dist/host/src/api/state.js +8 -9
- package/dist/host/src/executor-runner.js +368 -0
- package/dist/host/src/executor.js +138 -260
- package/dist/host/src/mcp.js +27 -1
- package/dist/host/src/ui.js +14 -4
- package/dist/server/utils/change-events.js +33 -0
- package/dist/server/utils/git.js +11 -16
- package/dist/server/utils/prd-service.js +231 -0
- package/dist/server/utils/prd-state.js +54 -162
- package/dist/server/utils/repos.js +72 -17
- package/dist/server/utils/state-schema.js +61 -0
- package/dist/server/utils/task-graph.js +13 -13
- package/package.json +2 -1
- package/.output/public/_nuxt/CVJh28bx.js +0 -1
- package/.output/public/_nuxt/CyZuidLG.js +0 -60
- package/.output/public/_nuxt/D0op9E2g.js +0 -1
- package/.output/public/_nuxt/DX8awZaa.js +0 -1
- package/.output/public/_nuxt/Detail.z33AHKev.css +0 -1
- package/.output/public/_nuxt/DiTJUZOC.js +0 -1
- package/.output/public/_nuxt/T_3JE9C-.js +0 -1
- package/.output/public/_nuxt/WOI2tLsR.js +0 -42
- package/.output/public/_nuxt/_prd_.KTotLoF_.css +0 -1
- package/.output/public/_nuxt/builds/meta/029070b0-b8e2-4988-84f4-d0c9ff55c998.json +0 -1
- package/.output/public/_nuxt/odRGDGwj.js +0 -1
- package/.output/server/chunks/build/Detail-CUfU85GY.mjs.map +0 -1
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-CS8FTppg.mjs +0 -4
- package/.output/server/chunks/build/DiffViewer-styles-1.mjs-CS8FTppg.mjs.map +0 -1
- package/.output/server/chunks/build/DiffViewer-styles.AUfwwelI.mjs +0 -10
- package/.output/server/chunks/build/DiffViewer-styles.AUfwwelI.mjs.map +0 -1
- package/.output/server/chunks/build/DiffViewer-styles.D_it8zfk.mjs +0 -8
- package/.output/server/chunks/build/DiffViewer-styles.D_it8zfk.mjs.map +0 -1
- package/.output/server/chunks/build/Viewer-styles.CshnetGw.mjs +0 -8
- package/.output/server/chunks/build/Viewer-styles.CshnetGw.mjs.map +0 -1
- package/.output/server/chunks/build/_prd_-CeVnQzOV.mjs.map +0 -1
- package/.output/server/chunks/build/default-DWCOHHTE.mjs.map +0 -1
- package/.output/server/chunks/build/index-CckL_NBD.mjs.map +0 -1
- package/.output/server/chunks/build/index-QVeSHT3L.mjs.map +0 -1
- package/.output/server/chunks/build/repo-graph-CHNl58mY.mjs.map +0 -1
- package/.output/server/chunks/build/usePrd-SqcxGyFU.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thxgg/steward-prod",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": true,
|
|
6
6
|
"dependencies": {
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"vue-demi": "0.14.10",
|
|
77
77
|
"vue-router": "4.6.4",
|
|
78
78
|
"vue-sonner": "1.3.2",
|
|
79
|
+
"zod": "3.25.76",
|
|
79
80
|
"zwitch": "2.0.4"
|
|
80
81
|
}
|
|
81
82
|
}
|
package/README.md
CHANGED
|
@@ -143,8 +143,8 @@ Detailed API docs and examples: `docs/MCP.md`
|
|
|
143
143
|
|
|
144
144
|
Steward reads local filesystem and git metadata by design.
|
|
145
145
|
|
|
146
|
-
-
|
|
147
|
-
-
|
|
146
|
+
- UI/API accept loopback requests only
|
|
147
|
+
- Non-loopback requests are rejected
|
|
148
148
|
- Treat as a workstation tool, not a hosted multi-user service
|
|
149
149
|
|
|
150
150
|
## Storage
|
|
@@ -1,190 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
3
|
-
import { resolveCommitRepo } from '../../../server/utils/git.js';
|
|
4
|
-
import { discoverGitRepos, getRepos, saveRepos } from '../../../server/utils/repos.js';
|
|
5
|
-
import { getPrdState, getPrdStateSummaries, migrateLegacyStateForRepo } from '../../../server/utils/prd-state.js';
|
|
1
|
+
import { listPrdDocuments, readPrdDocument, readPrdProgress, readPrdTasks, resolveTaskCommits } from '../../../server/utils/prd-service.js';
|
|
6
2
|
import { requireRepo } from './repo-context.js';
|
|
7
|
-
function parseMetadata(content) {
|
|
8
|
-
const metadata = {};
|
|
9
|
-
const authorMatch = content.match(/\*{0,2}Author\*{0,2}:\*{0,2}\s*(.+?)(?:\n|$)/i);
|
|
10
|
-
if (authorMatch && authorMatch[1]) {
|
|
11
|
-
metadata.author = authorMatch[1].trim();
|
|
12
|
-
}
|
|
13
|
-
const dateMatch = content.match(/\*{0,2}Date\*{0,2}:\*{0,2}\s*(.+?)(?:\n|$)/i);
|
|
14
|
-
if (dateMatch && dateMatch[1]) {
|
|
15
|
-
metadata.date = dateMatch[1].trim();
|
|
16
|
-
}
|
|
17
|
-
const statusMatch = content.match(/\*{0,2}Status\*{0,2}:\*{0,2}\s*(.+?)(?:\n|$)/i);
|
|
18
|
-
if (statusMatch && statusMatch[1]) {
|
|
19
|
-
metadata.status = statusMatch[1].trim();
|
|
20
|
-
}
|
|
21
|
-
const shortcutLinkMatch = content.match(/\[([Ss][Cc]-\d+)\]\(([^)]+)\)/);
|
|
22
|
-
if (shortcutLinkMatch && shortcutLinkMatch[1] && shortcutLinkMatch[2]) {
|
|
23
|
-
metadata.shortcutStory = shortcutLinkMatch[1];
|
|
24
|
-
metadata.shortcutUrl = shortcutLinkMatch[2];
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
const shortcutIdMatch = content.match(/\*{0,2}Shortcut(?:\s+Story)?\*{0,2}:\*{0,2}\s*([Ss][Cc]-\d+)/i);
|
|
28
|
-
if (shortcutIdMatch && shortcutIdMatch[1]) {
|
|
29
|
-
metadata.shortcutStory = shortcutIdMatch[1];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return metadata;
|
|
33
|
-
}
|
|
34
|
-
async function readPrdFile(repo, prdSlug) {
|
|
35
|
-
const prdPath = join(repo.path, 'docs', 'prd', `${prdSlug}.md`);
|
|
36
|
-
try {
|
|
37
|
-
return await fs.readFile(prdPath, 'utf-8');
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
throw new Error('PRD not found');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
3
|
export const prds = {
|
|
44
4
|
async list(repoId) {
|
|
45
5
|
const repo = await requireRepo(repoId);
|
|
46
|
-
await
|
|
47
|
-
const prdDir = join(repo.path, 'docs', 'prd');
|
|
48
|
-
let prdFiles = [];
|
|
49
|
-
try {
|
|
50
|
-
const files = await fs.readdir(prdDir);
|
|
51
|
-
prdFiles = files.filter((file) => file.endsWith('.md'));
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
const stateSummaries = await getPrdStateSummaries(repo.id);
|
|
57
|
-
const items = await Promise.all(prdFiles.map(async (filename) => {
|
|
58
|
-
const slug = basename(filename, '.md');
|
|
59
|
-
const filePath = join(prdDir, filename);
|
|
60
|
-
let name = slug;
|
|
61
|
-
let modifiedAt = 0;
|
|
62
|
-
try {
|
|
63
|
-
const [content, stat] = await Promise.all([
|
|
64
|
-
fs.readFile(filePath, 'utf-8'),
|
|
65
|
-
fs.stat(filePath)
|
|
66
|
-
]);
|
|
67
|
-
modifiedAt = stat.mtime.getTime();
|
|
68
|
-
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
69
|
-
if (h1Match && h1Match[1]) {
|
|
70
|
-
name = h1Match[1].trim();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// Keep default values when a file cannot be read.
|
|
75
|
-
}
|
|
76
|
-
const stateSummary = stateSummaries.get(slug);
|
|
77
|
-
return {
|
|
78
|
-
slug,
|
|
79
|
-
name,
|
|
80
|
-
source: `docs/prd/${filename}`,
|
|
81
|
-
hasState: !!stateSummary?.hasState,
|
|
82
|
-
modifiedAt,
|
|
83
|
-
...(stateSummary?.taskCount !== undefined && { taskCount: stateSummary.taskCount }),
|
|
84
|
-
...(stateSummary?.completedCount !== undefined && { completedCount: stateSummary.completedCount })
|
|
85
|
-
};
|
|
86
|
-
}));
|
|
87
|
-
items.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
88
|
-
return items;
|
|
6
|
+
return await listPrdDocuments(repo);
|
|
89
7
|
},
|
|
90
8
|
async getDocument(repoId, prdSlug) {
|
|
91
9
|
const repo = await requireRepo(repoId);
|
|
92
|
-
|
|
93
|
-
let name = prdSlug;
|
|
94
|
-
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
95
|
-
if (h1Match && h1Match[1]) {
|
|
96
|
-
name = h1Match[1].trim();
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
slug: prdSlug,
|
|
100
|
-
name,
|
|
101
|
-
content,
|
|
102
|
-
metadata: parseMetadata(content)
|
|
103
|
-
};
|
|
10
|
+
return await readPrdDocument(repo, prdSlug);
|
|
104
11
|
},
|
|
105
12
|
async getTasks(repoId, prdSlug) {
|
|
106
13
|
const repo = await requireRepo(repoId);
|
|
107
|
-
await
|
|
108
|
-
const state = await getPrdState(repo.id, prdSlug);
|
|
109
|
-
return state?.tasks ?? null;
|
|
14
|
+
return await readPrdTasks(repo, prdSlug);
|
|
110
15
|
},
|
|
111
16
|
async getProgress(repoId, prdSlug) {
|
|
112
17
|
const repo = await requireRepo(repoId);
|
|
113
|
-
await
|
|
114
|
-
const state = await getPrdState(repo.id, prdSlug);
|
|
115
|
-
return state?.progress ?? null;
|
|
18
|
+
return await readPrdProgress(repo, prdSlug);
|
|
116
19
|
},
|
|
117
20
|
async getTaskCommits(repoId, prdSlug, taskId) {
|
|
118
21
|
const repo = await requireRepo(repoId);
|
|
119
|
-
await
|
|
120
|
-
const state = await getPrdState(repo.id, prdSlug);
|
|
121
|
-
const progress = state?.progress ?? null;
|
|
122
|
-
if (!progress) {
|
|
123
|
-
return [];
|
|
124
|
-
}
|
|
125
|
-
const taskLogs = Array.isArray(progress.taskLogs) ? progress.taskLogs : [];
|
|
126
|
-
const taskLog = taskLogs.find((log) => log.taskId === taskId);
|
|
127
|
-
if (!taskLog) {
|
|
128
|
-
return [];
|
|
129
|
-
}
|
|
130
|
-
if (!taskLog.commits || taskLog.commits.length === 0) {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
const resolvedCommits = [];
|
|
134
|
-
const failedEntries = [];
|
|
135
|
-
for (const commitEntry of taskLog.commits) {
|
|
136
|
-
try {
|
|
137
|
-
const resolved = await resolveCommitRepo(repo, commitEntry);
|
|
138
|
-
resolvedCommits.push({
|
|
139
|
-
sha: resolved.sha,
|
|
140
|
-
repo: resolved.repoPath
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
failedEntries.push(commitEntry);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (failedEntries.length > 0) {
|
|
148
|
-
const newGitRepos = await discoverGitRepos(repo.path);
|
|
149
|
-
const existingPaths = new Set((repo.gitRepos || []).map((gitRepo) => gitRepo.relativePath));
|
|
150
|
-
const hasNewRepos = newGitRepos.some((gitRepo) => !existingPaths.has(gitRepo.relativePath));
|
|
151
|
-
let resolvedWithUpdatedRepo = false;
|
|
152
|
-
if (hasNewRepos) {
|
|
153
|
-
const allRepos = await getRepos();
|
|
154
|
-
const repoIndex = allRepos.findIndex((candidate) => candidate.id === repoId);
|
|
155
|
-
if (repoIndex !== -1) {
|
|
156
|
-
const updatedRepo = {
|
|
157
|
-
...allRepos[repoIndex],
|
|
158
|
-
gitRepos: newGitRepos.length > 0 ? newGitRepos : undefined
|
|
159
|
-
};
|
|
160
|
-
allRepos[repoIndex] = updatedRepo;
|
|
161
|
-
await saveRepos(allRepos);
|
|
162
|
-
resolvedWithUpdatedRepo = true;
|
|
163
|
-
for (const commitEntry of failedEntries) {
|
|
164
|
-
try {
|
|
165
|
-
const resolved = await resolveCommitRepo(updatedRepo, commitEntry);
|
|
166
|
-
resolvedCommits.push({
|
|
167
|
-
sha: resolved.sha,
|
|
168
|
-
repo: resolved.repoPath
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
const sha = typeof commitEntry === 'string' ? commitEntry : commitEntry.sha;
|
|
173
|
-
resolvedCommits.push({ sha, repo: '' });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (!resolvedWithUpdatedRepo) {
|
|
179
|
-
for (const commitEntry of failedEntries) {
|
|
180
|
-
const sha = typeof commitEntry === 'string' ? commitEntry : commitEntry.sha;
|
|
181
|
-
resolvedCommits.push({
|
|
182
|
-
sha,
|
|
183
|
-
repo: ''
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return resolvedCommits;
|
|
22
|
+
return await resolveTaskCommits(repo, prdSlug, taskId);
|
|
189
23
|
}
|
|
190
24
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { addRepo, discoverGitRepos, getRepoById, getRepos, removeRepo,
|
|
2
|
-
import { migrateLegacyStateForRepo } from '../../../server/utils/prd-state.js';
|
|
1
|
+
import { addRepo, discoverGitRepos, getRepoById, getRepos, removeRepo, updateRepoGitRepos, validateRepoPath } from '../../../server/utils/repos.js';
|
|
3
2
|
import { requireCurrentRepo, requireRepo } from './repo-context.js';
|
|
4
3
|
export const repos = {
|
|
5
4
|
async list() {
|
|
@@ -17,7 +16,6 @@ export const repos = {
|
|
|
17
16
|
throw new Error(validation.error || 'Invalid repository path');
|
|
18
17
|
}
|
|
19
18
|
const repo = await addRepo(path, name);
|
|
20
|
-
await migrateLegacyStateForRepo(repo);
|
|
21
19
|
return repo;
|
|
22
20
|
},
|
|
23
21
|
async remove(repoId) {
|
|
@@ -29,22 +27,9 @@ export const repos = {
|
|
|
29
27
|
return { removed: true };
|
|
30
28
|
},
|
|
31
29
|
async refreshGitRepos(repoId) {
|
|
32
|
-
await requireRepo(repoId);
|
|
33
|
-
const allRepos = await getRepos();
|
|
34
|
-
const repoIndex = allRepos.findIndex((repo) => repo.id === repoId);
|
|
35
|
-
if (repoIndex === -1) {
|
|
36
|
-
throw new Error('Repository not found');
|
|
37
|
-
}
|
|
38
|
-
const repo = allRepos[repoIndex];
|
|
30
|
+
const repo = await requireRepo(repoId);
|
|
39
31
|
const gitRepos = await discoverGitRepos(repo.path);
|
|
40
|
-
|
|
41
|
-
repo.gitRepos = gitRepos;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
delete repo.gitRepos;
|
|
45
|
-
}
|
|
46
|
-
allRepos[repoIndex] = repo;
|
|
47
|
-
await saveRepos(allRepos);
|
|
32
|
+
await updateRepoGitRepos(repo.id, gitRepos.length > 0 ? gitRepos : undefined);
|
|
48
33
|
return {
|
|
49
34
|
discovered: gitRepos.length,
|
|
50
35
|
gitRepos
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { getPrdState, getPrdStateSummaries,
|
|
1
|
+
import { getPrdState, getPrdStateSummaries, upsertPrdState } from '../../../server/utils/prd-state.js';
|
|
2
|
+
import { parseProgressFile, parseTasksFile } from '../../../server/utils/state-schema.js';
|
|
2
3
|
import { requireCurrentRepo, requireRepo, requireRepoByPath } from './repo-context.js';
|
|
3
4
|
function mapStateUpdate(payload) {
|
|
4
5
|
return {
|
|
5
|
-
...(payload.tasks !== undefined && {
|
|
6
|
-
|
|
6
|
+
...(payload.tasks !== undefined && {
|
|
7
|
+
tasks: payload.tasks === null ? null : parseTasksFile(payload.tasks)
|
|
8
|
+
}),
|
|
9
|
+
...(payload.progress !== undefined && {
|
|
10
|
+
progress: payload.progress === null ? null : parseProgressFile(payload.progress)
|
|
11
|
+
}),
|
|
7
12
|
...(payload.notes !== undefined && { notes: payload.notes })
|
|
8
13
|
};
|
|
9
14
|
}
|
|
@@ -13,34 +18,28 @@ function mapSummaryMap(summaries) {
|
|
|
13
18
|
export const state = {
|
|
14
19
|
async get(repoId, slug) {
|
|
15
20
|
const repo = await requireRepo(repoId);
|
|
16
|
-
await migrateLegacyStateForRepo(repo);
|
|
17
21
|
return await getPrdState(repo.id, slug);
|
|
18
22
|
},
|
|
19
23
|
async getByPath(repoPath, slug) {
|
|
20
24
|
const repo = await requireRepoByPath(repoPath);
|
|
21
|
-
await migrateLegacyStateForRepo(repo);
|
|
22
25
|
return await getPrdState(repo.id, slug);
|
|
23
26
|
},
|
|
24
27
|
async getCurrent(slug) {
|
|
25
28
|
const repo = await requireCurrentRepo();
|
|
26
|
-
await migrateLegacyStateForRepo(repo);
|
|
27
29
|
return await getPrdState(repo.id, slug);
|
|
28
30
|
},
|
|
29
31
|
async summaries(repoId) {
|
|
30
32
|
const repo = await requireRepo(repoId);
|
|
31
|
-
await migrateLegacyStateForRepo(repo);
|
|
32
33
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
33
34
|
return mapSummaryMap(summaries);
|
|
34
35
|
},
|
|
35
36
|
async summariesByPath(repoPath) {
|
|
36
37
|
const repo = await requireRepoByPath(repoPath);
|
|
37
|
-
await migrateLegacyStateForRepo(repo);
|
|
38
38
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
39
39
|
return mapSummaryMap(summaries);
|
|
40
40
|
},
|
|
41
41
|
async summariesCurrent() {
|
|
42
42
|
const repo = await requireCurrentRepo();
|
|
43
|
-
await migrateLegacyStateForRepo(repo);
|
|
44
43
|
const summaries = await getPrdStateSummaries(repo.id);
|
|
45
44
|
return mapSummaryMap(summaries);
|
|
46
45
|
},
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import vm from 'node:vm';
|
|
2
|
+
import { git, prds, repos, state } from './api/index.js';
|
|
3
|
+
import { getStewardHelp } from './help.js';
|
|
4
|
+
const MAX_OUTPUT_SIZE = 50_000;
|
|
5
|
+
const EXECUTION_TIMEOUT_MS = Number.parseInt(process.env.STEWARD_EXECUTION_TIMEOUT_MS || '30000', 10);
|
|
6
|
+
const MAX_TIMERS = 100;
|
|
7
|
+
const MAX_LOG_ENTRIES = 200;
|
|
8
|
+
const MAX_LOG_OUTPUT_SIZE = 20_000;
|
|
9
|
+
const MAX_LOG_ENTRY_SIZE = 2_000;
|
|
10
|
+
class ExecutionError extends Error {
|
|
11
|
+
options;
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.name = 'ExecutionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function deepFreeze(value) {
|
|
19
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function') || Object.isFrozen(value)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
23
|
+
const property = value[key];
|
|
24
|
+
if (property && (typeof property === 'object' || typeof property === 'function')) {
|
|
25
|
+
deepFreeze(property);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return Object.freeze(value);
|
|
29
|
+
}
|
|
30
|
+
function safeJsonStringify(value) {
|
|
31
|
+
const seen = new WeakSet();
|
|
32
|
+
try {
|
|
33
|
+
return JSON.stringify(value, (_key, currentValue) => {
|
|
34
|
+
if (typeof currentValue === 'bigint') {
|
|
35
|
+
return `${currentValue}n`;
|
|
36
|
+
}
|
|
37
|
+
if (typeof currentValue === 'function') {
|
|
38
|
+
const functionName = currentValue.name ? ` ${currentValue.name}` : '';
|
|
39
|
+
return `[Function${functionName}]`;
|
|
40
|
+
}
|
|
41
|
+
if (typeof currentValue === 'symbol') {
|
|
42
|
+
return currentValue.toString();
|
|
43
|
+
}
|
|
44
|
+
if (typeof currentValue === 'object' && currentValue !== null) {
|
|
45
|
+
if (seen.has(currentValue)) {
|
|
46
|
+
return '[Circular]';
|
|
47
|
+
}
|
|
48
|
+
seen.add(currentValue);
|
|
49
|
+
}
|
|
50
|
+
return currentValue;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function formatLogValue(value) {
|
|
58
|
+
if (typeof value === 'string') {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
const json = safeJsonStringify(value);
|
|
62
|
+
if (json !== undefined) {
|
|
63
|
+
return json;
|
|
64
|
+
}
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
function truncateResult(result) {
|
|
68
|
+
if (result === undefined) {
|
|
69
|
+
return {
|
|
70
|
+
result: null,
|
|
71
|
+
truncatedResult: false,
|
|
72
|
+
resultWasUndefined: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const json = safeJsonStringify(result);
|
|
76
|
+
if (json === undefined) {
|
|
77
|
+
return {
|
|
78
|
+
result: {
|
|
79
|
+
_unserializable: true,
|
|
80
|
+
preview: String(result)
|
|
81
|
+
},
|
|
82
|
+
truncatedResult: false,
|
|
83
|
+
resultWasUndefined: false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (json.length <= MAX_OUTPUT_SIZE) {
|
|
87
|
+
return {
|
|
88
|
+
result,
|
|
89
|
+
truncatedResult: false,
|
|
90
|
+
resultWasUndefined: false
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
result: {
|
|
95
|
+
_truncated: true,
|
|
96
|
+
size: json.length,
|
|
97
|
+
preview: json.slice(0, MAX_OUTPUT_SIZE),
|
|
98
|
+
message: `Output truncated (${json.length} chars, showing first ${MAX_OUTPUT_SIZE})`
|
|
99
|
+
},
|
|
100
|
+
truncatedResult: true,
|
|
101
|
+
resultWasUndefined: false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function normalizeFailure(error) {
|
|
105
|
+
if (error instanceof ExecutionError) {
|
|
106
|
+
return {
|
|
107
|
+
code: error.options?.code || 'EXECUTION_ERROR',
|
|
108
|
+
message: error.message,
|
|
109
|
+
...(error.options?.stackTrace && { stack: error.options.stackTrace }),
|
|
110
|
+
...(error.options?.details !== undefined && { details: error.options.details })
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
const { code, details } = error;
|
|
115
|
+
return {
|
|
116
|
+
code: typeof code === 'string' ? code : 'EXECUTION_ERROR',
|
|
117
|
+
message: error.message,
|
|
118
|
+
...(error.stack && { stack: error.stack }),
|
|
119
|
+
...(details !== undefined && { details })
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
code: 'EXECUTION_ERROR',
|
|
124
|
+
message: String(error)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function execute(code) {
|
|
128
|
+
const startedAt = Date.now();
|
|
129
|
+
const logs = [];
|
|
130
|
+
let totalLogChars = 0;
|
|
131
|
+
let logsTruncated = false;
|
|
132
|
+
const appendLog = (level, args) => {
|
|
133
|
+
if (logs.length >= MAX_LOG_ENTRIES) {
|
|
134
|
+
logsTruncated = true;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let message = args.map(formatLogValue).join(' ');
|
|
138
|
+
if (message.length > MAX_LOG_ENTRY_SIZE) {
|
|
139
|
+
message = `${message.slice(0, MAX_LOG_ENTRY_SIZE)}...`;
|
|
140
|
+
logsTruncated = true;
|
|
141
|
+
}
|
|
142
|
+
if (totalLogChars + message.length > MAX_LOG_OUTPUT_SIZE) {
|
|
143
|
+
logsTruncated = true;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
totalLogChars += message.length;
|
|
147
|
+
logs.push({
|
|
148
|
+
level,
|
|
149
|
+
message,
|
|
150
|
+
timestamp: new Date().toISOString()
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const buildEnvelope = (params) => ({
|
|
154
|
+
ok: params.ok,
|
|
155
|
+
result: params.result,
|
|
156
|
+
logs,
|
|
157
|
+
error: params.error,
|
|
158
|
+
meta: {
|
|
159
|
+
timeoutMs: EXECUTION_TIMEOUT_MS,
|
|
160
|
+
durationMs: Date.now() - startedAt,
|
|
161
|
+
truncatedResult: params.truncatedResult,
|
|
162
|
+
truncatedLogs: logsTruncated,
|
|
163
|
+
resultWasUndefined: params.resultWasUndefined
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (!code || !code.trim()) {
|
|
167
|
+
const error = normalizeFailure(new ExecutionError('Code cannot be empty', { code: 'EMPTY_CODE' }));
|
|
168
|
+
return buildEnvelope({
|
|
169
|
+
ok: false,
|
|
170
|
+
result: null,
|
|
171
|
+
error,
|
|
172
|
+
truncatedResult: false,
|
|
173
|
+
resultWasUndefined: false
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const timers = new Set();
|
|
177
|
+
let executionTimeout = null;
|
|
178
|
+
let asyncCallbackError = null;
|
|
179
|
+
const wrapTimerHandler = (handler) => {
|
|
180
|
+
return () => {
|
|
181
|
+
try {
|
|
182
|
+
handler();
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const normalizedError = error instanceof Error
|
|
186
|
+
? error
|
|
187
|
+
: new Error(String(error));
|
|
188
|
+
asyncCallbackError = normalizedError;
|
|
189
|
+
appendLog('error', ['Timer callback error:', normalizedError.message]);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
const ensureTimerHandler = (handler) => {
|
|
194
|
+
if (typeof handler !== 'function') {
|
|
195
|
+
throw new ExecutionError('Timer handler must be a function', {
|
|
196
|
+
code: 'INVALID_TIMER_HANDLER'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return wrapTimerHandler(handler);
|
|
200
|
+
};
|
|
201
|
+
const apiSurface = deepFreeze({
|
|
202
|
+
repos,
|
|
203
|
+
prds,
|
|
204
|
+
git,
|
|
205
|
+
state,
|
|
206
|
+
steward: {
|
|
207
|
+
help: () => getStewardHelp()
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
const sandbox = {
|
|
211
|
+
...apiSurface,
|
|
212
|
+
console: deepFreeze({
|
|
213
|
+
log: (...args) => appendLog('log', args),
|
|
214
|
+
info: (...args) => appendLog('info', args),
|
|
215
|
+
warn: (...args) => appendLog('warn', args),
|
|
216
|
+
error: (...args) => appendLog('error', args)
|
|
217
|
+
}),
|
|
218
|
+
setTimeout: (handler, timeout) => {
|
|
219
|
+
if (timers.size >= MAX_TIMERS) {
|
|
220
|
+
throw new ExecutionError(`Timer limit exceeded (max ${MAX_TIMERS})`, {
|
|
221
|
+
code: 'TIMER_LIMIT'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const wrappedHandler = ensureTimerHandler(handler);
|
|
225
|
+
const timer = setTimeout(() => {
|
|
226
|
+
timers.delete(timer);
|
|
227
|
+
wrappedHandler();
|
|
228
|
+
}, timeout);
|
|
229
|
+
timers.add(timer);
|
|
230
|
+
return timer;
|
|
231
|
+
},
|
|
232
|
+
clearTimeout: (timer) => {
|
|
233
|
+
timers.delete(timer);
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
},
|
|
236
|
+
setInterval: (handler, timeout) => {
|
|
237
|
+
if (timers.size >= MAX_TIMERS) {
|
|
238
|
+
throw new ExecutionError(`Timer limit exceeded (max ${MAX_TIMERS})`, {
|
|
239
|
+
code: 'TIMER_LIMIT'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const wrappedHandler = ensureTimerHandler(handler);
|
|
243
|
+
const timer = setInterval(wrappedHandler, timeout);
|
|
244
|
+
timers.add(timer);
|
|
245
|
+
return timer;
|
|
246
|
+
},
|
|
247
|
+
clearInterval: (timer) => {
|
|
248
|
+
timers.delete(timer);
|
|
249
|
+
clearInterval(timer);
|
|
250
|
+
},
|
|
251
|
+
Promise
|
|
252
|
+
};
|
|
253
|
+
const wrappedCode = `
|
|
254
|
+
(async () => {
|
|
255
|
+
${code}
|
|
256
|
+
})()
|
|
257
|
+
`;
|
|
258
|
+
try {
|
|
259
|
+
const script = new vm.Script(wrappedCode, {
|
|
260
|
+
filename: 'codemode.js'
|
|
261
|
+
});
|
|
262
|
+
const context = vm.createContext(sandbox);
|
|
263
|
+
const executionPromise = Promise.resolve(script.runInContext(context, {
|
|
264
|
+
timeout: EXECUTION_TIMEOUT_MS
|
|
265
|
+
}));
|
|
266
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
267
|
+
executionTimeout = setTimeout(() => {
|
|
268
|
+
reject(new ExecutionError(`Execution timed out after ${EXECUTION_TIMEOUT_MS}ms`, {
|
|
269
|
+
code: 'TIMEOUT'
|
|
270
|
+
}));
|
|
271
|
+
}, EXECUTION_TIMEOUT_MS);
|
|
272
|
+
});
|
|
273
|
+
const rawResult = await Promise.race([executionPromise, timeoutPromise]);
|
|
274
|
+
if (asyncCallbackError instanceof Error) {
|
|
275
|
+
throw new ExecutionError(asyncCallbackError.message, {
|
|
276
|
+
code: 'ASYNC_CALLBACK_ERROR',
|
|
277
|
+
stackTrace: asyncCallbackError.stack
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const truncated = truncateResult(rawResult);
|
|
281
|
+
return buildEnvelope({
|
|
282
|
+
ok: true,
|
|
283
|
+
result: truncated.result,
|
|
284
|
+
error: null,
|
|
285
|
+
truncatedResult: truncated.truncatedResult,
|
|
286
|
+
resultWasUndefined: truncated.resultWasUndefined
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
const failure = normalizeFailure(error);
|
|
291
|
+
appendLog('error', [`${failure.code}: ${failure.message}`]);
|
|
292
|
+
return buildEnvelope({
|
|
293
|
+
ok: false,
|
|
294
|
+
result: null,
|
|
295
|
+
error: failure,
|
|
296
|
+
truncatedResult: false,
|
|
297
|
+
resultWasUndefined: false
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
if (executionTimeout) {
|
|
302
|
+
clearTimeout(executionTimeout);
|
|
303
|
+
}
|
|
304
|
+
timers.forEach((timer) => {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
clearInterval(timer);
|
|
307
|
+
});
|
|
308
|
+
timers.clear();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async function readStdin() {
|
|
312
|
+
return await new Promise((resolveInput) => {
|
|
313
|
+
let input = '';
|
|
314
|
+
process.stdin.setEncoding('utf-8');
|
|
315
|
+
process.stdin.on('data', (chunk) => {
|
|
316
|
+
input += chunk;
|
|
317
|
+
});
|
|
318
|
+
process.stdin.on('end', () => {
|
|
319
|
+
resolveInput(input);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function printEnvelope(envelope) {
|
|
324
|
+
process.stdout.write(JSON.stringify(envelope));
|
|
325
|
+
}
|
|
326
|
+
function buildBootstrapFailure(code, message) {
|
|
327
|
+
return {
|
|
328
|
+
ok: false,
|
|
329
|
+
result: null,
|
|
330
|
+
logs: [],
|
|
331
|
+
error: {
|
|
332
|
+
code,
|
|
333
|
+
message
|
|
334
|
+
},
|
|
335
|
+
meta: {
|
|
336
|
+
timeoutMs: EXECUTION_TIMEOUT_MS,
|
|
337
|
+
durationMs: 0,
|
|
338
|
+
truncatedResult: false,
|
|
339
|
+
truncatedLogs: false,
|
|
340
|
+
resultWasUndefined: false
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
async function main() {
|
|
345
|
+
const rawInput = await readStdin();
|
|
346
|
+
if (!rawInput.trim()) {
|
|
347
|
+
printEnvelope(buildBootstrapFailure('EMPTY_PAYLOAD', 'Execution payload is empty'));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
let payload;
|
|
351
|
+
try {
|
|
352
|
+
payload = JSON.parse(rawInput);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
printEnvelope(buildBootstrapFailure('INVALID_PAYLOAD', `Failed to parse execution payload: ${String(error)}`));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (typeof payload.code !== 'string') {
|
|
359
|
+
printEnvelope(buildBootstrapFailure('INVALID_PAYLOAD', 'Execution payload must include a string "code" field'));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const envelope = await execute(payload.code);
|
|
363
|
+
printEnvelope(envelope);
|
|
364
|
+
}
|
|
365
|
+
main().catch((error) => {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
printEnvelope(buildBootstrapFailure('RUNNER_FAILURE', message));
|
|
368
|
+
});
|