@runfusion/fusion 0.30.0 → 0.31.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 +438 -235
- package/dist/client/assets/{AgentDetailView-Cu4TgvBn.js → AgentDetailView-Dx7T1-gM.js} +3 -3
- package/dist/client/assets/AgentPermissionPolicyEditor-Cbd6SG_T.css +1 -0
- package/dist/client/assets/AgentPermissionPolicyEditor-Ia7QkynK.js +6 -0
- package/dist/client/assets/{AgentsView-CIH4Acck.js → AgentsView-DBbdPWV-.js} +3 -3
- package/dist/client/assets/ChatView-DYpoWnCc.js +1 -0
- package/dist/client/assets/{DevServerView-Bd8JZ-3C.js → DevServerView-DotHOFZt.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BOGqZNiq.js → DirectoryPicker-CIj3ZGKv.js} +1 -1
- package/dist/client/assets/{DocumentsView-B-ELcw4o.js → DocumentsView-Dt27gDYa.js} +1 -1
- package/dist/client/assets/{EvalsView-Ccl9QBYN.js → EvalsView-HfWTc8Uv.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-BM7EnSvJ.js → ExperimentalAgentOnboardingModal-U7fXDhhN.js} +1 -1
- package/dist/client/assets/{InsightsView-DLF49bzu.js → InsightsView-C8bsAlMv.js} +1 -1
- package/dist/client/assets/{MemoryView-CE1D9euM.js → MemoryView-D-HLtRrr.js} +1 -1
- package/dist/client/assets/{NodesView-Zxusnhxu.js → NodesView-w3ms-XX2.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-BlMmxtLZ.js → PiExtensionsManager-DFAZAfNn.js} +2 -2
- package/dist/client/assets/{PluginManager-D4eZ0zA4.js → PluginManager-DN1VG7Qm.js} +1 -1
- package/dist/client/assets/ReliabilityView-D0Rtd7pX.css +1 -0
- package/dist/client/assets/{ResearchView-C14Zh7c9.js → ResearchView-YoxEFtNB.js} +1 -1
- package/dist/client/assets/{SettingsModal-BAx4gt2A.css → SettingsModal-1Y8N6MkL.css} +1 -1
- package/dist/client/assets/SettingsModal-B86XhYB1.js +31 -0
- package/dist/client/assets/{SettingsModal-DRnZ6eBW.js → SettingsModal-BRRPoxku.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-BmmRM3g-.js → SetupWizardModal-B4eA3bpz.js} +1 -1
- package/dist/client/assets/{SkillsView-DWVtT2-8.js → SkillsView-DZIfZoF5.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-CKkaWzzc.js → StashRecoveryView-mKXYsqam.js} +1 -1
- package/dist/client/assets/{TodoView-hbD0NOCY.js → TodoView-C6sLLEQu.js} +1 -1
- package/dist/client/assets/dashboard-view-DOY8-BIx.js +21 -0
- package/dist/client/assets/{folder-open-BTN2_Wuv.js → folder-open-C_fxRMq1.js} +1 -1
- package/dist/client/assets/index-DTFPgu6_.js +695 -0
- package/dist/client/assets/index-DdvAZIzj.css +1 -0
- package/dist/client/assets/{upload-euJEs_N2.js → upload-xjku_93-.js} +1 -1
- package/dist/client/assets/{users-B7miEHCZ.js → users-CbALDtDk.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +477 -225
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/skill/fusion/SKILL.md +1 -1
- package/skill/fusion/references/extension-tools.md +11 -0
- package/skill/fusion/references/fusion-capabilities.md +1 -0
- package/dist/client/assets/AgentPermissionPolicyEditor-Ceg2Dszn.js +0 -6
- package/dist/client/assets/AgentPermissionPolicyEditor-CthjqUL_.css +0 -1
- package/dist/client/assets/ChatView-DqJoFquZ.js +0 -1
- package/dist/client/assets/ReliabilityView-rxA_HgEG.css +0 -1
- package/dist/client/assets/SettingsModal-c1QwWno0.js +0 -31
- package/dist/client/assets/dashboard-view-VNPB3ca0.js +0 -21
- package/dist/client/assets/index-CZFBq256.css +0 -1
- package/dist/client/assets/index-DzSLVOnp.js +0 -695
- /package/dist/client/assets/{ReliabilityView-KmH60I_5.js → ReliabilityView-DAuqE-zi.js} +0 -0
package/dist/extension.js
CHANGED
|
@@ -864,6 +864,7 @@ Your job: help users transform high-level goals into structured mission plans wi
|
|
|
864
864
|
- Features should be specific and actionable
|
|
865
865
|
- ALWAYS include verification/acceptance criteria at every level:
|
|
866
866
|
- Milestone: "verification" field \u2014 how to confirm this phase is complete (e.g., "All API endpoints return correct responses, integration tests pass")
|
|
867
|
+
- Milestone: optional "acceptanceCriteria" field for explicit milestone-level completion bars (if omitted, acceptance can be auto-derived from child feature criteria/descriptions)
|
|
867
868
|
- Slice: "verification" field \u2014 how to confirm this work unit is done (e.g., "Auth flow works end-to-end from signup through login")
|
|
868
869
|
- Feature: "acceptanceCriteria" field \u2014 how to verify this specific deliverable (e.g., "JWT tokens expire after 1 hour and refresh correctly")
|
|
869
870
|
- Suggest sensible defaults and push for specificity
|
|
@@ -877,7 +878,7 @@ For questions:
|
|
|
877
878
|
{"type": "question", "data": {"id": "unique-id", "type": "text|single_select|multi_select|confirm", "question": "The question text", "description": "Helpful context", "options": [{"id": "opt1", "label": "Option 1", "description": "Details"}]}}
|
|
878
879
|
|
|
879
880
|
For completion (when you have enough information):
|
|
880
|
-
{"type": "complete", "data": {"missionTitle": "Refined mission title", "missionDescription": "Comprehensive mission description based on the conversation", "milestones": [{"title": "Milestone title", "description": "What this phase achieves", "verification": "How to confirm this milestone is complete", "slices": [{"title": "Slice title", "description": "What this work unit covers", "verification": "How to confirm this slice is done", "features": [{"title": "Feature title", "description": "What to build", "acceptanceCriteria": "How to verify this feature works"}]}]}]}}`
|
|
881
|
+
{"type": "complete", "data": {"missionTitle": "Refined mission title", "missionDescription": "Comprehensive mission description based on the conversation", "milestones": [{"title": "Milestone title", "description": "What this phase achieves", "verification": "How to confirm this milestone is complete", "acceptanceCriteria": "Optional explicit milestone criteria (omit to auto-derive from features)", "slices": [{"title": "Slice title", "description": "What this work unit covers", "verification": "How to confirm this slice is done", "features": [{"title": "Feature title", "description": "What to build", "acceptanceCriteria": "How to verify this feature works"}]}]}]}}`
|
|
881
882
|
},
|
|
882
883
|
"ai-refine-system": {
|
|
883
884
|
key: "ai-refine-system",
|
|
@@ -1449,7 +1450,7 @@ function validateMessageMetadata(metadata) {
|
|
|
1449
1450
|
throw new Error("metadata.wakeRecipient must be a boolean");
|
|
1450
1451
|
}
|
|
1451
1452
|
}
|
|
1452
|
-
var THINKING_LEVELS, COLUMNS, DEFAULT_COLUMN, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, MERGE_STRATEGY_OVERLAP_BEHAVIORS, POST_MERGE_AUDIT_MODES, MERGE_AUDIT_AUTO_RECOVERY_MODES, AUTO_RECOVERY_MODES, NOTIFICATION_EVENTS, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, REPO_OVERRIDE_RE, CheckoutConflictError, SHARED_STATE_SNAPSHOT_VERSION, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, DEFAULT_HEARTBEAT_PROCEDURE_PATH, DASHBOARD_USER_ID, DASHBOARD_USER_ALIASES;
|
|
1453
|
+
var THINKING_LEVELS, COLUMNS, DEFAULT_COLUMN, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, MERGE_STRATEGY_OVERLAP_BEHAVIORS, POST_MERGE_AUDIT_MODES, MERGE_AUDIT_AUTO_RECOVERY_MODES, AUTO_RECOVERY_MODES, NOTIFICATION_EVENTS, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, REPO_OVERRIDE_RE, CheckoutConflictError, SHARED_STATE_SNAPSHOT_VERSION, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES, AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES, AGENT_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, DEFAULT_HEARTBEAT_PROCEDURE_PATH, DASHBOARD_USER_ID, DASHBOARD_USER_ALIASES;
|
|
1453
1454
|
var init_types = __esm({
|
|
1454
1455
|
"../core/src/types.ts"() {
|
|
1455
1456
|
"use strict";
|
|
@@ -1883,6 +1884,36 @@ Do NOT spend time on nits when no real issues exist.`
|
|
|
1883
1884
|
"network_api",
|
|
1884
1885
|
"task_agent_mutation"
|
|
1885
1886
|
];
|
|
1887
|
+
AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES = {
|
|
1888
|
+
git_write: ["git commit", "git push", "git merge", "git branch -d", "git worktree add", "write", "edit"],
|
|
1889
|
+
file_write_delete: ["write", "edit", "fn_task_attach"],
|
|
1890
|
+
command_execution: ["bash (non-git)", "read", "find", "grep", "ls"],
|
|
1891
|
+
network_api: ["fn_research_run (web/research)", "fn_web_fetch"],
|
|
1892
|
+
task_agent_mutation: [
|
|
1893
|
+
"fn_task_create",
|
|
1894
|
+
"fn_delegate_task",
|
|
1895
|
+
"fn_task_import_github",
|
|
1896
|
+
"fn_task_import_github_issue",
|
|
1897
|
+
"fn_spawn_agent",
|
|
1898
|
+
"fn_update_agent_config",
|
|
1899
|
+
"fn_update_identity"
|
|
1900
|
+
]
|
|
1901
|
+
};
|
|
1902
|
+
AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES = [
|
|
1903
|
+
"fn_send_message",
|
|
1904
|
+
"fn_post_room_message",
|
|
1905
|
+
"fn_read_messages",
|
|
1906
|
+
"fn_task_log",
|
|
1907
|
+
"fn_task_done",
|
|
1908
|
+
"fn_heartbeat_done",
|
|
1909
|
+
"fn_task_document_write",
|
|
1910
|
+
"fn_task_document_read",
|
|
1911
|
+
"fn_memory_search",
|
|
1912
|
+
"fn_memory_get",
|
|
1913
|
+
"fn_memory_append",
|
|
1914
|
+
"fn_read_evaluations",
|
|
1915
|
+
"fn_reflect_on_performance"
|
|
1916
|
+
];
|
|
1886
1917
|
AGENT_PROVISIONING_APPROVAL_MODES = ["always", "trusted-only", "never"];
|
|
1887
1918
|
AGENT_PERMISSION_POLICY_PRESET_IDS = ["unrestricted", "approval-required", "locked-down", "custom"];
|
|
1888
1919
|
APPROVAL_REQUEST_STATUSES = ["pending", "approved", "denied", "completed"];
|
|
@@ -3725,7 +3756,7 @@ var init_db = __esm({
|
|
|
3725
3756
|
DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
|
|
3726
3757
|
DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
|
|
3727
3758
|
DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
|
|
3728
|
-
SCHEMA_VERSION =
|
|
3759
|
+
SCHEMA_VERSION = 81;
|
|
3729
3760
|
SCHEMA_SQL = `
|
|
3730
3761
|
-- Tasks table with JSON columns for nested data
|
|
3731
3762
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -3780,6 +3811,8 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
3780
3811
|
createdAt TEXT NOT NULL,
|
|
3781
3812
|
updatedAt TEXT NOT NULL,
|
|
3782
3813
|
columnMovedAt TEXT,
|
|
3814
|
+
firstExecutionAt TEXT,
|
|
3815
|
+
cumulativeActiveMs INTEGER,
|
|
3783
3816
|
executionStartedAt TEXT,
|
|
3784
3817
|
executionCompletedAt TEXT,
|
|
3785
3818
|
-- JSON columns for nested arrays/objects
|
|
@@ -4247,6 +4280,7 @@ CREATE TABLE IF NOT EXISTS milestones (
|
|
|
4247
4280
|
orderIndex INTEGER NOT NULL,
|
|
4248
4281
|
interviewState TEXT NOT NULL,
|
|
4249
4282
|
dependencies TEXT DEFAULT '[]',
|
|
4283
|
+
acceptanceCriteria TEXT,
|
|
4250
4284
|
createdAt TEXT NOT NULL,
|
|
4251
4285
|
updatedAt TEXT NOT NULL,
|
|
4252
4286
|
FOREIGN KEY (missionId) REFERENCES missions(id) ON DELETE CASCADE
|
|
@@ -6367,6 +6401,25 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6367
6401
|
this.addColumnIfMissing("tasks", "overlapBlockedBy", "TEXT");
|
|
6368
6402
|
});
|
|
6369
6403
|
}
|
|
6404
|
+
if (version < 80) {
|
|
6405
|
+
this.applyMigration(80, () => {
|
|
6406
|
+
this.addColumnIfMissing("milestones", "acceptanceCriteria", "TEXT");
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
if (version < 81) {
|
|
6410
|
+
this.applyMigration(81, () => {
|
|
6411
|
+
this.addColumnIfMissing("tasks", "firstExecutionAt", "TEXT");
|
|
6412
|
+
this.addColumnIfMissing("tasks", "cumulativeActiveMs", "INTEGER");
|
|
6413
|
+
if (this.hasColumn("tasks", "executionStartedAt")) {
|
|
6414
|
+
this.db.prepare(
|
|
6415
|
+
`UPDATE tasks
|
|
6416
|
+
SET firstExecutionAt = executionStartedAt
|
|
6417
|
+
WHERE firstExecutionAt IS NULL
|
|
6418
|
+
AND executionStartedAt IS NOT NULL`
|
|
6419
|
+
).run();
|
|
6420
|
+
}
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6370
6423
|
}
|
|
6371
6424
|
/**
|
|
6372
6425
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -10609,6 +10662,21 @@ var init_db_migrate = __esm({
|
|
|
10609
10662
|
|
|
10610
10663
|
// ../core/src/mission-store.ts
|
|
10611
10664
|
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
10665
|
+
function deriveMilestoneAcceptanceCriteriaFromFeatures(features) {
|
|
10666
|
+
const lines = features.map((feature) => {
|
|
10667
|
+
const acceptance = feature.acceptanceCriteria?.trim();
|
|
10668
|
+
const description = feature.description?.trim();
|
|
10669
|
+
const text = acceptance && acceptance.length > 0 ? acceptance : description && description.length > 0 ? description : void 0;
|
|
10670
|
+
if (!text) {
|
|
10671
|
+
return void 0;
|
|
10672
|
+
}
|
|
10673
|
+
return `- ${feature.title}: ${text}`;
|
|
10674
|
+
}).filter((line) => Boolean(line));
|
|
10675
|
+
if (lines.length === 0) {
|
|
10676
|
+
return void 0;
|
|
10677
|
+
}
|
|
10678
|
+
return lines.join("\n");
|
|
10679
|
+
}
|
|
10612
10680
|
var DEFAULT_IMPLEMENTATION_RETRY_BUDGET, MissionStore;
|
|
10613
10681
|
var init_mission_store = __esm({
|
|
10614
10682
|
"../core/src/mission-store.ts"() {
|
|
@@ -10670,6 +10738,7 @@ var init_mission_store = __esm({
|
|
|
10670
10738
|
dependencies: fromJson(row.dependencies) || [],
|
|
10671
10739
|
planningNotes: row.planningNotes || void 0,
|
|
10672
10740
|
verification: row.verification || void 0,
|
|
10741
|
+
acceptanceCriteria: row.acceptanceCriteria || void 0,
|
|
10673
10742
|
validationState: row.validationState || "not_started",
|
|
10674
10743
|
createdAt: row.createdAt,
|
|
10675
10744
|
updatedAt: row.updatedAt
|
|
@@ -11382,13 +11451,14 @@ var init_mission_store = __esm({
|
|
|
11382
11451
|
dependencies: input.dependencies || [],
|
|
11383
11452
|
planningNotes: input.planningNotes,
|
|
11384
11453
|
verification: input.verification,
|
|
11454
|
+
acceptanceCriteria: input.acceptanceCriteria,
|
|
11385
11455
|
validationState: "not_started",
|
|
11386
11456
|
createdAt: now,
|
|
11387
11457
|
updatedAt: now
|
|
11388
11458
|
};
|
|
11389
11459
|
this.db.prepare(`
|
|
11390
|
-
INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, validationState, createdAt, updatedAt)
|
|
11391
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11460
|
+
INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, acceptanceCriteria, validationState, createdAt, updatedAt)
|
|
11461
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11392
11462
|
`).run(
|
|
11393
11463
|
milestone.id,
|
|
11394
11464
|
milestone.missionId,
|
|
@@ -11400,6 +11470,7 @@ var init_mission_store = __esm({
|
|
|
11400
11470
|
toJson(milestone.dependencies),
|
|
11401
11471
|
milestone.planningNotes ?? null,
|
|
11402
11472
|
milestone.verification ?? null,
|
|
11473
|
+
milestone.acceptanceCriteria ?? null,
|
|
11403
11474
|
milestone.validationState,
|
|
11404
11475
|
milestone.createdAt,
|
|
11405
11476
|
milestone.updatedAt
|
|
@@ -11463,6 +11534,7 @@ var init_mission_store = __esm({
|
|
|
11463
11534
|
dependencies = ?,
|
|
11464
11535
|
planningNotes = ?,
|
|
11465
11536
|
verification = ?,
|
|
11537
|
+
acceptanceCriteria = ?,
|
|
11466
11538
|
validationState = ?,
|
|
11467
11539
|
updatedAt = ?
|
|
11468
11540
|
WHERE id = ?
|
|
@@ -11475,6 +11547,7 @@ var init_mission_store = __esm({
|
|
|
11475
11547
|
toJson(updated.dependencies),
|
|
11476
11548
|
updated.planningNotes ?? null,
|
|
11477
11549
|
updated.verification ?? null,
|
|
11550
|
+
updated.acceptanceCriteria ?? null,
|
|
11478
11551
|
updated.validationState || "not_started",
|
|
11479
11552
|
updated.updatedAt,
|
|
11480
11553
|
updated.id
|
|
@@ -11538,6 +11611,21 @@ var init_mission_store = __esm({
|
|
|
11538
11611
|
updateMilestoneInterviewState(id, state) {
|
|
11539
11612
|
return this.updateMilestone(id, { interviewState: state });
|
|
11540
11613
|
}
|
|
11614
|
+
applyDerivedMilestoneAcceptanceCriteria(milestoneId) {
|
|
11615
|
+
const milestone = this.getMilestone(milestoneId);
|
|
11616
|
+
if (!milestone) {
|
|
11617
|
+
throw new Error(`Milestone ${milestoneId} not found`);
|
|
11618
|
+
}
|
|
11619
|
+
if (milestone.acceptanceCriteria?.trim()) {
|
|
11620
|
+
return milestone;
|
|
11621
|
+
}
|
|
11622
|
+
const features = this.listSlices(milestoneId).flatMap((slice) => this.listFeatures(slice.id));
|
|
11623
|
+
const derivedAcceptanceCriteria = deriveMilestoneAcceptanceCriteriaFromFeatures(features);
|
|
11624
|
+
if (!derivedAcceptanceCriteria) {
|
|
11625
|
+
return milestone;
|
|
11626
|
+
}
|
|
11627
|
+
return this.updateMilestone(milestoneId, { acceptanceCriteria: derivedAcceptanceCriteria });
|
|
11628
|
+
}
|
|
11541
11629
|
// ── Slice Operations ───────────────────────────────────────────────
|
|
11542
11630
|
/**
|
|
11543
11631
|
* Add a slice to a milestone.
|
|
@@ -11809,6 +11897,7 @@ var init_mission_store = __esm({
|
|
|
11809
11897
|
this.db.bumpLastModified();
|
|
11810
11898
|
this.emit("feature:created", feature);
|
|
11811
11899
|
this.recomputeSliceStatus(sliceId);
|
|
11900
|
+
this.applyDerivedMilestoneAcceptanceCriteria(slice.milestoneId);
|
|
11812
11901
|
return feature;
|
|
11813
11902
|
}
|
|
11814
11903
|
/**
|
|
@@ -13178,11 +13267,11 @@ ${feature.acceptanceCriteria}`);
|
|
|
13178
13267
|
applied++;
|
|
13179
13268
|
}
|
|
13180
13269
|
for (const milestone of snapshot.payload.milestones) {
|
|
13181
|
-
this.db.prepare(`INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, validationState, createdAt, updatedAt)
|
|
13182
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
13270
|
+
this.db.prepare(`INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, acceptanceCriteria, validationState, createdAt, updatedAt)
|
|
13271
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
13183
13272
|
ON CONFLICT(id) DO UPDATE SET title=excluded.title, description=excluded.description, status=excluded.status, orderIndex=excluded.orderIndex,
|
|
13184
13273
|
interviewState=excluded.interviewState, dependencies=excluded.dependencies, planningNotes=excluded.planningNotes, verification=excluded.verification,
|
|
13185
|
-
validationState=excluded.validationState, updatedAt=excluded.updatedAt`).run(
|
|
13274
|
+
acceptanceCriteria=excluded.acceptanceCriteria, validationState=excluded.validationState, updatedAt=excluded.updatedAt`).run(
|
|
13186
13275
|
milestone.id,
|
|
13187
13276
|
milestone.missionId,
|
|
13188
13277
|
milestone.title,
|
|
@@ -13193,6 +13282,7 @@ ${feature.acceptanceCriteria}`);
|
|
|
13193
13282
|
toJsonNullable(milestone.dependencies),
|
|
13194
13283
|
milestone.planningNotes ?? null,
|
|
13195
13284
|
milestone.verification ?? null,
|
|
13285
|
+
milestone.acceptanceCriteria ?? null,
|
|
13196
13286
|
milestone.validationState ?? null,
|
|
13197
13287
|
milestone.createdAt,
|
|
13198
13288
|
milestone.updatedAt
|
|
@@ -37224,6 +37314,8 @@ var init_store = __esm({
|
|
|
37224
37314
|
createdAt: row.createdAt,
|
|
37225
37315
|
updatedAt: row.updatedAt,
|
|
37226
37316
|
columnMovedAt: row.columnMovedAt || void 0,
|
|
37317
|
+
firstExecutionAt: row.firstExecutionAt || void 0,
|
|
37318
|
+
cumulativeActiveMs: row.cumulativeActiveMs ?? void 0,
|
|
37227
37319
|
executionStartedAt: row.executionStartedAt || void 0,
|
|
37228
37320
|
executionCompletedAt: row.executionCompletedAt || void 0,
|
|
37229
37321
|
dependencies: fromJson(row.dependencies) || [],
|
|
@@ -37351,6 +37443,8 @@ var init_store = __esm({
|
|
|
37351
37443
|
createdAt: entry.createdAt,
|
|
37352
37444
|
updatedAt: entry.updatedAt,
|
|
37353
37445
|
columnMovedAt: entry.columnMovedAt,
|
|
37446
|
+
firstExecutionAt: entry.firstExecutionAt,
|
|
37447
|
+
cumulativeActiveMs: entry.cumulativeActiveMs,
|
|
37354
37448
|
executionStartedAt: entry.executionStartedAt,
|
|
37355
37449
|
executionCompletedAt: entry.executionCompletedAt,
|
|
37356
37450
|
modelPresetId: entry.modelPresetId,
|
|
@@ -37458,6 +37552,8 @@ ${recentText}` : void 0
|
|
|
37458
37552
|
createdAt: task.createdAt,
|
|
37459
37553
|
updatedAt: task.updatedAt,
|
|
37460
37554
|
columnMovedAt: task.columnMovedAt,
|
|
37555
|
+
firstExecutionAt: task.firstExecutionAt,
|
|
37556
|
+
cumulativeActiveMs: task.cumulativeActiveMs,
|
|
37461
37557
|
executionStartedAt: task.executionStartedAt,
|
|
37462
37558
|
executionCompletedAt: task.executionCompletedAt,
|
|
37463
37559
|
archivedAt,
|
|
@@ -37575,6 +37671,8 @@ ${recentText}` : void 0
|
|
|
37575
37671
|
"createdAt",
|
|
37576
37672
|
"updatedAt",
|
|
37577
37673
|
"columnMovedAt",
|
|
37674
|
+
"firstExecutionAt",
|
|
37675
|
+
"cumulativeActiveMs",
|
|
37578
37676
|
"executionStartedAt",
|
|
37579
37677
|
"executionCompletedAt",
|
|
37580
37678
|
"dependencies",
|
|
@@ -37711,6 +37809,8 @@ ${outcome}`;
|
|
|
37711
37809
|
"createdAt",
|
|
37712
37810
|
"updatedAt",
|
|
37713
37811
|
"columnMovedAt",
|
|
37812
|
+
"firstExecutionAt",
|
|
37813
|
+
"cumulativeActiveMs",
|
|
37714
37814
|
"executionStartedAt",
|
|
37715
37815
|
"executionCompletedAt",
|
|
37716
37816
|
"dependencies",
|
|
@@ -37836,6 +37936,8 @@ ${outcome}`;
|
|
|
37836
37936
|
task.createdAt,
|
|
37837
37937
|
task.updatedAt,
|
|
37838
37938
|
task.columnMovedAt ?? null,
|
|
37939
|
+
task.firstExecutionAt ?? null,
|
|
37940
|
+
task.cumulativeActiveMs ?? null,
|
|
37839
37941
|
task.executionStartedAt ?? null,
|
|
37840
37942
|
task.executionCompletedAt ?? null,
|
|
37841
37943
|
toJson(task.dependencies || []),
|
|
@@ -37900,7 +38002,7 @@ ${outcome}`;
|
|
|
37900
38002
|
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
|
|
37901
38003
|
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
37902
38004
|
tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
|
|
37903
|
-
executionStartedAt, executionCompletedAt,
|
|
38005
|
+
firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
|
|
37904
38006
|
dependencies, steps, log, attachments, steeringComments,
|
|
37905
38007
|
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
37906
38008
|
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
@@ -37924,7 +38026,7 @@ ${outcome}`;
|
|
|
37924
38026
|
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
|
|
37925
38027
|
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
37926
38028
|
tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
|
|
37927
|
-
executionStartedAt, executionCompletedAt,
|
|
38029
|
+
firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
|
|
37928
38030
|
dependencies, steps, log, attachments, steeringComments,
|
|
37929
38031
|
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
37930
38032
|
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
@@ -37986,6 +38088,8 @@ ${outcome}`;
|
|
|
37986
38088
|
createdAt = excluded.createdAt,
|
|
37987
38089
|
updatedAt = excluded.updatedAt,
|
|
37988
38090
|
columnMovedAt = excluded.columnMovedAt,
|
|
38091
|
+
firstExecutionAt = excluded.firstExecutionAt,
|
|
38092
|
+
cumulativeActiveMs = excluded.cumulativeActiveMs,
|
|
37989
38093
|
executionStartedAt = excluded.executionStartedAt,
|
|
37990
38094
|
executionCompletedAt = excluded.executionCompletedAt,
|
|
37991
38095
|
dependencies = excluded.dependencies,
|
|
@@ -39643,10 +39747,20 @@ ${newTask.description}
|
|
|
39643
39747
|
task.column = toColumn;
|
|
39644
39748
|
task.columnMovedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39645
39749
|
task.updatedAt = task.columnMovedAt;
|
|
39646
|
-
if (
|
|
39647
|
-
task.executionStartedAt
|
|
39750
|
+
if (fromColumn === "in-progress" && toColumn !== "in-progress") {
|
|
39751
|
+
const segmentStartMs = Date.parse(task.executionStartedAt ?? task.columnMovedAt);
|
|
39752
|
+
const segmentEndMs = Date.parse(task.columnMovedAt);
|
|
39753
|
+
const segmentDeltaMs = Number.isFinite(segmentStartMs) && Number.isFinite(segmentEndMs) ? Math.max(0, segmentEndMs - segmentStartMs) : 0;
|
|
39754
|
+
task.cumulativeActiveMs = Math.max(0, task.cumulativeActiveMs ?? 0) + segmentDeltaMs;
|
|
39648
39755
|
}
|
|
39649
39756
|
if (toColumn === "in-progress") {
|
|
39757
|
+
task.cumulativeActiveMs ??= 0;
|
|
39758
|
+
if (!task.firstExecutionAt) {
|
|
39759
|
+
task.firstExecutionAt = task.columnMovedAt;
|
|
39760
|
+
}
|
|
39761
|
+
if (!task.executionStartedAt) {
|
|
39762
|
+
task.executionStartedAt = task.columnMovedAt;
|
|
39763
|
+
}
|
|
39650
39764
|
task.userPaused = void 0;
|
|
39651
39765
|
}
|
|
39652
39766
|
if (toColumn === "done" && !task.executionCompletedAt) {
|
|
@@ -40088,6 +40202,16 @@ ${newTask.description}
|
|
|
40088
40202
|
} else if (updates.sessionFile !== void 0) {
|
|
40089
40203
|
task.sessionFile = updates.sessionFile;
|
|
40090
40204
|
}
|
|
40205
|
+
if (updates.firstExecutionAt === null) {
|
|
40206
|
+
task.firstExecutionAt = void 0;
|
|
40207
|
+
} else if (updates.firstExecutionAt !== void 0) {
|
|
40208
|
+
task.firstExecutionAt = updates.firstExecutionAt;
|
|
40209
|
+
}
|
|
40210
|
+
if (updates.cumulativeActiveMs === null) {
|
|
40211
|
+
task.cumulativeActiveMs = void 0;
|
|
40212
|
+
} else if (updates.cumulativeActiveMs !== void 0) {
|
|
40213
|
+
task.cumulativeActiveMs = updates.cumulativeActiveMs;
|
|
40214
|
+
}
|
|
40091
40215
|
if (updates.executionStartedAt === null) {
|
|
40092
40216
|
task.executionStartedAt = void 0;
|
|
40093
40217
|
} else if (updates.executionStartedAt !== void 0) {
|
|
@@ -40219,6 +40343,7 @@ ${newTask.description}
|
|
|
40219
40343
|
}
|
|
40220
40344
|
if (!paused) {
|
|
40221
40345
|
task.pausedByAgentId = void 0;
|
|
40346
|
+
task.userPaused = void 0;
|
|
40222
40347
|
}
|
|
40223
40348
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
40224
40349
|
task.status = paused ? "paused" : void 0;
|
|
@@ -60257,13 +60382,15 @@ var init_chat_store = __esm({
|
|
|
60257
60382
|
whereClauses.push("createdAt < ?");
|
|
60258
60383
|
params.push(filter.before);
|
|
60259
60384
|
}
|
|
60385
|
+
const order = filter?.order === "desc" ? "DESC" : "ASC";
|
|
60260
60386
|
const rows = this.db.prepare(`
|
|
60261
60387
|
SELECT * FROM chat_room_messages
|
|
60262
60388
|
WHERE ${whereClauses.join(" AND ")}
|
|
60263
|
-
ORDER BY createdAt
|
|
60389
|
+
ORDER BY createdAt ${order}
|
|
60264
60390
|
LIMIT ? OFFSET ?
|
|
60265
60391
|
`).all(...params, filter?.limit ?? 100, filter?.offset ?? 0);
|
|
60266
|
-
|
|
60392
|
+
const normalizedRows = filter?.order === "desc" ? [...rows].reverse() : rows;
|
|
60393
|
+
return normalizedRows.map((row) => this.rowToRoomMessage(row));
|
|
60267
60394
|
}
|
|
60268
60395
|
listRoomMessagesSince(roomId, sinceIso, options) {
|
|
60269
60396
|
const whereClauses = ["roomId = ?", "createdAt > ?"];
|
|
@@ -60544,6 +60671,8 @@ var src_exports = {};
|
|
|
60544
60671
|
__export(src_exports, {
|
|
60545
60672
|
AGENT_PERMISSIONS: () => AGENT_PERMISSIONS,
|
|
60546
60673
|
AGENT_PERMISSION_POLICY_ACTION_CATEGORIES: () => AGENT_PERMISSION_POLICY_ACTION_CATEGORIES,
|
|
60674
|
+
AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES: () => AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES,
|
|
60675
|
+
AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES: () => AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES,
|
|
60547
60676
|
AGENT_PERMISSION_POLICY_PRESET_IDS: () => AGENT_PERMISSION_POLICY_PRESET_IDS,
|
|
60548
60677
|
AGENT_PROVISIONING_APPROVAL_MODES: () => AGENT_PROVISIONING_APPROVAL_MODES,
|
|
60549
60678
|
AGENT_VALID_TRANSITIONS: () => AGENT_VALID_TRANSITIONS,
|
|
@@ -61604,6 +61733,7 @@ var init_gating_classifications = __esm({
|
|
|
61604
61733
|
"fn_slice_activate",
|
|
61605
61734
|
"fn_feature_link_task",
|
|
61606
61735
|
"fn_feature_update",
|
|
61736
|
+
"fn_milestone_update",
|
|
61607
61737
|
"fn_agent_stop",
|
|
61608
61738
|
"fn_agent_start"
|
|
61609
61739
|
];
|
|
@@ -61624,9 +61754,15 @@ var init_gating_classifications = __esm({
|
|
|
61624
61754
|
NETWORK_API_TOOLS = /* @__PURE__ */ new Set([
|
|
61625
61755
|
"fn_research_run",
|
|
61626
61756
|
"fn_research_cancel",
|
|
61627
|
-
"fn_research_retry"
|
|
61757
|
+
"fn_research_retry",
|
|
61758
|
+
"fn_web_fetch"
|
|
61759
|
+
// FN-4603: outbound HTTP fetch should be network-classified.
|
|
61760
|
+
]);
|
|
61761
|
+
ACTION_GATE_NETWORK_API_TOOLS = /* @__PURE__ */ new Set([
|
|
61762
|
+
"fn_research_run",
|
|
61763
|
+
"fn_web_fetch"
|
|
61764
|
+
// FN-4603: honor network_api approval policy for web fetches.
|
|
61628
61765
|
]);
|
|
61629
|
-
ACTION_GATE_NETWORK_API_TOOLS = /* @__PURE__ */ new Set(["fn_research_run"]);
|
|
61630
61766
|
READONLY_FN_TOOLS = /* @__PURE__ */ new Set([
|
|
61631
61767
|
"fn_task_list",
|
|
61632
61768
|
"fn_task_show",
|
|
@@ -83430,6 +83566,91 @@ var init_auto_recovery = __esm({
|
|
|
83430
83566
|
}
|
|
83431
83567
|
});
|
|
83432
83568
|
|
|
83569
|
+
// ../engine/src/restart-recovery-coordinator.ts
|
|
83570
|
+
function hasStepProgress(task) {
|
|
83571
|
+
const steps = Array.isArray(task.steps) ? task.steps : [];
|
|
83572
|
+
return steps.some((step) => step.status === "done" || step.status === "in-progress" || step.status === "skipped");
|
|
83573
|
+
}
|
|
83574
|
+
function isNoTaskDoneFailure(task) {
|
|
83575
|
+
return task.status === "failed" && typeof task.error === "string" && task.error.toLowerCase().includes("without calling fn_task_done");
|
|
83576
|
+
}
|
|
83577
|
+
function findMissingWorktreeSessionPrefix(error) {
|
|
83578
|
+
for (const prefix of MISSING_WORKTREE_SESSION_PREFIXES) {
|
|
83579
|
+
if (error.includes(prefix)) {
|
|
83580
|
+
return prefix;
|
|
83581
|
+
}
|
|
83582
|
+
}
|
|
83583
|
+
return null;
|
|
83584
|
+
}
|
|
83585
|
+
function isMissingWorktreeSessionStartFailure(error) {
|
|
83586
|
+
if (typeof error !== "string") {
|
|
83587
|
+
return false;
|
|
83588
|
+
}
|
|
83589
|
+
return findMissingWorktreeSessionPrefix(error) !== null;
|
|
83590
|
+
}
|
|
83591
|
+
function extractMissingWorktreePathFromSessionStartFailure(error) {
|
|
83592
|
+
if (typeof error !== "string") return null;
|
|
83593
|
+
const prefix = findMissingWorktreeSessionPrefix(error);
|
|
83594
|
+
if (!prefix) return null;
|
|
83595
|
+
const idx = error.indexOf(prefix);
|
|
83596
|
+
const pathPart = error.slice(idx + prefix.length).trim();
|
|
83597
|
+
return pathPart.length > 0 ? pathPart : null;
|
|
83598
|
+
}
|
|
83599
|
+
function isRecoverableMissingWorktreeReviewFailure(task) {
|
|
83600
|
+
return task.column === "in-review" && !task.paused && task.status === "failed" && isMissingWorktreeSessionStartFailure(task.error) && hasStepProgress(task);
|
|
83601
|
+
}
|
|
83602
|
+
var log14, MISSING_WORKTREE_SESSION_PREFIXES, RestartRecoveryCoordinator;
|
|
83603
|
+
var init_restart_recovery_coordinator = __esm({
|
|
83604
|
+
"../engine/src/restart-recovery-coordinator.ts"() {
|
|
83605
|
+
"use strict";
|
|
83606
|
+
init_logger2();
|
|
83607
|
+
log14 = createLogger2("restart-recovery");
|
|
83608
|
+
MISSING_WORKTREE_SESSION_PREFIXES = [
|
|
83609
|
+
"Refusing to start coding agent in missing worktree:",
|
|
83610
|
+
"Refusing to start coding agent in incomplete worktree:",
|
|
83611
|
+
"Refusing to start coding agent in unregistered git worktree:"
|
|
83612
|
+
];
|
|
83613
|
+
RestartRecoveryCoordinator = class {
|
|
83614
|
+
constructor(store, executor) {
|
|
83615
|
+
this.store = store;
|
|
83616
|
+
this.executor = executor;
|
|
83617
|
+
}
|
|
83618
|
+
async recoverInterruptedRuns() {
|
|
83619
|
+
const allInProgress = await this.store.listTasks({ slim: true, column: "in-progress" });
|
|
83620
|
+
const candidates = allInProgress.filter((task) => task.column === "in-progress" && !task.paused);
|
|
83621
|
+
if (candidates.length === 0) return;
|
|
83622
|
+
let requeued = 0;
|
|
83623
|
+
for (const task of candidates) {
|
|
83624
|
+
if (!this.mustSafeRetry(task)) continue;
|
|
83625
|
+
await this.safeRequeue(task);
|
|
83626
|
+
requeued++;
|
|
83627
|
+
}
|
|
83628
|
+
if (requeued > 0) {
|
|
83629
|
+
log14.log(`Restart recovery requeued ${requeued} interrupted task(s) for safe retry`);
|
|
83630
|
+
}
|
|
83631
|
+
await this.executor.resumeOrphaned();
|
|
83632
|
+
}
|
|
83633
|
+
mustSafeRetry(task) {
|
|
83634
|
+
return isNoTaskDoneFailure(task) && !hasStepProgress(task);
|
|
83635
|
+
}
|
|
83636
|
+
async safeRequeue(task) {
|
|
83637
|
+
await this.store.updateTask(task.id, {
|
|
83638
|
+
status: "stuck-killed",
|
|
83639
|
+
worktree: null,
|
|
83640
|
+
branch: null,
|
|
83641
|
+
sessionFile: null,
|
|
83642
|
+
error: null
|
|
83643
|
+
});
|
|
83644
|
+
await this.store.logEntry(
|
|
83645
|
+
task.id,
|
|
83646
|
+
"Restart recovery: interrupted run had no step progress and no fn_task_done \u2014 requeued to todo for safe retry"
|
|
83647
|
+
);
|
|
83648
|
+
await this.store.moveTask(task.id, "todo");
|
|
83649
|
+
}
|
|
83650
|
+
};
|
|
83651
|
+
}
|
|
83652
|
+
});
|
|
83653
|
+
|
|
83433
83654
|
// ../engine/src/auto-recovery-handlers/branch-worktree.ts
|
|
83434
83655
|
import { exec as exec9 } from "node:child_process";
|
|
83435
83656
|
import { existsSync as existsSync32 } from "node:fs";
|
|
@@ -84603,6 +84824,7 @@ var init_executor = __esm({
|
|
|
84603
84824
|
init_prompt_layers();
|
|
84604
84825
|
init_run_audit();
|
|
84605
84826
|
init_auto_recovery();
|
|
84827
|
+
init_restart_recovery_coordinator();
|
|
84606
84828
|
init_branch_worktree();
|
|
84607
84829
|
init_contamination();
|
|
84608
84830
|
init_file_scope();
|
|
@@ -87286,7 +87508,22 @@ ${summary}`,
|
|
|
87286
87508
|
this.options.onComplete?.(task);
|
|
87287
87509
|
} else {
|
|
87288
87510
|
let taskDoneSessionRetries = 0;
|
|
87511
|
+
let retryAbortedDueToReclaim = false;
|
|
87289
87512
|
while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
|
|
87513
|
+
const liveTask = await this.store.getTask(task.id);
|
|
87514
|
+
const hasExplicitWorktreeBinding = typeof liveTask.worktree === "string" || liveTask.worktree === null;
|
|
87515
|
+
const hasExplicitBranchBinding = typeof liveTask.branch === "string" || liveTask.branch === null;
|
|
87516
|
+
const worktreeContractIntact = liveTask.column === "in-progress" && !liveTask.paused && (!hasExplicitWorktreeBinding || liveTask.worktree === worktreePath) && (!hasExplicitBranchBinding || typeof liveTask.branch === "string" && liveTask.branch.length > 0);
|
|
87517
|
+
if (!worktreeContractIntact) {
|
|
87518
|
+
const reclaimMessage = `${task.id}: worktree/branch reclaimed during no-fn_task_done retry \u2014 aborting retry and requeueing`;
|
|
87519
|
+
executorLog.log(reclaimMessage);
|
|
87520
|
+
await this.store.logEntry(task.id, reclaimMessage, void 0, this.currentRunContext);
|
|
87521
|
+
this.activeSessions.delete(task.id);
|
|
87522
|
+
this.tokenUsageBaselines.delete(task.id);
|
|
87523
|
+
session.dispose();
|
|
87524
|
+
retryAbortedDueToReclaim = true;
|
|
87525
|
+
break;
|
|
87526
|
+
}
|
|
87290
87527
|
taskDoneSessionRetries++;
|
|
87291
87528
|
executorLog.log(
|
|
87292
87529
|
`\u26A0 ${task.id} finished without fn_task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
|
|
@@ -87313,88 +87550,112 @@ ${summary}`,
|
|
|
87313
87550
|
this.activeSessions.delete(task.id);
|
|
87314
87551
|
this.tokenUsageBaselines.delete(task.id);
|
|
87315
87552
|
session.dispose();
|
|
87316
|
-
|
|
87317
|
-
|
|
87318
|
-
|
|
87319
|
-
|
|
87320
|
-
|
|
87321
|
-
|
|
87322
|
-
|
|
87323
|
-
|
|
87324
|
-
|
|
87325
|
-
|
|
87326
|
-
|
|
87327
|
-
|
|
87328
|
-
|
|
87329
|
-
|
|
87330
|
-
|
|
87331
|
-
|
|
87332
|
-
|
|
87333
|
-
|
|
87334
|
-
|
|
87335
|
-
|
|
87336
|
-
|
|
87337
|
-
|
|
87338
|
-
|
|
87339
|
-
|
|
87340
|
-
|
|
87341
|
-
|
|
87342
|
-
this.store.updateTask(task.id, { sessionFile: retrySessionFile }).catch((err) => {
|
|
87343
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
87344
|
-
executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
|
|
87553
|
+
let retrySession = null;
|
|
87554
|
+
try {
|
|
87555
|
+
const createdRetrySession = await createResolvedAgentSession({
|
|
87556
|
+
sessionPurpose: "executor",
|
|
87557
|
+
runtimeHint: executorRuntimeHint,
|
|
87558
|
+
pluginRunner: this.options.pluginRunner,
|
|
87559
|
+
cwd: worktreePath,
|
|
87560
|
+
systemPrompt: executorSystemPromptFinal,
|
|
87561
|
+
systemPromptLayers: executorLayers,
|
|
87562
|
+
tools: "coding",
|
|
87563
|
+
customTools,
|
|
87564
|
+
onText: agentLogger.onText,
|
|
87565
|
+
onThinking: agentLogger.onThinking,
|
|
87566
|
+
onToolStart: agentLogger.onToolStart,
|
|
87567
|
+
onToolEnd: agentLogger.onToolEnd,
|
|
87568
|
+
defaultProvider: executorProvider,
|
|
87569
|
+
defaultModelId: executorModelId,
|
|
87570
|
+
fallbackProvider: executorFallbackProvider,
|
|
87571
|
+
fallbackModelId: executorFallbackModelId,
|
|
87572
|
+
defaultThinkingLevel: executorThinkingLevel,
|
|
87573
|
+
sessionManager: SessionManager2.create(worktreePath),
|
|
87574
|
+
taskEnv,
|
|
87575
|
+
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
87576
|
+
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
87577
|
+
actionGateContext: this.buildActionGateContext(task.id, assignedAgent, settings.defaultAgentPermissionPolicy),
|
|
87578
|
+
permanentAgentGating: this.buildPermanentAgentGatingContext(task.id, assignedAgent, settings.defaultAgentPermissionPolicy)
|
|
87345
87579
|
});
|
|
87580
|
+
retrySession = createdRetrySession.session;
|
|
87581
|
+
if (createdRetrySession.sessionFile) {
|
|
87582
|
+
this.store.updateTask(task.id, { sessionFile: createdRetrySession.sessionFile }).catch((err) => {
|
|
87583
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87584
|
+
executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
|
|
87585
|
+
});
|
|
87586
|
+
}
|
|
87587
|
+
session = retrySession;
|
|
87588
|
+
sessionRef.current = retrySession;
|
|
87589
|
+
this.activeSessions.set(task.id, {
|
|
87590
|
+
session: retrySession,
|
|
87591
|
+
seenSteeringIds,
|
|
87592
|
+
lastResolvedModelProvider: executorProvider,
|
|
87593
|
+
lastResolvedModelId: executorModelId,
|
|
87594
|
+
lastTaskModelProvider: detail.modelProvider,
|
|
87595
|
+
lastTaskModelId: detail.modelId,
|
|
87596
|
+
lastAssignedAgentId: detail.assignedAgentId ?? null
|
|
87597
|
+
});
|
|
87598
|
+
stuckDetector?.trackTask(task.id, retrySession);
|
|
87599
|
+
let retryPrompt;
|
|
87600
|
+
if (pseudoPause.kind !== "none") {
|
|
87601
|
+
const shortMatch = (pseudoPause.matched ?? "").slice(0, 120);
|
|
87602
|
+
retryPrompt = [
|
|
87603
|
+
`Your previous turn ended with a pseudo-pause: "${shortMatch}". This is forbidden.`,
|
|
87604
|
+
"",
|
|
87605
|
+
"Turn-ending rules you violated:",
|
|
87606
|
+
"- You MUST NOT end a turn by asking the user a question, summarizing progress, or requesting permission to continue.",
|
|
87607
|
+
"- Phrases like 'If you want, I can continue', 'Should I proceed?', 'Let me know if...' are FORBIDDEN turn-endings.",
|
|
87608
|
+
"- The user is not watching this conversation. Questions written as prose are ignored.",
|
|
87609
|
+
"- If you genuinely cannot proceed, call fn_task_done with a clear explanation \u2014 never write the blocker as plain prose.",
|
|
87610
|
+
"",
|
|
87611
|
+
"What you must do now:",
|
|
87612
|
+
"1. Review the PROMPT.md steps and identify the next pending step.",
|
|
87613
|
+
"2. Do the work for that step immediately \u2014 call fn_task_update, write code, run tests.",
|
|
87614
|
+
"3. Continue until all steps are done, then call fn_task_done.",
|
|
87615
|
+
"Do NOT ask for permission. Do NOT write a summary. Just call a tool and keep working.",
|
|
87616
|
+
"",
|
|
87617
|
+
"Original task:",
|
|
87618
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner)
|
|
87619
|
+
].join("\n");
|
|
87620
|
+
} else {
|
|
87621
|
+
retryPrompt = [
|
|
87622
|
+
"Your previous session ended without calling the fn_task_done tool.",
|
|
87623
|
+
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
87624
|
+
"1. If the work is done, call fn_task_done with a summary of what was accomplished.",
|
|
87625
|
+
"2. If there is remaining work, finish it and then call fn_task_done.",
|
|
87626
|
+
"",
|
|
87627
|
+
"Original task:",
|
|
87628
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner)
|
|
87629
|
+
].join("\n");
|
|
87630
|
+
}
|
|
87631
|
+
stuckDetector?.recordActivity(task.id);
|
|
87632
|
+
await promptWithFallback(retrySession, retryPrompt);
|
|
87633
|
+
checkSessionError(retrySession);
|
|
87634
|
+
await accumulateSessionTokenUsage(this.store, task.id, retrySession, {
|
|
87635
|
+
agentId: task.assignedAgentId ?? void 0,
|
|
87636
|
+
role: "executor"
|
|
87637
|
+
});
|
|
87638
|
+
} catch (retryError) {
|
|
87639
|
+
const retryErrorText = retryError instanceof Error ? retryError.message : String(retryError);
|
|
87640
|
+
if (!isMissingWorktreeSessionStartFailure(retryErrorText)) {
|
|
87641
|
+
throw retryError;
|
|
87642
|
+
}
|
|
87643
|
+
const recoveredPath = extractMissingWorktreePathFromSessionStartFailure(retryErrorText) ?? worktreePath;
|
|
87644
|
+
const reclaimMessage = `${task.id}: no-fn_task_done retry hit missing worktree session-start failure (${recoveredPath}) \u2014 clearing stale metadata and requeueing`;
|
|
87645
|
+
executorLog.log(reclaimMessage);
|
|
87646
|
+
await this.store.logEntry(task.id, reclaimMessage, void 0, this.currentRunContext);
|
|
87647
|
+
await this.store.updateTask(task.id, {
|
|
87648
|
+
sessionFile: null,
|
|
87649
|
+
worktree: null,
|
|
87650
|
+
branch: null,
|
|
87651
|
+
baseCommitSha: null
|
|
87652
|
+
});
|
|
87653
|
+
this.activeSessions.delete(task.id);
|
|
87654
|
+
this.tokenUsageBaselines.delete(task.id);
|
|
87655
|
+
retrySession?.dispose();
|
|
87656
|
+
retryAbortedDueToReclaim = true;
|
|
87657
|
+
break;
|
|
87346
87658
|
}
|
|
87347
|
-
session = retrySession;
|
|
87348
|
-
sessionRef.current = retrySession;
|
|
87349
|
-
this.activeSessions.set(task.id, {
|
|
87350
|
-
session: retrySession,
|
|
87351
|
-
seenSteeringIds,
|
|
87352
|
-
lastResolvedModelProvider: executorProvider,
|
|
87353
|
-
lastResolvedModelId: executorModelId,
|
|
87354
|
-
lastTaskModelProvider: detail.modelProvider,
|
|
87355
|
-
lastTaskModelId: detail.modelId,
|
|
87356
|
-
lastAssignedAgentId: detail.assignedAgentId ?? null
|
|
87357
|
-
});
|
|
87358
|
-
stuckDetector?.trackTask(task.id, retrySession);
|
|
87359
|
-
let retryPrompt;
|
|
87360
|
-
if (pseudoPause.kind !== "none") {
|
|
87361
|
-
const shortMatch = (pseudoPause.matched ?? "").slice(0, 120);
|
|
87362
|
-
retryPrompt = [
|
|
87363
|
-
`Your previous turn ended with a pseudo-pause: "${shortMatch}". This is forbidden.`,
|
|
87364
|
-
"",
|
|
87365
|
-
"Turn-ending rules you violated:",
|
|
87366
|
-
"- You MUST NOT end a turn by asking the user a question, summarizing progress, or requesting permission to continue.",
|
|
87367
|
-
"- Phrases like 'If you want, I can continue', 'Should I proceed?', 'Let me know if...' are FORBIDDEN turn-endings.",
|
|
87368
|
-
"- The user is not watching this conversation. Questions written as prose are ignored.",
|
|
87369
|
-
"- If you genuinely cannot proceed, call fn_task_done with a clear explanation \u2014 never write the blocker as plain prose.",
|
|
87370
|
-
"",
|
|
87371
|
-
"What you must do now:",
|
|
87372
|
-
"1. Review the PROMPT.md steps and identify the next pending step.",
|
|
87373
|
-
"2. Do the work for that step immediately \u2014 call fn_task_update, write code, run tests.",
|
|
87374
|
-
"3. Continue until all steps are done, then call fn_task_done.",
|
|
87375
|
-
"Do NOT ask for permission. Do NOT write a summary. Just call a tool and keep working.",
|
|
87376
|
-
"",
|
|
87377
|
-
"Original task:",
|
|
87378
|
-
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner)
|
|
87379
|
-
].join("\n");
|
|
87380
|
-
} else {
|
|
87381
|
-
retryPrompt = [
|
|
87382
|
-
"Your previous session ended without calling the fn_task_done tool.",
|
|
87383
|
-
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
87384
|
-
"1. If the work is done, call fn_task_done with a summary of what was accomplished.",
|
|
87385
|
-
"2. If there is remaining work, finish it and then call fn_task_done.",
|
|
87386
|
-
"",
|
|
87387
|
-
"Original task:",
|
|
87388
|
-
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner)
|
|
87389
|
-
].join("\n");
|
|
87390
|
-
}
|
|
87391
|
-
stuckDetector?.recordActivity(task.id);
|
|
87392
|
-
await promptWithFallback(retrySession, retryPrompt);
|
|
87393
|
-
checkSessionError(retrySession);
|
|
87394
|
-
await accumulateSessionTokenUsage(this.store, task.id, retrySession, {
|
|
87395
|
-
agentId: task.assignedAgentId ?? void 0,
|
|
87396
|
-
role: "executor"
|
|
87397
|
-
});
|
|
87398
87659
|
if (!taskDone) {
|
|
87399
87660
|
const implicitCheck = await this.store.getTask(task.id);
|
|
87400
87661
|
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
@@ -87457,7 +87718,7 @@ ${summary}`,
|
|
|
87457
87718
|
} else {
|
|
87458
87719
|
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
87459
87720
|
const nextRequeueCount = priorRequeues + 1;
|
|
87460
|
-
const errorMessage = `Agent finished without calling fn_task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
87721
|
+
const errorMessage = retryAbortedDueToReclaim ? "Worktree/branch reclaimed during no-fn_task_done retry \u2014 requeueing" : `Agent finished without calling fn_task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
87461
87722
|
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
87462
87723
|
await this.store.updateTask(task.id, {
|
|
87463
87724
|
status: "failed",
|
|
@@ -99783,7 +100044,7 @@ function extractJson(raw) {
|
|
|
99783
100044
|
if (first >= 0 && last > first) return trimmed.slice(first, last + 1);
|
|
99784
100045
|
return trimmed;
|
|
99785
100046
|
}
|
|
99786
|
-
var
|
|
100047
|
+
var log15, HybridEvaluatorService;
|
|
99787
100048
|
var init_evaluator = __esm({
|
|
99788
100049
|
"../engine/src/evaluator.ts"() {
|
|
99789
100050
|
"use strict";
|
|
@@ -99792,7 +100053,7 @@ var init_evaluator = __esm({
|
|
|
99792
100053
|
init_eval_followups();
|
|
99793
100054
|
init_pi();
|
|
99794
100055
|
init_logger2();
|
|
99795
|
-
|
|
100056
|
+
log15 = createLogger2("evaluator");
|
|
99796
100057
|
HybridEvaluatorService = class {
|
|
99797
100058
|
constructor(deps) {
|
|
99798
100059
|
this.deps = deps;
|
|
@@ -99884,7 +100145,7 @@ var init_evaluator = __esm({
|
|
|
99884
100145
|
try {
|
|
99885
100146
|
session.dispose();
|
|
99886
100147
|
} catch (error) {
|
|
99887
|
-
|
|
100148
|
+
log15.warn(`Evaluator session disposal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
99888
100149
|
}
|
|
99889
100150
|
}
|
|
99890
100151
|
}
|
|
@@ -100016,7 +100277,7 @@ function truncateOutput2(stdout, stderr) {
|
|
|
100016
100277
|
}
|
|
100017
100278
|
return combined;
|
|
100018
100279
|
}
|
|
100019
|
-
var
|
|
100280
|
+
var log16, FUSION_BINARY_TOKENS, SHELL_METACHARACTERS_REGEX, DEFAULT_TIMEOUT_MS8, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
100020
100281
|
var init_cron_runner = __esm({
|
|
100021
100282
|
"../engine/src/cron-runner.ts"() {
|
|
100022
100283
|
"use strict";
|
|
@@ -100025,7 +100286,7 @@ var init_cron_runner = __esm({
|
|
|
100025
100286
|
init_shell_utils();
|
|
100026
100287
|
init_pi();
|
|
100027
100288
|
init_evaluator();
|
|
100028
|
-
|
|
100289
|
+
log16 = createLogger2("cron-runner");
|
|
100029
100290
|
FUSION_BINARY_TOKENS = /* @__PURE__ */ new Set([
|
|
100030
100291
|
"fn",
|
|
100031
100292
|
"fusion",
|
|
@@ -100066,7 +100327,7 @@ var init_cron_runner = __esm({
|
|
|
100066
100327
|
start() {
|
|
100067
100328
|
if (this.running) return;
|
|
100068
100329
|
this.running = true;
|
|
100069
|
-
|
|
100330
|
+
log16.log(`Started (poll every ${this.pollIntervalMs / 1e3}s, scope: ${this.scope})`);
|
|
100070
100331
|
void this.tick();
|
|
100071
100332
|
this.pollInterval = setInterval(() => {
|
|
100072
100333
|
void this.tick();
|
|
@@ -100080,7 +100341,7 @@ var init_cron_runner = __esm({
|
|
|
100080
100341
|
clearInterval(this.pollInterval);
|
|
100081
100342
|
this.pollInterval = null;
|
|
100082
100343
|
}
|
|
100083
|
-
|
|
100344
|
+
log16.log("Stopped");
|
|
100084
100345
|
}
|
|
100085
100346
|
/**
|
|
100086
100347
|
* Single poll cycle: find due schedules and execute them.
|
|
@@ -100104,29 +100365,29 @@ var init_cron_runner = __esm({
|
|
|
100104
100365
|
const executedIds = /* @__PURE__ */ new Set();
|
|
100105
100366
|
for (const schedule of dueSchedules) {
|
|
100106
100367
|
if (this.inFlight.has(schedule.id)) {
|
|
100107
|
-
|
|
100368
|
+
log16.warn(`Skipping ${schedule.name} (${schedule.id}) \u2014 still running from previous tick`);
|
|
100108
100369
|
continue;
|
|
100109
100370
|
}
|
|
100110
100371
|
if (executedIds.has(schedule.id)) {
|
|
100111
|
-
|
|
100372
|
+
log16.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 already executed from another scope this tick`);
|
|
100112
100373
|
continue;
|
|
100113
100374
|
}
|
|
100114
100375
|
executedIds.add(schedule.id);
|
|
100115
100376
|
const scheduleScope = schedule.scope ?? "project";
|
|
100116
100377
|
if (scheduleScope !== this.scope && this.scope !== "all") {
|
|
100117
|
-
|
|
100378
|
+
log16.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 belongs to ${scheduleScope} scope, not polling`);
|
|
100118
100379
|
continue;
|
|
100119
100380
|
}
|
|
100120
|
-
|
|
100381
|
+
log16.log(`Executing ${schedule.name} (${schedule.id}) [scope: ${scheduleScope}]`);
|
|
100121
100382
|
const currentSettings = await this.store.getSettings();
|
|
100122
100383
|
if (currentSettings.globalPause || currentSettings.enginePaused) {
|
|
100123
|
-
|
|
100384
|
+
log16.log("Pause detected mid-tick \u2014 stopping schedule execution");
|
|
100124
100385
|
break;
|
|
100125
100386
|
}
|
|
100126
100387
|
await this.executeSchedule(schedule);
|
|
100127
100388
|
}
|
|
100128
100389
|
} catch (err) {
|
|
100129
|
-
|
|
100390
|
+
log16.error(`Tick error: ${err.message}`);
|
|
100130
100391
|
} finally {
|
|
100131
100392
|
this.ticking = false;
|
|
100132
100393
|
}
|
|
@@ -100156,13 +100417,13 @@ var init_cron_runner = __esm({
|
|
|
100156
100417
|
try {
|
|
100157
100418
|
await this.automationStore.recordRun(schedule.id, result);
|
|
100158
100419
|
} catch (recordErr) {
|
|
100159
|
-
|
|
100420
|
+
log16.error(`Failed to record run for ${schedule.id}: ${recordErr.message}`);
|
|
100160
100421
|
}
|
|
100161
100422
|
if (this.onScheduleRunProcessed) {
|
|
100162
100423
|
try {
|
|
100163
100424
|
await this.onScheduleRunProcessed(schedule, result);
|
|
100164
100425
|
} catch (callbackErr) {
|
|
100165
|
-
|
|
100426
|
+
log16.error(
|
|
100166
100427
|
`Post-run callback failed for ${schedule.name} (${schedule.id}): ${callbackErr.message}`
|
|
100167
100428
|
);
|
|
100168
100429
|
}
|
|
@@ -100173,7 +100434,7 @@ var init_cron_runner = __esm({
|
|
|
100173
100434
|
* Execute a legacy single-command schedule.
|
|
100174
100435
|
*/
|
|
100175
100436
|
async executeLegacyCommand(schedule, startedAt) {
|
|
100176
|
-
|
|
100437
|
+
log16.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
|
|
100177
100438
|
if (isInProcessBackupCommand(schedule.command)) {
|
|
100178
100439
|
return this.executeBackupInProcess(schedule, startedAt);
|
|
100179
100440
|
}
|
|
@@ -100191,7 +100452,7 @@ var init_cron_runner = __esm({
|
|
|
100191
100452
|
shell: defaultShell
|
|
100192
100453
|
});
|
|
100193
100454
|
const output = truncateOutput2(stdout, stderr);
|
|
100194
|
-
|
|
100455
|
+
log16.log(`\u2713 ${schedule.name} completed (${output.length} bytes output)`);
|
|
100195
100456
|
return {
|
|
100196
100457
|
success: true,
|
|
100197
100458
|
output,
|
|
@@ -100203,7 +100464,7 @@ var init_cron_runner = __esm({
|
|
|
100203
100464
|
const stderr = err.stderr ?? "";
|
|
100204
100465
|
const output = truncateOutput2(stdout, stderr);
|
|
100205
100466
|
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS8) / 1e3}s` : err.message ?? String(err);
|
|
100206
|
-
|
|
100467
|
+
log16.warn(`\u2717 ${schedule.name} failed: ${errorMessage}`);
|
|
100207
100468
|
return {
|
|
100208
100469
|
success: false,
|
|
100209
100470
|
output,
|
|
@@ -100221,9 +100482,9 @@ var init_cron_runner = __esm({
|
|
|
100221
100482
|
async executeBackupInProcess(schedule, startedAt) {
|
|
100222
100483
|
const action = await this.runBackupActionInProcess();
|
|
100223
100484
|
if (action.success) {
|
|
100224
|
-
|
|
100485
|
+
log16.log(`\u2713 ${schedule.name} completed in-process`);
|
|
100225
100486
|
} else {
|
|
100226
|
-
|
|
100487
|
+
log16.warn(`\u2717 ${schedule.name} in-process backup ${action.error ? `threw: ${action.error}` : `reported failure: ${action.output}`}`);
|
|
100227
100488
|
}
|
|
100228
100489
|
return {
|
|
100229
100490
|
success: action.success,
|
|
@@ -100236,9 +100497,9 @@ var init_cron_runner = __esm({
|
|
|
100236
100497
|
async executeMemoryBackupInProcess(schedule, startedAt) {
|
|
100237
100498
|
const action = await this.runMemoryBackupActionInProcess();
|
|
100238
100499
|
if (action.success) {
|
|
100239
|
-
|
|
100500
|
+
log16.log(`\u2713 ${schedule.name} completed in-process`);
|
|
100240
100501
|
} else {
|
|
100241
|
-
|
|
100502
|
+
log16.warn(`\u2717 ${schedule.name} in-process memory backup ${action.error ? `threw: ${action.error}` : `reported failure: ${action.output}`}`);
|
|
100242
100503
|
}
|
|
100243
100504
|
return {
|
|
100244
100505
|
success: action.success,
|
|
@@ -100327,25 +100588,25 @@ var init_cron_runner = __esm({
|
|
|
100327
100588
|
*/
|
|
100328
100589
|
async executeSteps(schedule, startedAt) {
|
|
100329
100590
|
const steps = schedule.steps;
|
|
100330
|
-
|
|
100591
|
+
log16.log(`Executing ${schedule.name} (${schedule.id}): ${steps.length} steps`);
|
|
100331
100592
|
const stepResults = [];
|
|
100332
100593
|
let overallSuccess = true;
|
|
100333
100594
|
let stoppedEarly = false;
|
|
100334
100595
|
for (let i = 0; i < steps.length; i++) {
|
|
100335
100596
|
const step = steps[i];
|
|
100336
|
-
|
|
100597
|
+
log16.log(` Step ${i + 1}/${steps.length}: ${step.name} (${step.type})`);
|
|
100337
100598
|
const stepResult = await this.executeStep(schedule, step, i);
|
|
100338
100599
|
stepResults.push(stepResult);
|
|
100339
100600
|
if (!stepResult.success) {
|
|
100340
100601
|
overallSuccess = false;
|
|
100341
100602
|
if (!step.continueOnFailure) {
|
|
100342
|
-
|
|
100603
|
+
log16.warn(` Step "${step.name}" failed \u2014 stopping execution`);
|
|
100343
100604
|
stoppedEarly = true;
|
|
100344
100605
|
break;
|
|
100345
100606
|
}
|
|
100346
|
-
|
|
100607
|
+
log16.warn(` Step "${step.name}" failed \u2014 continuing (continueOnFailure=true)`);
|
|
100347
100608
|
} else {
|
|
100348
|
-
|
|
100609
|
+
log16.log(` \u2713 Step "${step.name}" completed`);
|
|
100349
100610
|
}
|
|
100350
100611
|
}
|
|
100351
100612
|
const outputParts = [];
|
|
@@ -100358,7 +100619,7 @@ var init_cron_runner = __esm({
|
|
|
100358
100619
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
100359
100620
|
const error = failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0;
|
|
100360
100621
|
const status = overallSuccess ? "\u2713" : "\u2717";
|
|
100361
|
-
|
|
100622
|
+
log16.log(`${status} ${schedule.name}: ${stepResults.length}/${steps.length} steps executed, ${failedSteps.length} failed`);
|
|
100362
100623
|
return {
|
|
100363
100624
|
success: overallSuccess,
|
|
100364
100625
|
output,
|
|
@@ -100501,8 +100762,8 @@ var init_cron_runner = __esm({
|
|
|
100501
100762
|
const modelProvider = step.modelProvider?.trim() || defaultModel.provider;
|
|
100502
100763
|
const modelId = step.modelId?.trim() || defaultModel.modelId;
|
|
100503
100764
|
const model = modelProvider && modelId ? `${modelProvider}/${modelId}` : "default";
|
|
100504
|
-
|
|
100505
|
-
|
|
100765
|
+
log16.log(` AI prompt step "${step.name}" using model: ${model}`);
|
|
100766
|
+
log16.log(` Prompt: ${step.prompt.slice(0, 100)}${step.prompt.length > 100 ? "\u2026" : ""}`);
|
|
100506
100767
|
try {
|
|
100507
100768
|
const resultPromise = this.aiPromptExecutor(step.prompt, modelProvider, modelId);
|
|
100508
100769
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
@@ -100511,7 +100772,7 @@ var init_cron_runner = __esm({
|
|
|
100511
100772
|
const response = await Promise.race([resultPromise, timeoutPromise]);
|
|
100512
100773
|
const responseText = String(response ?? "");
|
|
100513
100774
|
const output = responseText.length > MAX_OUTPUT_LENGTH ? responseText.slice(0, MAX_OUTPUT_LENGTH) + "\n[output truncated]" : responseText;
|
|
100514
|
-
|
|
100775
|
+
log16.log(` \u2713 AI prompt step "${step.name}" completed (${responseText.length} chars)`);
|
|
100515
100776
|
return {
|
|
100516
100777
|
stepId: step.id,
|
|
100517
100778
|
stepName: step.name,
|
|
@@ -100523,7 +100784,7 @@ var init_cron_runner = __esm({
|
|
|
100523
100784
|
};
|
|
100524
100785
|
} catch (err) {
|
|
100525
100786
|
const errorMessage = err.message ?? String(err);
|
|
100526
|
-
|
|
100787
|
+
log16.warn(` \u2717 AI prompt step "${step.name}" failed: ${errorMessage}`);
|
|
100527
100788
|
return {
|
|
100528
100789
|
stepId: step.id,
|
|
100529
100790
|
stepName: step.name,
|
|
@@ -100567,7 +100828,7 @@ var init_cron_runner = __esm({
|
|
|
100567
100828
|
try {
|
|
100568
100829
|
const task = await this.store.createTask(taskInput);
|
|
100569
100830
|
const output = `Created task ${task.id}: ${task.title || task.description.slice(0, 80)}`;
|
|
100570
|
-
|
|
100831
|
+
log16.log(` \u2713 Create-task step "${step.name}" created task ${task.id}`);
|
|
100571
100832
|
return {
|
|
100572
100833
|
stepId: step.id,
|
|
100573
100834
|
stepName: step.name,
|
|
@@ -100579,7 +100840,7 @@ var init_cron_runner = __esm({
|
|
|
100579
100840
|
};
|
|
100580
100841
|
} catch (err) {
|
|
100581
100842
|
const errorMessage = err.message ?? String(err);
|
|
100582
|
-
|
|
100843
|
+
log16.warn(` \u2717 Create-task step "${step.name}" failed: ${errorMessage}`);
|
|
100583
100844
|
return {
|
|
100584
100845
|
stepId: step.id,
|
|
100585
100846
|
stepName: step.name,
|
|
@@ -100621,7 +100882,7 @@ function truncateOutput3(stdout, stderr) {
|
|
|
100621
100882
|
}
|
|
100622
100883
|
return output;
|
|
100623
100884
|
}
|
|
100624
|
-
var import_cron_parser4,
|
|
100885
|
+
var import_cron_parser4, log17, execAsync12, DEFAULT_TIMEOUT_MS9, MAX_BUFFER2, MAX_OUTPUT_LENGTH2, MAX_CATCH_UP_INTERVALS, RoutineRunner;
|
|
100625
100886
|
var init_routine_runner = __esm({
|
|
100626
100887
|
"../engine/src/routine-runner.ts"() {
|
|
100627
100888
|
"use strict";
|
|
@@ -100629,7 +100890,7 @@ var init_routine_runner = __esm({
|
|
|
100629
100890
|
init_cron_runner();
|
|
100630
100891
|
init_logger2();
|
|
100631
100892
|
init_shell_utils();
|
|
100632
|
-
|
|
100893
|
+
log17 = createLogger2("routine-runner");
|
|
100633
100894
|
execAsync12 = promisify17(exec14);
|
|
100634
100895
|
DEFAULT_TIMEOUT_MS9 = 5 * 60 * 1e3;
|
|
100635
100896
|
MAX_BUFFER2 = 1024 * 1024;
|
|
@@ -100666,7 +100927,7 @@ var init_routine_runner = __esm({
|
|
|
100666
100927
|
}
|
|
100667
100928
|
const concurrency = routine.executionPolicy ?? "queue";
|
|
100668
100929
|
if (concurrency === "reject" && this.inFlightExecutions.has(routineId)) {
|
|
100669
|
-
|
|
100930
|
+
log17.log(`Routine ${routineId} rejected \u2014 already running`);
|
|
100670
100931
|
return {
|
|
100671
100932
|
routineId,
|
|
100672
100933
|
success: false,
|
|
@@ -100677,7 +100938,7 @@ var init_routine_runner = __esm({
|
|
|
100677
100938
|
};
|
|
100678
100939
|
}
|
|
100679
100940
|
if (concurrency === "queue" && this.inFlightExecutions.has(routineId)) {
|
|
100680
|
-
|
|
100941
|
+
log17.log(`Routine ${routineId} queued \u2014 waiting for existing execution`);
|
|
100681
100942
|
const existingResult = await this.inFlightExecutions.get(routineId);
|
|
100682
100943
|
if (existingResult) {
|
|
100683
100944
|
await existingResult;
|
|
@@ -100725,7 +100986,7 @@ var init_routine_runner = __esm({
|
|
|
100725
100986
|
};
|
|
100726
100987
|
} catch (err) {
|
|
100727
100988
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
100728
|
-
|
|
100989
|
+
log17.error(`Routine ${routineId} execution failed: ${errorMessage}`);
|
|
100729
100990
|
try {
|
|
100730
100991
|
await this.options.routineStore.completeRoutineExecution(routineId, {
|
|
100731
100992
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -100733,7 +100994,7 @@ var init_routine_runner = __esm({
|
|
|
100733
100994
|
error: errorMessage
|
|
100734
100995
|
});
|
|
100735
100996
|
} catch (persistError) {
|
|
100736
|
-
|
|
100997
|
+
log17.error(`[${routineId}] Failed to persist error state: ${persistError}`);
|
|
100737
100998
|
}
|
|
100738
100999
|
return {
|
|
100739
101000
|
routineId,
|
|
@@ -100995,7 +101256,7 @@ var init_routine_runner = __esm({
|
|
|
100995
101256
|
if (missedIntervals.length === 0) {
|
|
100996
101257
|
return;
|
|
100997
101258
|
}
|
|
100998
|
-
|
|
101259
|
+
log17.log(`[${routine.id}] Running ${missedIntervals.length} catch-up executions`);
|
|
100999
101260
|
for (const missedInterval of missedIntervals) {
|
|
101000
101261
|
try {
|
|
101001
101262
|
await this.executeRoutine(routine.id, "cron", {
|
|
@@ -101003,11 +101264,11 @@ var init_routine_runner = __esm({
|
|
|
101003
101264
|
missedInterval: missedInterval.toISOString()
|
|
101004
101265
|
});
|
|
101005
101266
|
} catch (err) {
|
|
101006
|
-
|
|
101267
|
+
log17.error(`[${routine.id}] Catch-up execution failed: ${err}`);
|
|
101007
101268
|
}
|
|
101008
101269
|
}
|
|
101009
101270
|
} catch (err) {
|
|
101010
|
-
|
|
101271
|
+
log17.error(`[${routine.id}] Error calculating catch-up intervals: ${err}`);
|
|
101011
101272
|
}
|
|
101012
101273
|
}
|
|
101013
101274
|
/**
|
|
@@ -101593,91 +101854,6 @@ var init_stuck_task_detector = __esm({
|
|
|
101593
101854
|
}
|
|
101594
101855
|
});
|
|
101595
101856
|
|
|
101596
|
-
// ../engine/src/restart-recovery-coordinator.ts
|
|
101597
|
-
function hasStepProgress(task) {
|
|
101598
|
-
const steps = Array.isArray(task.steps) ? task.steps : [];
|
|
101599
|
-
return steps.some((step) => step.status === "done" || step.status === "in-progress" || step.status === "skipped");
|
|
101600
|
-
}
|
|
101601
|
-
function isNoTaskDoneFailure(task) {
|
|
101602
|
-
return task.status === "failed" && typeof task.error === "string" && task.error.toLowerCase().includes("without calling fn_task_done");
|
|
101603
|
-
}
|
|
101604
|
-
function findMissingWorktreeSessionPrefix(error) {
|
|
101605
|
-
for (const prefix of MISSING_WORKTREE_SESSION_PREFIXES) {
|
|
101606
|
-
if (error.includes(prefix)) {
|
|
101607
|
-
return prefix;
|
|
101608
|
-
}
|
|
101609
|
-
}
|
|
101610
|
-
return null;
|
|
101611
|
-
}
|
|
101612
|
-
function isMissingWorktreeSessionStartFailure(error) {
|
|
101613
|
-
if (typeof error !== "string") {
|
|
101614
|
-
return false;
|
|
101615
|
-
}
|
|
101616
|
-
return findMissingWorktreeSessionPrefix(error) !== null;
|
|
101617
|
-
}
|
|
101618
|
-
function extractMissingWorktreePathFromSessionStartFailure(error) {
|
|
101619
|
-
if (typeof error !== "string") return null;
|
|
101620
|
-
const prefix = findMissingWorktreeSessionPrefix(error);
|
|
101621
|
-
if (!prefix) return null;
|
|
101622
|
-
const idx = error.indexOf(prefix);
|
|
101623
|
-
const pathPart = error.slice(idx + prefix.length).trim();
|
|
101624
|
-
return pathPart.length > 0 ? pathPart : null;
|
|
101625
|
-
}
|
|
101626
|
-
function isRecoverableMissingWorktreeReviewFailure(task) {
|
|
101627
|
-
return task.column === "in-review" && !task.paused && task.status === "failed" && isMissingWorktreeSessionStartFailure(task.error) && hasStepProgress(task);
|
|
101628
|
-
}
|
|
101629
|
-
var log17, MISSING_WORKTREE_SESSION_PREFIXES, RestartRecoveryCoordinator;
|
|
101630
|
-
var init_restart_recovery_coordinator = __esm({
|
|
101631
|
-
"../engine/src/restart-recovery-coordinator.ts"() {
|
|
101632
|
-
"use strict";
|
|
101633
|
-
init_logger2();
|
|
101634
|
-
log17 = createLogger2("restart-recovery");
|
|
101635
|
-
MISSING_WORKTREE_SESSION_PREFIXES = [
|
|
101636
|
-
"Refusing to start coding agent in missing worktree:",
|
|
101637
|
-
"Refusing to start coding agent in incomplete worktree:",
|
|
101638
|
-
"Refusing to start coding agent in unregistered git worktree:"
|
|
101639
|
-
];
|
|
101640
|
-
RestartRecoveryCoordinator = class {
|
|
101641
|
-
constructor(store, executor) {
|
|
101642
|
-
this.store = store;
|
|
101643
|
-
this.executor = executor;
|
|
101644
|
-
}
|
|
101645
|
-
async recoverInterruptedRuns() {
|
|
101646
|
-
const allInProgress = await this.store.listTasks({ slim: true, column: "in-progress" });
|
|
101647
|
-
const candidates = allInProgress.filter((task) => task.column === "in-progress" && !task.paused);
|
|
101648
|
-
if (candidates.length === 0) return;
|
|
101649
|
-
let requeued = 0;
|
|
101650
|
-
for (const task of candidates) {
|
|
101651
|
-
if (!this.mustSafeRetry(task)) continue;
|
|
101652
|
-
await this.safeRequeue(task);
|
|
101653
|
-
requeued++;
|
|
101654
|
-
}
|
|
101655
|
-
if (requeued > 0) {
|
|
101656
|
-
log17.log(`Restart recovery requeued ${requeued} interrupted task(s) for safe retry`);
|
|
101657
|
-
}
|
|
101658
|
-
await this.executor.resumeOrphaned();
|
|
101659
|
-
}
|
|
101660
|
-
mustSafeRetry(task) {
|
|
101661
|
-
return isNoTaskDoneFailure(task) && !hasStepProgress(task);
|
|
101662
|
-
}
|
|
101663
|
-
async safeRequeue(task) {
|
|
101664
|
-
await this.store.updateTask(task.id, {
|
|
101665
|
-
status: "stuck-killed",
|
|
101666
|
-
worktree: null,
|
|
101667
|
-
branch: null,
|
|
101668
|
-
sessionFile: null,
|
|
101669
|
-
error: null
|
|
101670
|
-
});
|
|
101671
|
-
await this.store.logEntry(
|
|
101672
|
-
task.id,
|
|
101673
|
-
"Restart recovery: interrupted run had no step progress and no fn_task_done \u2014 requeued to todo for safe retry"
|
|
101674
|
-
);
|
|
101675
|
-
await this.store.moveTask(task.id, "todo");
|
|
101676
|
-
}
|
|
101677
|
-
};
|
|
101678
|
-
}
|
|
101679
|
-
});
|
|
101680
|
-
|
|
101681
101857
|
// ../engine/src/self-healing.ts
|
|
101682
101858
|
import { exec as exec15, execSync as execSync4 } from "node:child_process";
|
|
101683
101859
|
import { promisify as promisify18 } from "node:util";
|
|
@@ -107455,6 +107631,13 @@ var init_in_process_runtime = __esm({
|
|
|
107455
107631
|
getMessageStore() {
|
|
107456
107632
|
return this.messageStore;
|
|
107457
107633
|
}
|
|
107634
|
+
/**
|
|
107635
|
+
* Get the ChatStore instance (if initialized).
|
|
107636
|
+
* Returns undefined before start() or if initialization fails.
|
|
107637
|
+
*/
|
|
107638
|
+
getChatStore() {
|
|
107639
|
+
return this.chatStore;
|
|
107640
|
+
}
|
|
107458
107641
|
/**
|
|
107459
107642
|
* Get the project's Scheduler instance.
|
|
107460
107643
|
* @throws Error if runtime has not been started
|
|
@@ -110428,6 +110611,10 @@ ${detail}`
|
|
|
110428
110611
|
getMessageStore() {
|
|
110429
110612
|
return this.runtime.getMessageStore();
|
|
110430
110613
|
}
|
|
110614
|
+
/** Get the ChatStore (if initialized). Returns undefined before start(). */
|
|
110615
|
+
getChatStore() {
|
|
110616
|
+
return this.runtime.getChatStore();
|
|
110617
|
+
}
|
|
110431
110618
|
attachChatStore(chatStore) {
|
|
110432
110619
|
this.notificationService?.attachChatStore(chatStore);
|
|
110433
110620
|
}
|
|
@@ -134270,6 +134457,71 @@ Status: ${updated.status}`
|
|
|
134270
134457
|
};
|
|
134271
134458
|
}
|
|
134272
134459
|
});
|
|
134460
|
+
pi.registerTool({
|
|
134461
|
+
name: "fn_milestone_update",
|
|
134462
|
+
label: "fn: Update Milestone",
|
|
134463
|
+
description: "Update an existing milestone's title, description, or acceptance criteria (the structured pass/fail bar, distinct from verification's free-form how-to-confirm notes). Partial patches leave untouched fields intact.",
|
|
134464
|
+
promptSnippet: "Update an existing mission milestone",
|
|
134465
|
+
promptGuidelines: [
|
|
134466
|
+
"Use to revise milestone details without re-creating it",
|
|
134467
|
+
"Mission linkage and ordering are preserved",
|
|
134468
|
+
"Provide only the fields you want to change"
|
|
134469
|
+
],
|
|
134470
|
+
parameters: Type8.Object({
|
|
134471
|
+
id: Type8.String({ description: "Milestone ID to update (e.g., MS-001)" }),
|
|
134472
|
+
title: Type8.Optional(Type8.String({ description: "Updated milestone title" })),
|
|
134473
|
+
description: Type8.Optional(Type8.String({ description: "Updated milestone description" })),
|
|
134474
|
+
acceptanceCriteria: Type8.Optional(
|
|
134475
|
+
Type8.String({ description: "Updated acceptance criteria for completing the milestone" })
|
|
134476
|
+
)
|
|
134477
|
+
}),
|
|
134478
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
134479
|
+
const store = await getStore2(ctx.cwd);
|
|
134480
|
+
const missionStore = store.getMissionStore();
|
|
134481
|
+
const existingMilestone = missionStore.getMilestone(params.id);
|
|
134482
|
+
if (!existingMilestone) {
|
|
134483
|
+
return {
|
|
134484
|
+
content: [{ type: "text", text: `Milestone ${params.id} not found` }],
|
|
134485
|
+
isError: true,
|
|
134486
|
+
details: { error: "Milestone not found" }
|
|
134487
|
+
};
|
|
134488
|
+
}
|
|
134489
|
+
const updates = {};
|
|
134490
|
+
if ("title" in params) {
|
|
134491
|
+
updates.title = params.title?.trim();
|
|
134492
|
+
}
|
|
134493
|
+
if ("description" in params) {
|
|
134494
|
+
updates.description = params.description?.trim();
|
|
134495
|
+
}
|
|
134496
|
+
if ("acceptanceCriteria" in params) {
|
|
134497
|
+
updates.acceptanceCriteria = params.acceptanceCriteria?.trim();
|
|
134498
|
+
}
|
|
134499
|
+
if (Object.keys(updates).length === 0) {
|
|
134500
|
+
return {
|
|
134501
|
+
content: [
|
|
134502
|
+
{
|
|
134503
|
+
type: "text",
|
|
134504
|
+
text: "No fields to update (provide at least one of: title, description, acceptanceCriteria)"
|
|
134505
|
+
}
|
|
134506
|
+
],
|
|
134507
|
+
isError: true,
|
|
134508
|
+
details: { error: "No fields to update" }
|
|
134509
|
+
};
|
|
134510
|
+
}
|
|
134511
|
+
const milestone = missionStore.updateMilestone(params.id, updates);
|
|
134512
|
+
return {
|
|
134513
|
+
content: [{ type: "text", text: `Updated ${milestone.id}: "${milestone.title}"` }],
|
|
134514
|
+
details: {
|
|
134515
|
+
milestoneId: milestone.id,
|
|
134516
|
+
missionId: milestone.missionId,
|
|
134517
|
+
title: milestone.title,
|
|
134518
|
+
description: milestone.description,
|
|
134519
|
+
acceptanceCriteria: milestone.acceptanceCriteria,
|
|
134520
|
+
status: milestone.status
|
|
134521
|
+
}
|
|
134522
|
+
};
|
|
134523
|
+
}
|
|
134524
|
+
});
|
|
134273
134525
|
pi.registerTool({
|
|
134274
134526
|
name: "fn_agent_stop",
|
|
134275
134527
|
label: "fn: Stop Agent",
|