opencode-orchestrator 1.2.14 → 1.2.15
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/core/agents/manager/task-poller.d.ts +2 -1
- package/dist/core/loop/continuation-lock.d.ts +57 -0
- package/dist/core/loop/verification.d.ts +5 -0
- package/dist/core/session/session-health.d.ts +93 -0
- package/dist/index.js +429 -116
- package/dist/shared/constants/system-messages.d.ts +1 -1
- package/package.json +1 -1
|
@@ -14,9 +14,10 @@ export declare class TaskPoller {
|
|
|
14
14
|
private scheduleCleanup;
|
|
15
15
|
private pruneExpiredTasks;
|
|
16
16
|
private onTaskComplete?;
|
|
17
|
+
private onTaskError?;
|
|
17
18
|
private pollingInterval?;
|
|
18
19
|
private messageCache;
|
|
19
|
-
constructor(client: OpencodeClient, store: TaskStore, concurrency: ConcurrencyController, notifyParentIfAllComplete: (parentSessionID: string) => Promise<void>, scheduleCleanup: (taskId: string) => void, pruneExpiredTasks: () => void, onTaskComplete?: ((task: ParallelTask) => void | Promise<void>) | undefined);
|
|
20
|
+
constructor(client: OpencodeClient, store: TaskStore, concurrency: ConcurrencyController, notifyParentIfAllComplete: (parentSessionID: string) => Promise<void>, scheduleCleanup: (taskId: string) => void, pruneExpiredTasks: () => void, onTaskComplete?: ((task: ParallelTask) => void | Promise<void>) | undefined, onTaskError?: ((taskId: string, error: unknown) => void) | undefined);
|
|
20
21
|
start(): void;
|
|
21
22
|
stop(): void;
|
|
22
23
|
isRunning(): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation Lock - Ensures single execution of continuation logic
|
|
3
|
+
*
|
|
4
|
+
* Prevents simultaneous execution of Mission Loop and Todo Continuation systems.
|
|
5
|
+
* This resolves infinite loading issues caused by duplicate prompt injections.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* if (!tryAcquireContinuationLock(sessionID)) {
|
|
9
|
+
* return; // Another system is already processing
|
|
10
|
+
* }
|
|
11
|
+
* try {
|
|
12
|
+
* // continuation logic
|
|
13
|
+
* } finally {
|
|
14
|
+
* releaseContinuationLock(sessionID);
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
interface ContinuationLockState {
|
|
18
|
+
acquired: boolean;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
source?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Try to acquire the continuation lock
|
|
24
|
+
*
|
|
25
|
+
* @param sessionID - Session ID
|
|
26
|
+
* @param source - Source requesting the lock (for debugging)
|
|
27
|
+
* @returns true if acquired, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export declare function tryAcquireContinuationLock(sessionID: string, source?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Release the continuation lock
|
|
32
|
+
*
|
|
33
|
+
* @param sessionID - Session ID
|
|
34
|
+
*/
|
|
35
|
+
export declare function releaseContinuationLock(sessionID: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Check if the lock is currently active
|
|
38
|
+
*
|
|
39
|
+
* @param sessionID - Session ID
|
|
40
|
+
* @returns true if lock is held
|
|
41
|
+
*/
|
|
42
|
+
export declare function hasContinuationLock(sessionID: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Cleanup lock when session is cleaned up
|
|
45
|
+
*
|
|
46
|
+
* @param sessionID - Session ID
|
|
47
|
+
*/
|
|
48
|
+
export declare function cleanupContinuationLock(sessionID: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Clear all locks (for testing)
|
|
51
|
+
*/
|
|
52
|
+
export declare function clearAllLocks(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get lock status (for debugging)
|
|
55
|
+
*/
|
|
56
|
+
export declare function getLockStatus(sessionID: string): ContinuationLockState | undefined;
|
|
57
|
+
export {};
|
|
@@ -11,11 +11,16 @@ export type { ChecklistItem, ChecklistCategory, ChecklistVerificationResult, Ver
|
|
|
11
11
|
export declare const CHECKLIST_FILE: ".opencode/verification-checklist.md";
|
|
12
12
|
export declare function parseChecklist(content: string): ChecklistItem[];
|
|
13
13
|
export declare function readChecklist(directory: string): ChecklistItem[];
|
|
14
|
+
export declare function readChecklistAsync(directory: string): Promise<ChecklistItem[]>;
|
|
15
|
+
export declare function clearVerificationCache(): void;
|
|
14
16
|
export declare function verifyChecklist(directory: string): ChecklistVerificationResult;
|
|
17
|
+
export declare function verifyChecklistAsync(directory: string): Promise<ChecklistVerificationResult>;
|
|
15
18
|
export declare function hasValidChecklist(directory: string): boolean;
|
|
16
19
|
export declare function getChecklistSummary(directory: string): string;
|
|
17
20
|
export declare function buildChecklistFailurePrompt(result: ChecklistVerificationResult): string;
|
|
18
21
|
export declare function getChecklistCreationInstructions(): string;
|
|
22
|
+
export declare function verifyMissionCompletionSync(directory: string): VerificationResult;
|
|
23
|
+
export declare function verifyMissionCompletionAsync(directory: string): Promise<VerificationResult>;
|
|
19
24
|
export declare function verifyMissionCompletion(directory: string): VerificationResult;
|
|
20
25
|
export declare function buildVerificationFailurePrompt(result: VerificationResult): string;
|
|
21
26
|
export declare function buildTodoIncompletePrompt(result: VerificationResult): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Health Monitor
|
|
3
|
+
*
|
|
4
|
+
* Periodically checks session health and detects stale sessions.
|
|
5
|
+
* Early detection of infinite loading states improves system stability.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
8
|
+
type OpencodeClient = PluginInput["client"];
|
|
9
|
+
interface SessionHealthState {
|
|
10
|
+
sessionID: string;
|
|
11
|
+
lastActiveTime: number;
|
|
12
|
+
lastResponseTime: number;
|
|
13
|
+
isStale: boolean;
|
|
14
|
+
activityCount: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Record session activity
|
|
18
|
+
*
|
|
19
|
+
* Call this when an activity occurs in the session.
|
|
20
|
+
* (e.g., sending prompt, tool execution)
|
|
21
|
+
*
|
|
22
|
+
* @param sessionID - Session ID
|
|
23
|
+
*/
|
|
24
|
+
export declare function recordSessionActivity(sessionID: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Record session response receipt
|
|
27
|
+
*
|
|
28
|
+
* Call this when a response is received from the session.
|
|
29
|
+
* (e.g., assistant message received)
|
|
30
|
+
*
|
|
31
|
+
* @param sessionID - Session ID
|
|
32
|
+
*/
|
|
33
|
+
export declare function recordSessionResponse(sessionID: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if session is stale
|
|
36
|
+
*
|
|
37
|
+
* @param sessionID - Session ID
|
|
38
|
+
* @returns true if stale
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSessionStale(sessionID: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Get session response age
|
|
43
|
+
*
|
|
44
|
+
* @param sessionID - Session ID
|
|
45
|
+
* @returns Time elapsed since last response (ms), or -1 if session not found
|
|
46
|
+
*/
|
|
47
|
+
export declare function getSessionResponseAge(sessionID: string): number;
|
|
48
|
+
/**
|
|
49
|
+
* Get all stale session IDs
|
|
50
|
+
*
|
|
51
|
+
* @returns Array of stale session IDs
|
|
52
|
+
*/
|
|
53
|
+
export declare function getStaleSessions(): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Start health check monitor
|
|
56
|
+
*
|
|
57
|
+
* @param opencodeClient - OpenCode Client
|
|
58
|
+
*/
|
|
59
|
+
export declare function startHealthCheck(opencodeClient: OpencodeClient): void;
|
|
60
|
+
/**
|
|
61
|
+
* Stop health check monitor
|
|
62
|
+
*/
|
|
63
|
+
export declare function stopHealthCheck(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Perform actual health check (Exported for testing)
|
|
66
|
+
*/
|
|
67
|
+
export declare function performHealthCheck(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Cleanup session health state
|
|
70
|
+
*
|
|
71
|
+
* @param sessionID - Session ID
|
|
72
|
+
*/
|
|
73
|
+
export declare function cleanupSessionHealth(sessionID: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Clear all session health info (for testing)
|
|
76
|
+
*/
|
|
77
|
+
export declare function clearAllSessionHealth(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Get session health info (for debugging)
|
|
80
|
+
*
|
|
81
|
+
* @param sessionID - Session ID
|
|
82
|
+
*/
|
|
83
|
+
export declare function getSessionHealth(sessionID: string): SessionHealthState | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Get overall health stats (for debugging)
|
|
86
|
+
*/
|
|
87
|
+
export declare function getHealthStats(): {
|
|
88
|
+
total: number;
|
|
89
|
+
stale: number;
|
|
90
|
+
healthy: number;
|
|
91
|
+
avgResponseAge: number;
|
|
92
|
+
};
|
|
93
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -865,8 +865,8 @@ var init_types4 = __esm({
|
|
|
865
865
|
});
|
|
866
866
|
|
|
867
867
|
// src/core/notification/toast-core.ts
|
|
868
|
-
function initToastClient(
|
|
869
|
-
tuiClient =
|
|
868
|
+
function initToastClient(client2) {
|
|
869
|
+
tuiClient = client2;
|
|
870
870
|
}
|
|
871
871
|
function show(options) {
|
|
872
872
|
const toast = {
|
|
@@ -889,9 +889,9 @@ function show(options) {
|
|
|
889
889
|
}
|
|
890
890
|
}
|
|
891
891
|
if (tuiClient) {
|
|
892
|
-
const
|
|
893
|
-
if (
|
|
894
|
-
|
|
892
|
+
const client2 = tuiClient;
|
|
893
|
+
if (client2.tui?.showToast) {
|
|
894
|
+
client2.tui.showToast({
|
|
895
895
|
body: {
|
|
896
896
|
title: toast.title,
|
|
897
897
|
message: toast.message,
|
|
@@ -18823,6 +18823,75 @@ Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
|
18823
18823
|
</system-notification>`;
|
|
18824
18824
|
}
|
|
18825
18825
|
|
|
18826
|
+
// src/core/session/session-health.ts
|
|
18827
|
+
var sessionHealth = /* @__PURE__ */ new Map();
|
|
18828
|
+
var STALE_THRESHOLD_MS = 12e4;
|
|
18829
|
+
var HEALTH_CHECK_INTERVAL_MS = 3e4;
|
|
18830
|
+
var WARNING_THRESHOLD_MS = 6e4;
|
|
18831
|
+
var healthCheckTimer;
|
|
18832
|
+
var client;
|
|
18833
|
+
function recordSessionResponse(sessionID) {
|
|
18834
|
+
const health = sessionHealth.get(sessionID);
|
|
18835
|
+
if (health) {
|
|
18836
|
+
health.lastResponseTime = Date.now();
|
|
18837
|
+
health.isStale = false;
|
|
18838
|
+
}
|
|
18839
|
+
}
|
|
18840
|
+
function isSessionStale(sessionID) {
|
|
18841
|
+
const health = sessionHealth.get(sessionID);
|
|
18842
|
+
if (!health) return false;
|
|
18843
|
+
return health.isStale;
|
|
18844
|
+
}
|
|
18845
|
+
function startHealthCheck(opencodeClient) {
|
|
18846
|
+
if (healthCheckTimer) {
|
|
18847
|
+
log("[session-health] Health check already running");
|
|
18848
|
+
return;
|
|
18849
|
+
}
|
|
18850
|
+
client = opencodeClient;
|
|
18851
|
+
healthCheckTimer = setInterval(() => {
|
|
18852
|
+
performHealthCheck();
|
|
18853
|
+
}, HEALTH_CHECK_INTERVAL_MS);
|
|
18854
|
+
log("[session-health] Health check started", {
|
|
18855
|
+
intervalMs: HEALTH_CHECK_INTERVAL_MS,
|
|
18856
|
+
staleThresholdMs: STALE_THRESHOLD_MS
|
|
18857
|
+
});
|
|
18858
|
+
}
|
|
18859
|
+
function stopHealthCheck() {
|
|
18860
|
+
if (healthCheckTimer) {
|
|
18861
|
+
clearInterval(healthCheckTimer);
|
|
18862
|
+
healthCheckTimer = void 0;
|
|
18863
|
+
log("[session-health] Health check stopped");
|
|
18864
|
+
}
|
|
18865
|
+
}
|
|
18866
|
+
function performHealthCheck() {
|
|
18867
|
+
const now = Date.now();
|
|
18868
|
+
const warnings = [];
|
|
18869
|
+
const stales = [];
|
|
18870
|
+
for (const [sessionID, health] of sessionHealth) {
|
|
18871
|
+
const elapsed = now - health.lastResponseTime;
|
|
18872
|
+
if (elapsed > WARNING_THRESHOLD_MS && elapsed <= STALE_THRESHOLD_MS && !health.isStale) {
|
|
18873
|
+
warnings.push(sessionID.slice(0, 8));
|
|
18874
|
+
}
|
|
18875
|
+
if (elapsed > STALE_THRESHOLD_MS && !health.isStale) {
|
|
18876
|
+
health.isStale = true;
|
|
18877
|
+
stales.push(sessionID.slice(0, 8));
|
|
18878
|
+
}
|
|
18879
|
+
}
|
|
18880
|
+
if (warnings.length > 0) {
|
|
18881
|
+
log("[session-health] Sessions approaching stale threshold", {
|
|
18882
|
+
sessions: warnings
|
|
18883
|
+
});
|
|
18884
|
+
}
|
|
18885
|
+
if (stales.length > 0) {
|
|
18886
|
+
log("[session-health] Sessions marked as stale", {
|
|
18887
|
+
sessions: stales
|
|
18888
|
+
});
|
|
18889
|
+
}
|
|
18890
|
+
}
|
|
18891
|
+
function cleanupSessionHealth(sessionID) {
|
|
18892
|
+
sessionHealth.delete(sessionID);
|
|
18893
|
+
}
|
|
18894
|
+
|
|
18826
18895
|
// src/core/agents/manager/task-launcher.ts
|
|
18827
18896
|
init_shared();
|
|
18828
18897
|
init_shared();
|
|
@@ -18837,8 +18906,8 @@ var TaskToastManager = class {
|
|
|
18837
18906
|
/**
|
|
18838
18907
|
* Initialize the manager with OpenCode client
|
|
18839
18908
|
*/
|
|
18840
|
-
init(
|
|
18841
|
-
this.client =
|
|
18909
|
+
init(client2, concurrency) {
|
|
18910
|
+
this.client = client2;
|
|
18842
18911
|
this.concurrency = concurrency ?? null;
|
|
18843
18912
|
}
|
|
18844
18913
|
/**
|
|
@@ -19098,11 +19167,11 @@ var instance = null;
|
|
|
19098
19167
|
function getTaskToastManager() {
|
|
19099
19168
|
return instance;
|
|
19100
19169
|
}
|
|
19101
|
-
function initTaskToastManager(
|
|
19170
|
+
function initTaskToastManager(client2, concurrency) {
|
|
19102
19171
|
if (!instance) {
|
|
19103
19172
|
instance = new TaskToastManager();
|
|
19104
19173
|
}
|
|
19105
|
-
instance.init(
|
|
19174
|
+
instance.init(client2, concurrency);
|
|
19106
19175
|
return instance;
|
|
19107
19176
|
}
|
|
19108
19177
|
|
|
@@ -33314,8 +33383,8 @@ var AgentRegistry = class _AgentRegistry {
|
|
|
33314
33383
|
|
|
33315
33384
|
// src/core/agents/manager/task-launcher.ts
|
|
33316
33385
|
var TaskLauncher = class {
|
|
33317
|
-
constructor(
|
|
33318
|
-
this.client =
|
|
33386
|
+
constructor(client2, directory, store, concurrency, sessionPool2, onTaskError, startPolling) {
|
|
33387
|
+
this.client = client2;
|
|
33319
33388
|
this.directory = directory;
|
|
33320
33389
|
this.store = store;
|
|
33321
33390
|
this.concurrency = concurrency;
|
|
@@ -33475,8 +33544,8 @@ ${action.modifyPrompt}`;
|
|
|
33475
33544
|
// src/core/agents/manager/task-resumer.ts
|
|
33476
33545
|
init_shared();
|
|
33477
33546
|
var TaskResumer = class {
|
|
33478
|
-
constructor(
|
|
33479
|
-
this.client =
|
|
33547
|
+
constructor(client2, store, findBySession, startPolling, notifyParentIfAllComplete) {
|
|
33548
|
+
this.client = client2;
|
|
33480
33549
|
this.store = store;
|
|
33481
33550
|
this.findBySession = findBySession;
|
|
33482
33551
|
this.startPolling = startPolling;
|
|
@@ -33628,15 +33697,17 @@ var ProgressNotifier = class _ProgressNotifier {
|
|
|
33628
33697
|
var progressNotifier = ProgressNotifier.getInstance();
|
|
33629
33698
|
|
|
33630
33699
|
// src/core/agents/manager/task-poller.ts
|
|
33700
|
+
var MAX_TASK_DURATION_MS = 6e5;
|
|
33631
33701
|
var TaskPoller = class {
|
|
33632
|
-
constructor(
|
|
33633
|
-
this.client =
|
|
33702
|
+
constructor(client2, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks, onTaskComplete, onTaskError) {
|
|
33703
|
+
this.client = client2;
|
|
33634
33704
|
this.store = store;
|
|
33635
33705
|
this.concurrency = concurrency;
|
|
33636
33706
|
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
33637
33707
|
this.scheduleCleanup = scheduleCleanup;
|
|
33638
33708
|
this.pruneExpiredTasks = pruneExpiredTasks;
|
|
33639
33709
|
this.onTaskComplete = onTaskComplete;
|
|
33710
|
+
this.onTaskError = onTaskError;
|
|
33640
33711
|
}
|
|
33641
33712
|
pollingInterval;
|
|
33642
33713
|
messageCache = /* @__PURE__ */ new Map();
|
|
@@ -33668,6 +33739,17 @@ var TaskPoller = class {
|
|
|
33668
33739
|
const allStatuses = statusResult.data ?? {};
|
|
33669
33740
|
for (const task of running) {
|
|
33670
33741
|
try {
|
|
33742
|
+
const taskDuration = Date.now() - task.startedAt.getTime();
|
|
33743
|
+
if (isSessionStale(task.sessionID)) {
|
|
33744
|
+
log(`[task-poller] Task ${task.id} session is stale. Marking as error.`);
|
|
33745
|
+
this.onTaskError?.(task.id, new Error("Session became stale (no response from agent)"));
|
|
33746
|
+
continue;
|
|
33747
|
+
}
|
|
33748
|
+
if (taskDuration > MAX_TASK_DURATION_MS) {
|
|
33749
|
+
log(`[task-poller] Task ${task.id} exceeded max duration (${MAX_TASK_DURATION_MS}ms). Marking as error.`);
|
|
33750
|
+
this.onTaskError?.(task.id, new Error("Task exceeded maximum execution time"));
|
|
33751
|
+
continue;
|
|
33752
|
+
}
|
|
33671
33753
|
if (task.status === TASK_STATUS.PENDING) continue;
|
|
33672
33754
|
const sessionStatus = allStatuses[task.sessionID];
|
|
33673
33755
|
if (sessionStatus?.type === SESSION_STATUS.IDLE) {
|
|
@@ -33780,8 +33862,8 @@ var TaskPoller = class {
|
|
|
33780
33862
|
init_shared();
|
|
33781
33863
|
init_store();
|
|
33782
33864
|
var TaskCleaner = class {
|
|
33783
|
-
constructor(
|
|
33784
|
-
this.client =
|
|
33865
|
+
constructor(client2, store, concurrency, sessionPool2) {
|
|
33866
|
+
this.client = client2;
|
|
33785
33867
|
this.store = store;
|
|
33786
33868
|
this.concurrency = concurrency;
|
|
33787
33869
|
this.sessionPool = sessionPool2;
|
|
@@ -33892,9 +33974,69 @@ You will be notified when ALL tasks complete. Continue productive work.`;
|
|
|
33892
33974
|
|
|
33893
33975
|
// src/core/agents/manager/event-handler.ts
|
|
33894
33976
|
init_shared();
|
|
33977
|
+
|
|
33978
|
+
// src/core/loop/continuation-lock.ts
|
|
33979
|
+
var locks = /* @__PURE__ */ new Map();
|
|
33980
|
+
var LOCK_TIMEOUT_MS = 3e4;
|
|
33981
|
+
function tryAcquireContinuationLock(sessionID, source) {
|
|
33982
|
+
const now = Date.now();
|
|
33983
|
+
const existing = locks.get(sessionID);
|
|
33984
|
+
if (existing?.acquired) {
|
|
33985
|
+
const elapsed = now - existing.timestamp;
|
|
33986
|
+
if (elapsed < LOCK_TIMEOUT_MS) {
|
|
33987
|
+
log("[continuation-lock] Lock denied - already held", {
|
|
33988
|
+
sessionID: sessionID.slice(0, 8),
|
|
33989
|
+
heldBy: existing.source,
|
|
33990
|
+
requestedBy: source,
|
|
33991
|
+
elapsedMs: elapsed
|
|
33992
|
+
});
|
|
33993
|
+
return false;
|
|
33994
|
+
}
|
|
33995
|
+
log("[continuation-lock] Forcing stale lock release", {
|
|
33996
|
+
sessionID: sessionID.slice(0, 8),
|
|
33997
|
+
staleSource: existing.source,
|
|
33998
|
+
elapsedMs: elapsed
|
|
33999
|
+
});
|
|
34000
|
+
}
|
|
34001
|
+
locks.set(sessionID, { acquired: true, timestamp: now, source });
|
|
34002
|
+
log("[continuation-lock] Lock acquired", {
|
|
34003
|
+
sessionID: sessionID.slice(0, 8),
|
|
34004
|
+
source
|
|
34005
|
+
});
|
|
34006
|
+
return true;
|
|
34007
|
+
}
|
|
34008
|
+
function releaseContinuationLock(sessionID) {
|
|
34009
|
+
const existing = locks.get(sessionID);
|
|
34010
|
+
if (existing?.acquired) {
|
|
34011
|
+
const duration5 = Date.now() - existing.timestamp;
|
|
34012
|
+
log("[continuation-lock] Lock released", {
|
|
34013
|
+
sessionID: sessionID.slice(0, 8),
|
|
34014
|
+
source: existing.source,
|
|
34015
|
+
heldMs: duration5
|
|
34016
|
+
});
|
|
34017
|
+
}
|
|
34018
|
+
locks.delete(sessionID);
|
|
34019
|
+
}
|
|
34020
|
+
function hasContinuationLock(sessionID) {
|
|
34021
|
+
const lock = locks.get(sessionID);
|
|
34022
|
+
if (!lock?.acquired) return false;
|
|
34023
|
+
if (Date.now() - lock.timestamp >= LOCK_TIMEOUT_MS) {
|
|
34024
|
+
log("[continuation-lock] Stale lock detected during check", {
|
|
34025
|
+
sessionID: sessionID.slice(0, 8)
|
|
34026
|
+
});
|
|
34027
|
+
locks.delete(sessionID);
|
|
34028
|
+
return false;
|
|
34029
|
+
}
|
|
34030
|
+
return true;
|
|
34031
|
+
}
|
|
34032
|
+
function cleanupContinuationLock(sessionID) {
|
|
34033
|
+
locks.delete(sessionID);
|
|
34034
|
+
}
|
|
34035
|
+
|
|
34036
|
+
// src/core/agents/manager/event-handler.ts
|
|
33895
34037
|
var EventHandler = class {
|
|
33896
|
-
constructor(
|
|
33897
|
-
this.client =
|
|
34038
|
+
constructor(client2, store, concurrency, findBySession, notifyParentIfAllComplete, scheduleCleanup, validateSessionHasOutput2, onTaskComplete) {
|
|
34039
|
+
this.client = client2;
|
|
33898
34040
|
this.store = store;
|
|
33899
34041
|
this.concurrency = concurrency;
|
|
33900
34042
|
this.findBySession = findBySession;
|
|
@@ -33912,6 +34054,7 @@ var EventHandler = class {
|
|
|
33912
34054
|
if (event.type === SESSION_EVENTS.IDLE) {
|
|
33913
34055
|
const sessionID = props?.sessionID;
|
|
33914
34056
|
if (!sessionID) return;
|
|
34057
|
+
recordSessionResponse(sessionID);
|
|
33915
34058
|
const task = this.findBySession(sessionID);
|
|
33916
34059
|
if (!task || task.status !== TASK_STATUS.RUNNING) return;
|
|
33917
34060
|
this.handleSessionIdle(task).catch((err) => {
|
|
@@ -33973,6 +34116,8 @@ var EventHandler = class {
|
|
|
33973
34116
|
this.store.delete(task.id);
|
|
33974
34117
|
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
33975
34118
|
});
|
|
34119
|
+
cleanupSessionHealth(task.sessionID);
|
|
34120
|
+
cleanupContinuationLock(task.sessionID);
|
|
33976
34121
|
progressNotifier.update();
|
|
33977
34122
|
log(`Cleaned up deleted session task: ${task.id}`);
|
|
33978
34123
|
}
|
|
@@ -34002,18 +34147,18 @@ var SessionPool = class _SessionPool {
|
|
|
34002
34147
|
reuseHits: 0,
|
|
34003
34148
|
creationMisses: 0
|
|
34004
34149
|
};
|
|
34005
|
-
constructor(
|
|
34006
|
-
this.client =
|
|
34150
|
+
constructor(client2, directory, config3 = {}) {
|
|
34151
|
+
this.client = client2;
|
|
34007
34152
|
this.directory = directory;
|
|
34008
34153
|
this.config = { ...DEFAULT_CONFIG, ...config3 };
|
|
34009
34154
|
this.startHealthCheck();
|
|
34010
34155
|
}
|
|
34011
|
-
static getInstance(
|
|
34156
|
+
static getInstance(client2, directory, config3) {
|
|
34012
34157
|
if (!_SessionPool._instance) {
|
|
34013
|
-
if (!
|
|
34158
|
+
if (!client2 || !directory) {
|
|
34014
34159
|
throw new Error("SessionPool requires client and directory on first call");
|
|
34015
34160
|
}
|
|
34016
|
-
_SessionPool._instance = new _SessionPool(
|
|
34161
|
+
_SessionPool._instance = new _SessionPool(client2, directory, config3);
|
|
34017
34162
|
}
|
|
34018
34163
|
return _SessionPool._instance;
|
|
34019
34164
|
}
|
|
@@ -34423,27 +34568,29 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34423
34568
|
poller;
|
|
34424
34569
|
cleaner;
|
|
34425
34570
|
eventHandler;
|
|
34426
|
-
constructor(
|
|
34427
|
-
this.client =
|
|
34571
|
+
constructor(client2, directory) {
|
|
34572
|
+
this.client = client2;
|
|
34428
34573
|
this.directory = directory;
|
|
34574
|
+
startHealthCheck(client2);
|
|
34429
34575
|
const memory = MemoryManager.getInstance();
|
|
34430
34576
|
memory.add("system" /* SYSTEM */, CORE_PHILOSOPHY, 1);
|
|
34431
34577
|
memory.add("project" /* PROJECT */, `Working directory: ${directory}`, 0.9);
|
|
34432
34578
|
AgentRegistry.getInstance().setDirectory(directory);
|
|
34433
34579
|
TodoManager.getInstance().setDirectory(directory);
|
|
34434
|
-
this.sessionPool = SessionPool.getInstance(
|
|
34435
|
-
this.cleaner = new TaskCleaner(
|
|
34580
|
+
this.sessionPool = SessionPool.getInstance(client2, directory);
|
|
34581
|
+
this.cleaner = new TaskCleaner(client2, this.store, this.concurrency, this.sessionPool);
|
|
34436
34582
|
this.poller = new TaskPoller(
|
|
34437
|
-
|
|
34583
|
+
client2,
|
|
34438
34584
|
this.store,
|
|
34439
34585
|
this.concurrency,
|
|
34440
34586
|
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
34441
34587
|
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
34442
34588
|
() => this.cleaner.pruneExpiredTasks(),
|
|
34443
|
-
(task) => this.handleTaskComplete(task)
|
|
34589
|
+
(task) => this.handleTaskComplete(task),
|
|
34590
|
+
(taskId, error92) => this.handleTaskError(taskId, error92)
|
|
34444
34591
|
);
|
|
34445
34592
|
this.launcher = new TaskLauncher(
|
|
34446
|
-
|
|
34593
|
+
client2,
|
|
34447
34594
|
directory,
|
|
34448
34595
|
this.store,
|
|
34449
34596
|
this.concurrency,
|
|
@@ -34452,14 +34599,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34452
34599
|
() => this.poller.start()
|
|
34453
34600
|
);
|
|
34454
34601
|
this.resumer = new TaskResumer(
|
|
34455
|
-
|
|
34602
|
+
client2,
|
|
34456
34603
|
this.store,
|
|
34457
34604
|
(sessionID) => this.findBySession(sessionID),
|
|
34458
34605
|
() => this.poller.start(),
|
|
34459
34606
|
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID)
|
|
34460
34607
|
);
|
|
34461
34608
|
this.eventHandler = new EventHandler(
|
|
34462
|
-
|
|
34609
|
+
client2,
|
|
34463
34610
|
this.store,
|
|
34464
34611
|
this.concurrency,
|
|
34465
34612
|
(sessionID) => this.findBySession(sessionID),
|
|
@@ -34473,12 +34620,12 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34473
34620
|
log("Recovery error:", err);
|
|
34474
34621
|
});
|
|
34475
34622
|
}
|
|
34476
|
-
static getInstance(
|
|
34623
|
+
static getInstance(client2, directory) {
|
|
34477
34624
|
if (!_ParallelAgentManager._instance) {
|
|
34478
|
-
if (!
|
|
34625
|
+
if (!client2 || !directory) {
|
|
34479
34626
|
throw new Error("ParallelAgentManager requires client and directory on first call");
|
|
34480
34627
|
}
|
|
34481
|
-
_ParallelAgentManager._instance = new _ParallelAgentManager(
|
|
34628
|
+
_ParallelAgentManager._instance = new _ParallelAgentManager(client2, directory);
|
|
34482
34629
|
}
|
|
34483
34630
|
return _ParallelAgentManager._instance;
|
|
34484
34631
|
}
|
|
@@ -34557,6 +34704,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34557
34704
|
}
|
|
34558
34705
|
cleanup() {
|
|
34559
34706
|
this.poller.stop();
|
|
34707
|
+
stopHealthCheck();
|
|
34560
34708
|
this.store.clear();
|
|
34561
34709
|
MemoryManager.getInstance().clearTaskMemory();
|
|
34562
34710
|
Promise.resolve().then(() => (init_store(), store_exports)).then((store) => store.clearAll()).catch(() => {
|
|
@@ -34787,7 +34935,7 @@ async function extractSessionResult(session, sessionID) {
|
|
|
34787
34935
|
return "(Error extracting result)";
|
|
34788
34936
|
}
|
|
34789
34937
|
}
|
|
34790
|
-
var createDelegateTaskTool = (manager,
|
|
34938
|
+
var createDelegateTaskTool = (manager, client2) => tool({
|
|
34791
34939
|
description: `Delegate a task to an agent.
|
|
34792
34940
|
|
|
34793
34941
|
${PROMPT_TAGS.MODE.open}
|
|
@@ -34840,7 +34988,7 @@ If your task is too complex, please:
|
|
|
34840
34988
|
2. Request task decomposition at the ${AGENT_NAMES.PLANNER} level
|
|
34841
34989
|
3. Complete your assigned file directly without delegation`;
|
|
34842
34990
|
}
|
|
34843
|
-
const sessionClient =
|
|
34991
|
+
const sessionClient = client2;
|
|
34844
34992
|
if (background === void 0) {
|
|
34845
34993
|
return `${OUTPUT_LABEL.ERROR} 'background' parameter is REQUIRED.`;
|
|
34846
34994
|
}
|
|
@@ -35174,9 +35322,9 @@ var createUpdateTodoTool = () => tool({
|
|
|
35174
35322
|
|
|
35175
35323
|
// src/tools/parallel/index.ts
|
|
35176
35324
|
init_shared();
|
|
35177
|
-
function createAsyncAgentTools(manager,
|
|
35325
|
+
function createAsyncAgentTools(manager, client2) {
|
|
35178
35326
|
return {
|
|
35179
|
-
[TOOL_NAMES.DELEGATE_TASK]: createDelegateTaskTool(manager,
|
|
35327
|
+
[TOOL_NAMES.DELEGATE_TASK]: createDelegateTaskTool(manager, client2),
|
|
35180
35328
|
[TOOL_NAMES.GET_TASK_RESULT]: createGetTaskResultTool(manager),
|
|
35181
35329
|
[TOOL_NAMES.LIST_TASKS]: createListTasksTool(manager),
|
|
35182
35330
|
[TOOL_NAMES.CANCEL_TASK]: createCancelTaskTool(manager),
|
|
@@ -36470,15 +36618,15 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
|
36470
36618
|
</auto_continue>`;
|
|
36471
36619
|
var STAGNATION_INTERVENTION = `
|
|
36472
36620
|
<system_intervention type="stagnation_detected">
|
|
36473
|
-
\u26A0\uFE0F
|
|
36474
|
-
|
|
36621
|
+
\u26A0\uFE0F **WARNING: STAGNATION DETECTED**
|
|
36622
|
+
No substantial progress has been detected for several turns. Simply "monitoring" or repeating the same actions is prohibited.
|
|
36475
36623
|
|
|
36476
|
-
|
|
36477
|
-
1.
|
|
36478
|
-
2.
|
|
36479
|
-
3.
|
|
36624
|
+
**Self-Diagnosis and Resolution Guidelines:**
|
|
36625
|
+
1. **Check Live Logs**: Use \`check_background_task\` or \`read_file\` to directly check the output logs of running tasks.
|
|
36626
|
+
2. **Process Health Diagnosis**: If a task appears to be a zombie or stuck, kill it immediately and restart it with more granular steps.
|
|
36627
|
+
3. **Strategy Pivot**: If the same approach keeps failing, use different tools or methods to reach the goal.
|
|
36480
36628
|
|
|
36481
|
-
|
|
36629
|
+
**Intervene proactively NOW. Do NOT wait.**
|
|
36482
36630
|
</system_intervention>`;
|
|
36483
36631
|
var CLEANUP_INSTRUCTION = `
|
|
36484
36632
|
<system_maintenance type="continuous_hygiene">
|
|
@@ -36827,8 +36975,27 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
|
|
|
36827
36975
|
// src/core/loop/verification.ts
|
|
36828
36976
|
init_shared();
|
|
36829
36977
|
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
36978
|
+
import { access, readFile as readFile5 } from "node:fs/promises";
|
|
36979
|
+
import { constants } from "node:fs";
|
|
36830
36980
|
import { join as join8 } from "node:path";
|
|
36831
36981
|
var CHECKLIST_FILE = CHECKLIST.FILE;
|
|
36982
|
+
var fileCache = /* @__PURE__ */ new Map();
|
|
36983
|
+
var CACHE_TTL_MS = 5e3;
|
|
36984
|
+
async function readFileWithCache(filePath) {
|
|
36985
|
+
const now = Date.now();
|
|
36986
|
+
const cached3 = fileCache.get(filePath);
|
|
36987
|
+
if (cached3 && now - cached3.timestamp < CACHE_TTL_MS) {
|
|
36988
|
+
return cached3.content;
|
|
36989
|
+
}
|
|
36990
|
+
try {
|
|
36991
|
+
await access(filePath, constants.R_OK);
|
|
36992
|
+
const content = await readFile5(filePath, "utf-8");
|
|
36993
|
+
fileCache.set(filePath, { content, timestamp: now });
|
|
36994
|
+
return content;
|
|
36995
|
+
} catch {
|
|
36996
|
+
return null;
|
|
36997
|
+
}
|
|
36998
|
+
}
|
|
36832
36999
|
function parseChecklistLine(line, currentCategory) {
|
|
36833
37000
|
const trimmedLine = line.trim();
|
|
36834
37001
|
const idMatch = trimmedLine.match(CHECKLIST_PATTERNS.ITEM_WITH_ID);
|
|
@@ -36904,6 +37071,7 @@ function readChecklist(directory) {
|
|
|
36904
37071
|
return [];
|
|
36905
37072
|
}
|
|
36906
37073
|
}
|
|
37074
|
+
var lastVerificationResult = /* @__PURE__ */ new Map();
|
|
36907
37075
|
function verifyChecklist(directory) {
|
|
36908
37076
|
const result = {
|
|
36909
37077
|
passed: false,
|
|
@@ -36943,6 +37111,40 @@ function verifyChecklist(directory) {
|
|
|
36943
37111
|
});
|
|
36944
37112
|
return result;
|
|
36945
37113
|
}
|
|
37114
|
+
async function verifyChecklistAsync(directory) {
|
|
37115
|
+
const result = {
|
|
37116
|
+
passed: false,
|
|
37117
|
+
totalItems: 0,
|
|
37118
|
+
completedItems: 0,
|
|
37119
|
+
incompleteItems: 0,
|
|
37120
|
+
progress: "0/0",
|
|
37121
|
+
incompleteList: [],
|
|
37122
|
+
errors: []
|
|
37123
|
+
};
|
|
37124
|
+
const filePath = join8(directory, CHECKLIST_FILE);
|
|
37125
|
+
const content = await readFileWithCache(filePath);
|
|
37126
|
+
if (!content) {
|
|
37127
|
+
result.errors.push(`Verification checklist not found at ${CHECKLIST_FILE}`);
|
|
37128
|
+
result.errors.push("Create checklist with at least: build, tests, and any environment-specific checks");
|
|
37129
|
+
return result;
|
|
37130
|
+
}
|
|
37131
|
+
const items = parseChecklist(content);
|
|
37132
|
+
if (items.length === 0) {
|
|
37133
|
+
result.errors.push("Verification checklist is empty");
|
|
37134
|
+
result.errors.push("Add verification items (build, tests, environment checks)");
|
|
37135
|
+
return result;
|
|
37136
|
+
}
|
|
37137
|
+
result.totalItems = items.length;
|
|
37138
|
+
result.completedItems = items.filter((i) => i.completed).length;
|
|
37139
|
+
result.incompleteItems = result.totalItems - result.completedItems;
|
|
37140
|
+
result.progress = `${result.completedItems}/${result.totalItems}`;
|
|
37141
|
+
result.incompleteList = items.filter((i) => !i.completed).map((i) => `[${CHECKLIST_CATEGORIES.LABELS[i.category]}] ${i.description}`);
|
|
37142
|
+
if (result.incompleteItems > 0) {
|
|
37143
|
+
result.errors.push(`Checklist incomplete: ${result.progress}`);
|
|
37144
|
+
}
|
|
37145
|
+
result.passed = result.incompleteItems === 0 && result.totalItems > 0;
|
|
37146
|
+
return result;
|
|
37147
|
+
}
|
|
36946
37148
|
var TODO_INCOMPLETE_PATTERN = /^[-*]\s*\[\s*\]/gm;
|
|
36947
37149
|
var TODO_COMPLETE_PATTERN = /^[-*]\s*\[[xX]\]/gm;
|
|
36948
37150
|
var SYNC_ISSUE_PATTERNS = [
|
|
@@ -36968,7 +37170,7 @@ function hasRealSyncIssues(content) {
|
|
|
36968
37170
|
});
|
|
36969
37171
|
return lines.length > 0;
|
|
36970
37172
|
}
|
|
36971
|
-
function
|
|
37173
|
+
function verifyMissionCompletionSync(directory) {
|
|
36972
37174
|
const result = {
|
|
36973
37175
|
passed: false,
|
|
36974
37176
|
todoComplete: false,
|
|
@@ -37049,6 +37251,89 @@ function verifyMissionCompletion(directory) {
|
|
|
37049
37251
|
});
|
|
37050
37252
|
return result;
|
|
37051
37253
|
}
|
|
37254
|
+
async function verifyMissionCompletionAsync(directory) {
|
|
37255
|
+
const result = {
|
|
37256
|
+
passed: false,
|
|
37257
|
+
todoComplete: false,
|
|
37258
|
+
todoProgress: "0/0",
|
|
37259
|
+
todoIncomplete: 0,
|
|
37260
|
+
syncIssuesEmpty: true,
|
|
37261
|
+
syncIssuesCount: 0,
|
|
37262
|
+
checklistComplete: false,
|
|
37263
|
+
checklistProgress: "0/0",
|
|
37264
|
+
errors: []
|
|
37265
|
+
};
|
|
37266
|
+
const checklistResult = await verifyChecklistAsync(directory);
|
|
37267
|
+
result.checklistComplete = checklistResult.passed;
|
|
37268
|
+
result.checklistProgress = checklistResult.progress;
|
|
37269
|
+
const hasChecklist = checklistResult.totalItems > 0;
|
|
37270
|
+
if (hasChecklist && !checklistResult.passed) {
|
|
37271
|
+
result.errors.push(`Verification checklist incomplete: ${checklistResult.progress}`);
|
|
37272
|
+
result.errors.push(...checklistResult.incompleteList.slice(0, 5).map((i) => ` - ${i}`));
|
|
37273
|
+
if (checklistResult.incompleteList.length > 5) {
|
|
37274
|
+
result.errors.push(` ... and ${checklistResult.incompleteList.length - 5} more`);
|
|
37275
|
+
}
|
|
37276
|
+
}
|
|
37277
|
+
const todoPath = join8(directory, PATHS.TODO);
|
|
37278
|
+
const todoContent = await readFileWithCache(todoPath);
|
|
37279
|
+
if (todoContent) {
|
|
37280
|
+
try {
|
|
37281
|
+
const incompleteCount = countMatches(todoContent, TODO_INCOMPLETE_PATTERN);
|
|
37282
|
+
const completeCount = countMatches(todoContent, TODO_COMPLETE_PATTERN);
|
|
37283
|
+
const total = incompleteCount + completeCount;
|
|
37284
|
+
result.todoIncomplete = incompleteCount;
|
|
37285
|
+
result.todoComplete = incompleteCount === 0 && total > 0;
|
|
37286
|
+
result.todoProgress = `${completeCount}/${total}`;
|
|
37287
|
+
if (!result.todoComplete && !hasChecklist) {
|
|
37288
|
+
if (total === 0) {
|
|
37289
|
+
result.errors.push("No TODO items found - create tasks first");
|
|
37290
|
+
} else {
|
|
37291
|
+
result.errors.push(
|
|
37292
|
+
`TODO incomplete: ${result.todoProgress} (${incompleteCount} remaining)`
|
|
37293
|
+
);
|
|
37294
|
+
}
|
|
37295
|
+
}
|
|
37296
|
+
} catch (error92) {
|
|
37297
|
+
result.errors.push(`Failed to read TODO: ${error92}`);
|
|
37298
|
+
}
|
|
37299
|
+
} else if (!hasChecklist) {
|
|
37300
|
+
result.errors.push(`TODO file not found at ${PATHS.TODO}`);
|
|
37301
|
+
}
|
|
37302
|
+
const syncPath = join8(directory, PATHS.SYNC_ISSUES);
|
|
37303
|
+
const syncContent = await readFileWithCache(syncPath);
|
|
37304
|
+
if (syncContent) {
|
|
37305
|
+
try {
|
|
37306
|
+
result.syncIssuesEmpty = !hasRealSyncIssues(syncContent);
|
|
37307
|
+
if (!result.syncIssuesEmpty) {
|
|
37308
|
+
const issueLines = syncContent.split("\n").filter(
|
|
37309
|
+
(l) => /^[-*]\s+\S/.test(l.trim()) || /ERROR|FAIL|CONFLICT/i.test(l)
|
|
37310
|
+
);
|
|
37311
|
+
result.syncIssuesCount = issueLines.length;
|
|
37312
|
+
result.errors.push(
|
|
37313
|
+
`Sync issues not resolved: ${result.syncIssuesCount} issue(s) remain`
|
|
37314
|
+
);
|
|
37315
|
+
}
|
|
37316
|
+
} catch (error92) {
|
|
37317
|
+
result.syncIssuesEmpty = true;
|
|
37318
|
+
}
|
|
37319
|
+
}
|
|
37320
|
+
if (hasChecklist) {
|
|
37321
|
+
result.passed = result.checklistComplete && result.syncIssuesEmpty;
|
|
37322
|
+
} else {
|
|
37323
|
+
result.passed = result.todoComplete && result.syncIssuesEmpty;
|
|
37324
|
+
}
|
|
37325
|
+
lastVerificationResult.set(directory, { result, timestamp: Date.now() });
|
|
37326
|
+
return result;
|
|
37327
|
+
}
|
|
37328
|
+
function verifyMissionCompletion(directory) {
|
|
37329
|
+
const cached3 = lastVerificationResult.get(directory);
|
|
37330
|
+
if (cached3 && Date.now() - cached3.timestamp < CACHE_TTL_MS) {
|
|
37331
|
+
return cached3.result;
|
|
37332
|
+
}
|
|
37333
|
+
const result = verifyMissionCompletionSync(directory);
|
|
37334
|
+
lastVerificationResult.set(directory, { result, timestamp: Date.now() });
|
|
37335
|
+
return result;
|
|
37336
|
+
}
|
|
37052
37337
|
function buildVerificationFailurePrompt(result) {
|
|
37053
37338
|
const errorList = result.errors.map((e) => `\u274C ${e}`).join("\n");
|
|
37054
37339
|
const hasChecklist = result.checklistProgress !== "0/0";
|
|
@@ -37817,11 +38102,18 @@ There was a temporary processing issue. Please continue from where you left off.
|
|
|
37817
38102
|
3. Continue execution
|
|
37818
38103
|
</action>
|
|
37819
38104
|
</recovery>`;
|
|
37820
|
-
async function handleSessionError(
|
|
38105
|
+
async function handleSessionError(client2, sessionID, error92, properties) {
|
|
37821
38106
|
const state2 = getState2(sessionID);
|
|
37822
38107
|
if (state2.isRecovering) {
|
|
37823
|
-
|
|
37824
|
-
|
|
38108
|
+
const RECOVERY_TIMEOUT_MS = 1e4;
|
|
38109
|
+
const elapsed = Date.now() - (state2.recoveryStartTime ?? 0);
|
|
38110
|
+
if (elapsed > RECOVERY_TIMEOUT_MS) {
|
|
38111
|
+
log("[session-recovery] Forcibly clearing stale recovery state", { sessionID, elapsed });
|
|
38112
|
+
state2.isRecovering = false;
|
|
38113
|
+
} else {
|
|
38114
|
+
log("[session-recovery] Already recovering, skipping", { sessionID });
|
|
38115
|
+
return false;
|
|
38116
|
+
}
|
|
37825
38117
|
}
|
|
37826
38118
|
const now = Date.now();
|
|
37827
38119
|
if (now - state2.lastErrorTime < BACKGROUND_TASK.RETRY_COOLDOWN_MS) {
|
|
@@ -37842,6 +38134,7 @@ async function handleSessionError(client, sessionID, error92, properties) {
|
|
|
37842
38134
|
return false;
|
|
37843
38135
|
}
|
|
37844
38136
|
state2.isRecovering = true;
|
|
38137
|
+
state2.recoveryStartTime = Date.now();
|
|
37845
38138
|
try {
|
|
37846
38139
|
let recoveryPrompt = null;
|
|
37847
38140
|
let toastMessage = null;
|
|
@@ -37867,23 +38160,19 @@ async function handleSessionError(client, sessionID, error92, properties) {
|
|
|
37867
38160
|
log("[session-recovery] Rate limit, waiting", { delay: action.delay });
|
|
37868
38161
|
await new Promise((r) => setTimeout(r, action.delay));
|
|
37869
38162
|
}
|
|
37870
|
-
state2.isRecovering = false;
|
|
37871
38163
|
return true;
|
|
37872
38164
|
case ERROR_TYPE.CONTEXT_OVERFLOW:
|
|
37873
38165
|
toastMessage = "Context Overflow - Consider compaction";
|
|
37874
|
-
state2.isRecovering = false;
|
|
37875
38166
|
return false;
|
|
37876
38167
|
case ERROR_TYPE.MESSAGE_ABORTED:
|
|
37877
38168
|
log("[session-recovery] Message aborted by user, not recovering", { sessionID });
|
|
37878
|
-
state2.isRecovering = false;
|
|
37879
38169
|
return false;
|
|
37880
38170
|
default:
|
|
37881
|
-
state2.isRecovering = false;
|
|
37882
38171
|
return false;
|
|
37883
38172
|
}
|
|
37884
38173
|
if (recoveryPrompt && toastMessage) {
|
|
37885
38174
|
presets_exports.errorRecovery(toastMessage);
|
|
37886
|
-
|
|
38175
|
+
client2.session.prompt({
|
|
37887
38176
|
path: { id: sessionID },
|
|
37888
38177
|
body: {
|
|
37889
38178
|
parts: [{ type: PART_TYPES.TEXT, text: recoveryPrompt }]
|
|
@@ -37892,15 +38181,15 @@ async function handleSessionError(client, sessionID, error92, properties) {
|
|
|
37892
38181
|
log("[session-recovery] Failed to inject recovery prompt", { sessionID, error: injectionError });
|
|
37893
38182
|
});
|
|
37894
38183
|
log("[session-recovery] Recovery prompt injected (async)", { sessionID, errorType });
|
|
37895
|
-
state2.isRecovering = false;
|
|
37896
38184
|
return true;
|
|
37897
38185
|
}
|
|
37898
|
-
state2.isRecovering = false;
|
|
37899
38186
|
return false;
|
|
37900
|
-
} catch (
|
|
37901
|
-
log("[session-recovery]
|
|
37902
|
-
state2.isRecovering = false;
|
|
38187
|
+
} catch (recoveryError) {
|
|
38188
|
+
log("[session-recovery] Recovery failed", { sessionID, error: recoveryError });
|
|
37903
38189
|
return false;
|
|
38190
|
+
} finally {
|
|
38191
|
+
state2.isRecovering = false;
|
|
38192
|
+
state2.recoveryStartTime = void 0;
|
|
37904
38193
|
}
|
|
37905
38194
|
}
|
|
37906
38195
|
function markRecoveryComplete(sessionID) {
|
|
@@ -37961,9 +38250,9 @@ function hasRunningBackgroundTasks(parentSessionID) {
|
|
|
37961
38250
|
return false;
|
|
37962
38251
|
}
|
|
37963
38252
|
}
|
|
37964
|
-
async function showCountdownToast(
|
|
38253
|
+
async function showCountdownToast(client2, secondsRemaining, incompleteCount) {
|
|
37965
38254
|
try {
|
|
37966
|
-
const tuiClient2 =
|
|
38255
|
+
const tuiClient2 = client2;
|
|
37967
38256
|
if (tuiClient2.tui?.showToast) {
|
|
37968
38257
|
await tuiClient2.tui.showToast({
|
|
37969
38258
|
body: {
|
|
@@ -37977,7 +38266,7 @@ async function showCountdownToast(client, secondsRemaining, incompleteCount) {
|
|
|
37977
38266
|
} catch {
|
|
37978
38267
|
}
|
|
37979
38268
|
}
|
|
37980
|
-
async function injectContinuation(
|
|
38269
|
+
async function injectContinuation(client2, directory, sessionID, todos) {
|
|
37981
38270
|
const state2 = getState3(sessionID);
|
|
37982
38271
|
if (state2.isAborting) {
|
|
37983
38272
|
log("[todo-continuation] Skipped: user is aborting", { sessionID });
|
|
@@ -38007,24 +38296,22 @@ async function injectContinuation(client, directory, sessionID, todos) {
|
|
|
38007
38296
|
return;
|
|
38008
38297
|
}
|
|
38009
38298
|
try {
|
|
38010
|
-
|
|
38299
|
+
await client2.session.prompt({
|
|
38011
38300
|
path: { id: sessionID },
|
|
38012
38301
|
body: {
|
|
38013
38302
|
parts: [{ type: PART_TYPES.TEXT, text: prompt }]
|
|
38014
38303
|
}
|
|
38015
|
-
}).catch((error92) => {
|
|
38016
|
-
log("[todo-continuation] Failed to inject continuation", { sessionID, error: error92 });
|
|
38017
38304
|
});
|
|
38018
|
-
log("[todo-continuation] Injected continuation prompt
|
|
38305
|
+
log("[todo-continuation] Injected continuation prompt", {
|
|
38019
38306
|
sessionID,
|
|
38020
38307
|
incompleteCount: getIncompleteCount(todos),
|
|
38021
38308
|
progress: formatProgress(todos)
|
|
38022
38309
|
});
|
|
38023
38310
|
} catch (error92) {
|
|
38024
|
-
log("[todo-continuation] Failed to
|
|
38311
|
+
log("[todo-continuation] Failed to inject continuation", { sessionID, error: error92 });
|
|
38025
38312
|
}
|
|
38026
38313
|
}
|
|
38027
|
-
async function handleSessionIdle(
|
|
38314
|
+
async function handleSessionIdle(client2, directory, sessionID, mainSessionID) {
|
|
38028
38315
|
const state2 = getState3(sessionID);
|
|
38029
38316
|
const now = Date.now();
|
|
38030
38317
|
if (state2.lastIdleTime && now - state2.lastIdleTime < MIN_TIME_BETWEEN_CONTINUATIONS_MS) {
|
|
@@ -38054,18 +38341,23 @@ async function handleSessionIdle(client, directory, sessionID, mainSessionID) {
|
|
|
38054
38341
|
log("[todo-continuation] Skipped: background tasks running", { sessionID });
|
|
38055
38342
|
return;
|
|
38056
38343
|
}
|
|
38344
|
+
if (hasContinuationLock(sessionID)) {
|
|
38345
|
+
log("[todo-continuation] Mission loop has priority, skipping");
|
|
38346
|
+
return;
|
|
38347
|
+
}
|
|
38057
38348
|
let todos = [];
|
|
38058
38349
|
try {
|
|
38059
|
-
const response = await
|
|
38350
|
+
const response = await client2.session.todo({ path: { id: sessionID } });
|
|
38060
38351
|
todos = parseTodos(response.data ?? response);
|
|
38061
38352
|
} catch (error92) {
|
|
38062
38353
|
log("[todo-continuation] Failed to fetch todos", { sessionID, error: error92 });
|
|
38354
|
+
cancelCountdown(sessionID);
|
|
38063
38355
|
return;
|
|
38064
38356
|
}
|
|
38065
38357
|
const hasBuiltinWork = hasRemainingWork(todos);
|
|
38066
38358
|
let hasFileWork = false;
|
|
38067
38359
|
try {
|
|
38068
|
-
const verification =
|
|
38360
|
+
const verification = await verifyMissionCompletionAsync(directory);
|
|
38069
38361
|
hasFileWork = !verification.passed && (verification.todoIncomplete > 0 || verification.checklistProgress !== "0/0" && !verification.checklistComplete);
|
|
38070
38362
|
} catch (err) {
|
|
38071
38363
|
log("[todo-continuation] Failed to check file-based todos", err);
|
|
@@ -38082,26 +38374,34 @@ async function handleSessionIdle(client, directory, sessionID, mainSessionID) {
|
|
|
38082
38374
|
incompleteCount,
|
|
38083
38375
|
nextPending: nextPending?.id
|
|
38084
38376
|
});
|
|
38085
|
-
await showCountdownToast(
|
|
38377
|
+
await showCountdownToast(client2, COUNTDOWN_SECONDS, incompleteCount);
|
|
38086
38378
|
state2.countdownStartedAt = now;
|
|
38087
38379
|
state2.countdownTimer = setTimeout(async () => {
|
|
38088
38380
|
cancelCountdown(sessionID);
|
|
38381
|
+
if (!tryAcquireContinuationLock(sessionID, "todo-continuation")) {
|
|
38382
|
+
log("[todo-continuation] Failed to acquire lock, skipping");
|
|
38383
|
+
return;
|
|
38384
|
+
}
|
|
38089
38385
|
try {
|
|
38090
|
-
const freshResponse = await client.session.todo({ path: { id: sessionID } });
|
|
38091
|
-
const freshTodos = parseTodos(freshResponse.data ?? freshResponse);
|
|
38092
|
-
let freshFileWork = false;
|
|
38093
38386
|
try {
|
|
38094
|
-
const
|
|
38095
|
-
|
|
38387
|
+
const freshResponse = await client2.session.todo({ path: { id: sessionID } });
|
|
38388
|
+
const freshTodos = parseTodos(freshResponse.data ?? freshResponse);
|
|
38389
|
+
let freshFileWork = false;
|
|
38390
|
+
try {
|
|
38391
|
+
const v = await verifyMissionCompletionAsync(directory);
|
|
38392
|
+
freshFileWork = !v.passed && (v.todoIncomplete > 0 || v.checklistProgress !== "0/0" && !v.checklistComplete);
|
|
38393
|
+
} catch {
|
|
38394
|
+
}
|
|
38395
|
+
if (hasRemainingWork(freshTodos) || freshFileWork) {
|
|
38396
|
+
await injectContinuation(client2, directory, sessionID, freshTodos);
|
|
38397
|
+
} else {
|
|
38398
|
+
log("[todo-continuation] All work completed during countdown", { sessionID });
|
|
38399
|
+
}
|
|
38096
38400
|
} catch {
|
|
38401
|
+
log("[todo-continuation] Failed to re-fetch todos for continuation", { sessionID });
|
|
38097
38402
|
}
|
|
38098
|
-
|
|
38099
|
-
|
|
38100
|
-
} else {
|
|
38101
|
-
log("[todo-continuation] All work completed during countdown", { sessionID });
|
|
38102
|
-
}
|
|
38103
|
-
} catch {
|
|
38104
|
-
log("[todo-continuation] Failed to re-fetch todos for continuation", { sessionID });
|
|
38403
|
+
} finally {
|
|
38404
|
+
releaseContinuationLock(sessionID);
|
|
38105
38405
|
}
|
|
38106
38406
|
}, COUNTDOWN_SECONDS * TIME.SECOND);
|
|
38107
38407
|
}
|
|
@@ -38134,6 +38434,9 @@ function cleanupSession2(sessionID) {
|
|
|
38134
38434
|
cancelCountdown(sessionID);
|
|
38135
38435
|
sessionStates2.delete(sessionID);
|
|
38136
38436
|
}
|
|
38437
|
+
function hasPendingContinuation(sessionID) {
|
|
38438
|
+
return !!sessionStates2.get(sessionID)?.countdownTimer;
|
|
38439
|
+
}
|
|
38137
38440
|
|
|
38138
38441
|
// src/hooks/custom/user-activity.ts
|
|
38139
38442
|
var UserActivityHook = class {
|
|
@@ -38447,8 +38750,8 @@ var TodoSyncService = class {
|
|
|
38447
38750
|
updateTimeout = null;
|
|
38448
38751
|
watcher = null;
|
|
38449
38752
|
activeSessions = /* @__PURE__ */ new Set();
|
|
38450
|
-
constructor(
|
|
38451
|
-
this.client =
|
|
38753
|
+
constructor(client2, directory) {
|
|
38754
|
+
this.client = client2;
|
|
38452
38755
|
this.directory = directory;
|
|
38453
38756
|
this.todoPath = path9.join(this.directory, PATHS.TODO);
|
|
38454
38757
|
}
|
|
@@ -38857,9 +39160,9 @@ function hasRunningBackgroundTasks2(parentSessionID) {
|
|
|
38857
39160
|
return false;
|
|
38858
39161
|
}
|
|
38859
39162
|
}
|
|
38860
|
-
async function showCompletedToast(
|
|
39163
|
+
async function showCompletedToast(client2, state2) {
|
|
38861
39164
|
try {
|
|
38862
|
-
const tuiClient2 =
|
|
39165
|
+
const tuiClient2 = client2;
|
|
38863
39166
|
if (tuiClient2.tui?.showToast) {
|
|
38864
39167
|
await tuiClient2.tui.showToast({
|
|
38865
39168
|
body: {
|
|
@@ -38873,14 +39176,14 @@ async function showCompletedToast(client, state2) {
|
|
|
38873
39176
|
} catch {
|
|
38874
39177
|
}
|
|
38875
39178
|
}
|
|
38876
|
-
async function injectContinuation2(
|
|
39179
|
+
async function injectContinuation2(client2, directory, sessionID, loopState, customPrompt) {
|
|
38877
39180
|
const handlerState = getState4(sessionID);
|
|
38878
39181
|
if (handlerState.isAborting) return;
|
|
38879
39182
|
if (hasRunningBackgroundTasks2(sessionID)) return;
|
|
38880
39183
|
if (isSessionRecovering(sessionID)) return;
|
|
38881
|
-
const verification =
|
|
39184
|
+
const verification = await verifyMissionCompletionAsync(directory);
|
|
38882
39185
|
if (verification.passed) {
|
|
38883
|
-
await handleMissionComplete(
|
|
39186
|
+
await handleMissionComplete(client2, directory, loopState);
|
|
38884
39187
|
return;
|
|
38885
39188
|
}
|
|
38886
39189
|
const summary = buildVerificationSummary(verification);
|
|
@@ -38891,21 +39194,20 @@ async function injectContinuation2(client, directory, sessionID, loopState, cust
|
|
|
38891
39194
|
${prompt}`;
|
|
38892
39195
|
}
|
|
38893
39196
|
try {
|
|
38894
|
-
|
|
39197
|
+
await client2.session.prompt({
|
|
38895
39198
|
path: { id: sessionID },
|
|
38896
39199
|
body: {
|
|
38897
39200
|
parts: [{ type: PART_TYPES.TEXT, text: prompt }]
|
|
38898
39201
|
}
|
|
38899
|
-
}).catch((error92) => {
|
|
38900
|
-
log("[mission-loop-handler] Failed to inject continuation prompt", { sessionID, error: error92 });
|
|
38901
39202
|
});
|
|
38902
|
-
} catch {
|
|
39203
|
+
} catch (error92) {
|
|
39204
|
+
log("[mission-loop-handler] Failed to inject continuation prompt", { sessionID, error: error92 });
|
|
38903
39205
|
}
|
|
38904
39206
|
}
|
|
38905
|
-
async function handleMissionComplete(
|
|
39207
|
+
async function handleMissionComplete(client2, directory, loopState) {
|
|
38906
39208
|
const cleared = clearLoopState(directory);
|
|
38907
39209
|
if (cleared) {
|
|
38908
|
-
await showCompletedToast(
|
|
39210
|
+
await showCompletedToast(client2, loopState);
|
|
38909
39211
|
await sendMissionCompleteNotification(loopState);
|
|
38910
39212
|
}
|
|
38911
39213
|
}
|
|
@@ -38924,7 +39226,7 @@ async function sendMissionCompleteNotification(loopState) {
|
|
|
38924
39226
|
} catch {
|
|
38925
39227
|
}
|
|
38926
39228
|
}
|
|
38927
|
-
async function handleMissionIdle(
|
|
39229
|
+
async function handleMissionIdle(client2, directory, sessionID, mainSessionID) {
|
|
38928
39230
|
const handlerState = getState4(sessionID);
|
|
38929
39231
|
const now = Date.now();
|
|
38930
39232
|
if (handlerState.lastCheckTime && now - handlerState.lastCheckTime < LOOP.MIN_TIME_BETWEEN_CHECKS_MS) {
|
|
@@ -38944,10 +39246,10 @@ async function handleMissionIdle(client, directory, sessionID, mainSessionID) {
|
|
|
38944
39246
|
if (loopState.sessionID !== sessionID) {
|
|
38945
39247
|
return;
|
|
38946
39248
|
}
|
|
38947
|
-
const verification =
|
|
39249
|
+
const verification = await verifyMissionCompletionAsync(directory);
|
|
38948
39250
|
if (verification.passed) {
|
|
38949
39251
|
log(`[${MISSION_CONTROL.LOG_SOURCE}-handler] Verification passed for ${sessionID}. Completion confirmed.`);
|
|
38950
|
-
await handleMissionComplete(
|
|
39252
|
+
await handleMissionComplete(client2, directory, loopState);
|
|
38951
39253
|
return;
|
|
38952
39254
|
}
|
|
38953
39255
|
const currentProgress = verification.todoProgress;
|
|
@@ -38970,7 +39272,18 @@ async function handleMissionIdle(client, directory, sessionID, mainSessionID) {
|
|
|
38970
39272
|
const countdownMsg = isStagnant ? "Stagnation Detected! Intervening..." : `Continuing in ${MISSION_CONTROL.DEFAULT_COUNTDOWN_SECONDS}s... (iteration ${newState.iteration}/${newState.maxIterations})`;
|
|
38971
39273
|
handlerState.countdownTimer = setTimeout(async () => {
|
|
38972
39274
|
cancelCountdown2(sessionID);
|
|
38973
|
-
|
|
39275
|
+
if (hasPendingContinuation(sessionID)) {
|
|
39276
|
+
log("[mission-loop-handler] Todo continuation pending, deferring");
|
|
39277
|
+
return;
|
|
39278
|
+
}
|
|
39279
|
+
if (!tryAcquireContinuationLock(sessionID, "mission-loop")) {
|
|
39280
|
+
return;
|
|
39281
|
+
}
|
|
39282
|
+
try {
|
|
39283
|
+
await injectContinuation2(client2, directory, sessionID, newState, isStagnant ? STAGNATION_INTERVENTION : void 0);
|
|
39284
|
+
} finally {
|
|
39285
|
+
releaseContinuationLock(sessionID);
|
|
39286
|
+
}
|
|
38974
39287
|
}, MISSION_CONTROL.DEFAULT_COUNTDOWN_SECONDS * 1e3);
|
|
38975
39288
|
}
|
|
38976
39289
|
function handleUserMessage2(sessionID) {
|
|
@@ -38992,7 +39305,7 @@ function cleanupSession3(sessionID) {
|
|
|
38992
39305
|
// src/plugin-handlers/event-handler.ts
|
|
38993
39306
|
init_shared();
|
|
38994
39307
|
function createEventHandler(ctx) {
|
|
38995
|
-
const { client, directory, sessions, state: state2 } = ctx;
|
|
39308
|
+
const { client: client2, directory, sessions, state: state2 } = ctx;
|
|
38996
39309
|
return async (input) => {
|
|
38997
39310
|
const { event } = input;
|
|
38998
39311
|
try {
|
|
@@ -39029,7 +39342,7 @@ function createEventHandler(ctx) {
|
|
|
39029
39342
|
}
|
|
39030
39343
|
if (sessionID && error92) {
|
|
39031
39344
|
const recovered = await handleSessionError(
|
|
39032
|
-
|
|
39345
|
+
client2,
|
|
39033
39346
|
sessionID,
|
|
39034
39347
|
error92,
|
|
39035
39348
|
event.properties
|
|
@@ -39067,7 +39380,7 @@ function createEventHandler(ctx) {
|
|
|
39067
39380
|
if (session?.active) {
|
|
39068
39381
|
if (isLoopActive(directory, sessionID)) {
|
|
39069
39382
|
await handleMissionIdle(
|
|
39070
|
-
|
|
39383
|
+
client2,
|
|
39071
39384
|
directory,
|
|
39072
39385
|
sessionID,
|
|
39073
39386
|
sessionID
|
|
@@ -39075,7 +39388,7 @@ function createEventHandler(ctx) {
|
|
|
39075
39388
|
});
|
|
39076
39389
|
} else {
|
|
39077
39390
|
await handleSessionIdle(
|
|
39078
|
-
|
|
39391
|
+
client2,
|
|
39079
39392
|
directory,
|
|
39080
39393
|
sessionID,
|
|
39081
39394
|
sessionID
|
|
@@ -39133,7 +39446,7 @@ function createToolExecuteAfterHandler(ctx) {
|
|
|
39133
39446
|
// src/plugin-handlers/assistant-done-handler.ts
|
|
39134
39447
|
init_shared();
|
|
39135
39448
|
function createAssistantDoneHandler(ctx) {
|
|
39136
|
-
const { client, directory, sessions } = ctx;
|
|
39449
|
+
const { client: client2, directory, sessions } = ctx;
|
|
39137
39450
|
const hooks = HookRegistry.getInstance();
|
|
39138
39451
|
return async (assistantInput, assistantOutput) => {
|
|
39139
39452
|
const sessionID = assistantInput.sessionID;
|
|
@@ -39157,12 +39470,12 @@ function createAssistantDoneHandler(ctx) {
|
|
|
39157
39470
|
session.timestamp = now;
|
|
39158
39471
|
session.lastStepTime = now;
|
|
39159
39472
|
try {
|
|
39160
|
-
if (
|
|
39473
|
+
if (client2?.session?.prompt) {
|
|
39161
39474
|
const parts2 = result.prompts.map((p) => ({
|
|
39162
39475
|
type: PART_TYPES.TEXT,
|
|
39163
39476
|
text: p
|
|
39164
39477
|
}));
|
|
39165
|
-
|
|
39478
|
+
client2.session.prompt({
|
|
39166
39479
|
path: { id: sessionID },
|
|
39167
39480
|
body: { parts: parts2 }
|
|
39168
39481
|
}).catch((error92) => {
|
|
@@ -39322,24 +39635,24 @@ Use \`delegate_task\` with background=true for parallel work.
|
|
|
39322
39635
|
var require2 = createRequire(import.meta.url);
|
|
39323
39636
|
var { version: PLUGIN_VERSION } = require2("../package.json");
|
|
39324
39637
|
var OrchestratorPlugin = async (input) => {
|
|
39325
|
-
const { directory, client } = input;
|
|
39638
|
+
const { directory, client: client2 } = input;
|
|
39326
39639
|
initializeHooks();
|
|
39327
|
-
initToastClient(
|
|
39328
|
-
const taskToastManager = initTaskToastManager(
|
|
39640
|
+
initToastClient(client2);
|
|
39641
|
+
const taskToastManager = initTaskToastManager(client2);
|
|
39329
39642
|
const sessions = /* @__PURE__ */ new Map();
|
|
39330
|
-
const parallelAgentManager2 = ParallelAgentManager.getInstance(
|
|
39331
|
-
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2,
|
|
39643
|
+
const parallelAgentManager2 = ParallelAgentManager.getInstance(client2, directory);
|
|
39644
|
+
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client2);
|
|
39332
39645
|
const pluginManager = PluginManager.getInstance();
|
|
39333
39646
|
await pluginManager.initialize(directory);
|
|
39334
39647
|
const dynamicTools = pluginManager.getDynamicTools();
|
|
39335
39648
|
taskToastManager.setConcurrencyController(parallelAgentManager2.getConcurrency());
|
|
39336
|
-
const todoSync = new TodoSyncService(
|
|
39649
|
+
const todoSync = new TodoSyncService(client2, directory);
|
|
39337
39650
|
await todoSync.start();
|
|
39338
39651
|
taskToastManager.setTodoSync(todoSync);
|
|
39339
39652
|
const cleanupScheduler = new CleanupScheduler(directory);
|
|
39340
39653
|
cleanupScheduler.start();
|
|
39341
39654
|
const handlerContext = {
|
|
39342
|
-
client,
|
|
39655
|
+
client: client2,
|
|
39343
39656
|
directory,
|
|
39344
39657
|
sessions,
|
|
39345
39658
|
state
|
|
@@ -21,5 +21,5 @@ export declare const MISSION_MESSAGES: {
|
|
|
21
21
|
};
|
|
22
22
|
export declare const COMPACTION_PROMPT: string;
|
|
23
23
|
export declare const CONTINUE_INSTRUCTION: string;
|
|
24
|
-
export declare const STAGNATION_INTERVENTION = "\n<system_intervention type=\"stagnation_detected\">\n\u26A0\uFE0F
|
|
24
|
+
export declare const STAGNATION_INTERVENTION = "\n<system_intervention type=\"stagnation_detected\">\n\u26A0\uFE0F **WARNING: STAGNATION DETECTED**\nNo substantial progress has been detected for several turns. Simply \"monitoring\" or repeating the same actions is prohibited.\n\n**Self-Diagnosis and Resolution Guidelines:**\n1. **Check Live Logs**: Use `check_background_task` or `read_file` to directly check the output logs of running tasks.\n2. **Process Health Diagnosis**: If a task appears to be a zombie or stuck, kill it immediately and restart it with more granular steps.\n3. **Strategy Pivot**: If the same approach keeps failing, use different tools or methods to reach the goal.\n\n**Intervene proactively NOW. Do NOT wait.**\n</system_intervention>";
|
|
25
25
|
export declare const CLEANUP_INSTRUCTION = "\n<system_maintenance type=\"continuous_hygiene\">\n\uD83E\uDDF9 **DOCUMENTATION & STATE HYGIENE (Iteration %ITER%)**\nYou must maintain a pristine workspace. **As part of your move**, perform these checks:\n\n1. **Relevance Assessment**:\n - Review active documents (`.opencode/*.md`). Are they needed for the *current* objective?\n - If a file represents a solved problem or obsolete context, **Archive it** to `.opencode/archive/` or delete it.\n\n2. **Synchronization**:\n - Verify `TODO.md` matches the actual code state. Mark completed items immediately.\n - Check `sync-issues.md`. If issues are resolved, remove them.\n\n3. **Context Optimization**:\n - If `work-log.md` is getting noisy, summarize key decisions into `summary.md` and truncate the log.\n - Keep context lightweight.\n\n**Rule**: A cluttered workspace leads to hallucinations. Clean as you go.\n</system_maintenance>\n";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "opencode-orchestrator",
|
|
3
3
|
"displayName": "OpenCode Orchestrator",
|
|
4
4
|
"description": "Distributed Cognitive Architecture for OpenCode. Turns simple prompts into specialized multi-agent workflows (Planner, Coder, Reviewer).",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.15",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|