@my-life-buddies/cli 0.1.2 → 0.2.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 +42 -7
- package/dist/bin/buddy.js +8 -3
- package/dist/bin/buddy.js.map +2 -2
- package/dist/bin/core.js +475 -312
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +158 -46
- package/dist/bin/preview.js.map +2 -2
- package/dist/web/app.css +15 -8
- package/dist/web/app.js +85 -8
- package/dist/web/index.html +4 -0
- package/package.json +3 -3
package/dist/bin/core.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
// packages/cli-core/src/index.ts
|
|
2
|
-
import { lstat as
|
|
3
|
-
import { join as
|
|
2
|
+
import { lstat as lstat9 } from "node:fs/promises";
|
|
3
|
+
import { join as join10, resolve as resolve11 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// packages/cli-core/src/config/index.ts
|
|
6
6
|
import {
|
|
7
7
|
link,
|
|
8
|
-
lstat,
|
|
9
|
-
mkdir,
|
|
10
|
-
open,
|
|
11
|
-
readFile,
|
|
8
|
+
lstat as lstat3,
|
|
9
|
+
mkdir as mkdir2,
|
|
10
|
+
open as open2,
|
|
11
|
+
readFile as readFile2,
|
|
12
12
|
readdir,
|
|
13
13
|
rename,
|
|
14
|
-
unlink
|
|
14
|
+
unlink as unlink2
|
|
15
15
|
} from "node:fs/promises";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
-
import { isAbsolute, join, resolve, win32 } from "node:path";
|
|
17
|
+
import { isAbsolute, join as join2, resolve as resolve2, win32 } from "node:path";
|
|
18
|
+
import { isDeepStrictEqual } from "node:util";
|
|
18
19
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
19
20
|
|
|
20
21
|
// packages/cli-core/src/config/buddy.agent.schema.json
|
|
21
22
|
var buddy_agent_schema_default = {
|
|
22
23
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
23
24
|
title: "buddy.agent.json",
|
|
24
|
-
description: "\u642D\u642D\u9879\u76EE\u8EAB\u4EFD\u4E0E
|
|
25
|
+
description: "\u642D\u642D\u9879\u76EE\u8EAB\u4EFD\u3001\u6A21\u578B\u9009\u62E9\u4E0E\u6570\u636E\u8BBF\u95EE\u9700\u6C42\uFF1B\u5DE5\u7A0B\u662F\u5426\u53EF\u8FD0\u884C\u7531\u5B9E\u9645\u6587\u4EF6\u5224\u65AD",
|
|
25
26
|
type: "object",
|
|
26
27
|
additionalProperties: false,
|
|
27
28
|
required: [
|
|
@@ -46,26 +47,211 @@ var buddy_agent_schema_default = {
|
|
|
46
47
|
pattern: "^[^\\s\\u0000-\\u001f\\u007f]+$",
|
|
47
48
|
description: "\u4ECE buddy-cli models \u67E5\u8BE2\u7684\u6A21\u578B ID"
|
|
48
49
|
},
|
|
49
|
-
|
|
50
|
-
type: "
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
datasets: {
|
|
51
|
+
type: "array",
|
|
52
|
+
description: "\u672C\u642D\u5B50\u7533\u8BF7\u7684\u6570\u636E\u96C6\u53CA\u7528\u9014\uFF1B\u7701\u7565\u6216\u7A7A\u6570\u7EC4\u8868\u793A\u4E0D\u7533\u8BF7\u6570\u636E\uFF0C\u58F0\u660E\u4E0D\u4EE3\u8868\u7528\u6237\u5DF2\u6388\u6743",
|
|
53
|
+
default: [],
|
|
54
|
+
maxItems: 8,
|
|
55
|
+
items: {
|
|
56
|
+
type: "object",
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
required: ["id", "purpose"],
|
|
59
|
+
properties: {
|
|
60
|
+
id: {
|
|
61
|
+
type: "string",
|
|
62
|
+
enum: [
|
|
63
|
+
"health.sleep",
|
|
64
|
+
"health.workouts",
|
|
65
|
+
"health.distance",
|
|
66
|
+
"health.heartRate",
|
|
67
|
+
"health.hrv",
|
|
68
|
+
"health.respiratoryRate",
|
|
69
|
+
"health.oxygenSaturation",
|
|
70
|
+
"health.environmentalAudioExposure"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
purpose: {
|
|
74
|
+
type: "string",
|
|
75
|
+
minLength: 1,
|
|
76
|
+
maxLength: 200,
|
|
77
|
+
pattern: "\\S",
|
|
78
|
+
description: "\u5411\u7528\u6237\u8BF4\u660E\u8FD9\u53EA\u642D\u5B50\u4E3A\u4F55\u9700\u8981\u8BE5\u6570\u636E"
|
|
79
|
+
}
|
|
58
80
|
}
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
|
-
},
|
|
62
|
-
dependentRequired: {
|
|
63
|
-
model: [
|
|
64
|
-
"runtime"
|
|
65
|
-
]
|
|
66
83
|
}
|
|
67
84
|
};
|
|
68
85
|
|
|
86
|
+
// packages/cli-core/src/project-files.ts
|
|
87
|
+
import { lstat as lstat2 } from "node:fs/promises";
|
|
88
|
+
import { join } from "node:path";
|
|
89
|
+
|
|
90
|
+
// packages/cli-core/src/init/scaffold.ts
|
|
91
|
+
import { lstat, mkdir, open, readFile, rmdir, unlink } from "node:fs/promises";
|
|
92
|
+
import { resolve } from "node:path";
|
|
93
|
+
var BUDDY_PLATFORM_AGENT_ENTRY = "agent.md";
|
|
94
|
+
var BUDDY_PLATFORM_FACTORY_ENTRY = "src/buddy.mjs";
|
|
95
|
+
var BUDDY_PLATFORM_PACKAGE_JSON = "package.json";
|
|
96
|
+
var BUDDY_PLATFORM_PACKAGE_LOCK = "package-lock.json";
|
|
97
|
+
var BUDDY_PLATFORM_START_ENTRY = "start.mjs";
|
|
98
|
+
var PLATFORM_DIRECTORIES = ["src", "src/tools", "src/hooks", "src/services", "resources", "widgets"];
|
|
99
|
+
function promptFor(name) {
|
|
100
|
+
const safeName = name.trim() || "\u642D\u642D";
|
|
101
|
+
return `\u4F60\u662F${safeName}\u3002\u8BF7\u7406\u89E3\u7528\u6237\u7684\u771F\u5B9E\u9700\u6C42\uFF0C\u7ED9\u51FA\u81EA\u7136\u3001\u51C6\u786E\u4E14\u6709\u5E2E\u52A9\u7684\u5B8C\u6574\u56DE\u590D\u3002`;
|
|
102
|
+
}
|
|
103
|
+
function platformAgentTemplate(name) {
|
|
104
|
+
return `${promptFor(name)}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
function platformFactoryTemplate() {
|
|
108
|
+
return `import { readFileSync } from "node:fs";
|
|
109
|
+
|
|
110
|
+
const systemPrompt = readFileSync(new URL("../agent.md", import.meta.url), "utf8").trim();
|
|
111
|
+
if (!systemPrompt) throw new Error("agent.md must contain the Buddy system prompt");
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Called when the Runtime creates an agent for a conversation.
|
|
115
|
+
* Import tools from ./tools/ and hooks from ./hooks/, then register them below.
|
|
116
|
+
* Shared business code can live in ./services/; directories do not enable capabilities.
|
|
117
|
+
* Use conversation.memory / dataAccess / widgets / proactive for platform capabilities.
|
|
118
|
+
* Place widget assets in ../widgets/<type-id>/index.html and schema.json.
|
|
119
|
+
* Runtime uploads these files before connecting to MLB; upload failures stop startup.
|
|
120
|
+
* Select widget_* in platformTools to expose widget tools to the model.
|
|
121
|
+
* Use platform APIs only; do not call third-party services directly.
|
|
122
|
+
*/
|
|
123
|
+
export default function createBuddy(_conversation) {
|
|
124
|
+
return {
|
|
125
|
+
initialState: {
|
|
126
|
+
systemPrompt,
|
|
127
|
+
thinkingLevel: "off",
|
|
128
|
+
tools: [],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
function platformStartTemplate() {
|
|
135
|
+
return `// Generated startup: local DEV and hosted execution use the same Buddy Runtime.
|
|
136
|
+
import { readFileSync } from "node:fs";
|
|
137
|
+
import { BuddyServer } from "@my-life-buddies/buddy-runtime";
|
|
138
|
+
import createBuddy from "./src/buddy.mjs";
|
|
139
|
+
|
|
140
|
+
const config = JSON.parse(readFileSync(new URL("./buddy.agent.json", import.meta.url), "utf8"));
|
|
141
|
+
if (!config || config.schemaVersion !== 1 || typeof config.buddyId !== "string" || !config.buddyId) {
|
|
142
|
+
throw new Error("buddy.agent.json is not a complete Buddy project");
|
|
143
|
+
}
|
|
144
|
+
if (process.env.MLB_BUDDY_ID && process.env.MLB_BUDDY_ID !== config.buddyId) {
|
|
145
|
+
throw new Error("MLB_BUDDY_ID does not match buddy.agent.json");
|
|
146
|
+
}
|
|
147
|
+
const model = typeof config.model === "string" && config.model ? config.model : "kimi-k2.6";
|
|
148
|
+
|
|
149
|
+
const lifecycle = new AbortController();
|
|
150
|
+
const stop = () => lifecycle.abort();
|
|
151
|
+
process.on("SIGINT", stop);
|
|
152
|
+
process.on("SIGTERM", stop);
|
|
153
|
+
let runtime;
|
|
154
|
+
try {
|
|
155
|
+
runtime = await BuddyServer.start({ model, buddy: createBuddy });
|
|
156
|
+
if (!lifecycle.signal.aborted) {
|
|
157
|
+
await new Promise((resolve) => lifecycle.signal.addEventListener("abort", resolve, { once: true }));
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (!lifecycle.signal.aborted) {
|
|
161
|
+
console.error("buddy-agent:", error instanceof Error ? error.message : "Startup failed");
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
} finally {
|
|
165
|
+
await runtime?.stop();
|
|
166
|
+
process.off("SIGINT", stop);
|
|
167
|
+
process.off("SIGTERM", stop);
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
async function scaffoldPlatformAgent(projectRoot, name) {
|
|
172
|
+
const path = resolve(projectRoot, BUDDY_PLATFORM_AGENT_ENTRY);
|
|
173
|
+
const hasAlternateLockfile = (await Promise.all(["pnpm-lock.yaml", "yarn.lock"].map(async (name2) => {
|
|
174
|
+
try {
|
|
175
|
+
if (!(await lstat(resolve(projectRoot, name2))).isFile()) {
|
|
176
|
+
throw new TypeError(`\u4F9D\u8D56\u9501\u6587\u4EF6\u5FC5\u987B\u662F\u666E\u901A\u6587\u4EF6\uFF1A${name2}`);
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
} catch (cause) {
|
|
180
|
+
if (cause.code === "ENOENT") return false;
|
|
181
|
+
throw cause;
|
|
182
|
+
}
|
|
183
|
+
}))).some(Boolean);
|
|
184
|
+
const files = [
|
|
185
|
+
{ path, content: platformAgentTemplate(name) },
|
|
186
|
+
{ path: resolve(projectRoot, BUDDY_PLATFORM_FACTORY_ENTRY), content: platformFactoryTemplate() },
|
|
187
|
+
{ path: resolve(projectRoot, BUDDY_PLATFORM_PACKAGE_JSON), content: await readFile(new URL("../../resources/agent-template/package.json", import.meta.url), "utf8") },
|
|
188
|
+
...hasAlternateLockfile ? [] : [{ path: resolve(projectRoot, BUDDY_PLATFORM_PACKAGE_LOCK), content: await readFile(new URL("../../resources/agent-template/package-lock.json", import.meta.url), "utf8") }],
|
|
189
|
+
{ path: resolve(projectRoot, BUDDY_PLATFORM_START_ENTRY), content: platformStartTemplate() }
|
|
190
|
+
];
|
|
191
|
+
const handles = [];
|
|
192
|
+
const createdDirectories = [];
|
|
193
|
+
try {
|
|
194
|
+
for (const directory of PLATFORM_DIRECTORIES) {
|
|
195
|
+
const directoryPath = resolve(projectRoot, directory);
|
|
196
|
+
try {
|
|
197
|
+
await mkdir(directoryPath, { mode: 448 });
|
|
198
|
+
createdDirectories.push(directoryPath);
|
|
199
|
+
} catch (cause) {
|
|
200
|
+
if (cause.code !== "EEXIST") throw cause;
|
|
201
|
+
}
|
|
202
|
+
if (!(await lstat(directoryPath)).isDirectory()) {
|
|
203
|
+
throw new TypeError(`\u5B98\u65B9\u5DE5\u7A0B\u76EE\u5F55\u5FC5\u987B\u662F\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u4F7F\u7528\u6587\u4EF6\u6216\u7B26\u53F7\u94FE\u63A5\uFF1A${directoryPath}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const file of files) {
|
|
207
|
+
try {
|
|
208
|
+
handles.push({ path: file.path, handle: await open(file.path, "wx", 384) });
|
|
209
|
+
} catch (cause) {
|
|
210
|
+
if (cause.code !== "EEXIST") throw cause;
|
|
211
|
+
if (!(await lstat(file.path)).isFile()) {
|
|
212
|
+
throw new TypeError(`\u5B98\u65B9\u5DE5\u7A0B\u6587\u4EF6\u5FC5\u987B\u662F\u666E\u901A\u6587\u4EF6\uFF0C\u4E0D\u80FD\u4F7F\u7528\u76EE\u5F55\u6216\u7B26\u53F7\u94FE\u63A5\uFF1A${file.path}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const { path: filePath, handle } of handles) {
|
|
217
|
+
const file = files.find((candidate) => candidate.path === filePath);
|
|
218
|
+
await handle.writeFile(file.content, "utf8");
|
|
219
|
+
await handle.sync();
|
|
220
|
+
}
|
|
221
|
+
return Object.freeze({ path, created: handles.length > 0 || createdDirectories.length > 0 });
|
|
222
|
+
} catch (cause) {
|
|
223
|
+
await Promise.all(handles.map(({ handle }) => handle.close().catch(() => void 0)));
|
|
224
|
+
await Promise.all(handles.map(({ path: createdPath }) => unlink(createdPath).catch(() => void 0)));
|
|
225
|
+
for (const directory of createdDirectories.reverse()) {
|
|
226
|
+
await rmdir(directory).catch(() => void 0);
|
|
227
|
+
}
|
|
228
|
+
throw cause;
|
|
229
|
+
} finally {
|
|
230
|
+
await Promise.all(handles.map(({ handle }) => handle.close().catch(() => void 0)));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// packages/cli-core/src/project-files.ts
|
|
235
|
+
async function missingBuddyAgentFiles(projectRoot) {
|
|
236
|
+
const entries = [
|
|
237
|
+
["src", "directory"],
|
|
238
|
+
[BUDDY_PLATFORM_AGENT_ENTRY, "file"],
|
|
239
|
+
[BUDDY_PLATFORM_FACTORY_ENTRY, "file"],
|
|
240
|
+
[BUDDY_PLATFORM_PACKAGE_JSON, "file"],
|
|
241
|
+
[BUDDY_PLATFORM_START_ENTRY, "file"]
|
|
242
|
+
];
|
|
243
|
+
const missing = await Promise.all(entries.map(async ([path, kind]) => {
|
|
244
|
+
try {
|
|
245
|
+
const entry = await lstat2(join(projectRoot, path));
|
|
246
|
+
return (kind === "directory" ? entry.isDirectory() : entry.isFile()) ? void 0 : path;
|
|
247
|
+
} catch (cause) {
|
|
248
|
+
if (["ENOENT", "ENOTDIR"].includes(cause.code ?? "")) return path;
|
|
249
|
+
throw cause;
|
|
250
|
+
}
|
|
251
|
+
}));
|
|
252
|
+
return missing.filter((path) => path !== void 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
69
255
|
// packages/cli-core/src/config/index.ts
|
|
70
256
|
var BUDDY_AGENT_FILENAME = "buddy.agent.json";
|
|
71
257
|
var BUDDY_AGENT_SCHEMA = buddy_agent_schema_default;
|
|
@@ -124,15 +310,18 @@ function validateBuddyProjectManifest(value) {
|
|
|
124
310
|
if (!validateAgentSchema(value)) {
|
|
125
311
|
return { ok: false, issues: schemaIssues(validateAgentSchema.errors) };
|
|
126
312
|
}
|
|
127
|
-
|
|
313
|
+
const manifest = value;
|
|
314
|
+
const seen = /* @__PURE__ */ new Set();
|
|
315
|
+
for (const [index, dataset] of (manifest.datasets ?? []).entries()) {
|
|
316
|
+
if (seen.has(dataset.id)) {
|
|
317
|
+
return { ok: false, issues: [{ path: `/datasets/${index}/id`, message: `\u6570\u636E\u96C6\u4E0D\u80FD\u91CD\u590D\u58F0\u660E\uFF1A${dataset.id}` }] };
|
|
318
|
+
}
|
|
319
|
+
seen.add(dataset.id);
|
|
320
|
+
}
|
|
321
|
+
return { ok: true, value: manifest };
|
|
128
322
|
}
|
|
129
323
|
function validateBuddyAgentConfig(value) {
|
|
130
|
-
|
|
131
|
-
if (!result.ok) return result;
|
|
132
|
-
if (!("runtime" in result.value)) {
|
|
133
|
-
return { ok: false, issues: [{ path: "/runtime", message: "Agent \u5DE5\u7A0B\u5C1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u8FD0\u884C init --mode direct \u8865\u9F50 Runtime \u5DE5\u7A0B" }] };
|
|
134
|
-
}
|
|
135
|
-
return { ok: true, value: result.value };
|
|
324
|
+
return validateBuddyProjectManifest(value);
|
|
136
325
|
}
|
|
137
326
|
function assertBuddyAgentConfig(value) {
|
|
138
327
|
const result = validateBuddyAgentConfig(value);
|
|
@@ -148,12 +337,7 @@ function parseBuddyProjectManifest(source) {
|
|
|
148
337
|
return result.value;
|
|
149
338
|
}
|
|
150
339
|
function serializeBuddyDraftConfig(config) {
|
|
151
|
-
|
|
152
|
-
if (!result.ok || "runtime" in result.value) {
|
|
153
|
-
throw new BuddyConfigValidationError(result.ok ? [{ path: "/runtime", message: "\u5FC5\u987B\u662F\u672A\u5B8C\u6210\u7684\u521B\u4F5C\u9879\u76EE" }] : result.issues);
|
|
154
|
-
}
|
|
155
|
-
return `${JSON.stringify(result.value, null, 2)}
|
|
156
|
-
`;
|
|
340
|
+
return serializeBuddyAgentConfig(config);
|
|
157
341
|
}
|
|
158
342
|
function serializeBuddyAgentConfig(config) {
|
|
159
343
|
return `${JSON.stringify(assertBuddyAgentConfig(config), null, 2)}
|
|
@@ -169,14 +353,14 @@ function combineBuddyProject(config) {
|
|
|
169
353
|
function createBuddyConfig(options) {
|
|
170
354
|
return assertBuddyConfig({
|
|
171
355
|
buddyId: options.buddyId,
|
|
172
|
-
|
|
173
|
-
|
|
356
|
+
...options.model === void 0 ? {} : { model: options.model },
|
|
357
|
+
datasets: options.datasets ?? []
|
|
174
358
|
});
|
|
175
359
|
}
|
|
176
360
|
var ProjectInitializationError = class extends Error {
|
|
177
|
-
constructor(
|
|
361
|
+
constructor(code2, message, targetPath, options) {
|
|
178
362
|
super(message, options);
|
|
179
|
-
this.code =
|
|
363
|
+
this.code = code2;
|
|
180
364
|
this.targetPath = targetPath;
|
|
181
365
|
this.name = "ProjectInitializationError";
|
|
182
366
|
}
|
|
@@ -185,7 +369,7 @@ var ProjectInitializationError = class extends Error {
|
|
|
185
369
|
};
|
|
186
370
|
async function pathState(targetPath) {
|
|
187
371
|
try {
|
|
188
|
-
const entry = await
|
|
372
|
+
const entry = await lstat3(targetPath);
|
|
189
373
|
if (!entry.isDirectory() || entry.isSymbolicLink()) return { kind: "other" };
|
|
190
374
|
return { kind: "directory", identity: `${entry.dev}:${entry.ino}` };
|
|
191
375
|
} catch (cause) {
|
|
@@ -205,7 +389,7 @@ function entryIdentity(entry) {
|
|
|
205
389
|
}
|
|
206
390
|
async function fileIdentity(targetPath) {
|
|
207
391
|
try {
|
|
208
|
-
const entry = await
|
|
392
|
+
const entry = await lstat3(targetPath);
|
|
209
393
|
return entry.isFile() && !entry.isSymbolicLink() ? entryIdentity(entry) : void 0;
|
|
210
394
|
} catch (cause) {
|
|
211
395
|
if (cause.code === "ENOENT") return void 0;
|
|
@@ -214,11 +398,11 @@ async function fileIdentity(targetPath) {
|
|
|
214
398
|
}
|
|
215
399
|
async function unlinkMatchingFile(targetPath, expectedIdentity) {
|
|
216
400
|
try {
|
|
217
|
-
const entry = await
|
|
401
|
+
const entry = await lstat3(targetPath);
|
|
218
402
|
if (!entry.isFile() || entry.isSymbolicLink() || entryIdentity(entry) !== expectedIdentity) {
|
|
219
403
|
return false;
|
|
220
404
|
}
|
|
221
|
-
await
|
|
405
|
+
await unlink2(targetPath);
|
|
222
406
|
return true;
|
|
223
407
|
} catch (cause) {
|
|
224
408
|
return cause.code === "ENOENT";
|
|
@@ -252,7 +436,7 @@ async function readExistingManifest(agentConfigPath) {
|
|
|
252
436
|
agentConfigPath
|
|
253
437
|
);
|
|
254
438
|
}
|
|
255
|
-
const entry = await
|
|
439
|
+
const entry = await lstat3(agentConfigPath);
|
|
256
440
|
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
257
441
|
throw new ProjectInitializationError(
|
|
258
442
|
"TARGET_CHANGED",
|
|
@@ -261,7 +445,7 @@ async function readExistingManifest(agentConfigPath) {
|
|
|
261
445
|
);
|
|
262
446
|
}
|
|
263
447
|
try {
|
|
264
|
-
return parseBuddyProjectManifest(await
|
|
448
|
+
return parseBuddyProjectManifest(await readFile2(agentConfigPath, "utf8"));
|
|
265
449
|
} catch (cause) {
|
|
266
450
|
throw new ProjectInitializationError(
|
|
267
451
|
"TARGET_CHANGED",
|
|
@@ -272,8 +456,8 @@ async function readExistingManifest(agentConfigPath) {
|
|
|
272
456
|
}
|
|
273
457
|
}
|
|
274
458
|
async function planBuddyDraftInitialization(directory) {
|
|
275
|
-
const rootDirectory =
|
|
276
|
-
const agentConfigPath =
|
|
459
|
+
const rootDirectory = resolve2(directory);
|
|
460
|
+
const agentConfigPath = join2(rootDirectory, BUDDY_AGENT_FILENAME);
|
|
277
461
|
const state2 = await pathState(rootDirectory);
|
|
278
462
|
if (state2.kind === "other") {
|
|
279
463
|
throw new ProjectInitializationError(
|
|
@@ -283,7 +467,7 @@ async function planBuddyDraftInitialization(directory) {
|
|
|
283
467
|
);
|
|
284
468
|
}
|
|
285
469
|
const existingManifest = state2.kind === "directory" ? await readExistingManifest(agentConfigPath) : void 0;
|
|
286
|
-
if (existingManifest !== void 0 &&
|
|
470
|
+
if (existingManifest !== void 0 && (await missingBuddyAgentFiles(rootDirectory)).length === 0) {
|
|
287
471
|
throw new ProjectInitializationError(
|
|
288
472
|
"ALREADY_INITIALIZED",
|
|
289
473
|
`\u9879\u76EE\u5DF2\u7ECF\u662F\u5B8C\u6574 Agent \u5DE5\u7A0B\uFF0C\u4E0D\u9700\u8981\u91CD\u65B0\u8FDB\u5165\u521B\u4F5C\u521D\u59CB\u5316\u3002`,
|
|
@@ -300,8 +484,8 @@ async function planBuddyDraftInitialization(directory) {
|
|
|
300
484
|
};
|
|
301
485
|
}
|
|
302
486
|
async function planBuddyProjectInitialization(options) {
|
|
303
|
-
const rootDirectory =
|
|
304
|
-
const agentConfigPath =
|
|
487
|
+
const rootDirectory = resolve2(options.directory);
|
|
488
|
+
const agentConfigPath = resolve2(rootDirectory, BUDDY_AGENT_FILENAME);
|
|
305
489
|
const state2 = await pathState(rootDirectory);
|
|
306
490
|
if (state2.kind === "other") {
|
|
307
491
|
throw new ProjectInitializationError(
|
|
@@ -330,7 +514,7 @@ async function planBuddyProjectInitialization(options) {
|
|
|
330
514
|
async function prepareInitializationDirectory(plan) {
|
|
331
515
|
if (plan.createRootDirectory) {
|
|
332
516
|
if ((await pathState(plan.rootDirectory)).kind !== "missing") throw targetChanged(plan);
|
|
333
|
-
const firstCreatedDirectory = await
|
|
517
|
+
const firstCreatedDirectory = await mkdir2(plan.rootDirectory, { recursive: true });
|
|
334
518
|
if (firstCreatedDirectory === void 0) throw targetChanged(plan);
|
|
335
519
|
const created = await pathState(plan.rootDirectory);
|
|
336
520
|
if (created.kind !== "directory") throw targetChanged(plan);
|
|
@@ -345,14 +529,14 @@ async function prepareInitializationDirectory(plan) {
|
|
|
345
529
|
async function publishNewFile(options) {
|
|
346
530
|
let temporaryHandle;
|
|
347
531
|
let publishedHandle;
|
|
348
|
-
const temporaryPath =
|
|
532
|
+
const temporaryPath = join2(
|
|
349
533
|
options.rootDirectory,
|
|
350
534
|
`.${options.temporaryLabel}.${process.pid}.${randomUUID()}.tmp`
|
|
351
535
|
);
|
|
352
536
|
let temporaryIdentity;
|
|
353
537
|
let publishedIdentity;
|
|
354
538
|
try {
|
|
355
|
-
temporaryHandle = await
|
|
539
|
+
temporaryHandle = await open2(temporaryPath, "wx");
|
|
356
540
|
temporaryIdentity = entryIdentity(await temporaryHandle.stat());
|
|
357
541
|
await temporaryHandle.writeFile(options.contents, "utf8");
|
|
358
542
|
await temporaryHandle.sync();
|
|
@@ -369,13 +553,13 @@ async function publishNewFile(options) {
|
|
|
369
553
|
await link(temporaryPath, options.targetPath);
|
|
370
554
|
publishedIdentity = temporaryIdentity;
|
|
371
555
|
} catch (cause) {
|
|
372
|
-
const
|
|
373
|
-
if (!
|
|
556
|
+
const code2 = cause.code;
|
|
557
|
+
if (!code2 || !HARD_LINK_UNSUPPORTED_CODES.has(code2)) throw cause;
|
|
374
558
|
const fallbackBeforeOpen = await pathState(options.rootDirectory);
|
|
375
559
|
if (fallbackBeforeOpen.kind !== "directory" || fallbackBeforeOpen.identity !== options.directoryIdentity) {
|
|
376
560
|
throw targetChanged({ rootDirectory: options.rootDirectory });
|
|
377
561
|
}
|
|
378
|
-
publishedHandle = await
|
|
562
|
+
publishedHandle = await open2(options.targetPath, "wx");
|
|
379
563
|
publishedIdentity = entryIdentity(await publishedHandle.stat());
|
|
380
564
|
const fallbackRoot = await pathState(options.rootDirectory);
|
|
381
565
|
if (fallbackRoot.kind !== "directory" || fallbackRoot.identity !== options.directoryIdentity) {
|
|
@@ -389,7 +573,7 @@ async function publishNewFile(options) {
|
|
|
389
573
|
const afterPublish = await pathState(options.rootDirectory);
|
|
390
574
|
const publishedFileIdentity = await fileIdentity(options.targetPath);
|
|
391
575
|
const temporaryFileIdentity = await fileIdentity(temporaryPath);
|
|
392
|
-
const publishedContents = publishedFileIdentity ? await
|
|
576
|
+
const publishedContents = publishedFileIdentity ? await readFile2(options.targetPath, "utf8") : void 0;
|
|
393
577
|
if (afterPublish.kind !== "directory" || afterPublish.identity !== options.directoryIdentity || !publishedIdentity || publishedFileIdentity !== publishedIdentity || temporaryFileIdentity !== temporaryIdentity || publishedContents !== options.contents) {
|
|
394
578
|
if (afterPublish.kind === "directory" && afterPublish.identity === options.directoryIdentity && publishedIdentity) {
|
|
395
579
|
await unlinkMatchingFile(options.targetPath, publishedIdentity);
|
|
@@ -416,11 +600,11 @@ async function publishNewFile(options) {
|
|
|
416
600
|
}
|
|
417
601
|
}
|
|
418
602
|
async function replaceExistingFile(options) {
|
|
419
|
-
const temporaryPath =
|
|
603
|
+
const temporaryPath = join2(
|
|
420
604
|
options.rootDirectory,
|
|
421
605
|
`.${options.temporaryLabel}.${process.pid}.${randomUUID()}.tmp`
|
|
422
606
|
);
|
|
423
|
-
const handle = await
|
|
607
|
+
const handle = await open2(temporaryPath, "wx", 384);
|
|
424
608
|
try {
|
|
425
609
|
await handle.writeFile(options.contents, "utf8");
|
|
426
610
|
await handle.sync();
|
|
@@ -431,11 +615,11 @@ async function replaceExistingFile(options) {
|
|
|
431
615
|
const root = await pathState(options.rootDirectory);
|
|
432
616
|
if (root.kind !== "directory" || root.identity !== options.directoryIdentity || await fileIdentity(options.targetPath) !== options.expectedFileIdentity) throw targetChanged({ rootDirectory: options.rootDirectory });
|
|
433
617
|
await rename(temporaryPath, options.targetPath);
|
|
434
|
-
if (await
|
|
618
|
+
if (await readFile2(options.targetPath, "utf8") !== options.contents) {
|
|
435
619
|
throw targetChanged({ rootDirectory: options.rootDirectory });
|
|
436
620
|
}
|
|
437
621
|
} finally {
|
|
438
|
-
await
|
|
622
|
+
await unlink2(temporaryPath).catch((cause) => {
|
|
439
623
|
if (cause.code !== "ENOENT") throw cause;
|
|
440
624
|
});
|
|
441
625
|
}
|
|
@@ -445,14 +629,14 @@ async function applyBuddyDraftInitialization(plan, draft) {
|
|
|
445
629
|
const directoryIdentity = await prepareInitializationDirectory(plan);
|
|
446
630
|
if (plan.existingManifest !== void 0) {
|
|
447
631
|
const current = await readExistingManifest(plan.agentConfigPath);
|
|
448
|
-
if (current === void 0 ||
|
|
632
|
+
if (current === void 0 || current.buddyId !== draft.buddyId || (await missingBuddyAgentFiles(plan.rootDirectory)).length === 0) {
|
|
449
633
|
throw new ProjectInitializationError(
|
|
450
634
|
"ALREADY_INITIALIZED",
|
|
451
635
|
`\u5DF2\u6709 ${BUDDY_AGENT_FILENAME} \u7ED1\u5B9A\u7684 buddyId \u4E0E\u672C\u6B21\u4E91\u7AEF\u642D\u5B50\u4E0D\u4E00\u81F4\u3002`,
|
|
452
636
|
plan.agentConfigPath
|
|
453
637
|
);
|
|
454
638
|
}
|
|
455
|
-
return { rootDirectory: plan.rootDirectory, agentConfigPath: plan.agentConfigPath, manifest:
|
|
639
|
+
return { rootDirectory: plan.rootDirectory, agentConfigPath: plan.agentConfigPath, manifest: current };
|
|
456
640
|
}
|
|
457
641
|
try {
|
|
458
642
|
const published = await publishNewFile({
|
|
@@ -487,15 +671,16 @@ async function applyBuddyDraftInitialization(plan, draft) {
|
|
|
487
671
|
}
|
|
488
672
|
}
|
|
489
673
|
async function applyBuddyProjectInitialization(plan, config) {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
if (plan.existingManifest !== void 0 && plan.existingManifest.buddyId !== agentConfig.buddyId) {
|
|
674
|
+
const requestedConfig = buddyAgentConfig(config);
|
|
675
|
+
if (plan.existingManifest !== void 0 && plan.existingManifest.buddyId !== requestedConfig.buddyId) {
|
|
493
676
|
throw new ProjectInitializationError(
|
|
494
677
|
"ALREADY_INITIALIZED",
|
|
495
|
-
`\u5DF2\u6709 ${BUDDY_AGENT_FILENAME} \u7ED1\u5B9A\u7684\u662F ${plan.existingManifest.buddyId}\uFF0C\u4E0D\u80FD\u6539\u6210 ${
|
|
678
|
+
`\u5DF2\u6709 ${BUDDY_AGENT_FILENAME} \u7ED1\u5B9A\u7684\u662F ${plan.existingManifest.buddyId}\uFF0C\u4E0D\u80FD\u6539\u6210 ${requestedConfig.buddyId}\u3002`,
|
|
496
679
|
plan.agentConfigPath
|
|
497
680
|
);
|
|
498
681
|
}
|
|
682
|
+
const agentConfig = { ...requestedConfig, ...plan.existingManifest };
|
|
683
|
+
const contents = serializeBuddyAgentConfig(agentConfig);
|
|
499
684
|
const directoryIdentity = await prepareInitializationDirectory(plan);
|
|
500
685
|
if (plan.existingManifest !== void 0) {
|
|
501
686
|
const currentManifest = await readExistingManifest(plan.agentConfigPath);
|
|
@@ -504,7 +689,7 @@ async function applyBuddyProjectInitialization(plan, config) {
|
|
|
504
689
|
}
|
|
505
690
|
}
|
|
506
691
|
try {
|
|
507
|
-
if (plan.existingManifest !== void 0 &&
|
|
692
|
+
if (plan.existingManifest !== void 0 && isDeepStrictEqual(plan.existingManifest, agentConfig)) {
|
|
508
693
|
return { rootDirectory: plan.rootDirectory, agentConfigPath: plan.agentConfigPath };
|
|
509
694
|
}
|
|
510
695
|
let warning2;
|
|
@@ -555,167 +740,23 @@ async function initializeBuddyProject(options) {
|
|
|
555
740
|
return applyBuddyProjectInitialization(plan, options.config);
|
|
556
741
|
}
|
|
557
742
|
async function readBuddyProjectManifest(projectRoot) {
|
|
558
|
-
const rootDirectory =
|
|
559
|
-
return parseBuddyProjectManifest(await
|
|
743
|
+
const rootDirectory = resolve2(projectRoot);
|
|
744
|
+
return parseBuddyProjectManifest(await readFile2(resolve2(rootDirectory, BUDDY_AGENT_FILENAME), "utf8"));
|
|
560
745
|
}
|
|
561
746
|
async function readBuddyAgentConfig(projectRoot) {
|
|
562
|
-
const rootDirectory =
|
|
563
|
-
const source = await
|
|
747
|
+
const rootDirectory = resolve2(projectRoot);
|
|
748
|
+
const source = await readFile2(resolve2(rootDirectory, BUDDY_AGENT_FILENAME), "utf8");
|
|
564
749
|
return parseBuddyAgentConfig(source);
|
|
565
750
|
}
|
|
566
751
|
async function readBuddyConfig(projectRoot) {
|
|
567
|
-
const rootDirectory =
|
|
752
|
+
const rootDirectory = resolve2(projectRoot);
|
|
568
753
|
return combineBuddyProject(await readBuddyAgentConfig(rootDirectory));
|
|
569
754
|
}
|
|
570
755
|
|
|
571
|
-
// packages/cli-core/src/init/scaffold.ts
|
|
572
|
-
import { lstat as lstat2, mkdir as mkdir2, open as open2, readFile as readFile2, rmdir, unlink as unlink2 } from "node:fs/promises";
|
|
573
|
-
import { resolve as resolve2 } from "node:path";
|
|
574
|
-
var BUDDY_PLATFORM_AGENT_ENTRY = "agent.md";
|
|
575
|
-
var BUDDY_PLATFORM_FACTORY_ENTRY = "src/buddy.mjs";
|
|
576
|
-
var BUDDY_PLATFORM_PACKAGE_JSON = "package.json";
|
|
577
|
-
var BUDDY_PLATFORM_PACKAGE_LOCK = "package-lock.json";
|
|
578
|
-
var BUDDY_PLATFORM_START_ENTRY = "start.mjs";
|
|
579
|
-
var PLATFORM_DIRECTORIES = ["src", "src/tools", "src/hooks", "src/services", "resources", "widgets"];
|
|
580
|
-
function promptFor(name) {
|
|
581
|
-
const safeName = name.trim() || "\u642D\u642D";
|
|
582
|
-
return `\u4F60\u662F${safeName}\u3002\u8BF7\u7406\u89E3\u7528\u6237\u7684\u771F\u5B9E\u9700\u6C42\uFF0C\u7ED9\u51FA\u81EA\u7136\u3001\u51C6\u786E\u4E14\u6709\u5E2E\u52A9\u7684\u5B8C\u6574\u56DE\u590D\u3002`;
|
|
583
|
-
}
|
|
584
|
-
function platformAgentTemplate(name) {
|
|
585
|
-
return `${promptFor(name)}
|
|
586
|
-
`;
|
|
587
|
-
}
|
|
588
|
-
function platformFactoryTemplate() {
|
|
589
|
-
return `import { readFileSync } from "node:fs";
|
|
590
|
-
|
|
591
|
-
const systemPrompt = readFileSync(new URL("../agent.md", import.meta.url), "utf8").trim();
|
|
592
|
-
if (!systemPrompt) throw new Error("agent.md must contain the Buddy system prompt");
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Called when the Runtime creates an agent for a conversation.
|
|
596
|
-
* Import tools from ./tools/ and hooks from ./hooks/, then register them below.
|
|
597
|
-
* Shared business code can live in ./services/; directories do not enable capabilities.
|
|
598
|
-
* Use conversation.memory / dataAccess / widgets / proactive for platform capabilities.
|
|
599
|
-
* Place widget assets in ../widgets/<type-id>/index.html and schema.json.
|
|
600
|
-
* Runtime uploads these files before connecting to MLB; upload failures stop startup.
|
|
601
|
-
* Select widget_* in platformTools to expose widget tools to the model.
|
|
602
|
-
* Use platform APIs only; do not call third-party services directly.
|
|
603
|
-
*/
|
|
604
|
-
export default function createBuddy(_conversation) {
|
|
605
|
-
return {
|
|
606
|
-
initialState: {
|
|
607
|
-
systemPrompt,
|
|
608
|
-
thinkingLevel: "off",
|
|
609
|
-
tools: [],
|
|
610
|
-
},
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
`;
|
|
614
|
-
}
|
|
615
|
-
function platformStartTemplate() {
|
|
616
|
-
return `// Generated startup: local DEV and hosted execution use the same Buddy Runtime.
|
|
617
|
-
import { readFileSync } from "node:fs";
|
|
618
|
-
import { BuddyServer } from "@my-life-buddies/buddy-runtime";
|
|
619
|
-
import createBuddy from "./src/buddy.mjs";
|
|
620
|
-
|
|
621
|
-
const config = JSON.parse(readFileSync(new URL("./buddy.agent.json", import.meta.url), "utf8"));
|
|
622
|
-
if (!config || config.schemaVersion !== 1 || typeof config.buddyId !== "string" || !config.buddyId || config.runtime?.language !== "node") {
|
|
623
|
-
throw new Error("buddy.agent.json is not a complete Buddy project");
|
|
624
|
-
}
|
|
625
|
-
if (process.env.MLB_BUDDY_ID && process.env.MLB_BUDDY_ID !== config.buddyId) {
|
|
626
|
-
throw new Error("MLB_BUDDY_ID does not match buddy.agent.json");
|
|
627
|
-
}
|
|
628
|
-
const model = typeof config.model === "string" && config.model ? config.model : "kimi-k2.6";
|
|
629
|
-
|
|
630
|
-
const lifecycle = new AbortController();
|
|
631
|
-
const stop = () => lifecycle.abort();
|
|
632
|
-
process.on("SIGINT", stop);
|
|
633
|
-
process.on("SIGTERM", stop);
|
|
634
|
-
let runtime;
|
|
635
|
-
try {
|
|
636
|
-
runtime = await BuddyServer.start({ model, buddy: createBuddy });
|
|
637
|
-
if (!lifecycle.signal.aborted) {
|
|
638
|
-
await new Promise((resolve) => lifecycle.signal.addEventListener("abort", resolve, { once: true }));
|
|
639
|
-
}
|
|
640
|
-
} catch (error) {
|
|
641
|
-
if (!lifecycle.signal.aborted) {
|
|
642
|
-
console.error("buddy-agent:", error instanceof Error ? error.message : "Startup failed");
|
|
643
|
-
process.exitCode = 1;
|
|
644
|
-
}
|
|
645
|
-
} finally {
|
|
646
|
-
await runtime?.stop();
|
|
647
|
-
process.off("SIGINT", stop);
|
|
648
|
-
process.off("SIGTERM", stop);
|
|
649
|
-
}
|
|
650
|
-
`;
|
|
651
|
-
}
|
|
652
|
-
async function scaffoldPlatformAgent(projectRoot, name) {
|
|
653
|
-
const path = resolve2(projectRoot, BUDDY_PLATFORM_AGENT_ENTRY);
|
|
654
|
-
const hasAlternateLockfile = (await Promise.all(["pnpm-lock.yaml", "yarn.lock"].map(async (name2) => {
|
|
655
|
-
try {
|
|
656
|
-
if (!(await lstat2(resolve2(projectRoot, name2))).isFile()) {
|
|
657
|
-
throw new TypeError(`\u4F9D\u8D56\u9501\u6587\u4EF6\u5FC5\u987B\u662F\u666E\u901A\u6587\u4EF6\uFF1A${name2}`);
|
|
658
|
-
}
|
|
659
|
-
return true;
|
|
660
|
-
} catch (cause) {
|
|
661
|
-
if (cause.code === "ENOENT") return false;
|
|
662
|
-
throw cause;
|
|
663
|
-
}
|
|
664
|
-
}))).some(Boolean);
|
|
665
|
-
const files = [
|
|
666
|
-
{ path, content: platformAgentTemplate(name) },
|
|
667
|
-
{ path: resolve2(projectRoot, BUDDY_PLATFORM_FACTORY_ENTRY), content: platformFactoryTemplate() },
|
|
668
|
-
{ path: resolve2(projectRoot, BUDDY_PLATFORM_PACKAGE_JSON), content: await readFile2(new URL("../../resources/agent-template/package.json", import.meta.url), "utf8") },
|
|
669
|
-
...hasAlternateLockfile ? [] : [{ path: resolve2(projectRoot, BUDDY_PLATFORM_PACKAGE_LOCK), content: await readFile2(new URL("../../resources/agent-template/package-lock.json", import.meta.url), "utf8") }],
|
|
670
|
-
{ path: resolve2(projectRoot, BUDDY_PLATFORM_START_ENTRY), content: platformStartTemplate() }
|
|
671
|
-
];
|
|
672
|
-
const handles = [];
|
|
673
|
-
const createdDirectories = [];
|
|
674
|
-
try {
|
|
675
|
-
for (const directory of PLATFORM_DIRECTORIES) {
|
|
676
|
-
const directoryPath = resolve2(projectRoot, directory);
|
|
677
|
-
try {
|
|
678
|
-
await mkdir2(directoryPath, { mode: 448 });
|
|
679
|
-
createdDirectories.push(directoryPath);
|
|
680
|
-
} catch (cause) {
|
|
681
|
-
if (cause.code !== "EEXIST") throw cause;
|
|
682
|
-
}
|
|
683
|
-
if (!(await lstat2(directoryPath)).isDirectory()) {
|
|
684
|
-
throw new TypeError(`\u5B98\u65B9\u5DE5\u7A0B\u76EE\u5F55\u5FC5\u987B\u662F\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u4F7F\u7528\u6587\u4EF6\u6216\u7B26\u53F7\u94FE\u63A5\uFF1A${directoryPath}`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
for (const file of files) {
|
|
688
|
-
try {
|
|
689
|
-
handles.push({ path: file.path, handle: await open2(file.path, "wx", 384) });
|
|
690
|
-
} catch (cause) {
|
|
691
|
-
if (cause.code !== "EEXIST") throw cause;
|
|
692
|
-
if (!(await lstat2(file.path)).isFile()) {
|
|
693
|
-
throw new TypeError(`\u5B98\u65B9\u5DE5\u7A0B\u6587\u4EF6\u5FC5\u987B\u662F\u666E\u901A\u6587\u4EF6\uFF0C\u4E0D\u80FD\u4F7F\u7528\u76EE\u5F55\u6216\u7B26\u53F7\u94FE\u63A5\uFF1A${file.path}`);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
for (const { path: filePath, handle } of handles) {
|
|
698
|
-
const file = files.find((candidate) => candidate.path === filePath);
|
|
699
|
-
await handle.writeFile(file.content, "utf8");
|
|
700
|
-
await handle.sync();
|
|
701
|
-
}
|
|
702
|
-
return Object.freeze({ path, created: handles.length > 0 || createdDirectories.length > 0 });
|
|
703
|
-
} catch (cause) {
|
|
704
|
-
await Promise.all(handles.map(({ handle }) => handle.close().catch(() => void 0)));
|
|
705
|
-
await Promise.all(handles.map(({ path: createdPath }) => unlink2(createdPath).catch(() => void 0)));
|
|
706
|
-
for (const directory of createdDirectories.reverse()) {
|
|
707
|
-
await rmdir(directory).catch(() => void 0);
|
|
708
|
-
}
|
|
709
|
-
throw cause;
|
|
710
|
-
} finally {
|
|
711
|
-
await Promise.all(handles.map(({ handle }) => handle.close().catch(() => void 0)));
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
756
|
// packages/cli-core/src/init/creator.ts
|
|
716
757
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
717
|
-
import { link as link2, lstat as
|
|
718
|
-
import { join as
|
|
758
|
+
import { link as link2, lstat as lstat4, mkdir as mkdir3, readFile as readFile3, unlink as unlink3, writeFile } from "node:fs/promises";
|
|
759
|
+
import { join as join3 } from "node:path";
|
|
719
760
|
import { fileURLToPath } from "node:url";
|
|
720
761
|
|
|
721
762
|
// packages/cli-core/src/init/development-handoff.ts
|
|
@@ -769,7 +810,7 @@ Creator \u8FD4\u56DE\u6709\u6548\u7684 \`completion.manualPath\` \u540E\uFF0C\u8
|
|
|
769
810
|
|
|
770
811
|
| \u4F4D\u7F6E | \u804C\u8D23\u4E0E\u751F\u6210\u65F6\u673A |
|
|
771
812
|
| --- | --- |
|
|
772
|
-
| \`buddy.agent.json\` | \u9ED8\u8BA4\u751F\u6210\uFF0C\u4FDD\u5B58\u5E73\u53F0\u8EAB\u4EFD\u3001\
|
|
813
|
+
| \`buddy.agent.json\` | \u9ED8\u8BA4\u751F\u6210\uFF0C\u4FDD\u5B58\u5E73\u53F0\u8EAB\u4EFD\u3001\u53EF\u9009\u6A21\u578B\u548C\u6570\u636E\u9700\u6C42 datasets\uFF0C\u4E0D\u653E\u4E1A\u52A1\u51FD\u6570 |
|
|
773
814
|
| \`agent.md\` | \u9ED8\u8BA4\u751F\u6210\uFF0C\u4FDD\u5B58\u7CFB\u7EDF Prompt |
|
|
774
815
|
| \`start.mjs\` | \u9ED8\u8BA4\u751F\u6210\uFF0C\u8BFB\u53D6\u542F\u52A8\u914D\u7F6E\u3001\u542F\u52A8 Runtime \u5E76\u5904\u7406\u9000\u51FA |
|
|
775
816
|
| \`package.json\`\u3001\`package-lock.json\` | \u9ED8\u8BA4\u751F\u6210\uFF0C\u58F0\u660E\u542F\u52A8\u547D\u4EE4\u3001\u51C6\u786E Runtime \u7248\u672C\u5E76\u9501\u5B9A\u4F9D\u8D56 |
|
|
@@ -790,6 +831,8 @@ Widget \u5DE5\u7A0B\u7EA6\u5B9A\uFF1A\u4E00\u7EA7\u76EE\u5F55\u540D\u5C31\u662F\
|
|
|
790
831
|
|
|
791
832
|
\u5E73\u53F0\u80FD\u529B\u8981\u4EE5\u5B9E\u9645\u63A5\u5165\u4E3A\u51C6\u3002\u5F53\u524D Runtime \u63D0\u4F9B\u4F1A\u8BDD\u6D88\u606F\u3001Memory\u3001\u6388\u6743\u6570\u636E\u3001\u5C0F\u6302\u4EF6\u548C\u672C\u5730 Markdown \u8D44\u6599\u80FD\u529B\uFF1B\u6CA1\u6709\u63A5\u901A\u7684 Files\u3001Knowledge \u4E0D\u80FD\u56E0\u624B\u518C\u63D0\u5230\u5C31\u5BA3\u79F0\u53EF\u7528\u3002\u666E\u901A Tool \u4E5F\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u63D0\u4F9B\u5B9A\u65F6\u3001\u957F\u671F\u4EFB\u52A1\u3001\u540E\u53F0\u8C03\u5EA6\u6216\u4E3B\u52A8\u89E6\u8FBE\u3002\u624B\u518C\u63CF\u8FF0\u7684\u4ED8\u8D39\u3001\u53D1\u5E03\u7B49\u670D\u52A1\u8BBE\u8BA1\u4E5F\u4E0D\u6784\u6210\u6267\u884C\u6536\u8D39\u6216\u53D1\u5E03\u52A8\u4F5C\u7684\u6388\u6743\u3002
|
|
792
833
|
|
|
834
|
+
\u9700\u8981\u7528\u6237\u6570\u636E\u65F6\uFF0C\u5728 \`buddy.agent.json.datasets\` \u5199\u660E\u6240\u9700\u7684 \`id\` \u548C \`purpose\`\uFF0C\u540C\u4E00 ID \u53EA\u5199\u4E00\u6B21\uFF0C\u7528\u9014\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u6700\u591A 200 \u5B57\uFF1B\u65E0\u6570\u636E\u9700\u6C42\u65F6\u4FDD\u7559\u7A7A\u6570\u7EC4\u3002\u5F53\u524D\u63A5\u53D7 \`health.sleep\`\u3001\`health.workouts\`\u3001\`health.distance\`\u3001\`health.heartRate\`\u3001\`health.hrv\`\u3001\`health.respiratoryRate\`\u3001\`health.oxygenSaturation\` \u548C \`health.environmentalAudioExposure\`\u3002CLI \u6821\u9A8C\u5E76\u968F\u5305\u4E0A\u4F20\u58F0\u660E\uFF0C\u670D\u52A1\u7AEF\u89E3\u6790\u5305\u4E0E App \u6388\u6743\u94FE\u8DEF\u4ECD\u5F85\u63A5\u5165\uFF1B\u4E0D\u80FD\u628A\u586B\u5199\u914D\u7F6E\u8BF4\u6210\u7528\u6237\u5DF2\u7ECF\u6388\u6743\u3002Runtime \u4ECD\u8BFB\u53D6\u5E73\u53F0\u5DF2\u751F\u6548\u7684\u58F0\u660E\uFF1B\u6A21\u578B\u76F4\u63A5\u67E5\u6570\u636E\u9700\u5728 \`src/buddy.mjs\` \u663E\u5F0F\u542F\u7528 \`data_access_query\`\uFF0C\u4E1A\u52A1 Tool \u4E5F\u53EF\u8C03\u7528 \`conversation.dataAccess.query\`\u3002
|
|
835
|
+
|
|
793
836
|
### \u53EA\u6709\u5173\u952E\u7F3A\u53E3\u624D\u8865\u95EE
|
|
794
837
|
|
|
795
838
|
\u80FD\u4ECE\u624B\u518C\u548C\u73B0\u6709\u4EE3\u7801\u786E\u5B9A\u7684\u76F4\u63A5\u5B9E\u73B0\uFF0C\u5305\u62EC\u9009\u62E9\u54EA\u4E2A\u51FD\u6570\u3001\u6587\u4EF6\u3001Tool \u6216\u5DF2\u6709\u9ED8\u8BA4\u80FD\u529B\uFF1B\u4E0D\u8981\u628A\u6280\u672F\u6620\u5C04\u518D\u4EA4\u7ED9\u5F00\u53D1\u8005\u51B3\u5B9A\uFF0C\u4E0D\u8981\u6C42\u5BF9\u6574\u4EFD\u5B9E\u73B0\u65B9\u6848\u91CD\u65B0\u7B7E\u5B57\u3002
|
|
@@ -832,7 +875,7 @@ var REQUIRED = [
|
|
|
832
875
|
var CreatorPreparationError = class extends Error {
|
|
833
876
|
};
|
|
834
877
|
async function state(path) {
|
|
835
|
-
return
|
|
878
|
+
return lstat4(path).catch((cause) => {
|
|
836
879
|
if (cause.code === "ENOENT") return void 0;
|
|
837
880
|
throw cause;
|
|
838
881
|
});
|
|
@@ -841,7 +884,7 @@ async function safeDirectory(path) {
|
|
|
841
884
|
await mkdir3(path, { mode: 448 }).catch((cause) => {
|
|
842
885
|
if (cause.code !== "EEXIST") throw cause;
|
|
843
886
|
});
|
|
844
|
-
if (!(await
|
|
887
|
+
if (!(await lstat4(path)).isDirectory()) {
|
|
845
888
|
throw new CreatorPreparationError(`\u521B\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u4F7F\u7528\u7B26\u53F7\u94FE\u63A5\uFF1A${path}`);
|
|
846
889
|
}
|
|
847
890
|
}
|
|
@@ -866,17 +909,17 @@ async function writeOnce(path, bytes) {
|
|
|
866
909
|
}
|
|
867
910
|
async function bundledSkill(root) {
|
|
868
911
|
for (const path of REQUIRED) {
|
|
869
|
-
if (!(await state(
|
|
912
|
+
if (!(await state(join3(root, path)))?.isFile()) {
|
|
870
913
|
throw new CreatorPreparationError(`CLI \u5185\u7F6E Creator Skill \u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 CLI\uFF08\u7F3A\u5C11 ${path}\uFF09\u3002\u4E0D\u4F1A\u5C1D\u8BD5\u5728\u7EBF\u4E0B\u8F7D\u6216\u8986\u76D6\u9879\u76EE\u8D44\u6599\u3002`);
|
|
871
914
|
}
|
|
872
915
|
}
|
|
873
916
|
return root;
|
|
874
917
|
}
|
|
875
918
|
function handoffMarkdown(context, skillRoot, session, workspacePath) {
|
|
876
|
-
const contextPath =
|
|
919
|
+
const contextPath = join3(context.projectRoot, ".buddy", "creator", "initial-context.json");
|
|
877
920
|
const openArgs = [
|
|
878
921
|
"-B",
|
|
879
|
-
|
|
922
|
+
join3(skillRoot, "scripts/buddy.py"),
|
|
880
923
|
"open",
|
|
881
924
|
"--workspace",
|
|
882
925
|
workspacePath,
|
|
@@ -906,7 +949,7 @@ function handoffMarkdown(context, skillRoot, session, workspacePath) {
|
|
|
906
949
|
|
|
907
950
|
## \u73B0\u5728\u7531 Coding Agent \u6267\u884C
|
|
908
951
|
|
|
909
|
-
1. \u5B8C\u6574\u8BFB\u53D6 ${JSON.stringify(
|
|
952
|
+
1. \u5B8C\u6574\u8BFB\u53D6 ${JSON.stringify(join3(skillRoot, "SKILL.md"))} \u53CA\u5176\u6307\u5B9A\u7684 references\u3002\u4FDD\u7559\u539F\u6709\u56DB\u9636\u6BB5\u89C4\u5219\uFF0C\u7531\u5F53\u524D\u4E3B\u5BF9\u8BDD\u5B8C\u6210\u91C7\u8BBF\uFF1B\u65E0\u9700\u53E6\u8D77 Sub-agent \u6216\u8FD0\u884C\u91C7\u8BBF\u6A21\u578B\u670D\u52A1\u3002
|
|
910
953
|
2. \u67E5\u627E\u5BBF\u4E3B\u6216\u7CFB\u7EDF\u5B9E\u9645\u53EF\u7528\u7684 Python 3.9+\uFF0C\u68C0\u67E5\u7248\u672C\uFF0C\u4FDD\u5B58\u5176\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u8981\u5199\u6B7B\u672C\u673A\u8DEF\u5F84\uFF0C\u4E0D\u5B89\u88C5 pip/npm \u4F9D\u8D56\u3002\u6CA1\u6709 Python \u65F6\u62A5\u544A\u7F3A\u53E3\uFF0C\u4FDD\u7559\u672C\u5730\u9879\u76EE\u3002\u6240\u6709 Python \u8C03\u7528\u5747\u52A0 -B\uFF0C\u5E76\u8BBE\u7F6E\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF PYTHONDONTWRITEBYTECODE=1\uFF08\u7531\u9884\u89C8\u5B50\u8FDB\u7A0B\u7EE7\u627F\uFF09\uFF0C\u907F\u514D\u5411 CLI \u5B89\u88C5\u76EE\u5F55\u5199\u5165\u5B57\u8282\u7801\u7F13\u5B58\u3002
|
|
911
954
|
3. \u4F7F\u7528\u5DE5\u5177\u4F20\u9012\u72EC\u7ACB\u53C2\u6570\uFF0C\u4E0D\u62FC\u63A5 shell \u5B57\u7B26\u4E32\u3002\u5DE5\u4F5C\u76EE\u5F55\u4E3A ${JSON.stringify(context.projectRoot)}\uFF1B\u4EE5\u5B9E\u9645 Python \u8DEF\u5F84\u4E3A executable\uFF0C\u9996\u6B21\u6253\u5F00\u7684 argv \u5982\u4E0B\uFF1A
|
|
912
955
|
|
|
@@ -943,11 +986,11 @@ ${DEVELOPMENT_HANDOFF}
|
|
|
943
986
|
`;
|
|
944
987
|
}
|
|
945
988
|
async function prepareCreatorSkill(context, io, options = {}) {
|
|
946
|
-
const root =
|
|
989
|
+
const root = join3(context.projectRoot, ".buddy", "creator");
|
|
947
990
|
try {
|
|
948
|
-
await safeDirectory(
|
|
991
|
+
await safeDirectory(join3(context.projectRoot, ".buddy"));
|
|
949
992
|
await safeDirectory(root);
|
|
950
|
-
const sessionPath =
|
|
993
|
+
const sessionPath = join3(root, "session.json");
|
|
951
994
|
await writeOnce(sessionPath, JSON.stringify({
|
|
952
995
|
schemaVersion: 1,
|
|
953
996
|
platformBuddyId: context.buddyId,
|
|
@@ -959,11 +1002,11 @@ async function prepareCreatorSkill(context, io, options = {}) {
|
|
|
959
1002
|
}
|
|
960
1003
|
options.signal?.throwIfAborted();
|
|
961
1004
|
const skillRoot = await bundledSkill(options.bundledSkillRoot ?? BUNDLED_CREATOR_SKILL_ROOT);
|
|
962
|
-
await safeDirectory(
|
|
963
|
-
const workspacePath =
|
|
1005
|
+
await safeDirectory(join3(root, "workspaces"));
|
|
1006
|
+
const workspacePath = join3(root, "workspaces", session.creationKey);
|
|
964
1007
|
const workspace = await state(workspacePath);
|
|
965
1008
|
if (workspace && !workspace.isDirectory()) throw new CreatorPreparationError("\u5DF2\u6709\u521B\u4F5C\u5DE5\u4F5C\u533A\u4E0D\u662F\u666E\u901A\u76EE\u5F55\u3002");
|
|
966
|
-
const contextPath =
|
|
1009
|
+
const contextPath = join3(root, "initial-context.json");
|
|
967
1010
|
await writeOnce(contextPath, JSON.stringify({
|
|
968
1011
|
source: "platform_meta",
|
|
969
1012
|
name: context.appName,
|
|
@@ -979,7 +1022,7 @@ async function prepareCreatorSkill(context, io, options = {}) {
|
|
|
979
1022
|
} catch {
|
|
980
1023
|
throw new CreatorPreparationError(`\u5DF2\u6709\u521D\u59CB\u80CC\u666F\u65E0\u6548\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\uFF1A${contextPath}`);
|
|
981
1024
|
}
|
|
982
|
-
const handoffPath =
|
|
1025
|
+
const handoffPath = join3(root, "HANDOFF.md");
|
|
983
1026
|
const handoff = handoffMarkdown({
|
|
984
1027
|
...context,
|
|
985
1028
|
appName: initial.name,
|
|
@@ -990,11 +1033,11 @@ async function prepareCreatorSkill(context, io, options = {}) {
|
|
|
990
1033
|
if ((await safeRead(handoffPath))?.toString("utf8") !== handoff) {
|
|
991
1034
|
throw new CreatorPreparationError(`\u5DF2\u6709\u63A5\u7EED\u8BF4\u660E\u88AB\u4FEE\u6539\u6216\u9879\u76EE\u8DEF\u5F84\u5DF2\u53D8\u5316\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u3002\u8BF7\u5907\u4EFD\u79FB\u8D70\u8BE5\u8BF4\u660E\u540E\u91CD\u8BD5\uFF1A${handoffPath}`);
|
|
992
1035
|
}
|
|
993
|
-
io.write(`\u4F7F\u7528 CLI \u5185\u7F6E Creator Skill\uFF0C\u65E0\u9700\u989D\u5916\u4E0B\u8F7D\uFF1A${
|
|
1036
|
+
io.write(`\u4F7F\u7528 CLI \u5185\u7F6E Creator Skill\uFF0C\u65E0\u9700\u989D\u5916\u4E0B\u8F7D\uFF1A${join3(skillRoot, "SKILL.md")}`);
|
|
994
1037
|
io.write(`\u4E0B\u4E00\u6B65\u4EA4\u7ED9 Codex / Claude Code \u7B49 Coding Agent\uFF1A\u8BF7\u8BFB\u53D6 ${JSON.stringify(handoffPath)}\uFF0C\u6309\u5F53\u524D\u9636\u6BB5\u63A5\u7EED\uFF1BCreator \u5B8C\u6210\u540E\u8FD4\u56DE CLI \u5F00\u53D1\u6D41\u7A0B\u3002`);
|
|
995
1038
|
io.write(DEVELOPMENT_HANDOFF_SUMMARY);
|
|
996
1039
|
io.write("\u672C\u547D\u4EE4\u53EA\u8D1F\u8D23\u8EAB\u4EFD\u767B\u8BB0\u548C Skill \u51C6\u5907\uFF0C\u4E0D\u4EE3\u8868\u91C7\u8BBF\u3001Booklet \u6216 Agent \u5DF2\u5B8C\u6210\u3002\u8BF7\u6309\u5DF2\u6709\u4F5C\u54C1\u8FDB\u5EA6\u63A5\u7EED\uFF0C\u624B\u518C\u786E\u8BA4\u540E\u518D\u8865\u9F50\u5B98\u65B9 Runtime \u5DE5\u7A0B\uFF1B\u91CD\u590D create \u4F1A\u590D\u7528\u540C\u4E00\u4F5C\u54C1\u3002");
|
|
997
|
-
return { exitCode: 0, creator: { skillPath:
|
|
1040
|
+
return { exitCode: 0, creator: { skillPath: join3(skillRoot, "SKILL.md"), handoffPath, workspacePath } };
|
|
998
1041
|
} catch (cause) {
|
|
999
1042
|
const message = cause instanceof CreatorPreparationError ? cause.message : "\u5185\u7F6E\u8D44\u6E90\u8BFB\u53D6\u6216\u672C\u5730\u5199\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 CLI \u5B89\u88C5\u548C\u76EE\u5F55\u6743\u9650\u3002";
|
|
1000
1043
|
io.writeError(`Creator Skill \u51C6\u5907\u5931\u8D25\uFF1A${message}`);
|
|
@@ -1005,9 +1048,9 @@ async function prepareCreatorSkill(context, io, options = {}) {
|
|
|
1005
1048
|
|
|
1006
1049
|
// packages/cli-core/src/cloud/errors.ts
|
|
1007
1050
|
var CloudLifecycleError = class extends Error {
|
|
1008
|
-
constructor(
|
|
1051
|
+
constructor(code2, message, details = {}) {
|
|
1009
1052
|
super(message);
|
|
1010
|
-
this.code =
|
|
1053
|
+
this.code = code2;
|
|
1011
1054
|
this.name = "CloudLifecycleError";
|
|
1012
1055
|
this.details = Object.freeze({ ...details });
|
|
1013
1056
|
}
|
|
@@ -1345,9 +1388,9 @@ function buildAgentEnvironment(options) {
|
|
|
1345
1388
|
}
|
|
1346
1389
|
|
|
1347
1390
|
// packages/cli-core/src/dev/orchestrator.ts
|
|
1348
|
-
import { createHash } from "node:crypto";
|
|
1349
|
-
import { readFile as
|
|
1350
|
-
import { join as
|
|
1391
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1392
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1393
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
1351
1394
|
|
|
1352
1395
|
// packages/cli-core/src/dev/launch.ts
|
|
1353
1396
|
import { lstatSync, readFileSync } from "node:fs";
|
|
@@ -1443,9 +1486,9 @@ var NodeAgentProcessLauncher = class {
|
|
|
1443
1486
|
resolve12({ code: null, signal: null, error: error2 });
|
|
1444
1487
|
}
|
|
1445
1488
|
});
|
|
1446
|
-
child.once("exit", (
|
|
1489
|
+
child.once("exit", (code2, signal) => {
|
|
1447
1490
|
hasExited = true;
|
|
1448
|
-
resolve12({ code, signal });
|
|
1491
|
+
resolve12({ code: code2, signal });
|
|
1449
1492
|
});
|
|
1450
1493
|
});
|
|
1451
1494
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -1604,6 +1647,106 @@ function processTreeControllerFor(platform) {
|
|
|
1604
1647
|
});
|
|
1605
1648
|
}
|
|
1606
1649
|
|
|
1650
|
+
// packages/cli-core/src/dev/instance-lock.ts
|
|
1651
|
+
import { createHash, randomUUID as randomUUID3 } from "node:crypto";
|
|
1652
|
+
import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, rename as rename2, rmdir as rmdir2, unlink as unlink4, writeFile as writeFile2 } from "node:fs/promises";
|
|
1653
|
+
import { homedir } from "node:os";
|
|
1654
|
+
import { join as join4 } from "node:path";
|
|
1655
|
+
function alreadyRunningError() {
|
|
1656
|
+
const message = "\u8FD9\u4E2A\u642D\u5B50\u5DF2\u6709 DEV \u5B9E\u4F8B\u5728\u8FD0\u884C\u3002\u8BF7\u5148\u5728\u539F\u9884\u89C8\u9875\u70B9\u51FB\u300C\u505C\u6B62 DEV\u300D\uFF0C\u6216\u5728\u8FD0\u884C dev \u7684\u7EC8\u7AEF\u6309 Ctrl+C\uFF0C\u518D\u542F\u52A8\u3002";
|
|
1657
|
+
return Object.assign(new BuddyDevError({ code: "DEV_ALREADY_RUNNING", phase: "launch", message }), { publicMessage: message });
|
|
1658
|
+
}
|
|
1659
|
+
function alive(pid) {
|
|
1660
|
+
try {
|
|
1661
|
+
process.kill(pid, 0);
|
|
1662
|
+
return true;
|
|
1663
|
+
} catch (error2) {
|
|
1664
|
+
return error2.code !== "ESRCH";
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
function validPid(pid) {
|
|
1668
|
+
return Number.isSafeInteger(pid) && pid > 0;
|
|
1669
|
+
}
|
|
1670
|
+
function code(error2) {
|
|
1671
|
+
return error2?.code;
|
|
1672
|
+
}
|
|
1673
|
+
async function acquireDevInstanceLock(options) {
|
|
1674
|
+
const root = options.directory ?? join4(homedir(), ".buddy", "dev-locks");
|
|
1675
|
+
const key = createHash("sha256").update(`${new URL(options.gatewayUrl).origin}
|
|
1676
|
+
${options.devAgentId}`).digest("hex");
|
|
1677
|
+
const path = join4(root, key);
|
|
1678
|
+
const ownerName = `owner-${randomUUID3()}.json`;
|
|
1679
|
+
const candidate = join4(root, `.pending-${randomUUID3()}`);
|
|
1680
|
+
const owner = { pid: process.pid };
|
|
1681
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
1682
|
+
await mkdir4(candidate, { mode: 448 });
|
|
1683
|
+
await writeFile2(join4(candidate, ownerName), JSON.stringify(owner), { mode: 384, flag: "wx" });
|
|
1684
|
+
try {
|
|
1685
|
+
for (let attempt = 0; ; attempt++) {
|
|
1686
|
+
try {
|
|
1687
|
+
await rename2(candidate, path);
|
|
1688
|
+
break;
|
|
1689
|
+
} catch (error2) {
|
|
1690
|
+
if (!["EEXIST", "ENOTEMPTY"].includes(code(error2) ?? "")) throw error2;
|
|
1691
|
+
if (attempt >= 8) throw alreadyRunningError();
|
|
1692
|
+
}
|
|
1693
|
+
let names;
|
|
1694
|
+
try {
|
|
1695
|
+
names = await readdir2(path);
|
|
1696
|
+
} catch (error2) {
|
|
1697
|
+
if (code(error2) === "ENOENT") continue;
|
|
1698
|
+
throw error2;
|
|
1699
|
+
}
|
|
1700
|
+
if (names.length === 0) continue;
|
|
1701
|
+
if (names.length !== 1 || !/^owner-[a-f0-9-]+\.json$/u.test(names[0])) throw alreadyRunningError();
|
|
1702
|
+
const previousPath = join4(path, names[0]);
|
|
1703
|
+
let previous;
|
|
1704
|
+
try {
|
|
1705
|
+
previous = JSON.parse(await readFile4(previousPath, "utf8"));
|
|
1706
|
+
} catch (error2) {
|
|
1707
|
+
if (code(error2) === "ENOENT") continue;
|
|
1708
|
+
throw alreadyRunningError();
|
|
1709
|
+
}
|
|
1710
|
+
if (!validPid(previous.pid) || previous.runtimePid !== void 0 && !validPid(previous.runtimePid) || alive(previous.pid) || previous.runtimePid !== void 0 && alive(previous.runtimePid)) throw alreadyRunningError();
|
|
1711
|
+
try {
|
|
1712
|
+
await unlink4(previousPath);
|
|
1713
|
+
} catch (error2) {
|
|
1714
|
+
if (code(error2) === "ENOENT") continue;
|
|
1715
|
+
throw error2;
|
|
1716
|
+
}
|
|
1717
|
+
await rmdir2(path).catch((error2) => {
|
|
1718
|
+
if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(code(error2) ?? "")) throw error2;
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
} finally {
|
|
1722
|
+
await unlink4(join4(candidate, ownerName)).catch(() => void 0);
|
|
1723
|
+
await rmdir2(candidate).catch(() => void 0);
|
|
1724
|
+
}
|
|
1725
|
+
let released = false;
|
|
1726
|
+
return {
|
|
1727
|
+
async attachRuntime(pid) {
|
|
1728
|
+
if (!validPid(pid)) throw new TypeError("Runtime PID is invalid");
|
|
1729
|
+
const temporary = join4(root, `.update-${randomUUID3()}`);
|
|
1730
|
+
try {
|
|
1731
|
+
await writeFile2(temporary, JSON.stringify({ ...owner, runtimePid: pid }), { mode: 384, flag: "wx" });
|
|
1732
|
+
await rename2(temporary, join4(path, ownerName));
|
|
1733
|
+
} finally {
|
|
1734
|
+
await unlink4(temporary).catch(() => void 0);
|
|
1735
|
+
}
|
|
1736
|
+
},
|
|
1737
|
+
async release() {
|
|
1738
|
+
if (released) return;
|
|
1739
|
+
released = true;
|
|
1740
|
+
await unlink4(join4(path, ownerName)).catch((error2) => {
|
|
1741
|
+
if (code(error2) !== "ENOENT") throw error2;
|
|
1742
|
+
});
|
|
1743
|
+
await rmdir2(path).catch((error2) => {
|
|
1744
|
+
if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(code(error2) ?? "")) throw error2;
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1607
1750
|
// packages/cli-core/src/dev/orchestrator.ts
|
|
1608
1751
|
var DEFAULT_ONLINE_TIMEOUT_MS = 3e4;
|
|
1609
1752
|
var DEFAULT_POLL_INTERVAL_MS = 200;
|
|
@@ -1740,6 +1883,7 @@ var ReadyBuddyDevHandle = class {
|
|
|
1740
1883
|
completion;
|
|
1741
1884
|
#process;
|
|
1742
1885
|
#processTree;
|
|
1886
|
+
#instanceLock;
|
|
1743
1887
|
#lifecycle;
|
|
1744
1888
|
#stopGracePeriodMs;
|
|
1745
1889
|
#onStateChange;
|
|
@@ -1755,6 +1899,7 @@ var ReadyBuddyDevHandle = class {
|
|
|
1755
1899
|
this.gatewayUrl = new URL(options.gatewayUrl);
|
|
1756
1900
|
this.#process = options.process;
|
|
1757
1901
|
this.#processTree = options.processTree;
|
|
1902
|
+
this.#instanceLock = options.instanceLock;
|
|
1758
1903
|
this.#lifecycle = options.lifecycle;
|
|
1759
1904
|
this.#stopGracePeriodMs = options.stopGracePeriodMs;
|
|
1760
1905
|
this.#signal = options.signal;
|
|
@@ -1785,6 +1930,7 @@ var ReadyBuddyDevHandle = class {
|
|
|
1785
1930
|
this.#lifecycle.abort(new Error("DEV session stopped"));
|
|
1786
1931
|
try {
|
|
1787
1932
|
await this.#processTree.terminate(this.#process, { gracePeriodMs: this.#stopGracePeriodMs });
|
|
1933
|
+
await this.#instanceLock.release();
|
|
1788
1934
|
this.#emit("stopped");
|
|
1789
1935
|
this.#finish({ state: "stopped" });
|
|
1790
1936
|
} catch (cause) {
|
|
@@ -1805,7 +1951,7 @@ var ReadyBuddyDevHandle = class {
|
|
|
1805
1951
|
this.#lifecycle.abort(error2);
|
|
1806
1952
|
await this.#processTree.terminate(this.#process, {
|
|
1807
1953
|
gracePeriodMs: Math.min(this.#stopGracePeriodMs, 1e3)
|
|
1808
|
-
}).catch(() => void 0);
|
|
1954
|
+
}).then(() => this.#instanceLock.release()).catch(() => void 0);
|
|
1809
1955
|
this.#finish({ state: "failed", error: error2 });
|
|
1810
1956
|
}
|
|
1811
1957
|
#emit(state2) {
|
|
@@ -1838,6 +1984,7 @@ async function startBuddyDev(options) {
|
|
|
1838
1984
|
const signal = options.signal === void 0 ? lifecycle.signal : AbortSignal.any([lifecycle.signal, options.signal]);
|
|
1839
1985
|
let agentProcess;
|
|
1840
1986
|
let processTree;
|
|
1987
|
+
let instanceLock;
|
|
1841
1988
|
const emit = (state2) => {
|
|
1842
1989
|
try {
|
|
1843
1990
|
options.onStateChange?.(state2);
|
|
@@ -1850,7 +1997,7 @@ async function startBuddyDev(options) {
|
|
|
1850
1997
|
let config;
|
|
1851
1998
|
let configSource;
|
|
1852
1999
|
try {
|
|
1853
|
-
configSource = await
|
|
2000
|
+
configSource = await readFile5(join5(projectRoot, BUDDY_AGENT_FILENAME), "utf8");
|
|
1854
2001
|
config = combineBuddyProject(parseBuddyAgentConfig(configSource));
|
|
1855
2002
|
} catch (cause) {
|
|
1856
2003
|
throw new BuddyDevError({
|
|
@@ -1865,10 +2012,20 @@ async function startBuddyDev(options) {
|
|
|
1865
2012
|
client: options.registrationClient,
|
|
1866
2013
|
devAgentId,
|
|
1867
2014
|
configSource,
|
|
1868
|
-
configDigest: `sha256:${
|
|
2015
|
+
configDigest: `sha256:${createHash2("sha256").update(configSource).digest("hex")}`,
|
|
1869
2016
|
signal
|
|
1870
2017
|
});
|
|
1871
2018
|
throwIfDevAborted(signal);
|
|
2019
|
+
instanceLock = await acquireDevInstanceLock({
|
|
2020
|
+
gatewayUrl: registration.appServerUrl,
|
|
2021
|
+
devAgentId,
|
|
2022
|
+
directory: options.instanceLockDirectory
|
|
2023
|
+
});
|
|
2024
|
+
const online = await options.registrationClient.listOnline({
|
|
2025
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(onlineTimeoutMs)])
|
|
2026
|
+
});
|
|
2027
|
+
if (online.some((item) => item.devAgentId === devAgentId && item.online)) throw alreadyRunningError();
|
|
2028
|
+
throwIfDevAborted(signal);
|
|
1872
2029
|
const environment = buildAgentEnvironment({
|
|
1873
2030
|
parentEnv: options.parentEnv ?? process.env,
|
|
1874
2031
|
buddyId: devAgentId,
|
|
@@ -1889,6 +2046,7 @@ async function startBuddyDev(options) {
|
|
|
1889
2046
|
onStderr: (text) => options.onLog?.({ source: "agent.stderr", text })
|
|
1890
2047
|
}
|
|
1891
2048
|
});
|
|
2049
|
+
await instanceLock.attachRuntime(agentProcess.pid);
|
|
1892
2050
|
const exited = agentProcess.exit.then((exit) => {
|
|
1893
2051
|
throw agentExitedError(exit);
|
|
1894
2052
|
});
|
|
@@ -1910,6 +2068,7 @@ async function startBuddyDev(options) {
|
|
|
1910
2068
|
gatewayUrl: new URL(environment.MLB_GATEWAY_URL),
|
|
1911
2069
|
process: agentProcess,
|
|
1912
2070
|
processTree,
|
|
2071
|
+
instanceLock,
|
|
1913
2072
|
lifecycle,
|
|
1914
2073
|
stopGracePeriodMs,
|
|
1915
2074
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
@@ -1920,7 +2079,9 @@ async function startBuddyDev(options) {
|
|
|
1920
2079
|
const aborted = options.signal?.aborted || isAbortLike(cause) || cause instanceof BuddyDevError && cause.code === "DEV_ABORTED";
|
|
1921
2080
|
if (agentProcess !== void 0 || aborted) emit("stopping");
|
|
1922
2081
|
if (agentProcess !== void 0 && processTree !== void 0) {
|
|
1923
|
-
await processTree.terminate(agentProcess, { gracePeriodMs: stopGracePeriodMs }).catch(() => void 0);
|
|
2082
|
+
await processTree.terminate(agentProcess, { gracePeriodMs: stopGracePeriodMs }).then(() => instanceLock?.release()).catch(() => void 0);
|
|
2083
|
+
} else {
|
|
2084
|
+
await instanceLock?.release();
|
|
1924
2085
|
}
|
|
1925
2086
|
if (aborted) {
|
|
1926
2087
|
emit("stopped");
|
|
@@ -1959,12 +2120,12 @@ function createBuddyDevStarter(options = {}) {
|
|
|
1959
2120
|
}
|
|
1960
2121
|
|
|
1961
2122
|
// packages/cli-core/src/project-context.ts
|
|
1962
|
-
import { lstat as
|
|
1963
|
-
import { dirname as dirname2, join as
|
|
2123
|
+
import { lstat as lstat5, realpath } from "node:fs/promises";
|
|
2124
|
+
import { dirname as dirname2, join as join6, resolve as resolve6 } from "node:path";
|
|
1964
2125
|
var BuddyProjectContextError = class extends Error {
|
|
1965
|
-
constructor(
|
|
2126
|
+
constructor(code2, message, targetPath, options) {
|
|
1966
2127
|
super(message, options);
|
|
1967
|
-
this.code =
|
|
2128
|
+
this.code = code2;
|
|
1968
2129
|
this.targetPath = targetPath;
|
|
1969
2130
|
this.name = "BuddyProjectContextError";
|
|
1970
2131
|
}
|
|
@@ -1975,7 +2136,7 @@ async function existingDirectory(target) {
|
|
|
1975
2136
|
const requested = resolve6(target);
|
|
1976
2137
|
let entry;
|
|
1977
2138
|
try {
|
|
1978
|
-
entry = await
|
|
2139
|
+
entry = await lstat5(requested);
|
|
1979
2140
|
} catch (cause) {
|
|
1980
2141
|
if (cause.code === "ENOENT") {
|
|
1981
2142
|
throw new BuddyProjectContextError(
|
|
@@ -1997,9 +2158,9 @@ async function existingDirectory(target) {
|
|
|
1997
2158
|
return realpath(requested);
|
|
1998
2159
|
}
|
|
1999
2160
|
async function projectFileState(directory, filename) {
|
|
2000
|
-
const configPath =
|
|
2161
|
+
const configPath = join6(directory, filename);
|
|
2001
2162
|
try {
|
|
2002
|
-
const entry = await
|
|
2163
|
+
const entry = await lstat5(configPath);
|
|
2003
2164
|
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
2004
2165
|
throw new BuddyProjectContextError(
|
|
2005
2166
|
"PROJECT_CONFIG_INVALID",
|
|
@@ -2040,15 +2201,17 @@ async function requireBuddyProjectRoot(startDirectory) {
|
|
|
2040
2201
|
}
|
|
2041
2202
|
async function requireBuddyAgentProjectRoot(startDirectory) {
|
|
2042
2203
|
const root = await requireBuddyProjectRoot(startDirectory);
|
|
2043
|
-
const agentPath =
|
|
2204
|
+
const agentPath = join6(root, BUDDY_AGENT_FILENAME);
|
|
2044
2205
|
try {
|
|
2045
2206
|
await readBuddyAgentConfig(root);
|
|
2207
|
+
const missing = await missingBuddyAgentFiles(root);
|
|
2208
|
+
if (missing.length > 0) throw new Error(`\u5DE5\u7A0B\u7F3A\u5C11\u6709\u6548\u6587\u4EF6\uFF1A${missing.join("\u3001")}`);
|
|
2046
2209
|
return root;
|
|
2047
2210
|
} catch (cause) {
|
|
2048
2211
|
throw new BuddyProjectContextError(
|
|
2049
2212
|
"PROJECT_NOT_FOUND",
|
|
2050
2213
|
[
|
|
2051
|
-
`\u5F53\u524D
|
|
2214
|
+
`\u5F53\u524D buddy.agent.json \u6240\u5728\u5DE5\u7A0B\u5C1A\u4E0D\u53EF\u8FD0\u884C\uFF1A${cause instanceof Error ? cause.message : "\u8BF7\u68C0\u67E5 buddy.agent.json \u4E0E\u5DE5\u7A0B\u6587\u4EF6"}`,
|
|
2052
2215
|
"\u8BF7\u5148\u8FD0\u884C buddy-cli init --mode direct \u8865\u9F50\u5B98\u65B9 Runtime \u5DE5\u7A0B\uFF0C\u518D\u8FD0\u884C dev\u3001preview \u6216 push\u3002"
|
|
2053
2216
|
].join("\n"),
|
|
2054
2217
|
agentPath,
|
|
@@ -2060,7 +2223,7 @@ async function nearestExistingDirectory(target) {
|
|
|
2060
2223
|
let candidate = resolve6(target);
|
|
2061
2224
|
while (true) {
|
|
2062
2225
|
try {
|
|
2063
|
-
const entry = await
|
|
2226
|
+
const entry = await lstat5(candidate);
|
|
2064
2227
|
if (!entry.isDirectory()) {
|
|
2065
2228
|
throw new BuddyProjectContextError(
|
|
2066
2229
|
"PROJECT_DIRECTORY_INVALID",
|
|
@@ -2109,7 +2272,7 @@ async function assertInitializableBuddyTarget(target) {
|
|
|
2109
2272
|
} catch (cause) {
|
|
2110
2273
|
throw new BuddyProjectContextError(
|
|
2111
2274
|
"PROJECT_CONFIG_INVALID",
|
|
2112
|
-
`${
|
|
2275
|
+
`${join6(projectRoot, BUDDY_AGENT_FILENAME)} \u4E0D\u662F\u6709\u6548\u7684\u9879\u76EE\u6E05\u5355\u3002`,
|
|
2113
2276
|
requested,
|
|
2114
2277
|
{ cause }
|
|
2115
2278
|
);
|
|
@@ -2261,7 +2424,7 @@ async function runDevCommand(args, io, options = {}) {
|
|
|
2261
2424
|
import { resolve as resolve8 } from "node:path";
|
|
2262
2425
|
|
|
2263
2426
|
// packages/cli-core/src/cloud/auth.ts
|
|
2264
|
-
import { randomUUID as
|
|
2427
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
2265
2428
|
|
|
2266
2429
|
// packages/cli-core/src/cloud/validation.ts
|
|
2267
2430
|
function record(value, path = "request") {
|
|
@@ -2329,7 +2492,7 @@ var CloudAuthenticator = class {
|
|
|
2329
2492
|
this.#adapter = adapter;
|
|
2330
2493
|
this.#store = store;
|
|
2331
2494
|
this.#now = options.now ?? Date.now;
|
|
2332
|
-
this.#createState = options.createState ??
|
|
2495
|
+
this.#createState = options.createState ?? randomUUID4;
|
|
2333
2496
|
this.#minimumValidityMs = options.minimumValidityMs ?? 3e4;
|
|
2334
2497
|
if (!Number.isSafeInteger(this.#minimumValidityMs) || this.#minimumValidityMs < 0) {
|
|
2335
2498
|
throw new TypeError("minimumValidityMs must be a non-negative safe integer");
|
|
@@ -2548,8 +2711,8 @@ async function boundedJson(response) {
|
|
|
2548
2711
|
function serverError(response, body) {
|
|
2549
2712
|
const source = typeof body === "object" && body !== null && !Array.isArray(body) ? body : void 0;
|
|
2550
2713
|
const message = typeof source?.error === "string" && source.error.trim() ? source.error.slice(0, 8 * 1024) : `\u5E73\u53F0\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`;
|
|
2551
|
-
const
|
|
2552
|
-
return new CloudLifecycleError(
|
|
2714
|
+
const code2 = response.status === 401 ? "AUTH_TOKEN_EXPIRED" : response.status === 404 ? "APP_NOT_FOUND" : response.status >= 500 ? "PLATFORM_UNAVAILABLE" : "INVALID_REQUEST";
|
|
2715
|
+
return new CloudLifecycleError(code2, message, {
|
|
2553
2716
|
httpStatus: response.status,
|
|
2554
2717
|
retryable: response.status === 408 || response.status === 429 || response.status >= 500
|
|
2555
2718
|
});
|
|
@@ -2703,7 +2866,7 @@ var HttpDeveloperPlatformClient = class {
|
|
|
2703
2866
|
};
|
|
2704
2867
|
|
|
2705
2868
|
// packages/cli-core/src/cloud/browser-login.ts
|
|
2706
|
-
import { createHash as
|
|
2869
|
+
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
2707
2870
|
import { spawn } from "node:child_process";
|
|
2708
2871
|
import { createServer } from "node:http";
|
|
2709
2872
|
|
|
@@ -2766,7 +2929,7 @@ async function openExternalUrl(url) {
|
|
|
2766
2929
|
await new Promise((resolve12, reject) => {
|
|
2767
2930
|
const child = spawn(launch.command, launch.args, { shell: false, stdio: "ignore", windowsHide: true });
|
|
2768
2931
|
child.once("error", reject);
|
|
2769
|
-
child.once("exit", (
|
|
2932
|
+
child.once("exit", (code2) => code2 === 0 ? resolve12() : reject(new Error(`browser launcher exited with ${code2 ?? "unknown"}`)));
|
|
2770
2933
|
});
|
|
2771
2934
|
}
|
|
2772
2935
|
async function write(res, status, title, message, webUrl, tone = "error") {
|
|
@@ -2811,7 +2974,7 @@ var LoopbackBrowserLoginAdapter = class {
|
|
|
2811
2974
|
if (request.signal?.aborted) throw request.signal.reason;
|
|
2812
2975
|
const callbackSecret = randomBytes(24).toString("hex");
|
|
2813
2976
|
const codeVerifier = randomBytes(32).toString("base64url");
|
|
2814
|
-
const codeChallenge =
|
|
2977
|
+
const codeChallenge = createHash3("sha256").update(codeVerifier).digest("base64url");
|
|
2815
2978
|
let requestId = "";
|
|
2816
2979
|
let settle;
|
|
2817
2980
|
let reject;
|
|
@@ -2895,15 +3058,15 @@ var LoopbackBrowserLoginAdapter = class {
|
|
|
2895
3058
|
await write(res, 200, "\u5DF2\u53D6\u6D88\u6388\u6743", "\u6CA1\u6709\u5411 buddy-cli \u63D0\u4F9B\u8D26\u53F7\u6743\u9650\uFF0C\u53EF\u4EE5\u5173\u95ED\u8FD9\u4E2A\u9875\u9762\u3002", this.#developerWebUrl, "neutral");
|
|
2896
3059
|
throw new CloudLifecycleError("AUTH_LOGIN_CANCELLED", "\u7528\u6237\u53D6\u6D88\u4E86 CLI \u6388\u6743");
|
|
2897
3060
|
}
|
|
2898
|
-
const
|
|
2899
|
-
if (!
|
|
3061
|
+
const code2 = url.searchParams.get("code");
|
|
3062
|
+
if (!code2 || !AUTH_CODE.test(code2) || !expected.requestId) {
|
|
2900
3063
|
await write(res, 400, "\u767B\u5F55\u6CA1\u6709\u5B8C\u6210", "\u6388\u6743\u7ED3\u679C\u65E0\u6548\uFF0C\u8BF7\u56DE\u5230\u7EC8\u7AEF\u91CD\u8BD5\u3002", this.#developerWebUrl);
|
|
2901
3064
|
throw new CloudLifecycleError("PLATFORM_RESPONSE_INVALID", "\u642D\u642D\u521B\u4F5C\u8005\u5E73\u53F0\u8FD4\u56DE\u4E86\u65E0\u6548\u6388\u6743\u7801");
|
|
2902
3065
|
}
|
|
2903
3066
|
try {
|
|
2904
3067
|
const exchanged = await this.#post("api/cli/exchange", {
|
|
2905
3068
|
requestId: expected.requestId,
|
|
2906
|
-
code,
|
|
3069
|
+
code: code2,
|
|
2907
3070
|
codeVerifier: expected.codeVerifier
|
|
2908
3071
|
});
|
|
2909
3072
|
if (exchanged.platformOrigin !== this.#platformUrl.origin || typeof exchanged.token !== "string" || typeof exchanged.userId !== "string" || !Number.isSafeInteger(exchanged.expiresAt)) {
|
|
@@ -3006,8 +3169,8 @@ async function runCredentialCommand(input) {
|
|
|
3006
3169
|
child.stdout.on("data", (chunk) => collect(stdout, chunk));
|
|
3007
3170
|
child.stderr.on("data", (chunk) => collect(stderr, chunk));
|
|
3008
3171
|
child.once("error", reject);
|
|
3009
|
-
child.once("close", (
|
|
3010
|
-
code:
|
|
3172
|
+
child.once("close", (code2) => resolve12({
|
|
3173
|
+
code: code2 ?? 1,
|
|
3011
3174
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
3012
3175
|
stderr: Buffer.concat(stderr).toString("utf8")
|
|
3013
3176
|
}));
|
|
@@ -3360,8 +3523,8 @@ async function runCloudCommand(command, args, io, options = {}) {
|
|
|
3360
3523
|
var CLOUD_COMMAND_HELP = HELP;
|
|
3361
3524
|
|
|
3362
3525
|
// packages/cli-core/src/migration/index.ts
|
|
3363
|
-
import { lstat as
|
|
3364
|
-
import { extname, isAbsolute as isAbsolute2, join as
|
|
3526
|
+
import { lstat as lstat6, open as open3, readdir as readdir3, realpath as realpath2, stat } from "node:fs/promises";
|
|
3527
|
+
import { extname, isAbsolute as isAbsolute2, join as join7, relative, resolve as resolve9, sep, win32 as win322 } from "node:path";
|
|
3365
3528
|
var DEFAULT_SKILL_SCAN_LIMITS = Object.freeze({
|
|
3366
3529
|
maxFiles: 128,
|
|
3367
3530
|
maxFileBytes: 256 * 1024,
|
|
@@ -3404,20 +3567,20 @@ var KNOWN_HOST_TOOLS = [
|
|
|
3404
3567
|
"WebSearch",
|
|
3405
3568
|
"Write"
|
|
3406
3569
|
];
|
|
3407
|
-
function issue(
|
|
3408
|
-
return Object.freeze({ code, path, line, severity, message });
|
|
3570
|
+
function issue(code2, path, line, severity, message) {
|
|
3571
|
+
return Object.freeze({ code: code2, path, line, severity, message });
|
|
3409
3572
|
}
|
|
3410
|
-
function rejectScan(
|
|
3573
|
+
function rejectScan(code2, path, message, cause) {
|
|
3411
3574
|
throw new SkillPortabilityScanError(
|
|
3412
|
-
issue(
|
|
3575
|
+
issue(code2, path, 1, "error", message),
|
|
3413
3576
|
cause === void 0 ? void 0 : { cause }
|
|
3414
3577
|
);
|
|
3415
3578
|
}
|
|
3416
|
-
function addFinding(state2,
|
|
3417
|
-
const key = `${
|
|
3579
|
+
function addFinding(state2, code2, path, line, severity, message) {
|
|
3580
|
+
const key = `${code2}\0${path}\0${line}\0${message}`;
|
|
3418
3581
|
if (state2.issueKeys.has(key)) return;
|
|
3419
3582
|
state2.issueKeys.add(key);
|
|
3420
|
-
state2.issues.push(issue(
|
|
3583
|
+
state2.issues.push(issue(code2, path, line, severity, message));
|
|
3421
3584
|
}
|
|
3422
3585
|
function compareText(left, right) {
|
|
3423
3586
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -3530,9 +3693,9 @@ async function existingProjectRoot(value) {
|
|
|
3530
3693
|
}
|
|
3531
3694
|
}
|
|
3532
3695
|
async function existingSkillRoot(projectRoot, skillPath) {
|
|
3533
|
-
const candidate = skillPath === "." ? projectRoot :
|
|
3696
|
+
const candidate = skillPath === "." ? projectRoot : join7(projectRoot, ...skillPath.split("/"));
|
|
3534
3697
|
try {
|
|
3535
|
-
await
|
|
3698
|
+
await lstat6(candidate);
|
|
3536
3699
|
} catch (cause) {
|
|
3537
3700
|
if (filesystemCode(cause) === "ENOENT") {
|
|
3538
3701
|
return rejectScan("SKILL_ROOT_NOT_FOUND", skillPath, "\u627E\u4E0D\u5230\u6307\u5B9A\u7684 Skill \u76EE\u5F55\u3002");
|
|
@@ -3567,9 +3730,9 @@ async function existingSkillRoot(projectRoot, skillPath) {
|
|
|
3567
3730
|
}
|
|
3568
3731
|
async function assertEntrypoint(context) {
|
|
3569
3732
|
const entryPath = reportChild(context.skillPath, "SKILL.md");
|
|
3570
|
-
const candidate =
|
|
3733
|
+
const candidate = join7(context.skillRoot, "SKILL.md");
|
|
3571
3734
|
try {
|
|
3572
|
-
await
|
|
3735
|
+
await lstat6(candidate);
|
|
3573
3736
|
const target = await realpath2(candidate);
|
|
3574
3737
|
if (!isWithin(context.projectRoot, target) || !isWithin(context.skillRoot, target)) {
|
|
3575
3738
|
rejectScan(
|
|
@@ -3634,7 +3797,7 @@ async function walkDirectory(actualDirectory, reportDirectory, ancestors, contex
|
|
|
3634
3797
|
nextAncestors.add(directoryKey);
|
|
3635
3798
|
let entries;
|
|
3636
3799
|
try {
|
|
3637
|
-
entries = await
|
|
3800
|
+
entries = await readdir3(directory, { withFileTypes: true, encoding: "utf8" });
|
|
3638
3801
|
} catch (cause) {
|
|
3639
3802
|
return rejectScan("SKILL_SCAN_IO_ERROR", reportDirectory, "\u65E0\u6CD5\u5217\u51FA Skill \u5B50\u76EE\u5F55\u3002", cause);
|
|
3640
3803
|
}
|
|
@@ -3671,7 +3834,7 @@ async function walkDirectory(actualDirectory, reportDirectory, ancestors, contex
|
|
|
3671
3834
|
}
|
|
3672
3835
|
for (const entry of entries) {
|
|
3673
3836
|
const childReportPath = reportChild(reportDirectory, entry.name);
|
|
3674
|
-
const childPath =
|
|
3837
|
+
const childPath = join7(directory, entry.name);
|
|
3675
3838
|
let target;
|
|
3676
3839
|
try {
|
|
3677
3840
|
target = await realpath2(childPath);
|
|
@@ -4125,9 +4288,9 @@ async function scanExternalSkillPortability(options) {
|
|
|
4125
4288
|
}
|
|
4126
4289
|
|
|
4127
4290
|
// packages/cli-core/src/push/preflight.ts
|
|
4128
|
-
import { createHash as
|
|
4129
|
-
import { lstat as
|
|
4130
|
-
import { isAbsolute as isAbsolute3, join as
|
|
4291
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4292
|
+
import { lstat as lstat7, open as open4, readdir as readdir4, realpath as realpath3 } from "node:fs/promises";
|
|
4293
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve10, sep as sep2, win32 as win323 } from "node:path";
|
|
4131
4294
|
|
|
4132
4295
|
// packages/cli-core/src/push/errors.ts
|
|
4133
4296
|
var PushPreflightError = class extends Error {
|
|
@@ -4225,7 +4388,7 @@ function error(options) {
|
|
|
4225
4388
|
throw new PushPreflightError(options);
|
|
4226
4389
|
}
|
|
4227
4390
|
function sha256(value) {
|
|
4228
|
-
return `sha256:${
|
|
4391
|
+
return `sha256:${createHash4("sha256").update(value).digest("hex")}`;
|
|
4229
4392
|
}
|
|
4230
4393
|
function positiveLimit(value, name, hardMaximum) {
|
|
4231
4394
|
if (!Number.isSafeInteger(value) || value < 1 || value > hardMaximum) {
|
|
@@ -4318,7 +4481,7 @@ async function canonicalProjectRoot(projectRoot) {
|
|
|
4318
4481
|
const absolute = resolve10(projectRoot);
|
|
4319
4482
|
let entry;
|
|
4320
4483
|
try {
|
|
4321
|
-
entry = await
|
|
4484
|
+
entry = await lstat7(absolute);
|
|
4322
4485
|
} catch (cause) {
|
|
4323
4486
|
error({
|
|
4324
4487
|
code: "PUSH_PROJECT_INVALID",
|
|
@@ -4346,10 +4509,10 @@ async function canonicalProjectRoot(projectRoot) {
|
|
|
4346
4509
|
}
|
|
4347
4510
|
}
|
|
4348
4511
|
async function captureConfigWithinLimit(projectRoot, maxFileBytes, filename) {
|
|
4349
|
-
const path =
|
|
4512
|
+
const path = join8(projectRoot, filename);
|
|
4350
4513
|
let entry;
|
|
4351
4514
|
try {
|
|
4352
|
-
entry = await
|
|
4515
|
+
entry = await lstat7(path);
|
|
4353
4516
|
} catch (cause) {
|
|
4354
4517
|
error({
|
|
4355
4518
|
code: "PUSH_CONFIG_INVALID",
|
|
@@ -4416,7 +4579,7 @@ function parseCapturedProject(agentFile) {
|
|
|
4416
4579
|
async function selectLockfile(projectRoot) {
|
|
4417
4580
|
let names;
|
|
4418
4581
|
try {
|
|
4419
|
-
names = await
|
|
4582
|
+
names = await readdir4(projectRoot);
|
|
4420
4583
|
} catch (cause) {
|
|
4421
4584
|
error({
|
|
4422
4585
|
code: "PUSH_PROJECT_INVALID",
|
|
@@ -4471,7 +4634,7 @@ async function hashRegularFile(options) {
|
|
|
4471
4634
|
message: `\u751F\u6210\u6E05\u5355\u65F6\u6587\u4EF6\u53D1\u751F\u53D8\u5316\uFF1A${options.portablePath}`
|
|
4472
4635
|
});
|
|
4473
4636
|
}
|
|
4474
|
-
const hash =
|
|
4637
|
+
const hash = createHash4("sha256");
|
|
4475
4638
|
const captured = [];
|
|
4476
4639
|
const buffer = Buffer.allocUnsafe(READ_BUFFER_BYTES);
|
|
4477
4640
|
let bytes = 0;
|
|
@@ -4525,7 +4688,7 @@ async function createManifest(options) {
|
|
|
4525
4688
|
}
|
|
4526
4689
|
let names;
|
|
4527
4690
|
try {
|
|
4528
|
-
names = await
|
|
4691
|
+
names = await readdir4(directory);
|
|
4529
4692
|
} catch (cause) {
|
|
4530
4693
|
error({
|
|
4531
4694
|
code: "PUSH_PROJECT_INVALID",
|
|
@@ -4549,10 +4712,10 @@ async function createManifest(options) {
|
|
|
4549
4712
|
sensitiveEntriesExcluded += 1;
|
|
4550
4713
|
continue;
|
|
4551
4714
|
}
|
|
4552
|
-
const absolutePath =
|
|
4715
|
+
const absolutePath = join8(directory, name);
|
|
4553
4716
|
let entry;
|
|
4554
4717
|
try {
|
|
4555
|
-
entry = await
|
|
4718
|
+
entry = await lstat7(absolutePath);
|
|
4556
4719
|
} catch (cause) {
|
|
4557
4720
|
error({
|
|
4558
4721
|
code: "PUSH_FILE_CHANGED",
|
|
@@ -4739,8 +4902,8 @@ function bundleDigest(files) {
|
|
|
4739
4902
|
});
|
|
4740
4903
|
return sha256(`buddy-push-bundle-v1\0${descriptor}`);
|
|
4741
4904
|
}
|
|
4742
|
-
function warning(
|
|
4743
|
-
return Object.freeze({ code, message });
|
|
4905
|
+
function warning(code2, message) {
|
|
4906
|
+
return Object.freeze({ code: code2, message });
|
|
4744
4907
|
}
|
|
4745
4908
|
function assertOfficialProjectIncluded(files) {
|
|
4746
4909
|
for (const path of OFFICIAL_RUNTIME_INPUTS) {
|
|
@@ -4801,8 +4964,8 @@ async function assertCapturedConfigStable(options) {
|
|
|
4801
4964
|
}
|
|
4802
4965
|
let current;
|
|
4803
4966
|
try {
|
|
4804
|
-
const path =
|
|
4805
|
-
const entry = await
|
|
4967
|
+
const path = join8(options.projectRoot, options.filename);
|
|
4968
|
+
const entry = await lstat7(path);
|
|
4806
4969
|
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
4807
4970
|
error({
|
|
4808
4971
|
code: "PUSH_FILE_CHANGED",
|
|
@@ -4844,7 +5007,7 @@ async function createPushPreflight(options) {
|
|
|
4844
5007
|
limits.maxFileBytes,
|
|
4845
5008
|
BUDDY_AGENT_FILENAME
|
|
4846
5009
|
);
|
|
4847
|
-
|
|
5010
|
+
parseCapturedProject(initialAgentEntry);
|
|
4848
5011
|
const lockfile = await selectLockfile(projectRoot);
|
|
4849
5012
|
assertBookletIsNotRequiredInput(bookletPath, lockfile);
|
|
4850
5013
|
const manifest = await createManifest({
|
|
@@ -4902,7 +5065,7 @@ async function createPushPreflight(options) {
|
|
|
4902
5065
|
schemaVersion: "1",
|
|
4903
5066
|
assurance: "preflight-only",
|
|
4904
5067
|
projectRoot,
|
|
4905
|
-
runtimeLanguage:
|
|
5068
|
+
runtimeLanguage: "node",
|
|
4906
5069
|
...bookletPath === void 0 ? {} : { bookletPath },
|
|
4907
5070
|
lockfile,
|
|
4908
5071
|
limits,
|
|
@@ -4916,16 +5079,16 @@ async function createPushPreflight(options) {
|
|
|
4916
5079
|
}
|
|
4917
5080
|
|
|
4918
5081
|
// packages/cli-core/src/push/bundle.ts
|
|
4919
|
-
import { createHash as
|
|
4920
|
-
import { lstat as
|
|
4921
|
-
import { join as
|
|
5082
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
5083
|
+
import { lstat as lstat8, readFile as readFile6, realpath as realpath4 } from "node:fs/promises";
|
|
5084
|
+
import { join as join9, relative as relative3, sep as sep3, win32 as win324 } from "node:path";
|
|
4922
5085
|
import { Writable } from "node:stream";
|
|
4923
5086
|
import { pipeline } from "node:stream/promises";
|
|
4924
5087
|
import { createGzip } from "node:zlib";
|
|
4925
5088
|
import { pack } from "tar-stream";
|
|
4926
5089
|
var MEDIA_TYPE = "application/gzip";
|
|
4927
5090
|
function digest(bytes) {
|
|
4928
|
-
return `sha256:${
|
|
5091
|
+
return `sha256:${createHash5("sha256").update(bytes).digest("hex")}`;
|
|
4929
5092
|
}
|
|
4930
5093
|
function changed(path, message, cause) {
|
|
4931
5094
|
throw new PushPreflightError({
|
|
@@ -4940,15 +5103,15 @@ function inside(root, target) {
|
|
|
4940
5103
|
return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !path.startsWith("/") && !win324.isAbsolute(path);
|
|
4941
5104
|
}
|
|
4942
5105
|
async function capture(root, file) {
|
|
4943
|
-
const absolute =
|
|
5106
|
+
const absolute = join9(root, ...file.path.split("/"));
|
|
4944
5107
|
try {
|
|
4945
|
-
const entry = await
|
|
5108
|
+
const entry = await lstat8(absolute);
|
|
4946
5109
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.size !== file.bytes) {
|
|
4947
5110
|
changed(file.path, `\u63D0\u4EA4\u524D\u6587\u4EF6\u53D1\u751F\u53D8\u5316\uFF1A${file.path}`);
|
|
4948
5111
|
}
|
|
4949
5112
|
const canonical = await realpath4(absolute);
|
|
4950
5113
|
if (!inside(root, canonical)) changed(file.path, `\u63D0\u4EA4\u6587\u4EF6\u8D8A\u8FC7\u9879\u76EE\u6839\u76EE\u5F55\uFF1A${file.path}`);
|
|
4951
|
-
const contents = await
|
|
5114
|
+
const contents = await readFile6(absolute);
|
|
4952
5115
|
if (contents.byteLength !== file.bytes || digest(contents) !== file.digest) {
|
|
4953
5116
|
changed(file.path, `\u63D0\u4EA4\u524D\u6587\u4EF6\u5185\u5BB9\u53D1\u751F\u53D8\u5316\uFF1A${file.path}`);
|
|
4954
5117
|
}
|
|
@@ -4974,7 +5137,7 @@ async function createPushSourceBundle(plan) {
|
|
|
4974
5137
|
schemaVersion: "1",
|
|
4975
5138
|
files: files.map((file) => ({ path: file.path, bytes: file.bytes, digest: file.digest }))
|
|
4976
5139
|
});
|
|
4977
|
-
const calculatedBundleDigest = `sha256:${
|
|
5140
|
+
const calculatedBundleDigest = `sha256:${createHash5("sha256").update(`buddy-push-bundle-v1\0${descriptor}`).digest("hex")}`;
|
|
4978
5141
|
if (calculatedBundleDigest !== plan.bundleDigest) {
|
|
4979
5142
|
changed(plan.projectRoot, "\u63D0\u4EA4\u6E05\u5355\u8EAB\u4EFD\u5728\u6253\u5305\u524D\u53D1\u751F\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C push");
|
|
4980
5143
|
}
|
|
@@ -5370,7 +5533,7 @@ async function resumeProjectArguments(args, cwd, options) {
|
|
|
5370
5533
|
await assertInitializableBuddyTarget(root);
|
|
5371
5534
|
let entry;
|
|
5372
5535
|
try {
|
|
5373
|
-
entry = await
|
|
5536
|
+
entry = await lstat9(join10(root, "buddy.agent.json"));
|
|
5374
5537
|
} catch (cause) {
|
|
5375
5538
|
if (cause.code === "ENOENT") return;
|
|
5376
5539
|
throw cause;
|