@runfusion/fusion 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1434 -381
- package/dist/client/assets/{index-9GdD09x7.css → index-CYkQfLYV.css} +1 -1
- package/dist/client/assets/index-ep-146OC.js +1241 -0
- package/dist/client/index.html +2 -2
- package/dist/extension.js +160 -86
- package/package.json +1 -1
- package/dist/client/assets/index-wtVkS5Qz.js +0 -1241
package/dist/client/index.html
CHANGED
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
}
|
|
79
79
|
})();
|
|
80
80
|
</script>
|
|
81
|
-
<script type="module" crossorigin src="/assets/index-
|
|
81
|
+
<script type="module" crossorigin src="/assets/index-ep-146OC.js"></script>
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-K0fH_qHe.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DzcZoU0P.js">
|
|
84
84
|
<link rel="stylesheet" crossorigin href="/assets/vendor-xterm-LZoznX6r.css">
|
|
85
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
85
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CYkQfLYV.css">
|
|
86
86
|
</head>
|
|
87
87
|
<body>
|
|
88
88
|
<div id="root"></div>
|
package/dist/extension.js
CHANGED
|
@@ -133,9 +133,11 @@ var init_settings_schema = __esm({
|
|
|
133
133
|
defaultPresetBySize: {},
|
|
134
134
|
autoResolveConflicts: true,
|
|
135
135
|
smartConflictResolution: true,
|
|
136
|
+
worktreeRebaseBeforeMerge: true,
|
|
137
|
+
worktreeRebaseRemote: "",
|
|
136
138
|
strictScopeEnforcement: false,
|
|
137
139
|
buildRetryCount: 0,
|
|
138
|
-
verificationFixRetries:
|
|
140
|
+
verificationFixRetries: 3,
|
|
139
141
|
buildTimeoutMs: 3e5,
|
|
140
142
|
requirePlanApproval: false,
|
|
141
143
|
specStalenessEnabled: false,
|
|
@@ -30548,6 +30550,7 @@ ${newTask.description}
|
|
|
30548
30550
|
}
|
|
30549
30551
|
}
|
|
30550
30552
|
if (updates.steps !== void 0) task.steps = updates.steps;
|
|
30553
|
+
if (updates.currentStep !== void 0) task.currentStep = updates.currentStep;
|
|
30551
30554
|
if (updates.status === null) {
|
|
30552
30555
|
task.status = void 0;
|
|
30553
30556
|
} else if (updates.status !== void 0) {
|
|
@@ -53420,6 +53423,71 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53420
53423
|
mergerLog.warn(`${taskId}: unable to verify/checkout main branch \u2014 proceeding on current HEAD`);
|
|
53421
53424
|
}
|
|
53422
53425
|
}
|
|
53426
|
+
if (settings.worktreeRebaseBeforeMerge !== false) {
|
|
53427
|
+
try {
|
|
53428
|
+
let remote = settings.worktreeRebaseRemote?.trim();
|
|
53429
|
+
if (!remote) {
|
|
53430
|
+
try {
|
|
53431
|
+
const { stdout: mainBranchOut } = await execAsync2(
|
|
53432
|
+
"git rev-parse --abbrev-ref HEAD",
|
|
53433
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
53434
|
+
);
|
|
53435
|
+
const mainBranch = mainBranchOut.trim();
|
|
53436
|
+
const { stdout: configuredRemote } = await execAsync2(
|
|
53437
|
+
`git config --get branch.${mainBranch}.remote`,
|
|
53438
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
53439
|
+
).catch(() => ({ stdout: "" }));
|
|
53440
|
+
remote = configuredRemote.trim();
|
|
53441
|
+
} catch {
|
|
53442
|
+
}
|
|
53443
|
+
}
|
|
53444
|
+
if (!remote) {
|
|
53445
|
+
try {
|
|
53446
|
+
const { stdout: remotesOut } = await execAsync2("git remote", {
|
|
53447
|
+
cwd: rootDir,
|
|
53448
|
+
encoding: "utf-8"
|
|
53449
|
+
});
|
|
53450
|
+
const remotes = remotesOut.trim().split(/\s+/).filter(Boolean);
|
|
53451
|
+
if (remotes.length === 1) {
|
|
53452
|
+
remote = remotes[0];
|
|
53453
|
+
} else if (remotes.includes("origin")) {
|
|
53454
|
+
remote = "origin";
|
|
53455
|
+
}
|
|
53456
|
+
} catch {
|
|
53457
|
+
}
|
|
53458
|
+
}
|
|
53459
|
+
if (!remote) {
|
|
53460
|
+
mergerLog.log(`${taskId}: no remote resolvable \u2014 skipping pre-merge rebase`);
|
|
53461
|
+
} else {
|
|
53462
|
+
mergerLog.log(`${taskId}: fetching ${remote} before merge`);
|
|
53463
|
+
await execAsync2(`git fetch "${remote}"`, { cwd: rootDir });
|
|
53464
|
+
try {
|
|
53465
|
+
const { stdout: mainBranchOut } = await execAsync2(
|
|
53466
|
+
"git rev-parse --abbrev-ref HEAD",
|
|
53467
|
+
{ cwd: rootDir, encoding: "utf-8" }
|
|
53468
|
+
);
|
|
53469
|
+
const mainBranch = mainBranchOut.trim();
|
|
53470
|
+
const remoteRef = `${remote}/${mainBranch}`;
|
|
53471
|
+
if (worktreePath) {
|
|
53472
|
+
await execAsync2(`git rebase "${remoteRef}"`, { cwd: worktreePath });
|
|
53473
|
+
mergerLog.log(`${taskId}: rebased ${branch} onto ${remoteRef}`);
|
|
53474
|
+
} else {
|
|
53475
|
+
mergerLog.warn(`${taskId}: no worktreePath \u2014 skipping task branch rebase`);
|
|
53476
|
+
}
|
|
53477
|
+
} catch (rebaseErr) {
|
|
53478
|
+
const msg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
|
|
53479
|
+
mergerLog.warn(`${taskId}: pre-merge rebase failed (${msg}) \u2014 aborting rebase and falling through to smart/AI merge`);
|
|
53480
|
+
if (worktreePath) {
|
|
53481
|
+
await execAsync2("git rebase --abort", { cwd: worktreePath }).catch(() => {
|
|
53482
|
+
});
|
|
53483
|
+
}
|
|
53484
|
+
}
|
|
53485
|
+
}
|
|
53486
|
+
} catch (err) {
|
|
53487
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
53488
|
+
mergerLog.warn(`${taskId}: pre-merge rebase pipeline failed (${msg}) \u2014 proceeding without rebase`);
|
|
53489
|
+
}
|
|
53490
|
+
}
|
|
53423
53491
|
let commitLog = "";
|
|
53424
53492
|
let diffStat = "";
|
|
53425
53493
|
try {
|
|
@@ -53518,7 +53586,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53518
53586
|
} catch (error) {
|
|
53519
53587
|
if (error.name === "VerificationError") {
|
|
53520
53588
|
const verificationErr = error;
|
|
53521
|
-
const maxFixRetries = Math.min(settings.verificationFixRetries ??
|
|
53589
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
53522
53590
|
if (maxFixRetries > 0 && (verificationErr.verificationResult.testResult || verificationErr.verificationResult.buildResult)) {
|
|
53523
53591
|
mergerLog.log(`${taskId}: deterministic verification failed \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
|
|
53524
53592
|
await store.logEntry(taskId, `Verification failed during merge \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
|
|
@@ -53563,7 +53631,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53563
53631
|
throw error;
|
|
53564
53632
|
}
|
|
53565
53633
|
if (error.message?.includes("Build verification failed")) {
|
|
53566
|
-
const maxFixRetries = Math.min(settings.verificationFixRetries ??
|
|
53634
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
53567
53635
|
if (maxFixRetries > 0 && (effectiveTestCommand || effectiveBuildCommand)) {
|
|
53568
53636
|
mergerLog.log(`${taskId}: build verification failed \u2014 attempting in-merge fix`);
|
|
53569
53637
|
await store.logEntry(taskId, `Build verification failed during merge \u2014 attempting in-merge fix`);
|
|
@@ -55936,19 +56004,6 @@ import { existsSync as existsSync25 } from "node:fs";
|
|
|
55936
56004
|
import { readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
55937
56005
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
55938
56006
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
55939
|
-
function determineRevisionResetStart(steps, feedback) {
|
|
55940
|
-
const total = steps.length;
|
|
55941
|
-
if (total === 0) return 0;
|
|
55942
|
-
const skipPreflight = /preflight/i.test(steps[0].name);
|
|
55943
|
-
const firstCandidate = skipPreflight ? 1 : 0;
|
|
55944
|
-
if (firstCandidate >= total) return total;
|
|
55945
|
-
const fb = feedback.toLowerCase();
|
|
55946
|
-
for (let i = firstCandidate; i < total; i++) {
|
|
55947
|
-
const tokens = steps[i].name.toLowerCase().match(/[a-z][a-z]{4,}/g) ?? [];
|
|
55948
|
-
if (tokens.some((t) => fb.includes(t))) return i;
|
|
55949
|
-
}
|
|
55950
|
-
return firstCandidate;
|
|
55951
|
-
}
|
|
55952
56007
|
function truncateWorkflowScriptOutput2(output) {
|
|
55953
56008
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
|
|
55954
56009
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2} characters ...
|
|
@@ -58265,35 +58320,23 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
58265
58320
|
}
|
|
58266
58321
|
/**
|
|
58267
58322
|
* Handle a workflow step revision request.
|
|
58268
|
-
*
|
|
58269
|
-
*
|
|
58270
|
-
*
|
|
58271
|
-
*
|
|
58272
|
-
*
|
|
58273
|
-
*
|
|
58274
|
-
* The task stays in "in-progress" and is scheduled for a fresh executor pass.
|
|
58323
|
+
*
|
|
58324
|
+
* Re-opens ONLY the last step so the executor has exactly one pending slot
|
|
58325
|
+
* to re-enter through. All earlier done steps stay done — the agent reads
|
|
58326
|
+
* the injected feedback from PROMPT.md and applies an in-place fix rather
|
|
58327
|
+
* than redoing any completed step.
|
|
58275
58328
|
*/
|
|
58276
58329
|
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
|
|
58277
58330
|
executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
|
|
58278
58331
|
const updatedTask = await this.store.getTask(task.id);
|
|
58279
|
-
const
|
|
58280
|
-
const
|
|
58281
|
-
const resetSummary = resetStart >= updatedTask.steps.length ? "no steps to reset" : `resetting steps ${resetStart + 1}\u2013${updatedTask.steps.length} (starting at "${targetStepName}")`;
|
|
58332
|
+
const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
58333
|
+
const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
|
|
58282
58334
|
await this.store.logEntry(
|
|
58283
58335
|
task.id,
|
|
58284
|
-
`Workflow step "${stepName}" requested revision \u2014 ${
|
|
58336
|
+
`Workflow step "${stepName}" requested revision \u2014 ${reopenSummary}`,
|
|
58285
58337
|
feedback
|
|
58286
58338
|
);
|
|
58287
|
-
await this.injectWorkflowRevisionInstructions(task, feedback
|
|
58288
|
-
resetStart,
|
|
58289
|
-
targetStepName,
|
|
58290
|
-
totalSteps: updatedTask.steps.length
|
|
58291
|
-
});
|
|
58292
|
-
for (let i = resetStart; i < updatedTask.steps.length; i++) {
|
|
58293
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
58294
|
-
await this.store.updateStep(task.id, i, "pending");
|
|
58295
|
-
}
|
|
58296
|
-
}
|
|
58339
|
+
await this.injectWorkflowRevisionInstructions(task, feedback);
|
|
58297
58340
|
await this.store.updateTask(task.id, {
|
|
58298
58341
|
status: null,
|
|
58299
58342
|
sessionFile: null
|
|
@@ -58315,12 +58358,36 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
58315
58358
|
}
|
|
58316
58359
|
}, 0);
|
|
58317
58360
|
}
|
|
58361
|
+
/**
|
|
58362
|
+
* Re-open the last non-pending step so a revision/failure handler gives the
|
|
58363
|
+
* executor exactly one pending slot to re-enter through. Returns the index
|
|
58364
|
+
* and name of the step that was flipped to `pending`, or null when there
|
|
58365
|
+
* was nothing to re-open.
|
|
58366
|
+
*/
|
|
58367
|
+
async reopenLastStepForRevision(taskId, task) {
|
|
58368
|
+
const steps = task.steps;
|
|
58369
|
+
if (steps.length === 0) return null;
|
|
58370
|
+
let targetIndex = -1;
|
|
58371
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
58372
|
+
if (steps[i].status !== "pending") {
|
|
58373
|
+
targetIndex = i;
|
|
58374
|
+
break;
|
|
58375
|
+
}
|
|
58376
|
+
}
|
|
58377
|
+
if (targetIndex === -1) {
|
|
58378
|
+
await this.store.updateTask(taskId, { currentStep: 0 });
|
|
58379
|
+
return null;
|
|
58380
|
+
}
|
|
58381
|
+
await this.store.updateStep(taskId, targetIndex, "pending");
|
|
58382
|
+
await this.store.updateTask(taskId, { currentStep: targetIndex });
|
|
58383
|
+
return { index: targetIndex, name: steps[targetIndex].name };
|
|
58384
|
+
}
|
|
58318
58385
|
/**
|
|
58319
58386
|
* Inject or update the "Workflow Revision Instructions" section in PROMPT.md.
|
|
58320
58387
|
* This section contains feedback from workflow steps that requested revisions.
|
|
58321
58388
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
58322
58389
|
*/
|
|
58323
|
-
async injectWorkflowRevisionInstructions(task, feedback
|
|
58390
|
+
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
58324
58391
|
const promptPath = join32(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
58325
58392
|
let content;
|
|
58326
58393
|
try {
|
|
@@ -58329,14 +58396,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
58329
58396
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping revision injection`);
|
|
58330
58397
|
return;
|
|
58331
58398
|
}
|
|
58332
|
-
|
|
58333
|
-
if (scope && scope.targetStepName && scope.resetStart < scope.totalSteps) {
|
|
58334
|
-
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.`;
|
|
58335
|
-
} else if (scope && scope.resetStart >= scope.totalSteps) {
|
|
58336
|
-
scopeLine = "No steps were reset; apply the feedback as an in-place fix and call task_done() when complete.";
|
|
58337
|
-
} else {
|
|
58338
|
-
scopeLine = "Address the feedback above by making the necessary code changes, then mark all affected steps as done and call task_done() when complete.";
|
|
58339
|
-
}
|
|
58399
|
+
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.";
|
|
58340
58400
|
const revisionSectionHeader = "## Workflow Revision Instructions";
|
|
58341
58401
|
const revisionSectionContent = `${revisionSectionHeader}
|
|
58342
58402
|
|
|
@@ -58395,11 +58455,7 @@ ${feedback}
|
|
|
58395
58455
|
});
|
|
58396
58456
|
await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount);
|
|
58397
58457
|
const updatedTask = await this.store.getTask(task.id);
|
|
58398
|
-
|
|
58399
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
58400
|
-
await this.store.updateStep(task.id, i, "pending");
|
|
58401
|
-
}
|
|
58402
|
-
}
|
|
58458
|
+
await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
58403
58459
|
await this.store.updateTask(task.id, {
|
|
58404
58460
|
status: null,
|
|
58405
58461
|
sessionFile: null
|
|
@@ -58443,11 +58499,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
58443
58499
|
);
|
|
58444
58500
|
await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, MAX_WORKFLOW_STEP_RETRIES);
|
|
58445
58501
|
const updatedTask = await this.store.getTask(taskId);
|
|
58446
|
-
|
|
58447
|
-
if (updatedTask.steps[i].status !== "pending") {
|
|
58448
|
-
await this.store.updateStep(taskId, i, "pending");
|
|
58449
|
-
}
|
|
58450
|
-
}
|
|
58502
|
+
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
58451
58503
|
await this.store.updateTask(taskId, {
|
|
58452
58504
|
status: null,
|
|
58453
58505
|
error: null,
|
|
@@ -64246,6 +64298,12 @@ async function getHeartbeatMemorySettings(taskStore) {
|
|
|
64246
64298
|
}
|
|
64247
64299
|
return maybeGetSettings.call(taskStore);
|
|
64248
64300
|
}
|
|
64301
|
+
function isTickableState(state) {
|
|
64302
|
+
return state === "active" || state === "running";
|
|
64303
|
+
}
|
|
64304
|
+
function isHeartbeatManaged(agent) {
|
|
64305
|
+
return !isEphemeralAgent(agent);
|
|
64306
|
+
}
|
|
64249
64307
|
var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, heartbeatDoneParams, HeartbeatMonitor, HeartbeatTriggerScheduler;
|
|
64250
64308
|
var init_agent_heartbeat = __esm({
|
|
64251
64309
|
"../engine/src/agent-heartbeat.ts"() {
|
|
@@ -65482,10 +65540,6 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65482
65540
|
* @param config - Per-agent heartbeat config
|
|
65483
65541
|
*/
|
|
65484
65542
|
registerAgent(agentId, config) {
|
|
65485
|
-
if (config.enabled === false) {
|
|
65486
|
-
heartbeatLog.log(`Skipping timer registration for ${agentId} (disabled)`);
|
|
65487
|
-
return;
|
|
65488
|
-
}
|
|
65489
65543
|
let rawIntervalMs = config.heartbeatIntervalMs;
|
|
65490
65544
|
let usingDefaultInterval = false;
|
|
65491
65545
|
if (!rawIntervalMs || typeof rawIntervalMs !== "number" || !Number.isFinite(rawIntervalMs) || rawIntervalMs <= 0) {
|
|
@@ -65575,8 +65629,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65575
65629
|
this.assignedListener = async (agent, taskId) => {
|
|
65576
65630
|
if (!this.running) return;
|
|
65577
65631
|
try {
|
|
65578
|
-
if (agent
|
|
65579
|
-
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (
|
|
65632
|
+
if (!isHeartbeatManaged(agent)) {
|
|
65633
|
+
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (ephemeral/internal)`);
|
|
65634
|
+
return;
|
|
65635
|
+
}
|
|
65636
|
+
const runtimeConfig = agent.runtimeConfig ?? {};
|
|
65637
|
+
if (runtimeConfig.enabled === false) {
|
|
65638
|
+
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (disabled)`);
|
|
65580
65639
|
return;
|
|
65581
65640
|
}
|
|
65582
65641
|
const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
|
|
@@ -65645,9 +65704,21 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65645
65704
|
watchAgentLifecycle() {
|
|
65646
65705
|
if (this.updatedListener || this.deletedListener) return;
|
|
65647
65706
|
this.updatedListener = (agent) => {
|
|
65648
|
-
if (agent
|
|
65707
|
+
if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
|
|
65649
65708
|
this.unregisterAgent(agent.id);
|
|
65709
|
+
return;
|
|
65710
|
+
}
|
|
65711
|
+
if (this.timers.has(agent.id)) {
|
|
65712
|
+
return;
|
|
65650
65713
|
}
|
|
65714
|
+
const rc = agent.runtimeConfig ?? {};
|
|
65715
|
+
this.registerAgent(agent.id, {
|
|
65716
|
+
heartbeatIntervalMs: rc.heartbeatIntervalMs,
|
|
65717
|
+
maxConcurrentRuns: rc.maxConcurrentRuns
|
|
65718
|
+
});
|
|
65719
|
+
heartbeatLog.log(
|
|
65720
|
+
`State-driven registration: ${agent.id} is ${agent.state} \u2014 timer armed`
|
|
65721
|
+
);
|
|
65651
65722
|
};
|
|
65652
65723
|
this.deletedListener = (agentId) => {
|
|
65653
65724
|
this.unregisterAgent(agentId);
|
|
@@ -65678,8 +65749,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65678
65749
|
this.unregisterAgent(agentId);
|
|
65679
65750
|
return;
|
|
65680
65751
|
}
|
|
65681
|
-
if (agent
|
|
65682
|
-
heartbeatLog.log(`Timer tick skipped for ${agentId} (
|
|
65752
|
+
if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
|
|
65753
|
+
heartbeatLog.log(`Timer tick skipped for ${agentId} (state=${agent.state})`);
|
|
65683
65754
|
this.unregisterAgent(agentId);
|
|
65684
65755
|
return;
|
|
65685
65756
|
}
|
|
@@ -66023,6 +66094,10 @@ var init_self_healing = __esm({
|
|
|
66023
66094
|
const result = await readLog("HEAD");
|
|
66024
66095
|
stdout = result.stdout;
|
|
66025
66096
|
}
|
|
66097
|
+
if (!stdout.trim() && task.baseCommitSha) {
|
|
66098
|
+
const result = await readLog("HEAD");
|
|
66099
|
+
stdout = result.stdout;
|
|
66100
|
+
}
|
|
66026
66101
|
const firstLine = stdout.trim().split("\n").find(Boolean);
|
|
66027
66102
|
if (!firstLine) return null;
|
|
66028
66103
|
const [sha, subject] = firstLine.split("");
|
|
@@ -67884,13 +67959,13 @@ var init_in_process_runtime = __esm({
|
|
|
67884
67959
|
this.taskStore
|
|
67885
67960
|
);
|
|
67886
67961
|
this.triggerScheduler.start();
|
|
67962
|
+
const isHeartbeatEnabledAgent = (agent) => !isEphemeralAgent(agent) && agent.runtimeConfig?.enabled !== false;
|
|
67887
67963
|
this.agentCreatedListener = (agent) => {
|
|
67888
67964
|
if (!this.triggerScheduler) return;
|
|
67965
|
+
if (!isHeartbeatEnabledAgent(agent)) return;
|
|
67889
67966
|
const rc = agent.runtimeConfig;
|
|
67890
|
-
if (rc?.enabled === false) return;
|
|
67891
67967
|
this.triggerScheduler.registerAgent(agent.id, {
|
|
67892
67968
|
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
67893
|
-
enabled: rc?.enabled,
|
|
67894
67969
|
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
67895
67970
|
});
|
|
67896
67971
|
runtimeLog.log(`Registered new agent ${agent.id} for heartbeat triggers`);
|
|
@@ -67898,18 +67973,17 @@ var init_in_process_runtime = __esm({
|
|
|
67898
67973
|
this.agentStore.on("agent:created", this.agentCreatedListener);
|
|
67899
67974
|
this.agentUpdatedListener = (agent) => {
|
|
67900
67975
|
if (!this.triggerScheduler) return;
|
|
67901
|
-
|
|
67902
|
-
if (rc?.enabled === false) {
|
|
67976
|
+
if (!isHeartbeatEnabledAgent(agent)) {
|
|
67903
67977
|
this.triggerScheduler.unregisterAgent(agent.id);
|
|
67904
|
-
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers
|
|
67905
|
-
|
|
67906
|
-
this.triggerScheduler.registerAgent(agent.id, {
|
|
67907
|
-
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
67908
|
-
enabled: rc?.enabled,
|
|
67909
|
-
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
67910
|
-
});
|
|
67911
|
-
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
|
|
67978
|
+
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers`);
|
|
67979
|
+
return;
|
|
67912
67980
|
}
|
|
67981
|
+
const rc = agent.runtimeConfig;
|
|
67982
|
+
this.triggerScheduler.registerAgent(agent.id, {
|
|
67983
|
+
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
67984
|
+
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
67985
|
+
});
|
|
67986
|
+
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
|
|
67913
67987
|
};
|
|
67914
67988
|
this.agentStore.on("agent:updated", this.agentUpdatedListener);
|
|
67915
67989
|
this.ephemeralTerminationListener = (agentId, from, to) => {
|
|
@@ -67944,15 +68018,13 @@ var init_in_process_runtime = __esm({
|
|
|
67944
68018
|
const agents = await this.agentStore.listAgents();
|
|
67945
68019
|
let registeredCount = 0;
|
|
67946
68020
|
for (const agent of agents) {
|
|
68021
|
+
if (!isHeartbeatEnabledAgent(agent)) continue;
|
|
67947
68022
|
const rc = agent.runtimeConfig;
|
|
67948
|
-
|
|
67949
|
-
|
|
67950
|
-
|
|
67951
|
-
|
|
67952
|
-
|
|
67953
|
-
});
|
|
67954
|
-
registeredCount++;
|
|
67955
|
-
}
|
|
68023
|
+
this.triggerScheduler.registerAgent(agent.id, {
|
|
68024
|
+
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
68025
|
+
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
68026
|
+
});
|
|
68027
|
+
registeredCount++;
|
|
67956
68028
|
}
|
|
67957
68029
|
if (agents.length > 0) {
|
|
67958
68030
|
runtimeLog.log(`Registered ${registeredCount} of ${agents.length} agents for heartbeat triggers`);
|
|
@@ -72395,11 +72467,13 @@ var init_terminal = __esm({
|
|
|
72395
72467
|
});
|
|
72396
72468
|
|
|
72397
72469
|
// ../dashboard/src/terminal-service.ts
|
|
72398
|
-
|
|
72470
|
+
import { createRequire } from "node:module";
|
|
72471
|
+
var isBunBinary, require2;
|
|
72399
72472
|
var init_terminal_service = __esm({
|
|
72400
72473
|
"../dashboard/src/terminal-service.ts"() {
|
|
72401
72474
|
"use strict";
|
|
72402
72475
|
isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
72476
|
+
require2 = createRequire(import.meta.url);
|
|
72403
72477
|
}
|
|
72404
72478
|
});
|
|
72405
72479
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runfusion/fusion",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
|
|
6
6
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|