@runfusion/fusion 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +244 -98
- package/dist/client/assets/{index-9GdD09x7.css → index-CYkQfLYV.css} +1 -1
- package/dist/client/assets/index-zTogbMzz.js +1241 -0
- package/dist/client/index.html +2 -2
- package/dist/extension.js +145 -82
- package/package.json +1 -1
- package/dist/client/assets/index-wtVkS5Qz.js +0 -1241
package/dist/bin.js
CHANGED
|
@@ -134,6 +134,8 @@ var init_settings_schema = __esm({
|
|
|
134
134
|
defaultPresetBySize: {},
|
|
135
135
|
autoResolveConflicts: true,
|
|
136
136
|
smartConflictResolution: true,
|
|
137
|
+
worktreeRebaseBeforeMerge: true,
|
|
138
|
+
worktreeRebaseRemote: "",
|
|
137
139
|
strictScopeEnforcement: false,
|
|
138
140
|
buildRetryCount: 0,
|
|
139
141
|
verificationFixRetries: 1,
|
|
@@ -30549,6 +30551,7 @@ ${newTask.description}
|
|
|
30549
30551
|
}
|
|
30550
30552
|
}
|
|
30551
30553
|
if (updates.steps !== void 0) task.steps = updates.steps;
|
|
30554
|
+
if (updates.currentStep !== void 0) task.currentStep = updates.currentStep;
|
|
30552
30555
|
if (updates.status === null) {
|
|
30553
30556
|
task.status = void 0;
|
|
30554
30557
|
} else if (updates.status !== void 0) {
|
|
@@ -50790,12 +50793,59 @@ var require_lib = __commonJS({
|
|
|
50790
50793
|
import { EventEmitter as EventEmitter17 } from "events";
|
|
50791
50794
|
import * as os2 from "os";
|
|
50792
50795
|
import * as path from "path";
|
|
50793
|
-
import { existsSync as existsSync17 } from "node:fs";
|
|
50796
|
+
import { existsSync as existsSync17, chmodSync } from "node:fs";
|
|
50794
50797
|
import { join as join22, dirname as dirname7 } from "node:path";
|
|
50795
|
-
function
|
|
50798
|
+
function getNativePrebuildName() {
|
|
50796
50799
|
const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
50797
50800
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
50798
|
-
|
|
50801
|
+
return `${platform3}-${arch}`;
|
|
50802
|
+
}
|
|
50803
|
+
function findInstalledNodePtyNativeDir() {
|
|
50804
|
+
try {
|
|
50805
|
+
const packageJsonPath = __require.resolve("node-pty/package.json");
|
|
50806
|
+
const nativeDir = join22(dirname7(packageJsonPath), "prebuilds", getNativePrebuildName());
|
|
50807
|
+
return existsSync17(join22(nativeDir, "pty.node")) ? nativeDir : null;
|
|
50808
|
+
} catch {
|
|
50809
|
+
return null;
|
|
50810
|
+
}
|
|
50811
|
+
}
|
|
50812
|
+
function ensureNodePtyNativePermissions() {
|
|
50813
|
+
if (process.platform === "win32") {
|
|
50814
|
+
return;
|
|
50815
|
+
}
|
|
50816
|
+
const candidateDirs = /* @__PURE__ */ new Set();
|
|
50817
|
+
const envNativeDir = process.env.NODE_PTY_SPAWN_HELPER_DIR || process.env.FUSION_NATIVE_ASSETS_PATH;
|
|
50818
|
+
if (envNativeDir) {
|
|
50819
|
+
candidateDirs.add(envNativeDir);
|
|
50820
|
+
}
|
|
50821
|
+
const stagedNativeDir = findStagedNativeDir();
|
|
50822
|
+
if (stagedNativeDir) {
|
|
50823
|
+
candidateDirs.add(stagedNativeDir);
|
|
50824
|
+
}
|
|
50825
|
+
const installedNativeDir = findInstalledNodePtyNativeDir();
|
|
50826
|
+
if (installedNativeDir) {
|
|
50827
|
+
candidateDirs.add(installedNativeDir);
|
|
50828
|
+
}
|
|
50829
|
+
for (const nativeDir of candidateDirs) {
|
|
50830
|
+
const helperPath = join22(nativeDir, "spawn-helper");
|
|
50831
|
+
const nativeModulePath = join22(nativeDir, "pty.node");
|
|
50832
|
+
try {
|
|
50833
|
+
if (existsSync17(helperPath)) {
|
|
50834
|
+
chmodSync(helperPath, 493);
|
|
50835
|
+
}
|
|
50836
|
+
if (existsSync17(nativeModulePath)) {
|
|
50837
|
+
chmodSync(nativeModulePath, 493);
|
|
50838
|
+
}
|
|
50839
|
+
} catch (err) {
|
|
50840
|
+
console.warn("[terminal] Failed to repair node-pty native permissions:", {
|
|
50841
|
+
nativeDir,
|
|
50842
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50843
|
+
});
|
|
50844
|
+
}
|
|
50845
|
+
}
|
|
50846
|
+
}
|
|
50847
|
+
function findStagedNativeDir() {
|
|
50848
|
+
const prebuildName = getNativePrebuildName();
|
|
50799
50849
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
50800
50850
|
const envPath = join22(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
50801
50851
|
if (existsSync17(join22(envPath, "pty.node"))) {
|
|
@@ -50836,6 +50886,7 @@ async function loadPtyModule() {
|
|
|
50836
50886
|
}
|
|
50837
50887
|
}
|
|
50838
50888
|
try {
|
|
50889
|
+
ensureNodePtyNativePermissions();
|
|
50839
50890
|
const mod = await Promise.resolve().then(() => __toESM(require_lib(), 1));
|
|
50840
50891
|
ptyModule = mod;
|
|
50841
50892
|
return ptyModule;
|
|
@@ -65072,6 +65123,71 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
65072
65123
|
mergerLog.warn(`${taskId}: unable to verify/checkout main branch \u2014 proceeding on current HEAD`);
|
|
65073
65124
|
}
|
|
65074
65125
|
}
|
|
65126
|
+
if (settings.worktreeRebaseBeforeMerge !== false) {
|
|
65127
|
+
try {
|
|
65128
|
+
let remote = settings.worktreeRebaseRemote?.trim();
|
|
65129
|
+
if (!remote) {
|
|
65130
|
+
try {
|
|
65131
|
+
const { stdout: mainBranchOut } = await execAsync2(
|
|
65132
|
+
"git rev-parse --abbrev-ref HEAD",
|
|
65133
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
65134
|
+
);
|
|
65135
|
+
const mainBranch = mainBranchOut.trim();
|
|
65136
|
+
const { stdout: configuredRemote } = await execAsync2(
|
|
65137
|
+
`git config --get branch.${mainBranch}.remote`,
|
|
65138
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
65139
|
+
).catch(() => ({ stdout: "" }));
|
|
65140
|
+
remote = configuredRemote.trim();
|
|
65141
|
+
} catch {
|
|
65142
|
+
}
|
|
65143
|
+
}
|
|
65144
|
+
if (!remote) {
|
|
65145
|
+
try {
|
|
65146
|
+
const { stdout: remotesOut } = await execAsync2("git remote", {
|
|
65147
|
+
cwd: rootDir,
|
|
65148
|
+
encoding: "utf-8"
|
|
65149
|
+
});
|
|
65150
|
+
const remotes = remotesOut.trim().split(/\s+/).filter(Boolean);
|
|
65151
|
+
if (remotes.length === 1) {
|
|
65152
|
+
remote = remotes[0];
|
|
65153
|
+
} else if (remotes.includes("origin")) {
|
|
65154
|
+
remote = "origin";
|
|
65155
|
+
}
|
|
65156
|
+
} catch {
|
|
65157
|
+
}
|
|
65158
|
+
}
|
|
65159
|
+
if (!remote) {
|
|
65160
|
+
mergerLog.log(`${taskId}: no remote resolvable \u2014 skipping pre-merge rebase`);
|
|
65161
|
+
} else {
|
|
65162
|
+
mergerLog.log(`${taskId}: fetching ${remote} before merge`);
|
|
65163
|
+
await execAsync2(`git fetch "${remote}"`, { cwd: rootDir });
|
|
65164
|
+
try {
|
|
65165
|
+
const { stdout: mainBranchOut } = await execAsync2(
|
|
65166
|
+
"git rev-parse --abbrev-ref HEAD",
|
|
65167
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
65168
|
+
);
|
|
65169
|
+
const mainBranch = mainBranchOut.trim();
|
|
65170
|
+
const remoteRef = `${remote}/${mainBranch}`;
|
|
65171
|
+
if (worktreePath) {
|
|
65172
|
+
await execAsync2(`git rebase "${remoteRef}"`, { cwd: worktreePath });
|
|
65173
|
+
mergerLog.log(`${taskId}: rebased ${branch} onto ${remoteRef}`);
|
|
65174
|
+
} else {
|
|
65175
|
+
mergerLog.warn(`${taskId}: no worktreePath \u2014 skipping task branch rebase`);
|
|
65176
|
+
}
|
|
65177
|
+
} catch (rebaseErr) {
|
|
65178
|
+
const msg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
|
|
65179
|
+
mergerLog.warn(`${taskId}: pre-merge rebase failed (${msg}) \u2014 aborting rebase and falling through to smart/AI merge`);
|
|
65180
|
+
if (worktreePath) {
|
|
65181
|
+
await execAsync2("git rebase --abort", { cwd: worktreePath }).catch(() => {
|
|
65182
|
+
});
|
|
65183
|
+
}
|
|
65184
|
+
}
|
|
65185
|
+
}
|
|
65186
|
+
} catch (err) {
|
|
65187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65188
|
+
mergerLog.warn(`${taskId}: pre-merge rebase pipeline failed (${msg}) \u2014 proceeding without rebase`);
|
|
65189
|
+
}
|
|
65190
|
+
}
|
|
65075
65191
|
let commitLog = "";
|
|
65076
65192
|
let diffStat = "";
|
|
65077
65193
|
try {
|
|
@@ -67588,19 +67704,6 @@ import { existsSync as existsSync26 } from "node:fs";
|
|
|
67588
67704
|
import { readFile as readFile16, writeFile as writeFile12 } from "node:fs/promises";
|
|
67589
67705
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
67590
67706
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
67591
|
-
function determineRevisionResetStart(steps, feedback) {
|
|
67592
|
-
const total = steps.length;
|
|
67593
|
-
if (total === 0) return 0;
|
|
67594
|
-
const skipPreflight = /preflight/i.test(steps[0].name);
|
|
67595
|
-
const firstCandidate = skipPreflight ? 1 : 0;
|
|
67596
|
-
if (firstCandidate >= total) return total;
|
|
67597
|
-
const fb = feedback.toLowerCase();
|
|
67598
|
-
for (let i = firstCandidate; i < total; i++) {
|
|
67599
|
-
const tokens = steps[i].name.toLowerCase().match(/[a-z][a-z]{4,}/g) ?? [];
|
|
67600
|
-
if (tokens.some((t) => fb.includes(t))) return i;
|
|
67601
|
-
}
|
|
67602
|
-
return firstCandidate;
|
|
67603
|
-
}
|
|
67604
67707
|
function truncateWorkflowScriptOutput2(output) {
|
|
67605
67708
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
|
|
67606
67709
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2} characters ...
|
|
@@ -69917,35 +70020,23 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
69917
70020
|
}
|
|
69918
70021
|
/**
|
|
69919
70022
|
* Handle a workflow step revision request.
|
|
69920
|
-
*
|
|
69921
|
-
*
|
|
69922
|
-
*
|
|
69923
|
-
*
|
|
69924
|
-
*
|
|
69925
|
-
*
|
|
69926
|
-
* The task stays in "in-progress" and is scheduled for a fresh executor pass.
|
|
70023
|
+
*
|
|
70024
|
+
* Re-opens ONLY the last step so the executor has exactly one pending slot
|
|
70025
|
+
* to re-enter through. All earlier done steps stay done — the agent reads
|
|
70026
|
+
* the injected feedback from PROMPT.md and applies an in-place fix rather
|
|
70027
|
+
* than redoing any completed step.
|
|
69927
70028
|
*/
|
|
69928
70029
|
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
|
|
69929
70030
|
executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
|
|
69930
70031
|
const updatedTask = await this.store.getTask(task.id);
|
|
69931
|
-
const
|
|
69932
|
-
const
|
|
69933
|
-
const resetSummary = resetStart >= updatedTask.steps.length ? "no steps to reset" : `resetting steps ${resetStart + 1}\u2013${updatedTask.steps.length} (starting at "${targetStepName}")`;
|
|
70032
|
+
const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
70033
|
+
const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
|
|
69934
70034
|
await this.store.logEntry(
|
|
69935
70035
|
task.id,
|
|
69936
|
-
`Workflow step "${stepName}" requested revision \u2014 ${
|
|
70036
|
+
`Workflow step "${stepName}" requested revision \u2014 ${reopenSummary}`,
|
|
69937
70037
|
feedback
|
|
69938
70038
|
);
|
|
69939
|
-
await this.injectWorkflowRevisionInstructions(task, feedback
|
|
69940
|
-
resetStart,
|
|
69941
|
-
targetStepName,
|
|
69942
|
-
totalSteps: updatedTask.steps.length
|
|
69943
|
-
});
|
|
69944
|
-
for (let i = resetStart; i < updatedTask.steps.length; i++) {
|
|
69945
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
69946
|
-
await this.store.updateStep(task.id, i, "pending");
|
|
69947
|
-
}
|
|
69948
|
-
}
|
|
70039
|
+
await this.injectWorkflowRevisionInstructions(task, feedback);
|
|
69949
70040
|
await this.store.updateTask(task.id, {
|
|
69950
70041
|
status: null,
|
|
69951
70042
|
sessionFile: null
|
|
@@ -69967,12 +70058,36 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
69967
70058
|
}
|
|
69968
70059
|
}, 0);
|
|
69969
70060
|
}
|
|
70061
|
+
/**
|
|
70062
|
+
* Re-open the last non-pending step so a revision/failure handler gives the
|
|
70063
|
+
* executor exactly one pending slot to re-enter through. Returns the index
|
|
70064
|
+
* and name of the step that was flipped to `pending`, or null when there
|
|
70065
|
+
* was nothing to re-open.
|
|
70066
|
+
*/
|
|
70067
|
+
async reopenLastStepForRevision(taskId, task) {
|
|
70068
|
+
const steps = task.steps;
|
|
70069
|
+
if (steps.length === 0) return null;
|
|
70070
|
+
let targetIndex = -1;
|
|
70071
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
70072
|
+
if (steps[i].status !== "pending") {
|
|
70073
|
+
targetIndex = i;
|
|
70074
|
+
break;
|
|
70075
|
+
}
|
|
70076
|
+
}
|
|
70077
|
+
if (targetIndex === -1) {
|
|
70078
|
+
await this.store.updateTask(taskId, { currentStep: 0 });
|
|
70079
|
+
return null;
|
|
70080
|
+
}
|
|
70081
|
+
await this.store.updateStep(taskId, targetIndex, "pending");
|
|
70082
|
+
await this.store.updateTask(taskId, { currentStep: targetIndex });
|
|
70083
|
+
return { index: targetIndex, name: steps[targetIndex].name };
|
|
70084
|
+
}
|
|
69970
70085
|
/**
|
|
69971
70086
|
* Inject or update the "Workflow Revision Instructions" section in PROMPT.md.
|
|
69972
70087
|
* This section contains feedback from workflow steps that requested revisions.
|
|
69973
70088
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
69974
70089
|
*/
|
|
69975
|
-
async injectWorkflowRevisionInstructions(task, feedback
|
|
70090
|
+
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
69976
70091
|
const promptPath = join35(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
69977
70092
|
let content;
|
|
69978
70093
|
try {
|
|
@@ -69981,14 +70096,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
69981
70096
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping revision injection`);
|
|
69982
70097
|
return;
|
|
69983
70098
|
}
|
|
69984
|
-
|
|
69985
|
-
if (scope && scope.targetStepName && scope.resetStart < scope.totalSteps) {
|
|
69986
|
-
scopeLine = `Re-execution starts at **Step ${scope.resetStart + 1} ("${scope.targetStepName}")**. Earlier steps remain done \u2014 do not re-run them unless the feedback explicitly calls them out.`;
|
|
69987
|
-
} else if (scope && scope.resetStart >= scope.totalSteps) {
|
|
69988
|
-
scopeLine = "No steps were reset; apply the feedback as an in-place fix and call task_done() when complete.";
|
|
69989
|
-
} else {
|
|
69990
|
-
scopeLine = "Address the feedback above by making the necessary code changes, then mark all affected steps as done and call task_done() when complete.";
|
|
69991
|
-
}
|
|
70099
|
+
const scopeLine = "All prior steps remain **done**. Apply the feedback above as an in-place fix (make the necessary code changes, commit, and call `task_done()` when complete). Do **not** re-run or re-plan any earlier step unless the feedback explicitly calls it out.";
|
|
69992
70100
|
const revisionSectionHeader = "## Workflow Revision Instructions";
|
|
69993
70101
|
const revisionSectionContent = `${revisionSectionHeader}
|
|
69994
70102
|
|
|
@@ -70047,11 +70155,7 @@ ${feedback}
|
|
|
70047
70155
|
});
|
|
70048
70156
|
await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount);
|
|
70049
70157
|
const updatedTask = await this.store.getTask(task.id);
|
|
70050
|
-
|
|
70051
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
70052
|
-
await this.store.updateStep(task.id, i, "pending");
|
|
70053
|
-
}
|
|
70054
|
-
}
|
|
70158
|
+
await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
70055
70159
|
await this.store.updateTask(task.id, {
|
|
70056
70160
|
status: null,
|
|
70057
70161
|
sessionFile: null
|
|
@@ -70095,11 +70199,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
70095
70199
|
);
|
|
70096
70200
|
await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, MAX_WORKFLOW_STEP_RETRIES);
|
|
70097
70201
|
const updatedTask = await this.store.getTask(taskId);
|
|
70098
|
-
|
|
70099
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
70100
|
-
await this.store.updateStep(taskId, i, "pending");
|
|
70101
|
-
}
|
|
70102
|
-
}
|
|
70202
|
+
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
70103
70203
|
await this.store.updateTask(taskId, {
|
|
70104
70204
|
status: null,
|
|
70105
70205
|
error: null,
|
|
@@ -75898,6 +75998,12 @@ async function getHeartbeatMemorySettings(taskStore) {
|
|
|
75898
75998
|
}
|
|
75899
75999
|
return maybeGetSettings.call(taskStore);
|
|
75900
76000
|
}
|
|
76001
|
+
function isTickableState(state) {
|
|
76002
|
+
return state === "active" || state === "running";
|
|
76003
|
+
}
|
|
76004
|
+
function isHeartbeatManaged(agent) {
|
|
76005
|
+
return !isEphemeralAgent(agent);
|
|
76006
|
+
}
|
|
75901
76007
|
var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, heartbeatDoneParams, HeartbeatMonitor, HeartbeatTriggerScheduler;
|
|
75902
76008
|
var init_agent_heartbeat = __esm({
|
|
75903
76009
|
"../engine/src/agent-heartbeat.ts"() {
|
|
@@ -77134,10 +77240,6 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
77134
77240
|
* @param config - Per-agent heartbeat config
|
|
77135
77241
|
*/
|
|
77136
77242
|
registerAgent(agentId, config) {
|
|
77137
|
-
if (config.enabled === false) {
|
|
77138
|
-
heartbeatLog.log(`Skipping timer registration for ${agentId} (disabled)`);
|
|
77139
|
-
return;
|
|
77140
|
-
}
|
|
77141
77243
|
let rawIntervalMs = config.heartbeatIntervalMs;
|
|
77142
77244
|
let usingDefaultInterval = false;
|
|
77143
77245
|
if (!rawIntervalMs || typeof rawIntervalMs !== "number" || !Number.isFinite(rawIntervalMs) || rawIntervalMs <= 0) {
|
|
@@ -77227,8 +77329,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
77227
77329
|
this.assignedListener = async (agent, taskId) => {
|
|
77228
77330
|
if (!this.running) return;
|
|
77229
77331
|
try {
|
|
77230
|
-
if (agent
|
|
77231
|
-
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (
|
|
77332
|
+
if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
|
|
77333
|
+
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (state=${agent.state})`);
|
|
77232
77334
|
return;
|
|
77233
77335
|
}
|
|
77234
77336
|
const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
|
|
@@ -77297,9 +77399,21 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
77297
77399
|
watchAgentLifecycle() {
|
|
77298
77400
|
if (this.updatedListener || this.deletedListener) return;
|
|
77299
77401
|
this.updatedListener = (agent) => {
|
|
77300
|
-
if (agent
|
|
77402
|
+
if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
|
|
77301
77403
|
this.unregisterAgent(agent.id);
|
|
77404
|
+
return;
|
|
77405
|
+
}
|
|
77406
|
+
if (this.timers.has(agent.id)) {
|
|
77407
|
+
return;
|
|
77302
77408
|
}
|
|
77409
|
+
const rc = agent.runtimeConfig ?? {};
|
|
77410
|
+
this.registerAgent(agent.id, {
|
|
77411
|
+
heartbeatIntervalMs: rc.heartbeatIntervalMs,
|
|
77412
|
+
maxConcurrentRuns: rc.maxConcurrentRuns
|
|
77413
|
+
});
|
|
77414
|
+
heartbeatLog.log(
|
|
77415
|
+
`State-driven registration: ${agent.id} is ${agent.state} \u2014 timer armed`
|
|
77416
|
+
);
|
|
77303
77417
|
};
|
|
77304
77418
|
this.deletedListener = (agentId) => {
|
|
77305
77419
|
this.unregisterAgent(agentId);
|
|
@@ -77330,8 +77444,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
77330
77444
|
this.unregisterAgent(agentId);
|
|
77331
77445
|
return;
|
|
77332
77446
|
}
|
|
77333
|
-
if (agent
|
|
77334
|
-
heartbeatLog.log(`Timer tick skipped for ${agentId} (
|
|
77447
|
+
if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
|
|
77448
|
+
heartbeatLog.log(`Timer tick skipped for ${agentId} (state=${agent.state})`);
|
|
77335
77449
|
this.unregisterAgent(agentId);
|
|
77336
77450
|
return;
|
|
77337
77451
|
}
|
|
@@ -79536,13 +79650,13 @@ var init_in_process_runtime = __esm({
|
|
|
79536
79650
|
this.taskStore
|
|
79537
79651
|
);
|
|
79538
79652
|
this.triggerScheduler.start();
|
|
79653
|
+
const isTickable = (agent) => !isEphemeralAgent(agent) && (agent.state === "active" || agent.state === "running");
|
|
79539
79654
|
this.agentCreatedListener = (agent) => {
|
|
79540
79655
|
if (!this.triggerScheduler) return;
|
|
79656
|
+
if (!isTickable(agent)) return;
|
|
79541
79657
|
const rc = agent.runtimeConfig;
|
|
79542
|
-
if (rc?.enabled === false) return;
|
|
79543
79658
|
this.triggerScheduler.registerAgent(agent.id, {
|
|
79544
79659
|
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
79545
|
-
enabled: rc?.enabled,
|
|
79546
79660
|
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
79547
79661
|
});
|
|
79548
79662
|
runtimeLog.log(`Registered new agent ${agent.id} for heartbeat triggers`);
|
|
@@ -79550,18 +79664,17 @@ var init_in_process_runtime = __esm({
|
|
|
79550
79664
|
this.agentStore.on("agent:created", this.agentCreatedListener);
|
|
79551
79665
|
this.agentUpdatedListener = (agent) => {
|
|
79552
79666
|
if (!this.triggerScheduler) return;
|
|
79553
|
-
|
|
79554
|
-
if (rc?.enabled === false) {
|
|
79667
|
+
if (!isTickable(agent)) {
|
|
79555
79668
|
this.triggerScheduler.unregisterAgent(agent.id);
|
|
79556
|
-
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers (
|
|
79557
|
-
|
|
79558
|
-
this.triggerScheduler.registerAgent(agent.id, {
|
|
79559
|
-
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
79560
|
-
enabled: rc?.enabled,
|
|
79561
|
-
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
79562
|
-
});
|
|
79563
|
-
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
|
|
79669
|
+
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers (state=${agent.state})`);
|
|
79670
|
+
return;
|
|
79564
79671
|
}
|
|
79672
|
+
const rc = agent.runtimeConfig;
|
|
79673
|
+
this.triggerScheduler.registerAgent(agent.id, {
|
|
79674
|
+
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
79675
|
+
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
79676
|
+
});
|
|
79677
|
+
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers (state=${agent.state})`);
|
|
79565
79678
|
};
|
|
79566
79679
|
this.agentStore.on("agent:updated", this.agentUpdatedListener);
|
|
79567
79680
|
this.ephemeralTerminationListener = (agentId, from, to) => {
|
|
@@ -79596,15 +79709,13 @@ var init_in_process_runtime = __esm({
|
|
|
79596
79709
|
const agents = await this.agentStore.listAgents();
|
|
79597
79710
|
let registeredCount = 0;
|
|
79598
79711
|
for (const agent of agents) {
|
|
79712
|
+
if (!isTickable(agent)) continue;
|
|
79599
79713
|
const rc = agent.runtimeConfig;
|
|
79600
|
-
|
|
79601
|
-
|
|
79602
|
-
|
|
79603
|
-
|
|
79604
|
-
|
|
79605
|
-
});
|
|
79606
|
-
registeredCount++;
|
|
79607
|
-
}
|
|
79714
|
+
this.triggerScheduler.registerAgent(agent.id, {
|
|
79715
|
+
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
79716
|
+
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
79717
|
+
});
|
|
79718
|
+
registeredCount++;
|
|
79608
79719
|
}
|
|
79609
79720
|
if (agents.length > 0) {
|
|
79610
79721
|
runtimeLog.log(`Registered ${registeredCount} of ${agents.length} agents for heartbeat triggers`);
|
|
@@ -121468,6 +121579,34 @@ Description: ${step.description}`
|
|
|
121468
121579
|
rethrowAsApiError3(err);
|
|
121469
121580
|
}
|
|
121470
121581
|
});
|
|
121582
|
+
const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "archived"]);
|
|
121583
|
+
function isTerminalTaskStatus(status) {
|
|
121584
|
+
return status !== void 0 && TERMINAL_TASK_STATUSES.has(status);
|
|
121585
|
+
}
|
|
121586
|
+
async function sanitizeAgentTaskLinks(agents, scopedStore) {
|
|
121587
|
+
const taskIds = [...new Set(agents.map((a) => a.taskId).filter((id) => id !== void 0))];
|
|
121588
|
+
const taskStatusMap = /* @__PURE__ */ new Map();
|
|
121589
|
+
await Promise.all(
|
|
121590
|
+
taskIds.map(async (taskId) => {
|
|
121591
|
+
try {
|
|
121592
|
+
const task = await scopedStore.getTask(taskId);
|
|
121593
|
+
if (task) {
|
|
121594
|
+
taskStatusMap.set(taskId, task.column);
|
|
121595
|
+
}
|
|
121596
|
+
} catch {
|
|
121597
|
+
}
|
|
121598
|
+
})
|
|
121599
|
+
);
|
|
121600
|
+
return agents.map((agent) => {
|
|
121601
|
+
if (!agent.taskId) return agent;
|
|
121602
|
+
const taskStatus = taskStatusMap.get(agent.taskId);
|
|
121603
|
+
if (isTerminalTaskStatus(taskStatus)) {
|
|
121604
|
+
const { taskId: _omitted, ...sanitized } = agent;
|
|
121605
|
+
return sanitized;
|
|
121606
|
+
}
|
|
121607
|
+
return agent;
|
|
121608
|
+
});
|
|
121609
|
+
}
|
|
121471
121610
|
router.get("/agents", async (req, res) => {
|
|
121472
121611
|
try {
|
|
121473
121612
|
const filter2 = {};
|
|
@@ -121485,7 +121624,8 @@ Description: ${step.description}`
|
|
|
121485
121624
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
121486
121625
|
await agentStore.init();
|
|
121487
121626
|
const agents = await agentStore.listAgents(filter2);
|
|
121488
|
-
|
|
121627
|
+
const sanitizedAgents = await sanitizeAgentTaskLinks(agents, scopedStore);
|
|
121628
|
+
res.json(sanitizedAgents);
|
|
121489
121629
|
} catch (err) {
|
|
121490
121630
|
if (err instanceof ApiError) {
|
|
121491
121631
|
throw err;
|
|
@@ -122180,7 +122320,8 @@ ${body}`;
|
|
|
122180
122320
|
await agentStore.init();
|
|
122181
122321
|
const agents = await agentStore.listAgents();
|
|
122182
122322
|
const activeCount = agents.filter((a) => a.state === "active" || a.state === "running").length;
|
|
122183
|
-
const
|
|
122323
|
+
const sanitizedAgents = await sanitizeAgentTaskLinks(agents, scopedStore);
|
|
122324
|
+
const assignedTaskCount = sanitizedAgents.filter((a) => a.taskId).length;
|
|
122184
122325
|
let completedRuns = 0;
|
|
122185
122326
|
let failedRuns = 0;
|
|
122186
122327
|
for (const agent of agents) {
|
|
@@ -122242,7 +122383,8 @@ ${body}`;
|
|
|
122242
122383
|
if (!agent) {
|
|
122243
122384
|
throw notFound("Agent not found");
|
|
122244
122385
|
}
|
|
122245
|
-
|
|
122386
|
+
const [sanitizedAgent] = await sanitizeAgentTaskLinks([agent], scopedStore);
|
|
122387
|
+
res.json(sanitizedAgent);
|
|
122246
122388
|
} catch (err) {
|
|
122247
122389
|
if (err instanceof ApiError) {
|
|
122248
122390
|
throw err;
|
|
@@ -131577,6 +131719,9 @@ function isApiPath(path4) {
|
|
|
131577
131719
|
return path4 === "/api" || path4.startsWith("/api/");
|
|
131578
131720
|
}
|
|
131579
131721
|
function getDaemonToken(options) {
|
|
131722
|
+
if (options?.noAuth) {
|
|
131723
|
+
return void 0;
|
|
131724
|
+
}
|
|
131580
131725
|
if (options?.daemon?.token) {
|
|
131581
131726
|
return options.daemon.token;
|
|
131582
131727
|
}
|
|
@@ -131800,7 +131945,7 @@ function createServer(store, options) {
|
|
|
131800
131945
|
}
|
|
131801
131946
|
}
|
|
131802
131947
|
}));
|
|
131803
|
-
const daemonToken = options?.daemon?.token ?? process.env.FUSION_DAEMON_TOKEN;
|
|
131948
|
+
const daemonToken = options?.noAuth ? void 0 : options?.daemon?.token ?? process.env.FUSION_DAEMON_TOKEN;
|
|
131804
131949
|
if (daemonToken) {
|
|
131805
131950
|
app.use(createAuthMiddleware(daemonToken));
|
|
131806
131951
|
}
|
|
@@ -133953,7 +134098,8 @@ async function runDashboard(port, opts = {}) {
|
|
|
133953
134098
|
onProjectFirstAccessed: (projectId) => engineManager.onProjectAccessed(projectId),
|
|
133954
134099
|
skillsAdapter,
|
|
133955
134100
|
https: loadTlsCredentialsFromEnv(),
|
|
133956
|
-
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : void 0
|
|
134101
|
+
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : void 0,
|
|
134102
|
+
noAuth: opts.noAuth
|
|
133957
134103
|
});
|
|
133958
134104
|
const shutdown = async (signal) => {
|
|
133959
134105
|
if (shutdownInProgress) return;
|
|
@@ -134048,14 +134194,13 @@ async function runDashboard(port, opts = {}) {
|
|
|
134048
134194
|
triggerScheduler.start();
|
|
134049
134195
|
const agents = await agentStore.listAgents();
|
|
134050
134196
|
for (const agent of agents) {
|
|
134197
|
+
if (isEphemeralAgent(agent)) continue;
|
|
134198
|
+
if (agent.state !== "active" && agent.state !== "running") continue;
|
|
134051
134199
|
const rc = agent.runtimeConfig;
|
|
134052
|
-
|
|
134053
|
-
|
|
134054
|
-
|
|
134055
|
-
|
|
134056
|
-
maxConcurrentRuns: rc.maxConcurrentRuns
|
|
134057
|
-
});
|
|
134058
|
-
}
|
|
134200
|
+
triggerScheduler.registerAgent(agent.id, {
|
|
134201
|
+
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
134202
|
+
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
134203
|
+
});
|
|
134059
134204
|
}
|
|
134060
134205
|
if (agents.length > 0) {
|
|
134061
134206
|
console.log(`[engine] Registered ${triggerScheduler.getRegisteredAgents().length} agents for heartbeat triggers`);
|
|
@@ -134094,7 +134239,8 @@ async function runDashboard(port, opts = {}) {
|
|
|
134094
134239
|
pluginRunner: pluginLoader,
|
|
134095
134240
|
skillsAdapter,
|
|
134096
134241
|
https: loadTlsCredentialsFromEnv(),
|
|
134097
|
-
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : void 0
|
|
134242
|
+
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : void 0,
|
|
134243
|
+
noAuth: opts.noAuth
|
|
134098
134244
|
});
|
|
134099
134245
|
}
|
|
134100
134246
|
if (opts.dev) {
|