opencode-orchestrator 1.2.11 → 1.2.14
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/index.d.ts +1 -0
- package/dist/core/agents/manager/task-poller.d.ts +1 -0
- package/dist/core/cleanup/cleanup-scheduler.d.ts +11 -0
- package/dist/core/loop/verification.d.ts +0 -44
- package/dist/core/notification/task-toast-manager.d.ts +6 -0
- package/dist/core/sync/todo-parser.d.ts +5 -0
- package/dist/core/sync/todo-sync-service.d.ts +35 -0
- package/dist/index.js +346 -49
- package/dist/shared/core/constants/memory-hooks.d.ts +4 -0
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ export declare class TaskPoller {
|
|
|
15
15
|
private pruneExpiredTasks;
|
|
16
16
|
private onTaskComplete?;
|
|
17
17
|
private pollingInterval?;
|
|
18
|
+
private messageCache;
|
|
18
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);
|
|
19
20
|
start(): void;
|
|
20
21
|
stop(): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class CleanupScheduler {
|
|
2
|
+
private intervals;
|
|
3
|
+
private directory;
|
|
4
|
+
constructor(directory: string);
|
|
5
|
+
start(): void;
|
|
6
|
+
private schedule;
|
|
7
|
+
stop(): void;
|
|
8
|
+
compactWAL(): Promise<void>;
|
|
9
|
+
cleanDocs(): Promise<void>;
|
|
10
|
+
rotateHistory(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -5,62 +5,18 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The LLM creates and checks items in .opencode/verification-checklist.md
|
|
7
7
|
* The hook system verifies all items are checked before allowing CONCLUDE.
|
|
8
|
-
*
|
|
9
|
-
* This approach:
|
|
10
|
-
* 1. LLM discovers environment and creates appropriate checklist items
|
|
11
|
-
* 2. LLM executes and marks items as complete
|
|
12
|
-
* 3. Hook verifies all items are checked (hard gate)
|
|
13
8
|
*/
|
|
14
9
|
import { type ChecklistCategory, type ChecklistItem, type ChecklistVerificationResult, type VerificationResult } from "../../shared/index.js";
|
|
15
10
|
export type { ChecklistItem, ChecklistCategory, ChecklistVerificationResult, VerificationResult };
|
|
16
|
-
/** Path to the verification checklist file (re-export for convenience) */
|
|
17
11
|
export declare const CHECKLIST_FILE: ".opencode/verification-checklist.md";
|
|
18
|
-
/**
|
|
19
|
-
* Parse checklist from markdown content
|
|
20
|
-
*/
|
|
21
12
|
export declare function parseChecklist(content: string): ChecklistItem[];
|
|
22
|
-
/**
|
|
23
|
-
* Read checklist from file
|
|
24
|
-
*/
|
|
25
13
|
export declare function readChecklist(directory: string): ChecklistItem[];
|
|
26
|
-
/**
|
|
27
|
-
* Verify that all checklist items are complete
|
|
28
|
-
*/
|
|
29
14
|
export declare function verifyChecklist(directory: string): ChecklistVerificationResult;
|
|
30
|
-
/**
|
|
31
|
-
* Quick check if checklist exists and has items
|
|
32
|
-
*/
|
|
33
15
|
export declare function hasValidChecklist(directory: string): boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Get checklist summary for display
|
|
36
|
-
*/
|
|
37
16
|
export declare function getChecklistSummary(directory: string): string;
|
|
38
|
-
/**
|
|
39
|
-
* Build prompt for when checklist verification fails
|
|
40
|
-
*/
|
|
41
17
|
export declare function buildChecklistFailurePrompt(result: ChecklistVerificationResult): string;
|
|
42
|
-
/**
|
|
43
|
-
* Build checklist creation prompt (for inclusion in agent prompts)
|
|
44
|
-
*/
|
|
45
18
|
export declare function getChecklistCreationInstructions(): string;
|
|
46
|
-
/**
|
|
47
|
-
* Verify mission completion conditions
|
|
48
|
-
*
|
|
49
|
-
* Checks (in order):
|
|
50
|
-
* 1. Verification checklist (primary - if exists)
|
|
51
|
-
* 2. TODO completion rate (fallback)
|
|
52
|
-
* 3. Sync issues (always checked)
|
|
53
|
-
*/
|
|
54
19
|
export declare function verifyMissionCompletion(directory: string): VerificationResult;
|
|
55
|
-
/**
|
|
56
|
-
* Build prompt for when conclusion is rejected due to verification failure
|
|
57
|
-
*/
|
|
58
20
|
export declare function buildVerificationFailurePrompt(result: VerificationResult): string;
|
|
59
|
-
/**
|
|
60
|
-
* Build prompt for when TODO is incomplete
|
|
61
|
-
*/
|
|
62
21
|
export declare function buildTodoIncompletePrompt(result: VerificationResult): string;
|
|
63
|
-
/**
|
|
64
|
-
* Build a concise status summary for logs
|
|
65
|
-
*/
|
|
66
22
|
export declare function buildVerificationSummary(result: VerificationResult): string;
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
14
14
|
import type { ConcurrencyController } from "../agents/concurrency.js";
|
|
15
15
|
import { type TaskStatus, type TrackedTask, type TaskCompletionInfo } from "../../shared/index.js";
|
|
16
|
+
import type { TodoSyncService } from "../sync/todo-sync-service.js";
|
|
16
17
|
export type { TaskStatus, TrackedTask, TaskCompletionInfo } from "../../shared/index.js";
|
|
17
18
|
type OpencodeClient = PluginInput["client"];
|
|
18
19
|
export declare class TaskToastManager {
|
|
19
20
|
private tasks;
|
|
20
21
|
private client;
|
|
21
22
|
private concurrency;
|
|
23
|
+
private todoSync;
|
|
22
24
|
/**
|
|
23
25
|
* Initialize the manager with OpenCode client
|
|
24
26
|
*/
|
|
@@ -27,6 +29,10 @@ export declare class TaskToastManager {
|
|
|
27
29
|
* Set concurrency controller (can be set after init)
|
|
28
30
|
*/
|
|
29
31
|
setConcurrencyController(concurrency: ConcurrencyController): void;
|
|
32
|
+
/**
|
|
33
|
+
* Set TodoSyncService for TUI status synchronization
|
|
34
|
+
*/
|
|
35
|
+
setTodoSync(service: TodoSyncService): void;
|
|
30
36
|
/**
|
|
31
37
|
* Add a new task and show consolidated toast
|
|
32
38
|
*/
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
type OpencodeClient = PluginInput["client"];
|
|
3
|
+
interface TrackedTaskTodo {
|
|
4
|
+
id: string;
|
|
5
|
+
description: string;
|
|
6
|
+
status: string;
|
|
7
|
+
agent: string;
|
|
8
|
+
isBackground: boolean;
|
|
9
|
+
parentSessionID?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class TodoSyncService {
|
|
12
|
+
private client;
|
|
13
|
+
private directory;
|
|
14
|
+
private todoPath;
|
|
15
|
+
private fileTodos;
|
|
16
|
+
private taskTodos;
|
|
17
|
+
private updateTimeout;
|
|
18
|
+
private watcher;
|
|
19
|
+
private activeSessions;
|
|
20
|
+
constructor(client: OpencodeClient, directory: string);
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
registerSession(sessionID: string): void;
|
|
23
|
+
unregisterSession(sessionID: string): void;
|
|
24
|
+
private reloadFileTodos;
|
|
25
|
+
/**
|
|
26
|
+
* Called by TaskToastManager when tasks change
|
|
27
|
+
*/
|
|
28
|
+
updateTaskStatus(task: TrackedTaskTodo): void;
|
|
29
|
+
removeTask(taskId: string): void;
|
|
30
|
+
private broadcastUpdate;
|
|
31
|
+
private scheduleUpdate;
|
|
32
|
+
private sendTodosToSession;
|
|
33
|
+
stop(): void;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -417,6 +417,10 @@ var init_memory_hooks = __esm({
|
|
|
417
417
|
COMPLETED: "completed",
|
|
418
418
|
PROGRESS: "progress",
|
|
419
419
|
FAILED: "failed"
|
|
420
|
+
},
|
|
421
|
+
PREFIX: {
|
|
422
|
+
TASK: "task-",
|
|
423
|
+
FILE: "file-task-"
|
|
420
424
|
}
|
|
421
425
|
};
|
|
422
426
|
}
|
|
@@ -5978,15 +5982,15 @@ function mergeDefs(...defs) {
|
|
|
5978
5982
|
function cloneDef(schema) {
|
|
5979
5983
|
return mergeDefs(schema._zod.def);
|
|
5980
5984
|
}
|
|
5981
|
-
function getElementAtPath(obj,
|
|
5982
|
-
if (!
|
|
5985
|
+
function getElementAtPath(obj, path12) {
|
|
5986
|
+
if (!path12)
|
|
5983
5987
|
return obj;
|
|
5984
|
-
return
|
|
5988
|
+
return path12.reduce((acc, key) => acc?.[key], obj);
|
|
5985
5989
|
}
|
|
5986
5990
|
function promiseAllObject(promisesObj) {
|
|
5987
5991
|
const keys = Object.keys(promisesObj);
|
|
5988
|
-
const
|
|
5989
|
-
return Promise.all(
|
|
5992
|
+
const promises4 = keys.map((key) => promisesObj[key]);
|
|
5993
|
+
return Promise.all(promises4).then((results) => {
|
|
5990
5994
|
const resolvedObj = {};
|
|
5991
5995
|
for (let i = 0; i < keys.length; i++) {
|
|
5992
5996
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -6342,11 +6346,11 @@ function aborted(x, startIndex = 0) {
|
|
|
6342
6346
|
}
|
|
6343
6347
|
return false;
|
|
6344
6348
|
}
|
|
6345
|
-
function prefixIssues(
|
|
6349
|
+
function prefixIssues(path12, issues) {
|
|
6346
6350
|
return issues.map((iss) => {
|
|
6347
6351
|
var _a2;
|
|
6348
6352
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
6349
|
-
iss.path.unshift(
|
|
6353
|
+
iss.path.unshift(path12);
|
|
6350
6354
|
return iss;
|
|
6351
6355
|
});
|
|
6352
6356
|
}
|
|
@@ -6514,7 +6518,7 @@ function treeifyError(error92, _mapper) {
|
|
|
6514
6518
|
return issue3.message;
|
|
6515
6519
|
};
|
|
6516
6520
|
const result = { errors: [] };
|
|
6517
|
-
const processError = (error93,
|
|
6521
|
+
const processError = (error93, path12 = []) => {
|
|
6518
6522
|
var _a2, _b;
|
|
6519
6523
|
for (const issue3 of error93.issues) {
|
|
6520
6524
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -6524,7 +6528,7 @@ function treeifyError(error92, _mapper) {
|
|
|
6524
6528
|
} else if (issue3.code === "invalid_element") {
|
|
6525
6529
|
processError({ issues: issue3.issues }, issue3.path);
|
|
6526
6530
|
} else {
|
|
6527
|
-
const fullpath = [...
|
|
6531
|
+
const fullpath = [...path12, ...issue3.path];
|
|
6528
6532
|
if (fullpath.length === 0) {
|
|
6529
6533
|
result.errors.push(mapper(issue3));
|
|
6530
6534
|
continue;
|
|
@@ -6556,8 +6560,8 @@ function treeifyError(error92, _mapper) {
|
|
|
6556
6560
|
}
|
|
6557
6561
|
function toDotPath(_path) {
|
|
6558
6562
|
const segs = [];
|
|
6559
|
-
const
|
|
6560
|
-
for (const seg of
|
|
6563
|
+
const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
6564
|
+
for (const seg of path12) {
|
|
6561
6565
|
if (typeof seg === "number")
|
|
6562
6566
|
segs.push(`[${seg}]`);
|
|
6563
6567
|
else if (typeof seg === "symbol")
|
|
@@ -18829,6 +18833,7 @@ var TaskToastManager = class {
|
|
|
18829
18833
|
tasks = /* @__PURE__ */ new Map();
|
|
18830
18834
|
client = null;
|
|
18831
18835
|
concurrency = null;
|
|
18836
|
+
todoSync = null;
|
|
18832
18837
|
/**
|
|
18833
18838
|
* Initialize the manager with OpenCode client
|
|
18834
18839
|
*/
|
|
@@ -18842,6 +18847,12 @@ var TaskToastManager = class {
|
|
|
18842
18847
|
setConcurrencyController(concurrency) {
|
|
18843
18848
|
this.concurrency = concurrency;
|
|
18844
18849
|
}
|
|
18850
|
+
/**
|
|
18851
|
+
* Set TodoSyncService for TUI status synchronization
|
|
18852
|
+
*/
|
|
18853
|
+
setTodoSync(service) {
|
|
18854
|
+
this.todoSync = service;
|
|
18855
|
+
}
|
|
18845
18856
|
/**
|
|
18846
18857
|
* Add a new task and show consolidated toast
|
|
18847
18858
|
*/
|
|
@@ -18857,6 +18868,7 @@ var TaskToastManager = class {
|
|
|
18857
18868
|
sessionID: task.sessionID
|
|
18858
18869
|
};
|
|
18859
18870
|
this.tasks.set(task.id, trackedTask);
|
|
18871
|
+
this.todoSync?.updateTaskStatus(trackedTask);
|
|
18860
18872
|
this.showTaskListToast(trackedTask);
|
|
18861
18873
|
}
|
|
18862
18874
|
/**
|
|
@@ -18866,6 +18878,7 @@ var TaskToastManager = class {
|
|
|
18866
18878
|
const task = this.tasks.get(id);
|
|
18867
18879
|
if (task) {
|
|
18868
18880
|
task.status = status;
|
|
18881
|
+
this.todoSync?.updateTaskStatus(task);
|
|
18869
18882
|
}
|
|
18870
18883
|
}
|
|
18871
18884
|
/**
|
|
@@ -18873,6 +18886,7 @@ var TaskToastManager = class {
|
|
|
18873
18886
|
*/
|
|
18874
18887
|
removeTask(id) {
|
|
18875
18888
|
this.tasks.delete(id);
|
|
18889
|
+
this.todoSync?.removeTask(id);
|
|
18876
18890
|
}
|
|
18877
18891
|
/**
|
|
18878
18892
|
* Get all running tasks (newest first)
|
|
@@ -20215,15 +20229,15 @@ function mergeDefs2(...defs) {
|
|
|
20215
20229
|
function cloneDef2(schema) {
|
|
20216
20230
|
return mergeDefs2(schema._zod.def);
|
|
20217
20231
|
}
|
|
20218
|
-
function getElementAtPath2(obj,
|
|
20219
|
-
if (!
|
|
20232
|
+
function getElementAtPath2(obj, path12) {
|
|
20233
|
+
if (!path12)
|
|
20220
20234
|
return obj;
|
|
20221
|
-
return
|
|
20235
|
+
return path12.reduce((acc, key) => acc?.[key], obj);
|
|
20222
20236
|
}
|
|
20223
20237
|
function promiseAllObject2(promisesObj) {
|
|
20224
20238
|
const keys = Object.keys(promisesObj);
|
|
20225
|
-
const
|
|
20226
|
-
return Promise.all(
|
|
20239
|
+
const promises4 = keys.map((key) => promisesObj[key]);
|
|
20240
|
+
return Promise.all(promises4).then((results) => {
|
|
20227
20241
|
const resolvedObj = {};
|
|
20228
20242
|
for (let i = 0; i < keys.length; i++) {
|
|
20229
20243
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -20601,11 +20615,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
20601
20615
|
}
|
|
20602
20616
|
return false;
|
|
20603
20617
|
}
|
|
20604
|
-
function prefixIssues2(
|
|
20618
|
+
function prefixIssues2(path12, issues) {
|
|
20605
20619
|
return issues.map((iss) => {
|
|
20606
20620
|
var _a2;
|
|
20607
20621
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
20608
|
-
iss.path.unshift(
|
|
20622
|
+
iss.path.unshift(path12);
|
|
20609
20623
|
return iss;
|
|
20610
20624
|
});
|
|
20611
20625
|
}
|
|
@@ -20788,7 +20802,7 @@ function formatError2(error92, mapper = (issue3) => issue3.message) {
|
|
|
20788
20802
|
}
|
|
20789
20803
|
function treeifyError2(error92, mapper = (issue3) => issue3.message) {
|
|
20790
20804
|
const result = { errors: [] };
|
|
20791
|
-
const processError = (error93,
|
|
20805
|
+
const processError = (error93, path12 = []) => {
|
|
20792
20806
|
var _a2, _b;
|
|
20793
20807
|
for (const issue3 of error93.issues) {
|
|
20794
20808
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -20798,7 +20812,7 @@ function treeifyError2(error92, mapper = (issue3) => issue3.message) {
|
|
|
20798
20812
|
} else if (issue3.code === "invalid_element") {
|
|
20799
20813
|
processError({ issues: issue3.issues }, issue3.path);
|
|
20800
20814
|
} else {
|
|
20801
|
-
const fullpath = [...
|
|
20815
|
+
const fullpath = [...path12, ...issue3.path];
|
|
20802
20816
|
if (fullpath.length === 0) {
|
|
20803
20817
|
result.errors.push(mapper(issue3));
|
|
20804
20818
|
continue;
|
|
@@ -20830,8 +20844,8 @@ function treeifyError2(error92, mapper = (issue3) => issue3.message) {
|
|
|
20830
20844
|
}
|
|
20831
20845
|
function toDotPath2(_path) {
|
|
20832
20846
|
const segs = [];
|
|
20833
|
-
const
|
|
20834
|
-
for (const seg of
|
|
20847
|
+
const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
20848
|
+
for (const seg of path12) {
|
|
20835
20849
|
if (typeof seg === "number")
|
|
20836
20850
|
segs.push(`[${seg}]`);
|
|
20837
20851
|
else if (typeof seg === "symbol")
|
|
@@ -32808,13 +32822,13 @@ function resolveRef(ref, ctx) {
|
|
|
32808
32822
|
if (!ref.startsWith("#")) {
|
|
32809
32823
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
32810
32824
|
}
|
|
32811
|
-
const
|
|
32812
|
-
if (
|
|
32825
|
+
const path12 = ref.slice(1).split("/").filter(Boolean);
|
|
32826
|
+
if (path12.length === 0) {
|
|
32813
32827
|
return ctx.rootSchema;
|
|
32814
32828
|
}
|
|
32815
32829
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
32816
|
-
if (
|
|
32817
|
-
const key =
|
|
32830
|
+
if (path12[0] === defsKey) {
|
|
32831
|
+
const key = path12[1];
|
|
32818
32832
|
if (!key || !ctx.defs[key]) {
|
|
32819
32833
|
throw new Error(`Reference not found: ${ref}`);
|
|
32820
32834
|
}
|
|
@@ -33625,6 +33639,7 @@ var TaskPoller = class {
|
|
|
33625
33639
|
this.onTaskComplete = onTaskComplete;
|
|
33626
33640
|
}
|
|
33627
33641
|
pollingInterval;
|
|
33642
|
+
messageCache = /* @__PURE__ */ new Map();
|
|
33628
33643
|
start() {
|
|
33629
33644
|
if (this.pollingInterval) return;
|
|
33630
33645
|
log("[task-poller.ts] start() - polling started");
|
|
@@ -33717,7 +33732,16 @@ var TaskPoller = class {
|
|
|
33717
33732
|
}
|
|
33718
33733
|
async updateTaskProgress(task) {
|
|
33719
33734
|
try {
|
|
33735
|
+
const cached3 = this.messageCache.get(task.sessionID);
|
|
33736
|
+
const statusResult = await this.client.session.status();
|
|
33737
|
+
const sessionInfo = statusResult.data?.[task.sessionID];
|
|
33738
|
+
const currentMsgCount = sessionInfo?.messageCount ?? 0;
|
|
33739
|
+
if (cached3 && cached3.count === currentMsgCount) {
|
|
33740
|
+
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
33741
|
+
return;
|
|
33742
|
+
}
|
|
33720
33743
|
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
33744
|
+
this.messageCache.set(task.sessionID, { count: currentMsgCount, lastChecked: /* @__PURE__ */ new Date() });
|
|
33721
33745
|
if (result.error) return;
|
|
33722
33746
|
const messages = result.data ?? [];
|
|
33723
33747
|
const assistantMsgs = messages.filter((m) => m.info?.role === MESSAGE_ROLES.ASSISTANT);
|
|
@@ -33741,9 +33765,8 @@ var TaskPoller = class {
|
|
|
33741
33765
|
lastMessage: lastMessage?.slice(0, 100),
|
|
33742
33766
|
lastUpdate: /* @__PURE__ */ new Date()
|
|
33743
33767
|
};
|
|
33744
|
-
const currentMsgCount = messages.length;
|
|
33745
33768
|
if (task.lastMsgCount === currentMsgCount) {
|
|
33746
|
-
task.stablePolls =
|
|
33769
|
+
task.stablePolls = 0;
|
|
33747
33770
|
} else {
|
|
33748
33771
|
task.stablePolls = 0;
|
|
33749
33772
|
}
|
|
@@ -37135,10 +37158,10 @@ async function resolveCommandPath(key, commandName) {
|
|
|
37135
37158
|
const currentPending = pending.get(key);
|
|
37136
37159
|
if (currentPending) return currentPending;
|
|
37137
37160
|
const promise3 = (async () => {
|
|
37138
|
-
const
|
|
37139
|
-
cache[key] =
|
|
37161
|
+
const path12 = await findCommand(commandName);
|
|
37162
|
+
cache[key] = path12;
|
|
37140
37163
|
pending.delete(key);
|
|
37141
|
-
return
|
|
37164
|
+
return path12;
|
|
37142
37165
|
})();
|
|
37143
37166
|
pending.set(key, promise3);
|
|
37144
37167
|
return promise3;
|
|
@@ -37147,21 +37170,21 @@ async function resolveCommandPath(key, commandName) {
|
|
|
37147
37170
|
// src/core/notification/os-notify/notifier.ts
|
|
37148
37171
|
var execAsync2 = promisify2(exec2);
|
|
37149
37172
|
async function notifyDarwin(title, message) {
|
|
37150
|
-
const
|
|
37173
|
+
const path12 = await resolveCommandPath(
|
|
37151
37174
|
NOTIFICATION_COMMAND_KEYS.OSASCRIPT,
|
|
37152
37175
|
NOTIFICATION_COMMANDS.OSASCRIPT
|
|
37153
37176
|
);
|
|
37154
|
-
if (!
|
|
37177
|
+
if (!path12) return;
|
|
37155
37178
|
const escT = title.replace(/"/g, '\\"');
|
|
37156
37179
|
const escM = message.replace(/"/g, '\\"');
|
|
37157
|
-
await execAsync2(`${
|
|
37180
|
+
await execAsync2(`${path12} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"'`);
|
|
37158
37181
|
}
|
|
37159
37182
|
async function notifyLinux(title, message) {
|
|
37160
|
-
const
|
|
37183
|
+
const path12 = await resolveCommandPath(
|
|
37161
37184
|
NOTIFICATION_COMMAND_KEYS.NOTIFY_SEND,
|
|
37162
37185
|
NOTIFICATION_COMMANDS.NOTIFY_SEND
|
|
37163
37186
|
);
|
|
37164
|
-
if (
|
|
37187
|
+
if (path12) await execAsync2(`${path12} "${title}" "${message}" 2>/dev/null`);
|
|
37165
37188
|
}
|
|
37166
37189
|
async function notifyWindows(title, message) {
|
|
37167
37190
|
const ps = await resolveCommandPath(
|
|
@@ -37208,11 +37231,11 @@ init_os();
|
|
|
37208
37231
|
async function playDarwin(soundPath) {
|
|
37209
37232
|
if (!soundPath) return;
|
|
37210
37233
|
try {
|
|
37211
|
-
const
|
|
37234
|
+
const path12 = await resolveCommandPath(
|
|
37212
37235
|
NOTIFICATION_COMMAND_KEYS.AFPLAY,
|
|
37213
37236
|
NOTIFICATION_COMMANDS.AFPLAY
|
|
37214
37237
|
);
|
|
37215
|
-
if (
|
|
37238
|
+
if (path12) exec3(`"${path12}" "${soundPath}"`);
|
|
37216
37239
|
} catch (err) {
|
|
37217
37240
|
log(`[session-notify] Error playing sound (Darwin): ${err}`);
|
|
37218
37241
|
}
|
|
@@ -38364,6 +38387,262 @@ var PluginManager = class _PluginManager {
|
|
|
38364
38387
|
}
|
|
38365
38388
|
};
|
|
38366
38389
|
|
|
38390
|
+
// src/core/sync/todo-sync-service.ts
|
|
38391
|
+
init_shared();
|
|
38392
|
+
import * as fs10 from "node:fs";
|
|
38393
|
+
import * as path9 from "node:path";
|
|
38394
|
+
|
|
38395
|
+
// src/core/sync/todo-parser.ts
|
|
38396
|
+
init_shared();
|
|
38397
|
+
function parseTodoMd(content) {
|
|
38398
|
+
const lines = content.split("\n");
|
|
38399
|
+
const todos = [];
|
|
38400
|
+
const generateId = (text, index2) => {
|
|
38401
|
+
return `${TODO_CONSTANTS.PREFIX.FILE}${index2}-${text.substring(0, 10).replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
38402
|
+
};
|
|
38403
|
+
let index = 0;
|
|
38404
|
+
for (const line of lines) {
|
|
38405
|
+
const match = line.match(/^\s*-\s*\[([ xX\/\-\.])\]\s*(.+)$/);
|
|
38406
|
+
if (match) {
|
|
38407
|
+
const [, statusChar, text] = match;
|
|
38408
|
+
const content2 = text.trim();
|
|
38409
|
+
let status = TODO_STATUS2.PENDING;
|
|
38410
|
+
switch (statusChar.toLowerCase()) {
|
|
38411
|
+
case "x":
|
|
38412
|
+
status = TODO_STATUS2.COMPLETED;
|
|
38413
|
+
break;
|
|
38414
|
+
case "/":
|
|
38415
|
+
case ".":
|
|
38416
|
+
status = TODO_STATUS2.IN_PROGRESS;
|
|
38417
|
+
break;
|
|
38418
|
+
case "-":
|
|
38419
|
+
status = TODO_STATUS2.CANCELLED;
|
|
38420
|
+
break;
|
|
38421
|
+
case " ":
|
|
38422
|
+
default:
|
|
38423
|
+
status = TODO_STATUS2.PENDING;
|
|
38424
|
+
break;
|
|
38425
|
+
}
|
|
38426
|
+
todos.push({
|
|
38427
|
+
id: generateId(content2, index),
|
|
38428
|
+
content: content2,
|
|
38429
|
+
status,
|
|
38430
|
+
priority: STATUS_LABEL.MEDIUM,
|
|
38431
|
+
// Default priority for file items
|
|
38432
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
38433
|
+
});
|
|
38434
|
+
index++;
|
|
38435
|
+
}
|
|
38436
|
+
}
|
|
38437
|
+
return todos;
|
|
38438
|
+
}
|
|
38439
|
+
|
|
38440
|
+
// src/core/sync/todo-sync-service.ts
|
|
38441
|
+
var TodoSyncService = class {
|
|
38442
|
+
client;
|
|
38443
|
+
directory;
|
|
38444
|
+
todoPath;
|
|
38445
|
+
fileTodos = [];
|
|
38446
|
+
taskTodos = /* @__PURE__ */ new Map();
|
|
38447
|
+
updateTimeout = null;
|
|
38448
|
+
watcher = null;
|
|
38449
|
+
activeSessions = /* @__PURE__ */ new Set();
|
|
38450
|
+
constructor(client, directory) {
|
|
38451
|
+
this.client = client;
|
|
38452
|
+
this.directory = directory;
|
|
38453
|
+
this.todoPath = path9.join(this.directory, PATHS.TODO);
|
|
38454
|
+
}
|
|
38455
|
+
async start() {
|
|
38456
|
+
await this.reloadFileTodos();
|
|
38457
|
+
if (fs10.existsSync(this.todoPath)) {
|
|
38458
|
+
let timer;
|
|
38459
|
+
this.watcher = fs10.watch(this.todoPath, (eventType) => {
|
|
38460
|
+
if (eventType === "change" || eventType === "rename") {
|
|
38461
|
+
clearTimeout(timer);
|
|
38462
|
+
timer = setTimeout(() => {
|
|
38463
|
+
this.reloadFileTodos().catch((err) => log(`[TodoSync] Error reloading: ${err}`));
|
|
38464
|
+
}, 500);
|
|
38465
|
+
}
|
|
38466
|
+
});
|
|
38467
|
+
}
|
|
38468
|
+
}
|
|
38469
|
+
registerSession(sessionID) {
|
|
38470
|
+
this.activeSessions.add(sessionID);
|
|
38471
|
+
this.scheduleUpdate(sessionID);
|
|
38472
|
+
}
|
|
38473
|
+
unregisterSession(sessionID) {
|
|
38474
|
+
this.activeSessions.delete(sessionID);
|
|
38475
|
+
}
|
|
38476
|
+
async reloadFileTodos() {
|
|
38477
|
+
try {
|
|
38478
|
+
if (fs10.existsSync(this.todoPath)) {
|
|
38479
|
+
const content = await fs10.promises.readFile(this.todoPath, "utf-8");
|
|
38480
|
+
this.fileTodos = parseTodoMd(content);
|
|
38481
|
+
this.broadcastUpdate();
|
|
38482
|
+
}
|
|
38483
|
+
} catch (error92) {
|
|
38484
|
+
log(`[TodoSync] Failed to read todo.md: ${error92}`);
|
|
38485
|
+
}
|
|
38486
|
+
}
|
|
38487
|
+
/**
|
|
38488
|
+
* Called by TaskToastManager when tasks change
|
|
38489
|
+
*/
|
|
38490
|
+
updateTaskStatus(task) {
|
|
38491
|
+
this.taskTodos.set(task.id, task);
|
|
38492
|
+
if (task.parentSessionID) {
|
|
38493
|
+
this.scheduleUpdate(task.parentSessionID);
|
|
38494
|
+
} else {
|
|
38495
|
+
this.broadcastUpdate();
|
|
38496
|
+
}
|
|
38497
|
+
}
|
|
38498
|
+
removeTask(taskId) {
|
|
38499
|
+
const task = this.taskTodos.get(taskId);
|
|
38500
|
+
if (task) {
|
|
38501
|
+
this.taskTodos.delete(taskId);
|
|
38502
|
+
if (task.parentSessionID) {
|
|
38503
|
+
this.scheduleUpdate(task.parentSessionID);
|
|
38504
|
+
} else {
|
|
38505
|
+
this.broadcastUpdate();
|
|
38506
|
+
}
|
|
38507
|
+
}
|
|
38508
|
+
}
|
|
38509
|
+
broadcastUpdate() {
|
|
38510
|
+
for (const sessionID of this.activeSessions) {
|
|
38511
|
+
this.scheduleUpdate(sessionID);
|
|
38512
|
+
}
|
|
38513
|
+
}
|
|
38514
|
+
scheduleUpdate(sessionID) {
|
|
38515
|
+
this.sendTodosToSession(sessionID).catch((err) => {
|
|
38516
|
+
});
|
|
38517
|
+
}
|
|
38518
|
+
async sendTodosToSession(sessionID) {
|
|
38519
|
+
const taskTodosList = Array.from(this.taskTodos.values()).map((t) => {
|
|
38520
|
+
let status = TODO_STATUS2.PENDING;
|
|
38521
|
+
const s = t.status.toLowerCase();
|
|
38522
|
+
if (s.includes(STATUS_LABEL.RUNNING) || s.includes("wait") || s.includes("que")) status = TODO_STATUS2.IN_PROGRESS;
|
|
38523
|
+
else if (s.includes(STATUS_LABEL.COMPLETED) || s.includes(STATUS_LABEL.DONE)) status = TODO_STATUS2.COMPLETED;
|
|
38524
|
+
else if (s.includes(STATUS_LABEL.FAILED) || s.includes(STATUS_LABEL.ERROR)) status = TODO_STATUS2.CANCELLED;
|
|
38525
|
+
else if (s.includes(STATUS_LABEL.CANCELLED)) status = TODO_STATUS2.CANCELLED;
|
|
38526
|
+
return {
|
|
38527
|
+
id: `${TODO_CONSTANTS.PREFIX.TASK}${t.id}`,
|
|
38528
|
+
// Prefix to avoid collision
|
|
38529
|
+
content: `[${t.agent.toUpperCase()}] ${t.description}`,
|
|
38530
|
+
status,
|
|
38531
|
+
priority: t.isBackground ? STATUS_LABEL.LOW : STATUS_LABEL.HIGH,
|
|
38532
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
38533
|
+
};
|
|
38534
|
+
});
|
|
38535
|
+
const merged = [
|
|
38536
|
+
...this.fileTodos,
|
|
38537
|
+
...taskTodosList
|
|
38538
|
+
];
|
|
38539
|
+
const payloadTodos = merged.map((todo) => ({
|
|
38540
|
+
id: todo.id,
|
|
38541
|
+
content: todo.content,
|
|
38542
|
+
status: todo.status,
|
|
38543
|
+
priority: todo.priority
|
|
38544
|
+
}));
|
|
38545
|
+
try {
|
|
38546
|
+
await this.client.session.todo({
|
|
38547
|
+
path: { id: sessionID },
|
|
38548
|
+
// Standardize to id
|
|
38549
|
+
body: { todos: payloadTodos }
|
|
38550
|
+
});
|
|
38551
|
+
} catch (error92) {
|
|
38552
|
+
}
|
|
38553
|
+
}
|
|
38554
|
+
stop() {
|
|
38555
|
+
if (this.watcher) {
|
|
38556
|
+
this.watcher.close();
|
|
38557
|
+
}
|
|
38558
|
+
}
|
|
38559
|
+
};
|
|
38560
|
+
|
|
38561
|
+
// src/core/cleanup/cleanup-scheduler.ts
|
|
38562
|
+
import * as fs11 from "node:fs";
|
|
38563
|
+
import * as path10 from "node:path";
|
|
38564
|
+
init_shared();
|
|
38565
|
+
var CleanupScheduler = class {
|
|
38566
|
+
intervals = /* @__PURE__ */ new Map();
|
|
38567
|
+
directory;
|
|
38568
|
+
constructor(directory) {
|
|
38569
|
+
this.directory = directory;
|
|
38570
|
+
}
|
|
38571
|
+
start() {
|
|
38572
|
+
this.schedule("wal-compact", () => this.compactWAL(), 10 * 60 * 1e3);
|
|
38573
|
+
this.schedule("docs-clean", () => this.cleanDocs(), 60 * 60 * 1e3);
|
|
38574
|
+
this.schedule("history-rotate", () => this.rotateHistory(), 24 * 60 * 60 * 1e3);
|
|
38575
|
+
log(`[Cleanup] Scheduler started`);
|
|
38576
|
+
}
|
|
38577
|
+
schedule(name, fn, intervalMs) {
|
|
38578
|
+
const timer = setInterval(() => {
|
|
38579
|
+
fn().catch((err) => log(`[Cleanup] ${name} failed:`, err));
|
|
38580
|
+
}, intervalMs);
|
|
38581
|
+
if (timer.unref) timer.unref();
|
|
38582
|
+
this.intervals.set(name, timer);
|
|
38583
|
+
}
|
|
38584
|
+
stop() {
|
|
38585
|
+
for (const timer of this.intervals.values()) {
|
|
38586
|
+
clearInterval(timer);
|
|
38587
|
+
}
|
|
38588
|
+
this.intervals.clear();
|
|
38589
|
+
log(`[Cleanup] Scheduler stopped`);
|
|
38590
|
+
}
|
|
38591
|
+
async compactWAL() {
|
|
38592
|
+
try {
|
|
38593
|
+
const manager = parallelAgentManager.getInstance();
|
|
38594
|
+
const activeTasks = manager.getAllTasks().filter((t) => t.status === TASK_STATUS.RUNNING || t.status === TASK_STATUS.PENDING);
|
|
38595
|
+
await taskWAL.compact(activeTasks);
|
|
38596
|
+
} catch (error92) {
|
|
38597
|
+
log(`[Cleanup] Failed to compact WAL: ${error92}`);
|
|
38598
|
+
}
|
|
38599
|
+
}
|
|
38600
|
+
async cleanDocs() {
|
|
38601
|
+
try {
|
|
38602
|
+
const stats2 = await stats();
|
|
38603
|
+
if (stats2.totalSize > 10 * 1024 * 1024) {
|
|
38604
|
+
const allDocs = await list();
|
|
38605
|
+
allDocs.sort((a, b) => new Date(a.fetchedAt).getTime() - new Date(b.fetchedAt).getTime());
|
|
38606
|
+
const toDelete = allDocs.slice(0, Math.floor(allDocs.length / 2));
|
|
38607
|
+
for (const doc of toDelete) {
|
|
38608
|
+
await remove(doc.url);
|
|
38609
|
+
}
|
|
38610
|
+
log(`[Cleanup] Pruned ${toDelete.length} documents due to size limit`);
|
|
38611
|
+
}
|
|
38612
|
+
} catch (error92) {
|
|
38613
|
+
}
|
|
38614
|
+
}
|
|
38615
|
+
async rotateHistory() {
|
|
38616
|
+
try {
|
|
38617
|
+
const historyPath = path10.join(this.directory, ".opencode/archive/todo_history.jsonl");
|
|
38618
|
+
if (!fs11.existsSync(historyPath)) return;
|
|
38619
|
+
const stat = await fs11.promises.stat(historyPath);
|
|
38620
|
+
if (stat.size === 0) return;
|
|
38621
|
+
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
38622
|
+
const archivePath = path10.join(
|
|
38623
|
+
this.directory,
|
|
38624
|
+
`.opencode/archive/todo_history.${dateStr}.jsonl`
|
|
38625
|
+
);
|
|
38626
|
+
await fs11.promises.rename(historyPath, archivePath);
|
|
38627
|
+
await fs11.promises.writeFile(historyPath, "");
|
|
38628
|
+
const archiveDir = path10.dirname(historyPath);
|
|
38629
|
+
const files = await fs11.promises.readdir(archiveDir);
|
|
38630
|
+
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1e3;
|
|
38631
|
+
for (const file3 of files) {
|
|
38632
|
+
if (file3.startsWith("todo_history.") && file3.endsWith(".jsonl")) {
|
|
38633
|
+
const filePath = path10.join(archiveDir, file3);
|
|
38634
|
+
const fStat = await fs11.promises.stat(filePath);
|
|
38635
|
+
if (fStat.mtimeMs < cutoff) {
|
|
38636
|
+
await fs11.promises.unlink(filePath);
|
|
38637
|
+
}
|
|
38638
|
+
}
|
|
38639
|
+
}
|
|
38640
|
+
} catch (error92) {
|
|
38641
|
+
log(`[Cleanup] History rotation error: ${error92}`);
|
|
38642
|
+
}
|
|
38643
|
+
}
|
|
38644
|
+
};
|
|
38645
|
+
|
|
38367
38646
|
// src/plugin-handlers/tool-execute-pre-handler.ts
|
|
38368
38647
|
function createToolExecuteBeforeHandler(ctx) {
|
|
38369
38648
|
const { sessions, directory } = ctx;
|
|
@@ -38427,17 +38706,17 @@ function createChatMessageHandler(ctx) {
|
|
|
38427
38706
|
init_shared();
|
|
38428
38707
|
|
|
38429
38708
|
// src/utils/compatibility/claude.ts
|
|
38430
|
-
import
|
|
38431
|
-
import
|
|
38709
|
+
import fs12 from "fs";
|
|
38710
|
+
import path11 from "path";
|
|
38432
38711
|
function findClaudeRules(startDir = process.cwd()) {
|
|
38433
38712
|
try {
|
|
38434
38713
|
let currentDir = startDir;
|
|
38435
|
-
const root =
|
|
38714
|
+
const root = path11.parse(startDir).root;
|
|
38436
38715
|
while (true) {
|
|
38437
|
-
const claudeMdPath =
|
|
38438
|
-
if (
|
|
38716
|
+
const claudeMdPath = path11.join(currentDir, "CLAUDE.md");
|
|
38717
|
+
if (fs12.existsSync(claudeMdPath)) {
|
|
38439
38718
|
try {
|
|
38440
|
-
const content =
|
|
38719
|
+
const content = fs12.readFileSync(claudeMdPath, "utf-8");
|
|
38441
38720
|
log(`[compatibility] Loaded CLAUDE.md from ${claudeMdPath}`);
|
|
38442
38721
|
return formatRules("CLAUDE.md", content);
|
|
38443
38722
|
} catch (e) {
|
|
@@ -38445,11 +38724,11 @@ function findClaudeRules(startDir = process.cwd()) {
|
|
|
38445
38724
|
}
|
|
38446
38725
|
}
|
|
38447
38726
|
if (currentDir === root) break;
|
|
38448
|
-
currentDir =
|
|
38727
|
+
currentDir = path11.dirname(currentDir);
|
|
38449
38728
|
}
|
|
38450
|
-
const copilotPath =
|
|
38451
|
-
if (
|
|
38452
|
-
return formatRules("Copilot Instructions",
|
|
38729
|
+
const copilotPath = path11.join(startDir, ".github", "copilot-instructions.md");
|
|
38730
|
+
if (fs12.existsSync(copilotPath)) {
|
|
38731
|
+
return formatRules("Copilot Instructions", fs12.readFileSync(copilotPath, "utf-8"));
|
|
38453
38732
|
}
|
|
38454
38733
|
return null;
|
|
38455
38734
|
} catch (error92) {
|
|
@@ -39054,6 +39333,11 @@ var OrchestratorPlugin = async (input) => {
|
|
|
39054
39333
|
await pluginManager.initialize(directory);
|
|
39055
39334
|
const dynamicTools = pluginManager.getDynamicTools();
|
|
39056
39335
|
taskToastManager.setConcurrencyController(parallelAgentManager2.getConcurrency());
|
|
39336
|
+
const todoSync = new TodoSyncService(client, directory);
|
|
39337
|
+
await todoSync.start();
|
|
39338
|
+
taskToastManager.setTodoSync(todoSync);
|
|
39339
|
+
const cleanupScheduler = new CleanupScheduler(directory);
|
|
39340
|
+
cleanupScheduler.start();
|
|
39057
39341
|
const handlerContext = {
|
|
39058
39342
|
client,
|
|
39059
39343
|
directory,
|
|
@@ -39110,7 +39394,20 @@ var OrchestratorPlugin = async (input) => {
|
|
|
39110
39394
|
// -----------------------------------------------------------------
|
|
39111
39395
|
// Event hook - handles OpenCode events
|
|
39112
39396
|
// -----------------------------------------------------------------
|
|
39113
|
-
|
|
39397
|
+
// -----------------------------------------------------------------
|
|
39398
|
+
// Event hook - handles OpenCode events
|
|
39399
|
+
// -----------------------------------------------------------------
|
|
39400
|
+
event: async (payload) => {
|
|
39401
|
+
const result = await createEventHandler(handlerContext)(payload);
|
|
39402
|
+
const { event } = payload;
|
|
39403
|
+
if (event.type === "session.created" && event.properties) {
|
|
39404
|
+
const sessionID = event.properties.sessionID || event.properties.id || event.properties.info?.sessionID;
|
|
39405
|
+
if (sessionID) {
|
|
39406
|
+
todoSync.registerSession(sessionID);
|
|
39407
|
+
}
|
|
39408
|
+
}
|
|
39409
|
+
return result;
|
|
39410
|
+
},
|
|
39114
39411
|
// -----------------------------------------------------------------
|
|
39115
39412
|
// chat.message hook - intercepts commands and sets up sessions
|
|
39116
39413
|
// -----------------------------------------------------------------
|
|
@@ -47,6 +47,10 @@ export declare const TODO_CONSTANTS: {
|
|
|
47
47
|
readonly PROGRESS: "progress";
|
|
48
48
|
readonly FAILED: "failed";
|
|
49
49
|
};
|
|
50
|
+
readonly PREFIX: {
|
|
51
|
+
readonly TASK: "task-";
|
|
52
|
+
readonly FILE: "file-task-";
|
|
53
|
+
};
|
|
50
54
|
};
|
|
51
55
|
export declare const TUI_CONSTANTS: {
|
|
52
56
|
readonly BAR_WIDTH: 30;
|
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.14",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|