@runfusion/fusion 0.13.0 → 0.14.1
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 +13 -0
- package/dist/bin.js +1332 -528
- package/dist/client/assets/AgentDetailView-B3KAsP2O.js +18 -0
- package/dist/client/assets/{AgentsView-Dvf_xUkx.js → AgentsView-DoXb_amw.js} +4 -4
- package/dist/client/assets/ChatView-BJ2c7wvd.js +1 -0
- package/dist/client/assets/{DevServerView-C2qTJch7.js → DevServerView-DbgM4tlT.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DRfhg9zz.js → DirectoryPicker-DfmtfMiu.js} +1 -1
- package/dist/client/assets/{DocumentsView-j8ic1xUw.js → DocumentsView-_-Efkx_W.js} +1 -1
- package/dist/client/assets/{InsightsView-CpAz3o0i.js → InsightsView-DUjcfW53.js} +1 -1
- package/dist/client/assets/{MemoryView-BcQsi_JK.js → MemoryView-DxMPBb0q.js} +1 -1
- package/dist/client/assets/{NodesView-Bo_Yhr4N.js → NodesView-BEBTI15s.js} +1 -1
- package/dist/client/assets/PiExtensionsManager-BpMYhHH_.js +11 -0
- package/dist/client/assets/PluginManager-CPv7yQd3.js +1 -0
- package/dist/client/assets/PluginManager-DA_T0GHn.css +1 -0
- package/dist/client/assets/{ResearchView-CLyyqAWE.js → ResearchView-BrFvdyXT.js} +1 -1
- package/dist/client/assets/{RoadmapsView-tG7IdOoc.js → RoadmapsView-BDjLrtcj.js} +1 -1
- package/dist/client/assets/SettingsModal-Cd-QGB0C.js +31 -0
- package/dist/client/assets/{SettingsModal-CXUGeZ0_.js → SettingsModal-CxDxiTRy.js} +1 -1
- package/dist/client/assets/SettingsModal-D_AFkDJa.css +1 -0
- package/dist/client/assets/{SetupWizardModal-BMJL6eNR.js → SetupWizardModal-DFUA4X3z.js} +1 -1
- package/dist/client/assets/{SkillMultiselect-ILMft-Kz.js → SkillMultiselect-BUWe5ujb.js} +1 -1
- package/dist/client/assets/{SkillsView-x4_YwBz6.js → SkillsView-RAkqGX3y.js} +1 -1
- package/dist/client/assets/TodoView-Ceb0wrg1.js +6 -0
- package/dist/client/assets/TodoView-SeO9o7km.css +1 -0
- package/dist/client/assets/{folder-open-DDdJt8aE.js → folder-open-DcM-Vd6r.js} +1 -1
- package/dist/client/assets/index-C1prPuSl.css +1 -0
- package/dist/client/assets/index-DH3aprf6.js +661 -0
- package/dist/client/assets/{list-checks-DFxQ9biT.js → list-checks-ByGHVQpZ.js} +1 -1
- package/dist/client/assets/{star-BKs1bgJN.js → star-DlEYI8GL.js} +1 -1
- package/dist/client/assets/{upload-Bb5Pidne.js → upload-DKshabz-.js} +1 -1
- package/dist/client/assets/{users-BImNn91Q.js → users-X6tYPPBV.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +6 -0
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +127 -0
- package/dist/droid-cli/package.json +37 -0
- package/dist/droid-cli/src/__tests__/control-handler.test.ts +164 -0
- package/dist/droid-cli/src/__tests__/event-bridge.test.ts +1318 -0
- package/dist/droid-cli/src/__tests__/mcp-config.test.ts +310 -0
- package/dist/droid-cli/src/__tests__/process-manager.test.ts +818 -0
- package/dist/droid-cli/src/__tests__/prompt-builder.test.ts +1206 -0
- package/dist/droid-cli/src/__tests__/provider.test.ts +1894 -0
- package/dist/droid-cli/src/__tests__/setup-test-isolation.test.ts +32 -0
- package/dist/droid-cli/src/__tests__/setup-test-isolation.ts +14 -0
- package/dist/droid-cli/src/__tests__/stream-parser.test.ts +188 -0
- package/dist/droid-cli/src/__tests__/thinking-config.test.ts +141 -0
- package/dist/droid-cli/src/__tests__/tool-mapping.test.ts +253 -0
- package/dist/droid-cli/src/control-handler.ts +82 -0
- package/dist/droid-cli/src/event-bridge.ts +397 -0
- package/dist/droid-cli/src/mcp-config.ts +144 -0
- package/dist/droid-cli/src/mcp-schema-server.cjs +49 -0
- package/dist/droid-cli/src/process-manager.ts +358 -0
- package/dist/droid-cli/src/prompt-builder.ts +629 -0
- package/dist/droid-cli/src/provider.ts +447 -0
- package/dist/droid-cli/src/stream-parser.ts +37 -0
- package/dist/droid-cli/src/thinking-config.ts +83 -0
- package/dist/droid-cli/src/tool-mapping.ts +147 -0
- package/dist/droid-cli/src/types.ts +87 -0
- package/dist/extension.js +555 -125
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +2 -1
- package/dist/client/assets/AgentDetailView-B7j297GT.js +0 -18
- package/dist/client/assets/ChatView-BgUt38ty.js +0 -1
- package/dist/client/assets/PiExtensionsManager-DHt2zFg8.js +0 -11
- package/dist/client/assets/PluginManager-BQhBHWrB.js +0 -1
- package/dist/client/assets/PluginManager-jyNkJZSz.css +0 -1
- package/dist/client/assets/SettingsModal-9HS8MnmW.css +0 -1
- package/dist/client/assets/SettingsModal-UziTDnLh.js +0 -31
- package/dist/client/assets/TodoView-BBYcMbXE.js +0 -6
- package/dist/client/assets/TodoView-C1g65hJo.css +0 -1
- package/dist/client/assets/index-B15xwijw.css +0 -1
- package/dist/client/assets/index-DmSs2FGE.js +0 -661
package/dist/extension.js
CHANGED
|
@@ -1007,11 +1007,40 @@ function hasAgentIdentity(agent) {
|
|
|
1007
1007
|
if (!agent) return false;
|
|
1008
1008
|
return !!(agent.soul?.trim() || agent.instructionsText?.trim() || agent.instructionsPath?.trim() || agent.memory?.trim());
|
|
1009
1009
|
}
|
|
1010
|
-
function
|
|
1010
|
+
function slugifyAgentAssetSegment(value) {
|
|
1011
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1012
|
+
}
|
|
1013
|
+
function getSafeAgentAssetIdSegment(agentId) {
|
|
1014
|
+
const slug = slugifyAgentAssetSegment(agentId);
|
|
1015
|
+
return slug || "agent";
|
|
1016
|
+
}
|
|
1017
|
+
function getCanonicalAgentAssetDirectoryName(agentName, agentId) {
|
|
1018
|
+
if (!agentId || typeof agentId !== "string") {
|
|
1019
|
+
throw new Error("getCanonicalAgentAssetDirectoryName requires a non-empty agentId");
|
|
1020
|
+
}
|
|
1021
|
+
const safeId = getSafeAgentAssetIdSegment(agentId);
|
|
1022
|
+
const nameSlug = slugifyAgentAssetSegment(agentName ?? "");
|
|
1023
|
+
const prefix = nameSlug || safeId;
|
|
1024
|
+
return `${prefix}-${safeId}`;
|
|
1025
|
+
}
|
|
1026
|
+
function getLegacyAgentAssetDirectoryName(agentId) {
|
|
1027
|
+
if (!agentId || typeof agentId !== "string") {
|
|
1028
|
+
throw new Error("getLegacyAgentAssetDirectoryName requires a non-empty agentId");
|
|
1029
|
+
}
|
|
1030
|
+
return agentId;
|
|
1031
|
+
}
|
|
1032
|
+
function getCanonicalAgentInstructionsBundleDirName(agentName, agentId) {
|
|
1033
|
+
return `${getCanonicalAgentAssetDirectoryName(agentName, agentId)}-instructions`;
|
|
1034
|
+
}
|
|
1035
|
+
function getLegacyAgentInstructionsBundleDirName(agentId) {
|
|
1036
|
+
return `${getLegacyAgentAssetDirectoryName(agentId)}-instructions`;
|
|
1037
|
+
}
|
|
1038
|
+
function getDefaultHeartbeatProcedurePath(agentId, agentName) {
|
|
1011
1039
|
if (!agentId || typeof agentId !== "string") {
|
|
1012
1040
|
throw new Error("getDefaultHeartbeatProcedurePath requires a non-empty agentId");
|
|
1013
1041
|
}
|
|
1014
|
-
|
|
1042
|
+
const directory = agentName ? getCanonicalAgentAssetDirectoryName(agentName, agentId) : getLegacyAgentAssetDirectoryName(agentId);
|
|
1043
|
+
return `.fusion/agents/${directory}/HEARTBEAT.md`;
|
|
1015
1044
|
}
|
|
1016
1045
|
function agentToConfigSnapshot(agent) {
|
|
1017
1046
|
return {
|
|
@@ -2705,7 +2734,7 @@ var init_db = __esm({
|
|
|
2705
2734
|
"use strict";
|
|
2706
2735
|
init_sqlite_adapter();
|
|
2707
2736
|
init_types();
|
|
2708
|
-
SCHEMA_VERSION =
|
|
2737
|
+
SCHEMA_VERSION = 58;
|
|
2709
2738
|
SCHEMA_SQL = `
|
|
2710
2739
|
-- Tasks table with JSON columns for nested data
|
|
2711
2740
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -4406,6 +4435,27 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
4406
4435
|
}
|
|
4407
4436
|
});
|
|
4408
4437
|
}
|
|
4438
|
+
if (version < 58) {
|
|
4439
|
+
this.applyMigration(58, () => {
|
|
4440
|
+
const newCommand = "npx runfusion.ai backup --create";
|
|
4441
|
+
if (this.hasTable("automations") && this.hasColumn("automations", "command")) {
|
|
4442
|
+
this.db.prepare(
|
|
4443
|
+
`UPDATE automations
|
|
4444
|
+
SET command = ?, updatedAt = ?
|
|
4445
|
+
WHERE name = 'Database Backup'
|
|
4446
|
+
AND (command LIKE 'fn backup%' OR command LIKE 'kb backup%' OR command LIKE 'fusion backup%')`
|
|
4447
|
+
).run(newCommand, (/* @__PURE__ */ new Date()).toISOString());
|
|
4448
|
+
}
|
|
4449
|
+
if (this.hasTable("routines") && this.hasColumn("routines", "command")) {
|
|
4450
|
+
this.db.prepare(
|
|
4451
|
+
`UPDATE routines
|
|
4452
|
+
SET command = ?, updatedAt = ?
|
|
4453
|
+
WHERE name = 'Database Backup'
|
|
4454
|
+
AND (command LIKE 'fn backup%' OR command LIKE 'kb backup%' OR command LIKE 'fusion backup%')`
|
|
4455
|
+
).run(newCommand, (/* @__PURE__ */ new Date()).toISOString());
|
|
4456
|
+
}
|
|
4457
|
+
});
|
|
4458
|
+
}
|
|
4409
4459
|
}
|
|
4410
4460
|
/**
|
|
4411
4461
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -4669,7 +4719,7 @@ var init_agent_store = __esm({
|
|
|
4669
4719
|
if (agent.heartbeatProcedurePath !== DEFAULT_HEARTBEAT_PROCEDURE_PATH) {
|
|
4670
4720
|
continue;
|
|
4671
4721
|
}
|
|
4672
|
-
const newRelPath =
|
|
4722
|
+
const newRelPath = await this.resolveCompatibleHeartbeatProcedurePath(agent);
|
|
4673
4723
|
const newAbsPath = join3(this.rootDir, "..", newRelPath);
|
|
4674
4724
|
if (legacyContent !== null) {
|
|
4675
4725
|
try {
|
|
@@ -4830,7 +4880,7 @@ var init_agent_store = __esm({
|
|
|
4830
4880
|
const metadata = input.metadata ?? {};
|
|
4831
4881
|
const runtimeConfig = resolveCreationRuntimeConfig(input.runtimeConfig, metadata);
|
|
4832
4882
|
const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
|
|
4833
|
-
const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : getDefaultHeartbeatProcedurePath(agentId));
|
|
4883
|
+
const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : getDefaultHeartbeatProcedurePath(agentId, input.name));
|
|
4834
4884
|
const agent = {
|
|
4835
4885
|
id: agentId,
|
|
4836
4886
|
name: input.name.trim(),
|
|
@@ -5067,14 +5117,16 @@ var init_agent_store = __esm({
|
|
|
5067
5117
|
* Does not create the directory.
|
|
5068
5118
|
*/
|
|
5069
5119
|
getInstructionsDir(agentId) {
|
|
5070
|
-
|
|
5120
|
+
const agent = this.readAgent(agentId);
|
|
5121
|
+
const agentName = agent?.name ?? "";
|
|
5122
|
+
return join3(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agentName, agentId));
|
|
5071
5123
|
}
|
|
5072
5124
|
/**
|
|
5073
5125
|
* List markdown files in an agent's managed instructions bundle.
|
|
5074
5126
|
* Returns [] when the bundle directory does not exist.
|
|
5075
5127
|
*/
|
|
5076
5128
|
async listBundleFiles(agentId) {
|
|
5077
|
-
const bundleDir = this.
|
|
5129
|
+
const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
|
|
5078
5130
|
try {
|
|
5079
5131
|
const entries = await readdir(bundleDir, { withFileTypes: true });
|
|
5080
5132
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
@@ -5090,7 +5142,8 @@ var init_agent_store = __esm({
|
|
|
5090
5142
|
*/
|
|
5091
5143
|
async readBundleFile(agentId, filePath) {
|
|
5092
5144
|
this.validateBundleFilePath(filePath);
|
|
5093
|
-
const
|
|
5145
|
+
const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
|
|
5146
|
+
const resolvedPath = join3(bundleDir, filePath);
|
|
5094
5147
|
return readFile(resolvedPath, "utf-8");
|
|
5095
5148
|
}
|
|
5096
5149
|
/**
|
|
@@ -5099,7 +5152,7 @@ var init_agent_store = __esm({
|
|
|
5099
5152
|
async writeBundleFile(agentId, filePath, content) {
|
|
5100
5153
|
return this.withLock(agentId, async () => {
|
|
5101
5154
|
this.validateBundleFilePath(filePath);
|
|
5102
|
-
const bundleDir = this.
|
|
5155
|
+
const bundleDir = await this.resolveCompatibleBundleDir(agentId, true);
|
|
5103
5156
|
await mkdir(bundleDir, { recursive: true });
|
|
5104
5157
|
const existingFiles = await this.listBundleFiles(agentId);
|
|
5105
5158
|
const isOverwrite = existingFiles.includes(filePath);
|
|
@@ -5118,7 +5171,8 @@ var init_agent_store = __esm({
|
|
|
5118
5171
|
async deleteBundleFile(agentId, filePath) {
|
|
5119
5172
|
return this.withLock(agentId, async () => {
|
|
5120
5173
|
this.validateBundleFilePath(filePath);
|
|
5121
|
-
await
|
|
5174
|
+
const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
|
|
5175
|
+
await unlink(join3(bundleDir, filePath));
|
|
5122
5176
|
});
|
|
5123
5177
|
}
|
|
5124
5178
|
/**
|
|
@@ -5140,7 +5194,7 @@ var init_agent_store = __esm({
|
|
|
5140
5194
|
};
|
|
5141
5195
|
const updated = await this.updateAgent(agentId, { bundleConfig: normalizedConfig });
|
|
5142
5196
|
if (normalizedConfig.mode === "managed") {
|
|
5143
|
-
await mkdir(this.
|
|
5197
|
+
await mkdir(await this.resolveCompatibleBundleDir(agentId, true), { recursive: true });
|
|
5144
5198
|
}
|
|
5145
5199
|
return updated;
|
|
5146
5200
|
}
|
|
@@ -5163,7 +5217,7 @@ var init_agent_store = __esm({
|
|
|
5163
5217
|
bundleConfig: { mode: "managed", entryFile, files: [] }
|
|
5164
5218
|
});
|
|
5165
5219
|
}
|
|
5166
|
-
await mkdir(this.
|
|
5220
|
+
await mkdir(await this.resolveCompatibleBundleDir(agentId, true), { recursive: true });
|
|
5167
5221
|
const files = [];
|
|
5168
5222
|
if (hasInstructionsText) {
|
|
5169
5223
|
await this.writeBundleFile(agentId, entryFile, agent.instructionsText ?? "");
|
|
@@ -6124,8 +6178,87 @@ var init_agent_store = __esm({
|
|
|
6124
6178
|
}
|
|
6125
6179
|
return null;
|
|
6126
6180
|
}
|
|
6127
|
-
|
|
6128
|
-
return join3(this.agentsDir,
|
|
6181
|
+
getCanonicalBundleDir(agent) {
|
|
6182
|
+
return join3(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id));
|
|
6183
|
+
}
|
|
6184
|
+
getLegacyBundleDir(agentId) {
|
|
6185
|
+
return join3(this.agentsDir, getLegacyAgentInstructionsBundleDirName(agentId));
|
|
6186
|
+
}
|
|
6187
|
+
async resolveCompatibleBundleDir(agentId, createIfMissing) {
|
|
6188
|
+
const agent = this.readAgent(agentId);
|
|
6189
|
+
if (!agent) {
|
|
6190
|
+
throw new Error(`Agent ${agentId} not found`);
|
|
6191
|
+
}
|
|
6192
|
+
const canonicalDir = this.getCanonicalBundleDir(agent);
|
|
6193
|
+
if (await this.pathExists(canonicalDir)) {
|
|
6194
|
+
return canonicalDir;
|
|
6195
|
+
}
|
|
6196
|
+
const compatibleDir = await this.findExistingDisplayNameBundleDir(agent);
|
|
6197
|
+
if (compatibleDir) {
|
|
6198
|
+
return compatibleDir;
|
|
6199
|
+
}
|
|
6200
|
+
const legacyDir = this.getLegacyBundleDir(agent.id);
|
|
6201
|
+
if (await this.pathExists(legacyDir)) {
|
|
6202
|
+
return legacyDir;
|
|
6203
|
+
}
|
|
6204
|
+
return createIfMissing ? canonicalDir : canonicalDir;
|
|
6205
|
+
}
|
|
6206
|
+
async findExistingDisplayNameBundleDir(agent) {
|
|
6207
|
+
const safeId = getSafeAgentAssetIdSegment(agent.id);
|
|
6208
|
+
try {
|
|
6209
|
+
const entries = await readdir(this.agentsDir, { withFileTypes: true });
|
|
6210
|
+
const candidates = entries.filter((entry) => entry.isDirectory() && entry.name.endsWith("-instructions")).map((entry) => entry.name).filter((name) => {
|
|
6211
|
+
const base = name.slice(0, -"-instructions".length);
|
|
6212
|
+
return base.endsWith(`-${safeId}`);
|
|
6213
|
+
}).sort((a, b) => a.localeCompare(b));
|
|
6214
|
+
if (candidates.length === 0) {
|
|
6215
|
+
return null;
|
|
6216
|
+
}
|
|
6217
|
+
const canonicalName = getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id);
|
|
6218
|
+
const selected = candidates.find((candidate) => candidate === canonicalName) ?? candidates[0];
|
|
6219
|
+
return join3(this.agentsDir, selected);
|
|
6220
|
+
} catch (err) {
|
|
6221
|
+
if (err.code === "ENOENT") {
|
|
6222
|
+
return null;
|
|
6223
|
+
}
|
|
6224
|
+
throw err;
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
async resolveCompatibleHeartbeatProcedurePath(agent) {
|
|
6228
|
+
const canonicalPath = getDefaultHeartbeatProcedurePath(agent.id, agent.name);
|
|
6229
|
+
const canonicalAbs = join3(this.rootDir, "..", canonicalPath);
|
|
6230
|
+
if (await this.pathExists(canonicalAbs)) {
|
|
6231
|
+
return canonicalPath;
|
|
6232
|
+
}
|
|
6233
|
+
const safeId = getSafeAgentAssetIdSegment(agent.id);
|
|
6234
|
+
try {
|
|
6235
|
+
const entries = await readdir(this.agentsDir, { withFileTypes: true });
|
|
6236
|
+
const compatibleDir = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).find((name) => name.endsWith(`-${safeId}`));
|
|
6237
|
+
if (compatibleDir) {
|
|
6238
|
+
const candidatePath = `.fusion/agents/${compatibleDir}/HEARTBEAT.md`;
|
|
6239
|
+
if (await this.pathExists(join3(this.rootDir, "..", candidatePath))) {
|
|
6240
|
+
return candidatePath;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
} catch (err) {
|
|
6244
|
+
if (err.code !== "ENOENT") {
|
|
6245
|
+
throw err;
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
const legacyPath = `.fusion/agents/${getLegacyAgentAssetDirectoryName(agent.id)}/HEARTBEAT.md`;
|
|
6249
|
+
const legacyAbs = join3(this.rootDir, "..", legacyPath);
|
|
6250
|
+
if (await this.pathExists(legacyAbs)) {
|
|
6251
|
+
return legacyPath;
|
|
6252
|
+
}
|
|
6253
|
+
return canonicalPath;
|
|
6254
|
+
}
|
|
6255
|
+
async pathExists(path2) {
|
|
6256
|
+
try {
|
|
6257
|
+
await access(path2, fsConstants.F_OK);
|
|
6258
|
+
return true;
|
|
6259
|
+
} catch {
|
|
6260
|
+
return false;
|
|
6261
|
+
}
|
|
6129
6262
|
}
|
|
6130
6263
|
validateBundleFilePath(filePath) {
|
|
6131
6264
|
if (typeof filePath !== "string") {
|
|
@@ -10238,7 +10371,7 @@ function validatePluginManifest(manifest) {
|
|
|
10238
10371
|
const m = manifest;
|
|
10239
10372
|
if (!m.id || typeof m.id !== "string" || m.id.trim() === "") {
|
|
10240
10373
|
errors.push("id is required and must be a non-empty string");
|
|
10241
|
-
} else if (
|
|
10374
|
+
} else if (!SLUG_PATTERN.test(m.id)) {
|
|
10242
10375
|
errors.push("id must be a valid slug (lowercase, alphanumeric, hyphens only, cannot start or end with hyphen)");
|
|
10243
10376
|
}
|
|
10244
10377
|
if (!m.name || typeof m.name !== "string" || m.name.trim() === "") {
|
|
@@ -10291,7 +10424,7 @@ function validatePluginManifest(manifest) {
|
|
|
10291
10424
|
const runtime = m.runtime;
|
|
10292
10425
|
if (!runtime.runtimeId || typeof runtime.runtimeId !== "string" || runtime.runtimeId.trim() === "") {
|
|
10293
10426
|
errors.push("runtime.runtimeId is required and must be a non-empty string");
|
|
10294
|
-
} else if (
|
|
10427
|
+
} else if (!SLUG_PATTERN.test(runtime.runtimeId)) {
|
|
10295
10428
|
errors.push("runtime.runtimeId must be a valid slug (lowercase, alphanumeric, hyphens only, cannot start or end with hyphen)");
|
|
10296
10429
|
}
|
|
10297
10430
|
if (!runtime.name || typeof runtime.name !== "string" || runtime.name.trim() === "") {
|
|
@@ -10306,14 +10439,93 @@ function validatePluginManifest(manifest) {
|
|
|
10306
10439
|
}
|
|
10307
10440
|
}
|
|
10308
10441
|
}
|
|
10442
|
+
if (m.skills !== void 0) {
|
|
10443
|
+
if (!Array.isArray(m.skills)) {
|
|
10444
|
+
errors.push("skills must be an array");
|
|
10445
|
+
} else {
|
|
10446
|
+
for (const [index, skill] of m.skills.entries()) {
|
|
10447
|
+
if (!skill || typeof skill !== "object") {
|
|
10448
|
+
errors.push(`skills[${index}] must be an object`);
|
|
10449
|
+
continue;
|
|
10450
|
+
}
|
|
10451
|
+
const skillMeta = skill;
|
|
10452
|
+
if (!skillMeta.skillId || typeof skillMeta.skillId !== "string" || skillMeta.skillId.trim() === "") {
|
|
10453
|
+
errors.push(`skills[${index}].skillId is required and must be a non-empty string`);
|
|
10454
|
+
} else if (!SLUG_PATTERN.test(skillMeta.skillId)) {
|
|
10455
|
+
errors.push(`skills[${index}].skillId must be a valid slug (lowercase, alphanumeric, hyphens only, cannot start or end with hyphen)`);
|
|
10456
|
+
}
|
|
10457
|
+
if (!skillMeta.name || typeof skillMeta.name !== "string" || skillMeta.name.trim() === "") {
|
|
10458
|
+
errors.push(`skills[${index}].name is required and must be a non-empty string`);
|
|
10459
|
+
}
|
|
10460
|
+
}
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
if (m.workflowSteps !== void 0) {
|
|
10464
|
+
if (!Array.isArray(m.workflowSteps)) {
|
|
10465
|
+
errors.push("workflowSteps must be an array");
|
|
10466
|
+
} else {
|
|
10467
|
+
for (const [index, step] of m.workflowSteps.entries()) {
|
|
10468
|
+
if (!step || typeof step !== "object") {
|
|
10469
|
+
errors.push(`workflowSteps[${index}] must be an object`);
|
|
10470
|
+
continue;
|
|
10471
|
+
}
|
|
10472
|
+
const stepMeta = step;
|
|
10473
|
+
if (!stepMeta.stepId || typeof stepMeta.stepId !== "string" || stepMeta.stepId.trim() === "") {
|
|
10474
|
+
errors.push(`workflowSteps[${index}].stepId is required and must be a non-empty string`);
|
|
10475
|
+
} else if (!SLUG_PATTERN.test(stepMeta.stepId)) {
|
|
10476
|
+
errors.push(`workflowSteps[${index}].stepId must be a valid slug (lowercase, alphanumeric, hyphens only, cannot start or end with hyphen)`);
|
|
10477
|
+
}
|
|
10478
|
+
if (!stepMeta.name || typeof stepMeta.name !== "string" || stepMeta.name.trim() === "") {
|
|
10479
|
+
errors.push(`workflowSteps[${index}].name is required and must be a non-empty string`);
|
|
10480
|
+
}
|
|
10481
|
+
if (stepMeta.mode !== void 0 && (typeof stepMeta.mode !== "string" || !["prompt", "script"].includes(stepMeta.mode))) {
|
|
10482
|
+
errors.push(`workflowSteps[${index}].mode must be one of: prompt, script`);
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
}
|
|
10487
|
+
if (m.promptSurfaces !== void 0) {
|
|
10488
|
+
if (!Array.isArray(m.promptSurfaces)) {
|
|
10489
|
+
errors.push("promptSurfaces must be an array");
|
|
10490
|
+
} else {
|
|
10491
|
+
for (const [index, surface] of m.promptSurfaces.entries()) {
|
|
10492
|
+
if (typeof surface !== "string" || !PROMPT_CONTRIBUTION_SURFACES.includes(surface)) {
|
|
10493
|
+
errors.push(`promptSurfaces[${index}] must be one of: ${PROMPT_CONTRIBUTION_SURFACES.join(", ")}`);
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
if (m.setup !== void 0) {
|
|
10499
|
+
if (typeof m.setup !== "object" || m.setup === null) {
|
|
10500
|
+
errors.push("setup must be an object");
|
|
10501
|
+
} else {
|
|
10502
|
+
const setup = m.setup;
|
|
10503
|
+
if (!setup.binaryName || typeof setup.binaryName !== "string" || setup.binaryName.trim() === "") {
|
|
10504
|
+
errors.push("setup.binaryName is required and must be a non-empty string");
|
|
10505
|
+
}
|
|
10506
|
+
if (!setup.description || typeof setup.description !== "string" || setup.description.trim() === "") {
|
|
10507
|
+
errors.push("setup.description is required and must be a non-empty string");
|
|
10508
|
+
}
|
|
10509
|
+
if (setup.channel !== void 0 && (typeof setup.channel !== "string" || !SETUP_CHANNELS.includes(setup.channel))) {
|
|
10510
|
+
errors.push(`setup.channel must be one of: ${SETUP_CHANNELS.join(", ")}`);
|
|
10511
|
+
}
|
|
10512
|
+
if (setup.defaultTimeoutMs !== void 0 && (typeof setup.defaultTimeoutMs !== "number" || !Number.isFinite(setup.defaultTimeoutMs) || setup.defaultTimeoutMs <= 0)) {
|
|
10513
|
+
errors.push("setup.defaultTimeoutMs must be a positive finite number");
|
|
10514
|
+
}
|
|
10515
|
+
}
|
|
10516
|
+
}
|
|
10309
10517
|
return {
|
|
10310
10518
|
valid: errors.length === 0,
|
|
10311
10519
|
errors
|
|
10312
10520
|
};
|
|
10313
10521
|
}
|
|
10522
|
+
var SLUG_PATTERN, PROMPT_CONTRIBUTION_SURFACES, SETUP_CHANNELS;
|
|
10314
10523
|
var init_plugin_types = __esm({
|
|
10315
10524
|
"../core/src/plugin-types.ts"() {
|
|
10316
10525
|
"use strict";
|
|
10526
|
+
SLUG_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
10527
|
+
PROMPT_CONTRIBUTION_SURFACES = ["executor-system", "executor-task", "triage", "reviewer", "heartbeat"];
|
|
10528
|
+
SETUP_CHANNELS = ["stable", "beta", "nightly"];
|
|
10317
10529
|
}
|
|
10318
10530
|
});
|
|
10319
10531
|
|
|
@@ -35857,6 +36069,20 @@ function reconcileClaudeCliPaths(paths, vendoredPath) {
|
|
|
35857
36069
|
}
|
|
35858
36070
|
return filtered;
|
|
35859
36071
|
}
|
|
36072
|
+
function isExternalDroidCliPath(p, vendoredPath) {
|
|
36073
|
+
if (vendoredPath && p === vendoredPath) return false;
|
|
36074
|
+
return /(^|[/\\])droid-cli([/\\]|$)/i.test(p);
|
|
36075
|
+
}
|
|
36076
|
+
function reconcileDroidCliPaths(paths, vendoredPath) {
|
|
36077
|
+
if (!vendoredPath) {
|
|
36078
|
+
return [...paths];
|
|
36079
|
+
}
|
|
36080
|
+
const filtered = paths.filter((p) => !isExternalDroidCliPath(p, vendoredPath));
|
|
36081
|
+
if (!filtered.includes(vendoredPath)) {
|
|
36082
|
+
return [vendoredPath, ...filtered];
|
|
36083
|
+
}
|
|
36084
|
+
return filtered;
|
|
36085
|
+
}
|
|
35860
36086
|
function getDisplayPathWithinRoot(root, targetPath) {
|
|
35861
36087
|
const usesWindowsPaths = /^[A-Za-z]:[\\/]/.test(root) || /^[A-Za-z]:[\\/]/.test(targetPath) || root.includes("\\") || targetPath.includes("\\");
|
|
35862
36088
|
const pathApi = usesWindowsPaths ? win32 : { relative: relative2, isAbsolute: isAbsolute5, sep: sep4 };
|
|
@@ -36114,6 +36340,85 @@ var init_gh_cli = __esm({
|
|
|
36114
36340
|
}
|
|
36115
36341
|
});
|
|
36116
36342
|
|
|
36343
|
+
// ../core/src/fn-binary.ts
|
|
36344
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
36345
|
+
import { platform as platform2 } from "node:os";
|
|
36346
|
+
function runProbe(command, args, timeoutMs) {
|
|
36347
|
+
return new Promise((resolve19) => {
|
|
36348
|
+
let stdout = "";
|
|
36349
|
+
let stderr = "";
|
|
36350
|
+
const child = spawn2(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
36351
|
+
const timer = setTimeout(() => {
|
|
36352
|
+
try {
|
|
36353
|
+
child.kill("SIGKILL");
|
|
36354
|
+
} catch {
|
|
36355
|
+
}
|
|
36356
|
+
}, timeoutMs);
|
|
36357
|
+
child.stdout?.on("data", (chunk) => {
|
|
36358
|
+
stdout += chunk.toString("utf8");
|
|
36359
|
+
});
|
|
36360
|
+
child.stderr?.on("data", (chunk) => {
|
|
36361
|
+
stderr += chunk.toString("utf8");
|
|
36362
|
+
});
|
|
36363
|
+
child.on("error", (err) => {
|
|
36364
|
+
clearTimeout(timer);
|
|
36365
|
+
resolve19({ exitCode: null, stdout, stderr: stderr || err.message });
|
|
36366
|
+
});
|
|
36367
|
+
child.on("close", (exitCode) => {
|
|
36368
|
+
clearTimeout(timer);
|
|
36369
|
+
resolve19({ exitCode, stdout, stderr });
|
|
36370
|
+
});
|
|
36371
|
+
});
|
|
36372
|
+
}
|
|
36373
|
+
async function whichBinary(name) {
|
|
36374
|
+
const isWindows = platform2() === "win32";
|
|
36375
|
+
const lookup = isWindows ? "where" : "which";
|
|
36376
|
+
const result = await runProbe(lookup, [name], 5e3);
|
|
36377
|
+
if (result.exitCode !== 0) return void 0;
|
|
36378
|
+
const firstLine = result.stdout.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
36379
|
+
return firstLine || void 0;
|
|
36380
|
+
}
|
|
36381
|
+
async function probeVersion(binary) {
|
|
36382
|
+
const result = await runProbe(binary, ["--version"], 1e4);
|
|
36383
|
+
if (result.exitCode !== 0) return void 0;
|
|
36384
|
+
const text = (result.stdout || result.stderr).trim();
|
|
36385
|
+
if (!text) return void 0;
|
|
36386
|
+
const match = text.match(/\d+\.\d+\.\d+(?:-[\w.]+)?/);
|
|
36387
|
+
return match ? match[0] : text.split(/\s+/)[0];
|
|
36388
|
+
}
|
|
36389
|
+
async function detectFnBinary() {
|
|
36390
|
+
for (const candidate of CANDIDATES) {
|
|
36391
|
+
try {
|
|
36392
|
+
const resolvedPath = await whichBinary(candidate);
|
|
36393
|
+
if (!resolvedPath) continue;
|
|
36394
|
+
const version = await probeVersion(candidate);
|
|
36395
|
+
return {
|
|
36396
|
+
installed: true,
|
|
36397
|
+
binary: candidate,
|
|
36398
|
+
path: resolvedPath,
|
|
36399
|
+
version,
|
|
36400
|
+
invocation: candidate
|
|
36401
|
+
};
|
|
36402
|
+
} catch {
|
|
36403
|
+
}
|
|
36404
|
+
}
|
|
36405
|
+
return {
|
|
36406
|
+
installed: false,
|
|
36407
|
+
invocation: FN_NPX_INVOCATION
|
|
36408
|
+
};
|
|
36409
|
+
}
|
|
36410
|
+
var FN_NPM_PACKAGE, FN_INSTALL_CURL, FN_INSTALL_NPM, FN_NPX_INVOCATION, CANDIDATES;
|
|
36411
|
+
var init_fn_binary = __esm({
|
|
36412
|
+
"../core/src/fn-binary.ts"() {
|
|
36413
|
+
"use strict";
|
|
36414
|
+
FN_NPM_PACKAGE = "runfusion.ai";
|
|
36415
|
+
FN_INSTALL_CURL = "curl -fsSL https://runfusion.ai/install.sh | sh";
|
|
36416
|
+
FN_INSTALL_NPM = `npm install -g ${FN_NPM_PACKAGE}`;
|
|
36417
|
+
FN_NPX_INVOCATION = `npx -y ${FN_NPM_PACKAGE}`;
|
|
36418
|
+
CANDIDATES = ["fn", "fusion"];
|
|
36419
|
+
}
|
|
36420
|
+
});
|
|
36421
|
+
|
|
36117
36422
|
// ../core/src/settings-validation.ts
|
|
36118
36423
|
function validateUnavailableNodePolicy(value) {
|
|
36119
36424
|
if (value === void 0) {
|
|
@@ -50401,6 +50706,10 @@ __export(src_exports, {
|
|
|
50401
50706
|
EXECUTION_MODES: () => EXECUTION_MODES,
|
|
50402
50707
|
FEATURE_LOOP_STATES: () => FEATURE_LOOP_STATES,
|
|
50403
50708
|
FEATURE_STATUSES: () => FEATURE_STATUSES,
|
|
50709
|
+
FN_INSTALL_CURL: () => FN_INSTALL_CURL,
|
|
50710
|
+
FN_INSTALL_NPM: () => FN_INSTALL_NPM,
|
|
50711
|
+
FN_NPM_PACKAGE: () => FN_NPM_PACKAGE,
|
|
50712
|
+
FN_NPX_INVOCATION: () => FN_NPX_INVOCATION,
|
|
50404
50713
|
FileMemoryBackend: () => FileMemoryBackend,
|
|
50405
50714
|
FirstRunDetector: () => FirstRunDetector,
|
|
50406
50715
|
GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
|
|
@@ -50512,6 +50821,7 @@ __export(src_exports, {
|
|
|
50512
50821
|
createInsightExtractionAutomation: () => createInsightExtractionAutomation,
|
|
50513
50822
|
createMemoryDreamsAutomation: () => createMemoryDreamsAutomation,
|
|
50514
50823
|
dailyMemoryPath: () => dailyMemoryPath,
|
|
50824
|
+
detectFnBinary: () => detectFnBinary,
|
|
50515
50825
|
detectLegacyData: () => detectLegacyData,
|
|
50516
50826
|
diffConfigSnapshots: () => diffConfigSnapshots,
|
|
50517
50827
|
discoverPiExtensions: () => discoverPiExtensions,
|
|
@@ -50633,6 +50943,7 @@ __export(src_exports, {
|
|
|
50633
50943
|
readProjectMemoryWithBackend: () => readProjectMemoryWithBackend,
|
|
50634
50944
|
readWorkingMemory: () => readWorkingMemory,
|
|
50635
50945
|
reconcileClaudeCliPaths: () => reconcileClaudeCliPaths,
|
|
50946
|
+
reconcileDroidCliPaths: () => reconcileDroidCliPaths,
|
|
50636
50947
|
refreshQmdProjectMemoryIndex: () => refreshQmdProjectMemoryIndex,
|
|
50637
50948
|
registerMemoryBackend: () => registerMemoryBackend,
|
|
50638
50949
|
renderMemoryAuditMarkdown: () => renderMemoryAuditMarkdown,
|
|
@@ -50722,6 +51033,7 @@ var init_src = __esm({
|
|
|
50722
51033
|
init_automation();
|
|
50723
51034
|
init_automation_store();
|
|
50724
51035
|
init_run_command();
|
|
51036
|
+
init_fn_binary();
|
|
50725
51037
|
init_node_override_guard();
|
|
50726
51038
|
init_settings_validation();
|
|
50727
51039
|
init_routine();
|
|
@@ -52708,6 +53020,21 @@ function resolveVendoredClaudeCliEntry() {
|
|
|
52708
53020
|
return null;
|
|
52709
53021
|
}
|
|
52710
53022
|
}
|
|
53023
|
+
function resolveVendoredDroidCliEntry() {
|
|
53024
|
+
try {
|
|
53025
|
+
const require_ = createRequire2(import.meta.url);
|
|
53026
|
+
const pkgJsonPath = require_.resolve("@fusion/droid-cli/package.json");
|
|
53027
|
+
const pkgJson = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
53028
|
+
const extensions = pkgJson.pi?.extensions;
|
|
53029
|
+
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
53030
|
+
const entry = extensions[0];
|
|
53031
|
+
if (typeof entry !== "string" || entry.length === 0) return null;
|
|
53032
|
+
const path2 = resolve11(dirname8(pkgJsonPath), entry);
|
|
53033
|
+
return existsSync20(path2) ? path2 : null;
|
|
53034
|
+
} catch {
|
|
53035
|
+
return null;
|
|
53036
|
+
}
|
|
53037
|
+
}
|
|
52711
53038
|
async function registerExtensionProviders(cwd, modelRegistry) {
|
|
52712
53039
|
try {
|
|
52713
53040
|
const agentDir = getPackageManagerAgentDir();
|
|
@@ -52723,8 +53050,13 @@ async function registerExtensionProviders(cwd, modelRegistry) {
|
|
|
52723
53050
|
[...getEnabledPiExtensionPaths(cwd), ...packageExtensionPaths],
|
|
52724
53051
|
vendoredClaudeCli
|
|
52725
53052
|
);
|
|
52726
|
-
const
|
|
53053
|
+
const vendoredDroidCli = resolveVendoredDroidCliEntry();
|
|
53054
|
+
const doubleReconciledPaths = reconcileDroidCliPaths(
|
|
52727
53055
|
reconciledPaths,
|
|
53056
|
+
vendoredDroidCli
|
|
53057
|
+
);
|
|
53058
|
+
const extensionsResult = await discoverAndLoadExtensions(
|
|
53059
|
+
doubleReconciledPaths,
|
|
52728
53060
|
cwd,
|
|
52729
53061
|
join25(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
|
|
52730
53062
|
);
|
|
@@ -52968,11 +53300,8 @@ async function createFnAgent2(options) {
|
|
|
52968
53300
|
const createSessionWithModel = async (modelOverride) => {
|
|
52969
53301
|
const customToolList = [
|
|
52970
53302
|
...wrappedTools,
|
|
52971
|
-
...
|
|
53303
|
+
...options.customTools ?? []
|
|
52972
53304
|
];
|
|
52973
|
-
if (isReadonly && (options.customTools?.length ?? 0) > 0) {
|
|
52974
|
-
piLog.log(`readonly session \u2014 customTools (${options.customTools.length}) skipped`);
|
|
52975
|
-
}
|
|
52976
53305
|
if (options.beforeSpawnSession) {
|
|
52977
53306
|
await options.beforeSpawnSession();
|
|
52978
53307
|
}
|
|
@@ -54381,14 +54710,25 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path2, startLine = 1,
|
|
|
54381
54710
|
backend: "agent-memory"
|
|
54382
54711
|
};
|
|
54383
54712
|
}
|
|
54384
|
-
function
|
|
54713
|
+
async function createAgentTask(store, input, options) {
|
|
54714
|
+
const settings = typeof store.getSettings === "function" ? await store.getSettings() : {};
|
|
54715
|
+
const rootDir = options?.rootDir;
|
|
54716
|
+
return store.createTask(input, {
|
|
54717
|
+
settings: { autoSummarizeTitles: settings.autoSummarizeTitles === true },
|
|
54718
|
+
onSummarize: rootDir ? async (description) => {
|
|
54719
|
+
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
|
54720
|
+
return summarizeTitle(description, rootDir, resolved.provider, resolved.modelId);
|
|
54721
|
+
} : void 0
|
|
54722
|
+
});
|
|
54723
|
+
}
|
|
54724
|
+
function createTaskCreateTool(store, provenance, options) {
|
|
54385
54725
|
return {
|
|
54386
54726
|
name: "fn_task_create",
|
|
54387
54727
|
label: "Create Task",
|
|
54388
54728
|
description: "Create a new task for out-of-scope work discovered during execution. The task goes into triage where it will be specified by the AI. Optionally set dependencies (e.g., the new task depends on the current one, or the current task should wait for the new one).",
|
|
54389
54729
|
parameters: taskCreateParams,
|
|
54390
54730
|
execute: async (_id, params) => {
|
|
54391
|
-
const task = await store
|
|
54731
|
+
const task = await createAgentTask(store, {
|
|
54392
54732
|
description: params.description,
|
|
54393
54733
|
dependencies: params.dependencies,
|
|
54394
54734
|
column: "triage",
|
|
@@ -54397,7 +54737,7 @@ function createTaskCreateTool(store, provenance) {
|
|
|
54397
54737
|
sourceAgentId: provenance.sourceAgentId,
|
|
54398
54738
|
sourceRunId: provenance.sourceRunId
|
|
54399
54739
|
} : void 0
|
|
54400
|
-
});
|
|
54740
|
+
}, options);
|
|
54401
54741
|
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
54402
54742
|
return {
|
|
54403
54743
|
content: [{
|
|
@@ -54750,7 +55090,7 @@ ${lines.join("\n\n")}` }],
|
|
|
54750
55090
|
}
|
|
54751
55091
|
};
|
|
54752
55092
|
}
|
|
54753
|
-
function createDelegateTaskTool(agentStore, taskStore) {
|
|
55093
|
+
function createDelegateTaskTool(agentStore, taskStore, options) {
|
|
54754
55094
|
return {
|
|
54755
55095
|
name: "fn_delegate_task",
|
|
54756
55096
|
label: "Delegate Task",
|
|
@@ -54770,13 +55110,13 @@ function createDelegateTaskTool(agentStore, taskStore) {
|
|
|
54770
55110
|
details: {}
|
|
54771
55111
|
};
|
|
54772
55112
|
}
|
|
54773
|
-
const task = await taskStore
|
|
55113
|
+
const task = await createAgentTask(taskStore, {
|
|
54774
55114
|
description: params.description,
|
|
54775
55115
|
dependencies: params.dependencies,
|
|
54776
55116
|
column: "todo",
|
|
54777
55117
|
assignedAgentId: params.agent_id,
|
|
54778
55118
|
source: { sourceType: "api" }
|
|
54779
|
-
});
|
|
55119
|
+
}, options);
|
|
54780
55120
|
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
54781
55121
|
return {
|
|
54782
55122
|
content: [{
|
|
@@ -57603,7 +57943,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
57603
57943
|
// Agent delegation tools — discover and delegate work to other agents.
|
|
57604
57944
|
...this.options.agentStore ? [
|
|
57605
57945
|
createListAgentsTool(this.options.agentStore),
|
|
57606
|
-
createDelegateTaskTool(this.options.agentStore, this.store)
|
|
57946
|
+
createDelegateTaskTool(this.options.agentStore, this.store, { rootDir: this.rootDir })
|
|
57607
57947
|
] : [],
|
|
57608
57948
|
this.createReviewSpecTool(
|
|
57609
57949
|
task.id,
|
|
@@ -58142,7 +58482,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
58142
58482
|
planLog.warn(`${options.parentTaskId}: failed to load parent task for fn_task_create inheritance: ${msg}`);
|
|
58143
58483
|
parentTask = void 0;
|
|
58144
58484
|
}
|
|
58145
|
-
const newTask = await store
|
|
58485
|
+
const newTask = await createAgentTask(store, {
|
|
58146
58486
|
title: params.title,
|
|
58147
58487
|
description: params.description,
|
|
58148
58488
|
dependencies: validDeps,
|
|
@@ -58156,7 +58496,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
58156
58496
|
sourceType: "agent_heartbeat",
|
|
58157
58497
|
sourceParentTaskId: options.parentTaskId
|
|
58158
58498
|
}
|
|
58159
|
-
});
|
|
58499
|
+
}, { rootDir: this.rootDir });
|
|
58160
58500
|
options.createdSubtasksRef.current.push(newTask.id);
|
|
58161
58501
|
return {
|
|
58162
58502
|
content: [
|
|
@@ -58584,7 +58924,7 @@ var init_run_audit = __esm({
|
|
|
58584
58924
|
});
|
|
58585
58925
|
|
|
58586
58926
|
// ../engine/src/merger.ts
|
|
58587
|
-
import { execSync, exec as exec2, spawn as
|
|
58927
|
+
import { execSync, exec as exec2, spawn as spawn3 } from "node:child_process";
|
|
58588
58928
|
import { promisify as promisify3 } from "node:util";
|
|
58589
58929
|
import { existsSync as existsSync22 } from "node:fs";
|
|
58590
58930
|
import { join as join29 } from "node:path";
|
|
@@ -58599,7 +58939,7 @@ async function execWithProcessGroup(command, options) {
|
|
|
58599
58939
|
return;
|
|
58600
58940
|
}
|
|
58601
58941
|
const useProcessGroup = process.platform !== "win32";
|
|
58602
|
-
const child =
|
|
58942
|
+
const child = spawn3(command, {
|
|
58603
58943
|
cwd: options.cwd,
|
|
58604
58944
|
shell: true,
|
|
58605
58945
|
detached: useProcessGroup,
|
|
@@ -63335,10 +63675,10 @@ var init_step_session_executor = __esm({
|
|
|
63335
63675
|
] : [];
|
|
63336
63676
|
const memoryTools = createMemoryTools(this.options.rootDir, settings);
|
|
63337
63677
|
const taskLogTool = this.options.store ? [createTaskLogTool(this.options.store, taskDetail.id)] : [];
|
|
63338
|
-
const taskCreateTool = this.options.store ? [createTaskCreateTool(this.options.store)] : [];
|
|
63678
|
+
const taskCreateTool = this.options.store ? [createTaskCreateTool(this.options.store, void 0, { rootDir: this.options.rootDir })] : [];
|
|
63339
63679
|
const delegationTools = this.options.agentStore ? [
|
|
63340
63680
|
createListAgentsTool(this.options.agentStore),
|
|
63341
|
-
createDelegateTaskTool(this.options.agentStore, this.options.store)
|
|
63681
|
+
createDelegateTaskTool(this.options.agentStore, this.options.store, { rootDir: this.options.rootDir })
|
|
63342
63682
|
] : [];
|
|
63343
63683
|
const messagingTools = this.options.messageStore && taskDetail.assignedAgentId ? [
|
|
63344
63684
|
createSendMessageTool(this.options.messageStore, taskDetail.assignedAgentId),
|
|
@@ -63749,7 +64089,7 @@ var init_task_completion = __esm({
|
|
|
63749
64089
|
});
|
|
63750
64090
|
|
|
63751
64091
|
// ../engine/src/run-verification-tool.ts
|
|
63752
|
-
import { spawn as
|
|
64092
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
63753
64093
|
import { existsSync as existsSync26 } from "node:fs";
|
|
63754
64094
|
import { isAbsolute as isAbsolute10, join as join34 } from "node:path";
|
|
63755
64095
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
@@ -63782,7 +64122,7 @@ async function runVerificationCommand2(opts) {
|
|
|
63782
64122
|
const stdoutBuf = { head: "", tail: "", totalBytes: 0 };
|
|
63783
64123
|
const stderrBuf = { head: "", tail: "", totalBytes: 0 };
|
|
63784
64124
|
return new Promise((resolve19) => {
|
|
63785
|
-
const child =
|
|
64125
|
+
const child = spawn4(command, {
|
|
63786
64126
|
cwd,
|
|
63787
64127
|
stdio: ["ignore", "pipe", "pipe"],
|
|
63788
64128
|
env: { ...process.env },
|
|
@@ -66070,7 +66410,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66070
66410
|
// Agent delegation tools — discover and delegate work to other agents.
|
|
66071
66411
|
...this.options.agentStore ? [
|
|
66072
66412
|
createListAgentsTool(this.options.agentStore),
|
|
66073
|
-
createDelegateTaskTool(this.options.agentStore, this.store)
|
|
66413
|
+
createDelegateTaskTool(this.options.agentStore, this.store, { rootDir: this.rootDir })
|
|
66074
66414
|
] : [],
|
|
66075
66415
|
// Messaging tools — allows executor agents to send and receive messages.
|
|
66076
66416
|
...this.options.messageStore && assignedAgentId ? [
|
|
@@ -66764,7 +67104,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66764
67104
|
return createTaskLogTool(this.store, taskId);
|
|
66765
67105
|
}
|
|
66766
67106
|
createTaskCreateTool() {
|
|
66767
|
-
return createTaskCreateTool(this.store, { sourceType: "api" });
|
|
67107
|
+
return createTaskCreateTool(this.store, { sourceType: "api" }, { rootDir: this.rootDir });
|
|
66768
67108
|
}
|
|
66769
67109
|
createTaskDocumentWriteTool(taskId) {
|
|
66770
67110
|
return createTaskDocumentWriteTool(this.store, taskId);
|
|
@@ -71699,7 +72039,7 @@ function isTickableState(state) {
|
|
|
71699
72039
|
function isHeartbeatManaged(agent) {
|
|
71700
72040
|
return !isEphemeralAgent(agent);
|
|
71701
72041
|
}
|
|
71702
|
-
var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, OVERDUE_FIRE_JITTER_MS, HeartbeatTriggerScheduler;
|
|
72042
|
+
var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, HEARTBEAT_NO_TASK_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, OVERDUE_FIRE_JITTER_MS, HeartbeatTriggerScheduler;
|
|
71703
72043
|
var init_agent_heartbeat = __esm({
|
|
71704
72044
|
"../engine/src/agent-heartbeat.ts"() {
|
|
71705
72045
|
"use strict";
|
|
@@ -71889,6 +72229,32 @@ When sending messages:
|
|
|
71889
72229
|
Critical: a heartbeat without observable progress (a log, a document write, a
|
|
71890
72230
|
status change, a comment, a delegation, or an explicit "no-op with reason") is
|
|
71891
72231
|
a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
72232
|
+
HEARTBEAT_NO_TASK_PROCEDURE = `## Heartbeat Procedure (run every tick, in order)
|
|
72233
|
+
|
|
72234
|
+
1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
|
|
72235
|
+
this prompt. Confirm your role, soul, instructions, and memory match what
|
|
72236
|
+
you expect, and surface any anomalies in your first text output before
|
|
72237
|
+
doing anything else. (If fn_identity is available in your runtime you may
|
|
72238
|
+
also call it for full structured detail; the snapshot above is the
|
|
72239
|
+
authoritative source.)
|
|
72240
|
+
2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
|
|
72241
|
+
messages first; reply with reply_to_message_id when answering.
|
|
72242
|
+
3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
|
|
72243
|
+
highest-priority change for this heartbeat. If you were woken by a comment
|
|
72244
|
+
or a message, acknowledge it before doing anything else.
|
|
72245
|
+
4. **Ambient review** \u2014 since you have no assigned task, review board/project
|
|
72246
|
+
signals and recent memory context before acting.
|
|
72247
|
+
5. **Pick the next concrete action** \u2014 exactly ONE useful action this heartbeat:
|
|
72248
|
+
create a focused task, delegate work, send/reply to a message, or append
|
|
72249
|
+
durable memory.
|
|
72250
|
+
6. **Persist progress** \u2014 use available ambient tools only:
|
|
72251
|
+
fn_task_create, fn_delegate_task, fn_send_message, fn_memory_append.
|
|
72252
|
+
7. **Exit** \u2014 call fn_heartbeat_done with a one-line summary of what changed
|
|
72253
|
+
this tick. If you took no action, say so and explain why.
|
|
72254
|
+
|
|
72255
|
+
Critical: a heartbeat without observable progress (a created task, delegation,
|
|
72256
|
+
message reply, memory append, or explicit "no-op with reason") is a bug. Do
|
|
72257
|
+
not loop on the same plan across heartbeats without recording why.`;
|
|
71892
72258
|
heartbeatDoneParams = Type6.Object({
|
|
71893
72259
|
summary: Type6.Optional(Type6.String({ description: "Summary of what was accomplished this heartbeat" }))
|
|
71894
72260
|
});
|
|
@@ -71928,13 +72294,6 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
71928
72294
|
this.rootDir = options.rootDir;
|
|
71929
72295
|
this.messageStore = options.messageStore;
|
|
71930
72296
|
this.pluginRunner = options.pluginRunner;
|
|
71931
|
-
this.onRecovered = options.onRecovered;
|
|
71932
|
-
this.onTerminated = options.onTerminated;
|
|
71933
|
-
this.onRunStarted = options.onRunStarted;
|
|
71934
|
-
this.onRunCompleted = options.onRunCompleted;
|
|
71935
|
-
this.taskStore = options.taskStore;
|
|
71936
|
-
this.rootDir = options.rootDir;
|
|
71937
|
-
this.messageStore = options.messageStore;
|
|
71938
72297
|
}
|
|
71939
72298
|
/**
|
|
71940
72299
|
* Start the heartbeat monitoring loop.
|
|
@@ -72633,9 +72992,9 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72633
72992
|
sourceType: "agent_heartbeat",
|
|
72634
72993
|
sourceAgentId: agentId,
|
|
72635
72994
|
sourceRunId: runContext?.runId
|
|
72636
|
-
}));
|
|
72995
|
+
}, { rootDir: this.rootDir }));
|
|
72637
72996
|
heartbeatTools.push(createListAgentsTool(this.store));
|
|
72638
|
-
heartbeatTools.push(createDelegateTaskTool(this.store, taskStore));
|
|
72997
|
+
heartbeatTools.push(createDelegateTaskTool(this.store, taskStore, { rootDir: this.rootDir }));
|
|
72639
72998
|
if (this.messageStore) {
|
|
72640
72999
|
heartbeatTools.push(createSendMessageTool(this.messageStore, agentId));
|
|
72641
73000
|
heartbeatTools.push(createReadMessagesTool(this.messageStore, agentId));
|
|
@@ -72658,22 +73017,27 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72658
73017
|
heartbeatLog.warn(`Failed to configure heartbeat memory tools for ${agentId}: ${message}`);
|
|
72659
73018
|
}
|
|
72660
73019
|
const skillContext = buildSessionSkillContextSync2(agent, "heartbeat", rootDir);
|
|
72661
|
-
|
|
72662
|
-
const baseHeartbeatSystemPrompt = systemPrompt;
|
|
73020
|
+
const baseHeartbeatSystemPrompt = isNoTaskRun ? HEARTBEAT_NO_TASK_SYSTEM_PROMPT : HEARTBEAT_SYSTEM_PROMPT;
|
|
72663
73021
|
let resolvedInstructionsForIdentity = "";
|
|
72664
73022
|
try {
|
|
72665
|
-
|
|
72666
|
-
resolvedInstructionsForIdentity = agentInstructions;
|
|
72667
|
-
const memoryInstructions = memorySettings?.memoryEnabled === false ? "" : buildExecutionMemoryInstructions(rootDir, memorySettings);
|
|
72668
|
-
systemPrompt = buildSystemPromptWithInstructions(
|
|
72669
|
-
baseHeartbeatSystemPrompt,
|
|
72670
|
-
[agentInstructions, memoryInstructions].filter((part) => part.trim()).join("\n\n")
|
|
72671
|
-
);
|
|
73023
|
+
resolvedInstructionsForIdentity = await resolveAgentInstructionsWithRatings(agent, rootDir, this.store);
|
|
72672
73024
|
} catch (instructionError) {
|
|
72673
|
-
systemPrompt = baseHeartbeatSystemPrompt;
|
|
72674
73025
|
const message = instructionError instanceof Error ? instructionError.message : String(instructionError);
|
|
72675
|
-
heartbeatLog.warn(`Failed to
|
|
73026
|
+
heartbeatLog.warn(`Failed to resolve agent instructions for heartbeat ${agentId}: ${message}`);
|
|
72676
73027
|
}
|
|
73028
|
+
let memoryInstructions = "";
|
|
73029
|
+
if (memorySettings?.memoryEnabled !== false) {
|
|
73030
|
+
try {
|
|
73031
|
+
memoryInstructions = buildExecutionMemoryInstructions(rootDir, memorySettings);
|
|
73032
|
+
} catch (memoryInstructionErr) {
|
|
73033
|
+
const message = memoryInstructionErr instanceof Error ? memoryInstructionErr.message : String(memoryInstructionErr);
|
|
73034
|
+
heartbeatLog.warn(`Failed to resolve project memory instructions for heartbeat ${agentId}: ${message}`);
|
|
73035
|
+
}
|
|
73036
|
+
}
|
|
73037
|
+
const systemPrompt = buildSystemPromptWithInstructions(
|
|
73038
|
+
baseHeartbeatSystemPrompt,
|
|
73039
|
+
[resolvedInstructionsForIdentity, memoryInstructions].filter((part) => part.trim()).join("\n\n")
|
|
73040
|
+
);
|
|
72677
73041
|
heartbeatTools.push(createIdentityTool({ agent, resolvedInstructions: resolvedInstructionsForIdentity }));
|
|
72678
73042
|
heartbeatTools.push(heartbeatDoneTool);
|
|
72679
73043
|
if (isNoTaskRun) {
|
|
@@ -72734,7 +73098,7 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72734
73098
|
};
|
|
72735
73099
|
const wakeReason = deriveWakeReason();
|
|
72736
73100
|
const customProcedure = await resolveAgentHeartbeatProcedure(agent, rootDir);
|
|
72737
|
-
const heartbeatProcedureText = customProcedure ?? HEARTBEAT_PROCEDURE;
|
|
73101
|
+
const heartbeatProcedureText = customProcedure ?? (isNoTaskRun ? HEARTBEAT_NO_TASK_PROCEDURE : HEARTBEAT_PROCEDURE);
|
|
72738
73102
|
if (isNoTaskRun) {
|
|
72739
73103
|
if (this.messageStore) {
|
|
72740
73104
|
try {
|
|
@@ -72767,6 +73131,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72767
73131
|
`- pending messages: ${pendingMessages.length}`,
|
|
72768
73132
|
"",
|
|
72769
73133
|
"Treat this wake delta as the highest-priority change for this heartbeat.",
|
|
73134
|
+
"This is an autonomous heartbeat run (manual or automatic): re-anchor on",
|
|
73135
|
+
"identity, process wake context, then complete ONE concrete action.",
|
|
72770
73136
|
"Run the Heartbeat Procedure (below) before doing anything else \u2014 even a",
|
|
72771
73137
|
"timer-only wake should re-check messages, memory, and project state.",
|
|
72772
73138
|
"",
|
|
@@ -72858,6 +73224,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72858
73224
|
`- triggering comments: ${effectiveTriggeringCommentIds?.length ?? 0}`,
|
|
72859
73225
|
"",
|
|
72860
73226
|
"Treat this wake delta as the highest-priority change for this heartbeat.",
|
|
73227
|
+
"This is an autonomous heartbeat run (manual or automatic): re-anchor on",
|
|
73228
|
+
"identity, process wake context, then complete ONE concrete action.",
|
|
72861
73229
|
"Before resuming prior task work, run the Heartbeat Procedure (below) and",
|
|
72862
73230
|
"decide what action this delta requires. Your assigned task is one input",
|
|
72863
73231
|
"to the procedure \u2014 not the only thing to consider.",
|
|
@@ -73017,7 +73385,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
73017
73385
|
const baseCreateTool = createTaskCreateTool(taskStore, {
|
|
73018
73386
|
sourceType: "agent_heartbeat",
|
|
73019
73387
|
sourceAgentId: agentId
|
|
73020
|
-
});
|
|
73388
|
+
}, { rootDir: this.rootDir });
|
|
73021
73389
|
const trackedCreateTool = {
|
|
73022
73390
|
...baseCreateTool,
|
|
73023
73391
|
execute: async (id, params, signal, onUpdate, ctx) => {
|
|
@@ -73044,7 +73412,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
73044
73412
|
tools.push(createTaskDocumentWriteTool(taskStore, taskId));
|
|
73045
73413
|
tools.push(createTaskDocumentReadTool(taskStore, taskId));
|
|
73046
73414
|
tools.push(createListAgentsTool(this.store));
|
|
73047
|
-
tools.push(createDelegateTaskTool(this.store, taskStore));
|
|
73415
|
+
tools.push(createDelegateTaskTool(this.store, taskStore, { rootDir: this.rootDir }));
|
|
73048
73416
|
if (messageStore) {
|
|
73049
73417
|
tools.push(createSendMessageTool(messageStore, agentId));
|
|
73050
73418
|
tools.push(createReadMessagesTool(messageStore, agentId));
|
|
@@ -80746,7 +81114,7 @@ var init_provider_adapters = __esm({
|
|
|
80746
81114
|
|
|
80747
81115
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
80748
81116
|
import { EventEmitter as EventEmitter23 } from "node:events";
|
|
80749
|
-
import { exec as exec9, execFile as execFile3, spawn as
|
|
81117
|
+
import { exec as exec9, execFile as execFile3, spawn as spawn5 } from "node:child_process";
|
|
80750
81118
|
import { promisify as promisify9 } from "node:util";
|
|
80751
81119
|
function nowIso() {
|
|
80752
81120
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -80837,7 +81205,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
80837
81205
|
super();
|
|
80838
81206
|
this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
|
|
80839
81207
|
this.defaultStopTimeoutMs = options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS2;
|
|
80840
|
-
this.spawnImpl = options.spawnImpl ??
|
|
81208
|
+
this.spawnImpl = options.spawnImpl ?? spawn5;
|
|
80841
81209
|
}
|
|
80842
81210
|
getStatus() {
|
|
80843
81211
|
return { ...this.status, lastError: this.status.lastError ? { ...this.status.lastError } : null };
|
|
@@ -81211,6 +81579,7 @@ var execFileAsync2, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
|
81211
81579
|
var init_project_engine = __esm({
|
|
81212
81580
|
"../engine/src/project-engine.ts"() {
|
|
81213
81581
|
"use strict";
|
|
81582
|
+
init_src();
|
|
81214
81583
|
init_in_process_runtime();
|
|
81215
81584
|
init_pr_monitor();
|
|
81216
81585
|
init_pr_comment_handler();
|
|
@@ -81905,6 +82274,48 @@ ${detail}`
|
|
|
81905
82274
|
if (task.status === "failed") return false;
|
|
81906
82275
|
return (task.mergeRetries ?? 0) < _ProjectEngine.MAX_AUTO_MERGE_RETRIES || this.hasAutoHealableVerificationBufferFailure(task) || this.isRetryCooldownElapsed(task);
|
|
81907
82276
|
}
|
|
82277
|
+
/**
|
|
82278
|
+
* Remove and return the highest-priority taskId from the merge queue.
|
|
82279
|
+
* Ordering: priority (urgent→low), then createdAt ASC, then id ASC — matching
|
|
82280
|
+
* the triage and scheduler comparators. Manual merges (onMerge resolvers) are
|
|
82281
|
+
* preferred over auto-merges so awaited callers aren't starved by a flood of
|
|
82282
|
+
* higher-priority auto-enqueues. IDs whose tasks can't be loaded fall back to
|
|
82283
|
+
* FIFO order so they still drain.
|
|
82284
|
+
*/
|
|
82285
|
+
async pickNextMergeTaskId(store) {
|
|
82286
|
+
if (this.mergeQueue.length === 0) return void 0;
|
|
82287
|
+
if (this.mergeQueue.length === 1) {
|
|
82288
|
+
return this.mergeQueue.shift();
|
|
82289
|
+
}
|
|
82290
|
+
const queueSnapshot = [...this.mergeQueue];
|
|
82291
|
+
const entries = [];
|
|
82292
|
+
for (let i = 0; i < queueSnapshot.length; i++) {
|
|
82293
|
+
const taskId = queueSnapshot[i];
|
|
82294
|
+
const task = await store.getTask(taskId).catch(() => void 0);
|
|
82295
|
+
entries.push({
|
|
82296
|
+
taskId,
|
|
82297
|
+
task,
|
|
82298
|
+
manual: this.manualMergeResolvers.has(taskId),
|
|
82299
|
+
order: i
|
|
82300
|
+
});
|
|
82301
|
+
}
|
|
82302
|
+
if (this.shuttingDown) return void 0;
|
|
82303
|
+
entries.sort((a, b) => {
|
|
82304
|
+
if (a.manual !== b.manual) return a.manual ? -1 : 1;
|
|
82305
|
+
if (a.task && b.task) return compareTasksByPriorityThenAgeAndId(a.task, b.task);
|
|
82306
|
+
if (a.task) return -1;
|
|
82307
|
+
if (b.task) return 1;
|
|
82308
|
+
return a.order - b.order;
|
|
82309
|
+
});
|
|
82310
|
+
for (const entry of entries) {
|
|
82311
|
+
const liveIndex = this.mergeQueue.indexOf(entry.taskId);
|
|
82312
|
+
if (liveIndex !== -1) {
|
|
82313
|
+
this.mergeQueue.splice(liveIndex, 1);
|
|
82314
|
+
return entry.taskId;
|
|
82315
|
+
}
|
|
82316
|
+
}
|
|
82317
|
+
return void 0;
|
|
82318
|
+
}
|
|
81908
82319
|
internalEnqueueMerge(taskId) {
|
|
81909
82320
|
if (this.shuttingDown) return;
|
|
81910
82321
|
if (this.mergeActive.has(taskId)) return;
|
|
@@ -81912,6 +82323,23 @@ ${detail}`
|
|
|
81912
82323
|
this.mergeQueue.push(taskId);
|
|
81913
82324
|
void this.drainMergeQueue();
|
|
81914
82325
|
}
|
|
82326
|
+
/**
|
|
82327
|
+
* Filter a sweep's listTasks() result to merge-eligible tasks, sort by
|
|
82328
|
+
* priority (urgent → low, then createdAt ASC, then id ASC), and enqueue.
|
|
82329
|
+
* Sorting before enqueue matters because each enqueue may immediately
|
|
82330
|
+
* trigger drainMergeQueue's single-item fast path, so the first task
|
|
82331
|
+
* pushed wins. listTasks returns createdAt ASC — without this sort an
|
|
82332
|
+
* older low-priority task would start before a later urgent one.
|
|
82333
|
+
*/
|
|
82334
|
+
enqueueEligibleInReviewTasks(tasks) {
|
|
82335
|
+
const eligible = sortTasksByPriorityThenAgeAndId(
|
|
82336
|
+
tasks.filter((t) => !t.paused && this.canMergeTask(t))
|
|
82337
|
+
);
|
|
82338
|
+
for (const t of eligible) {
|
|
82339
|
+
this.internalEnqueueMerge(t.id);
|
|
82340
|
+
}
|
|
82341
|
+
return eligible.length;
|
|
82342
|
+
}
|
|
81915
82343
|
async drainMergeQueue() {
|
|
81916
82344
|
if (this.mergeRunning) return;
|
|
81917
82345
|
this.mergeRunning = true;
|
|
@@ -81919,7 +82347,9 @@ ${detail}`
|
|
|
81919
82347
|
const store = this.runtime.getTaskStore();
|
|
81920
82348
|
const cwd = this.config.workingDirectory;
|
|
81921
82349
|
while (this.mergeQueue.length > 0 && !this.shuttingDown) {
|
|
81922
|
-
const taskId = this.
|
|
82350
|
+
const taskId = await this.pickNextMergeTaskId(store);
|
|
82351
|
+
if (!taskId) break;
|
|
82352
|
+
if (this.shuttingDown) break;
|
|
81923
82353
|
const manualResolver = this.manualMergeResolvers.get(taskId);
|
|
81924
82354
|
try {
|
|
81925
82355
|
if (!manualResolver) {
|
|
@@ -82389,12 +82819,9 @@ ${detail}`
|
|
|
82389
82819
|
}
|
|
82390
82820
|
const settings = await store.getSettings();
|
|
82391
82821
|
if (!settings.autoMerge) return;
|
|
82392
|
-
const
|
|
82393
|
-
if (
|
|
82394
|
-
runtimeLog.log(`Auto-merge startup sweep: enqueueing ${
|
|
82395
|
-
for (const t of eligible) {
|
|
82396
|
-
this.internalEnqueueMerge(t.id);
|
|
82397
|
-
}
|
|
82822
|
+
const enqueued = this.enqueueEligibleInReviewTasks(tasks);
|
|
82823
|
+
if (enqueued > 0) {
|
|
82824
|
+
runtimeLog.log(`Auto-merge startup sweep: enqueueing ${enqueued} task(s)`);
|
|
82398
82825
|
}
|
|
82399
82826
|
} catch (err) {
|
|
82400
82827
|
runtimeLog.warn(
|
|
@@ -82410,14 +82837,7 @@ ${detail}`
|
|
|
82410
82837
|
const settings = await store.getSettings();
|
|
82411
82838
|
if (!settings.globalPause && !settings.enginePaused && settings.autoMerge) {
|
|
82412
82839
|
const tasks = await store.listTasks({ column: "in-review" });
|
|
82413
|
-
|
|
82414
|
-
if (t.paused) {
|
|
82415
|
-
continue;
|
|
82416
|
-
}
|
|
82417
|
-
if (this.canMergeTask(t)) {
|
|
82418
|
-
this.internalEnqueueMerge(t.id);
|
|
82419
|
-
}
|
|
82420
|
-
}
|
|
82840
|
+
this.enqueueEligibleInReviewTasks(tasks);
|
|
82421
82841
|
}
|
|
82422
82842
|
} catch (err) {
|
|
82423
82843
|
runtimeLog.warn(
|
|
@@ -82473,14 +82893,7 @@ ${detail}`
|
|
|
82473
82893
|
if (s.autoMerge) {
|
|
82474
82894
|
try {
|
|
82475
82895
|
const tasks = await store.listTasks({ column: "in-review" });
|
|
82476
|
-
|
|
82477
|
-
if (t.paused) {
|
|
82478
|
-
continue;
|
|
82479
|
-
}
|
|
82480
|
-
if (this.canMergeTask(t)) {
|
|
82481
|
-
this.internalEnqueueMerge(t.id);
|
|
82482
|
-
}
|
|
82483
|
-
}
|
|
82896
|
+
this.enqueueEligibleInReviewTasks(tasks);
|
|
82484
82897
|
} catch (err) {
|
|
82485
82898
|
runtimeLog.warn(
|
|
82486
82899
|
`Global unpause: failed to scan in-review tasks for auto-merge: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -82510,14 +82923,7 @@ ${detail}`
|
|
|
82510
82923
|
if (s.autoMerge) {
|
|
82511
82924
|
try {
|
|
82512
82925
|
const tasks = await store.listTasks({ column: "in-review" });
|
|
82513
|
-
|
|
82514
|
-
if (t.paused) {
|
|
82515
|
-
continue;
|
|
82516
|
-
}
|
|
82517
|
-
if (this.canMergeTask(t)) {
|
|
82518
|
-
this.internalEnqueueMerge(t.id);
|
|
82519
|
-
}
|
|
82520
|
-
}
|
|
82926
|
+
this.enqueueEligibleInReviewTasks(tasks);
|
|
82521
82927
|
} catch (err) {
|
|
82522
82928
|
runtimeLog.warn(
|
|
82523
82929
|
`Engine unpause: failed to scan in-review tasks for auto-merge: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -83110,7 +83516,7 @@ var init_peer_exchange_service = __esm({
|
|
|
83110
83516
|
syncIntervalMs;
|
|
83111
83517
|
interval = null;
|
|
83112
83518
|
activeSync = null;
|
|
83113
|
-
|
|
83519
|
+
running = false;
|
|
83114
83520
|
/** Whether settings sync is enabled. Default: false. */
|
|
83115
83521
|
settingsSyncEnabled;
|
|
83116
83522
|
/** Minimum interval between settings syncs with the same node in ms. Default: 5 minutes. */
|
|
@@ -83152,10 +83558,11 @@ var init_peer_exchange_service = __esm({
|
|
|
83152
83558
|
* Begins periodic gossip with all online remote nodes.
|
|
83153
83559
|
*/
|
|
83154
83560
|
start() {
|
|
83155
|
-
if (this.
|
|
83156
|
-
peerExchangeLog.
|
|
83561
|
+
if (this.running) {
|
|
83562
|
+
peerExchangeLog.log("Peer exchange service already running");
|
|
83157
83563
|
return;
|
|
83158
83564
|
}
|
|
83565
|
+
this.running = true;
|
|
83159
83566
|
this.centralCore.listNodes().then((nodes) => {
|
|
83160
83567
|
const onlineRemoteCount = nodes.filter(
|
|
83161
83568
|
(n) => n.type === "remote" && n.status === "online" && n.url
|
|
@@ -83165,6 +83572,7 @@ var init_peer_exchange_service = __esm({
|
|
|
83165
83572
|
peerExchangeLog.warn(`Failed to get initial peer count: ${err}`);
|
|
83166
83573
|
});
|
|
83167
83574
|
this.interval = setInterval(() => {
|
|
83575
|
+
if (!this.running) return;
|
|
83168
83576
|
void this.syncWithAllPeers();
|
|
83169
83577
|
}, this.syncIntervalMs);
|
|
83170
83578
|
}
|
|
@@ -83172,12 +83580,21 @@ var init_peer_exchange_service = __esm({
|
|
|
83172
83580
|
* Stop the peer exchange service.
|
|
83173
83581
|
* Clears the sync interval and prevents further syncs.
|
|
83174
83582
|
*/
|
|
83175
|
-
stop() {
|
|
83583
|
+
async stop() {
|
|
83584
|
+
if (!this.running) {
|
|
83585
|
+
return;
|
|
83586
|
+
}
|
|
83587
|
+
this.running = false;
|
|
83176
83588
|
if (this.interval) {
|
|
83177
83589
|
clearInterval(this.interval);
|
|
83178
83590
|
this.interval = null;
|
|
83179
83591
|
}
|
|
83180
|
-
this.
|
|
83592
|
+
if (this.activeSync) {
|
|
83593
|
+
try {
|
|
83594
|
+
await this.activeSync;
|
|
83595
|
+
} catch {
|
|
83596
|
+
}
|
|
83597
|
+
}
|
|
83181
83598
|
peerExchangeLog.log("Stopped peer exchange service");
|
|
83182
83599
|
}
|
|
83183
83600
|
/**
|
|
@@ -84597,7 +85014,7 @@ For completion:
|
|
|
84597
85014
|
}`;
|
|
84598
85015
|
SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
84599
85016
|
CLEANUP_INTERVAL_MS2 = 5 * 60 * 1e3;
|
|
84600
|
-
MAX_SESSIONS_PER_IP_PER_HOUR =
|
|
85017
|
+
MAX_SESSIONS_PER_IP_PER_HOUR = 1e3;
|
|
84601
85018
|
RATE_LIMIT_WINDOW_MS2 = 60 * 60 * 1e3;
|
|
84602
85019
|
GENERATION_TIMEOUT_MS = 12e4;
|
|
84603
85020
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -85220,7 +85637,7 @@ var init_src3 = __esm({
|
|
|
85220
85637
|
});
|
|
85221
85638
|
|
|
85222
85639
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/cli-spawn.js
|
|
85223
|
-
import { spawn as
|
|
85640
|
+
import { spawn as spawn6, spawnSync } from "node:child_process";
|
|
85224
85641
|
import os2 from "node:os";
|
|
85225
85642
|
import path, { sep as PATH_SEP } from "node:path";
|
|
85226
85643
|
function resolveBinaryForSpawn(binary) {
|
|
@@ -85330,7 +85747,7 @@ async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
|
|
|
85330
85747
|
if (settings.profile) {
|
|
85331
85748
|
spawnEnv.HERMES_HOME = hermesProfileHome(settings.profile);
|
|
85332
85749
|
}
|
|
85333
|
-
const child =
|
|
85750
|
+
const child = spawn6(binary, args, {
|
|
85334
85751
|
stdio: ["ignore", "pipe", "pipe"],
|
|
85335
85752
|
env: spawnEnv
|
|
85336
85753
|
});
|
|
@@ -85543,7 +85960,7 @@ var init_dist = __esm({
|
|
|
85543
85960
|
});
|
|
85544
85961
|
|
|
85545
85962
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/pi-module.js
|
|
85546
|
-
import { spawn as
|
|
85963
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
85547
85964
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
85548
85965
|
function asString(v) {
|
|
85549
85966
|
return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
@@ -85620,7 +86037,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
85620
86037
|
cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
|
|
85621
86038
|
return new Promise((resolve19, reject) => {
|
|
85622
86039
|
let settled = false;
|
|
85623
|
-
const child =
|
|
86040
|
+
const child = spawn7(config.binaryPath, args, {
|
|
85624
86041
|
stdio: ["ignore", "pipe", "pipe"]
|
|
85625
86042
|
});
|
|
85626
86043
|
const hardKill = setTimeout(() => {
|
|
@@ -85771,7 +86188,7 @@ var init_runtime_adapter2 = __esm({
|
|
|
85771
86188
|
});
|
|
85772
86189
|
|
|
85773
86190
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/probe.js
|
|
85774
|
-
import { spawn as
|
|
86191
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
85775
86192
|
async function probeOpenClawBinary(opts = {}) {
|
|
85776
86193
|
const startedAt = Date.now();
|
|
85777
86194
|
const binary = opts.binaryPath ?? "openclaw";
|
|
@@ -85782,7 +86199,7 @@ async function probeOpenClawBinary(opts = {}) {
|
|
|
85782
86199
|
resolvePromise({ ...partial, probeDurationMs: Date.now() - startedAt });
|
|
85783
86200
|
};
|
|
85784
86201
|
let settled = false;
|
|
85785
|
-
const child =
|
|
86202
|
+
const child = spawn8(resolvedPath ?? binary, ["--version"], {
|
|
85786
86203
|
stdio: ["ignore", "pipe", "pipe"]
|
|
85787
86204
|
});
|
|
85788
86205
|
const timer = setTimeout(() => {
|
|
@@ -85843,7 +86260,7 @@ async function probeOpenClawBinary(opts = {}) {
|
|
|
85843
86260
|
async function tryResolveBinaryPath(binary) {
|
|
85844
86261
|
return new Promise((resolvePromise) => {
|
|
85845
86262
|
const which = process.platform === "win32" ? "where" : "which";
|
|
85846
|
-
const child =
|
|
86263
|
+
const child = spawn8(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
|
|
85847
86264
|
let out = "";
|
|
85848
86265
|
child.stdout?.on("data", (chunk) => {
|
|
85849
86266
|
out += chunk.toString("utf-8");
|
|
@@ -92432,7 +92849,7 @@ var init_register_git_github = __esm({
|
|
|
92432
92849
|
});
|
|
92433
92850
|
|
|
92434
92851
|
// ../dashboard/src/terminal.ts
|
|
92435
|
-
import { spawn as
|
|
92852
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
92436
92853
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
92437
92854
|
import { EventEmitter as EventEmitter29 } from "node:events";
|
|
92438
92855
|
function extractBaseCommand(command) {
|
|
@@ -92594,7 +93011,7 @@ var init_terminal = __esm({
|
|
|
92594
93011
|
return { sessionId: "", error: validation.error };
|
|
92595
93012
|
}
|
|
92596
93013
|
const sessionId = randomUUID13();
|
|
92597
|
-
const childProcess =
|
|
93014
|
+
const childProcess = spawn9(command, [], {
|
|
92598
93015
|
cwd,
|
|
92599
93016
|
shell: true,
|
|
92600
93017
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -93318,7 +93735,7 @@ function remapSpawnError(err, bin) {
|
|
|
93318
93735
|
return err instanceof Error ? err : new Error(String(err));
|
|
93319
93736
|
}
|
|
93320
93737
|
async function spawnPaperclipCliJson(args, opts) {
|
|
93321
|
-
const { spawn:
|
|
93738
|
+
const { spawn: spawn12 } = await import("node:child_process");
|
|
93322
93739
|
const bin = opts.cliBinaryPath ?? "paperclipai";
|
|
93323
93740
|
const fullArgs = [...args, "--json"];
|
|
93324
93741
|
if (opts.cliConfigPath) {
|
|
@@ -93329,7 +93746,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
93329
93746
|
return new Promise((resolve19, reject) => {
|
|
93330
93747
|
let child;
|
|
93331
93748
|
try {
|
|
93332
|
-
child =
|
|
93749
|
+
child = spawn12(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
93333
93750
|
} catch (err) {
|
|
93334
93751
|
reject(remapSpawnError(err, bin));
|
|
93335
93752
|
return;
|
|
@@ -93841,6 +94258,25 @@ var init_register_runtime_provider_routes = __esm({
|
|
|
93841
94258
|
}
|
|
93842
94259
|
});
|
|
93843
94260
|
|
|
94261
|
+
// ../dashboard/src/cli-package-version.ts
|
|
94262
|
+
var init_cli_package_version = __esm({
|
|
94263
|
+
"../dashboard/src/cli-package-version.ts"() {
|
|
94264
|
+
"use strict";
|
|
94265
|
+
}
|
|
94266
|
+
});
|
|
94267
|
+
|
|
94268
|
+
// ../dashboard/src/routes/register-fn-binary-routes.ts
|
|
94269
|
+
var MAX_OUTPUT_BYTES2;
|
|
94270
|
+
var init_register_fn_binary_routes = __esm({
|
|
94271
|
+
"../dashboard/src/routes/register-fn-binary-routes.ts"() {
|
|
94272
|
+
"use strict";
|
|
94273
|
+
init_src();
|
|
94274
|
+
init_api_error();
|
|
94275
|
+
init_cli_package_version();
|
|
94276
|
+
MAX_OUTPUT_BYTES2 = 64 * 1024;
|
|
94277
|
+
}
|
|
94278
|
+
});
|
|
94279
|
+
|
|
93844
94280
|
// ../dashboard/src/update-check.ts
|
|
93845
94281
|
var DAY_MS;
|
|
93846
94282
|
var init_update_check = __esm({
|
|
@@ -93850,13 +94286,6 @@ var init_update_check = __esm({
|
|
|
93850
94286
|
}
|
|
93851
94287
|
});
|
|
93852
94288
|
|
|
93853
|
-
// ../dashboard/src/cli-package-version.ts
|
|
93854
|
-
var init_cli_package_version = __esm({
|
|
93855
|
-
"../dashboard/src/cli-package-version.ts"() {
|
|
93856
|
-
"use strict";
|
|
93857
|
-
}
|
|
93858
|
-
});
|
|
93859
|
-
|
|
93860
94289
|
// ../dashboard/src/routes/register-update-check-routes.ts
|
|
93861
94290
|
var init_register_update_check_routes = __esm({
|
|
93862
94291
|
"../dashboard/src/routes/register-update-check-routes.ts"() {
|
|
@@ -94042,6 +94471,7 @@ var init_routes = __esm({
|
|
|
94042
94471
|
init_register_usage_routes();
|
|
94043
94472
|
init_register_auth_routes();
|
|
94044
94473
|
init_register_runtime_provider_routes();
|
|
94474
|
+
init_register_fn_binary_routes();
|
|
94045
94475
|
init_register_update_check_routes();
|
|
94046
94476
|
init_register_integrated_routers();
|
|
94047
94477
|
init_resolve_diff_base();
|
|
@@ -99369,7 +99799,7 @@ async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
|
|
|
99369
99799
|
} catch (err) {
|
|
99370
99800
|
clearThinking();
|
|
99371
99801
|
if (err instanceof RateLimitError2) {
|
|
99372
|
-
console.error("\n Rate limit exceeded. Maximum
|
|
99802
|
+
console.error("\n Rate limit exceeded. Maximum 1000 planning sessions per hour.\n");
|
|
99373
99803
|
process.exit(1);
|
|
99374
99804
|
}
|
|
99375
99805
|
console.error(`
|
|
@@ -99526,7 +99956,7 @@ __export(skills_exports, {
|
|
|
99526
99956
|
runSkillsSearch: () => runSkillsSearch,
|
|
99527
99957
|
searchSkills: () => searchSkills
|
|
99528
99958
|
});
|
|
99529
|
-
import { spawn as
|
|
99959
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
99530
99960
|
async function searchSkills(query, limit = 10) {
|
|
99531
99961
|
const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
99532
99962
|
try {
|
|
@@ -99604,7 +100034,7 @@ async function runSkillsInstall(args, options) {
|
|
|
99604
100034
|
npxArgs.push("--skill", options.skill);
|
|
99605
100035
|
}
|
|
99606
100036
|
npxArgs.push("-y", "-a", "pi");
|
|
99607
|
-
const child =
|
|
100037
|
+
const child = spawn10("npx", npxArgs, {
|
|
99608
100038
|
cwd: process.cwd(),
|
|
99609
100039
|
stdio: "inherit",
|
|
99610
100040
|
shell: true
|
|
@@ -99641,7 +100071,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
|
|
|
99641
100071
|
import { resolve as resolve18, basename as basename11, extname as extname3, join as join41 } from "node:path";
|
|
99642
100072
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
99643
100073
|
import { existsSync as existsSync31 } from "node:fs";
|
|
99644
|
-
import { spawn as
|
|
100074
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
99645
100075
|
var MIME_TYPES2 = {
|
|
99646
100076
|
".png": "image/png",
|
|
99647
100077
|
".jpg": "image/jpeg",
|
|
@@ -101450,7 +101880,7 @@ Status: ${updated.status}`
|
|
|
101450
101880
|
npxArgs.push("--skill", params.skill);
|
|
101451
101881
|
}
|
|
101452
101882
|
npxArgs.push("-y", "-a", "pi");
|
|
101453
|
-
const child =
|
|
101883
|
+
const child = spawn11("npx", npxArgs, {
|
|
101454
101884
|
cwd: resolveProjectRoot(ctx.cwd),
|
|
101455
101885
|
stdio: "pipe",
|
|
101456
101886
|
shell: true
|
|
@@ -101532,7 +101962,7 @@ Status: ${updated.status}`
|
|
|
101532
101962
|
return;
|
|
101533
101963
|
}
|
|
101534
101964
|
const port = trimmed ? parseInt(trimmed, 10) || 4040 : 4040;
|
|
101535
|
-
const child =
|
|
101965
|
+
const child = spawn11("fn", ["dashboard", "--port", String(port)], {
|
|
101536
101966
|
cwd: resolveProjectRoot(ctx.cwd),
|
|
101537
101967
|
stdio: ["ignore", "pipe", "pipe"],
|
|
101538
101968
|
detached: false,
|