opencode-forge 0.1.5
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.
Potentially problematic release.
This version of opencode-forge might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +534 -0
- package/config.jsonc +47 -0
- package/dist/agents/architect.d.ts +3 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/architect.js +152 -0
- package/dist/agents/architect.js.map +1 -0
- package/dist/agents/auditor.d.ts +3 -0
- package/dist/agents/auditor.d.ts.map +1 -0
- package/dist/agents/auditor.js +168 -0
- package/dist/agents/auditor.js.map +1 -0
- package/dist/agents/code.d.ts +3 -0
- package/dist/agents/code.d.ts.map +1 -0
- package/dist/agents/code.js +67 -0
- package/dist/agents/code.js.map +1 -0
- package/dist/agents/index.d.ts +4 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +9 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/prompts.d.ts +1 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +4 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/agents/types.d.ts +34 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/memory-cache.d.ts +14 -0
- package/dist/cache/memory-cache.d.ts.map +1 -0
- package/dist/cache/memory-cache.js +51 -0
- package/dist/cache/memory-cache.js.map +1 -0
- package/dist/cache/types.d.ts +8 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +2 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/cli/commands/cancel.d.ts +15 -0
- package/dist/cli/commands/cancel.d.ts.map +1 -0
- package/dist/cli/commands/cancel.js +194 -0
- package/dist/cli/commands/cancel.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +16 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +208 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/restart.d.ts +15 -0
- package/dist/cli/commands/restart.d.ts.map +1 -0
- package/dist/cli/commands/restart.js +268 -0
- package/dist/cli/commands/restart.js.map +1 -0
- package/dist/cli/commands/status.d.ts +17 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +356 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/upgrade.d.ts +3 -0
- package/dist/cli/commands/upgrade.d.ts.map +1 -0
- package/dist/cli/commands/upgrade.js +40 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +224 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils.d.ts +36 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +163 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/command/template/review.txt +101 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +186 -0
- package/dist/config.js.map +1 -0
- package/dist/constants/loop.d.ts +10 -0
- package/dist/constants/loop.d.ts.map +1 -0
- package/dist/constants/loop.js +6 -0
- package/dist/constants/loop.js.map +1 -0
- package/dist/graph/cache.d.ts +17 -0
- package/dist/graph/cache.d.ts.map +1 -0
- package/dist/graph/cache.js +50 -0
- package/dist/graph/cache.js.map +1 -0
- package/dist/graph/client.d.ts +51 -0
- package/dist/graph/client.d.ts.map +1 -0
- package/dist/graph/client.js +152 -0
- package/dist/graph/client.js.map +1 -0
- package/dist/graph/clone-detection.d.ts +9 -0
- package/dist/graph/clone-detection.d.ts.map +1 -0
- package/dist/graph/clone-detection.js +148 -0
- package/dist/graph/clone-detection.js.map +1 -0
- package/dist/graph/constants.d.ts +18 -0
- package/dist/graph/constants.d.ts.map +1 -0
- package/dist/graph/constants.js +532 -0
- package/dist/graph/constants.js.map +1 -0
- package/dist/graph/database.d.ts +11 -0
- package/dist/graph/database.d.ts.map +1 -0
- package/dist/graph/database.js +250 -0
- package/dist/graph/database.js.map +1 -0
- package/dist/graph/index.d.ts +14 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/repo-map.d.ts +59 -0
- package/dist/graph/repo-map.d.ts.map +1 -0
- package/dist/graph/repo-map.js +948 -0
- package/dist/graph/repo-map.js.map +1 -0
- package/dist/graph/rpc.d.ts +34 -0
- package/dist/graph/rpc.d.ts.map +1 -0
- package/dist/graph/rpc.js +139 -0
- package/dist/graph/rpc.js.map +1 -0
- package/dist/graph/service.d.ts +46 -0
- package/dist/graph/service.d.ts.map +1 -0
- package/dist/graph/service.js +329 -0
- package/dist/graph/service.js.map +1 -0
- package/dist/graph/tree-sitter.d.ts +40 -0
- package/dist/graph/tree-sitter.d.ts.map +1 -0
- package/dist/graph/tree-sitter.js +799 -0
- package/dist/graph/tree-sitter.js.map +1 -0
- package/dist/graph/types.d.ts +175 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +105 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/utils.d.ts +64 -0
- package/dist/graph/utils.d.ts.map +1 -0
- package/dist/graph/utils.js +406 -0
- package/dist/graph/utils.js.map +1 -0
- package/dist/graph/worker.d.ts +2 -0
- package/dist/graph/worker.d.ts.map +1 -0
- package/dist/graph/worker.js +6043 -0
- package/dist/graph/worker.js.map +1 -0
- package/dist/hooks/compaction-utils.d.ts +21 -0
- package/dist/hooks/compaction-utils.d.ts.map +1 -0
- package/dist/hooks/compaction-utils.js +82 -0
- package/dist/hooks/compaction-utils.js.map +1 -0
- package/dist/hooks/graph-command.d.ts +27 -0
- package/dist/hooks/graph-command.d.ts.map +1 -0
- package/dist/hooks/graph-command.js +57 -0
- package/dist/hooks/graph-command.js.map +1 -0
- package/dist/hooks/graph-tools.d.ts +11 -0
- package/dist/hooks/graph-tools.d.ts.map +1 -0
- package/dist/hooks/graph-tools.js +125 -0
- package/dist/hooks/graph-tools.js.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/loop.d.ts +23 -0
- package/dist/hooks/loop.d.ts.map +1 -0
- package/dist/hooks/loop.js +667 -0
- package/dist/hooks/loop.js.map +1 -0
- package/dist/hooks/sandbox-tools.d.ts +13 -0
- package/dist/hooks/sandbox-tools.d.ts.map +1 -0
- package/dist/hooks/sandbox-tools.js +105 -0
- package/dist/hooks/sandbox-tools.js.map +1 -0
- package/dist/hooks/session.d.ts +19 -0
- package/dist/hooks/session.d.ts.map +1 -0
- package/dist/hooks/session.js +56 -0
- package/dist/hooks/session.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +298 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/context.d.ts +27 -0
- package/dist/sandbox/context.d.ts.map +1 -0
- package/dist/sandbox/context.js +18 -0
- package/dist/sandbox/context.js.map +1 -0
- package/dist/sandbox/docker.d.ts +29 -0
- package/dist/sandbox/docker.d.ts.map +1 -0
- package/dist/sandbox/docker.js +213 -0
- package/dist/sandbox/docker.js.map +1 -0
- package/dist/sandbox/manager.d.ts +23 -0
- package/dist/sandbox/manager.d.ts.map +1 -0
- package/dist/sandbox/manager.js +131 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/path.d.ts +4 -0
- package/dist/sandbox/path.d.ts.map +1 -0
- package/dist/sandbox/path.js +27 -0
- package/dist/sandbox/path.js.map +1 -0
- package/dist/services/kv.d.ts +17 -0
- package/dist/services/kv.d.ts.map +1 -0
- package/dist/services/kv.js +62 -0
- package/dist/services/kv.js.map +1 -0
- package/dist/services/loop.d.ts +96 -0
- package/dist/services/loop.d.ts.map +1 -0
- package/dist/services/loop.js +315 -0
- package/dist/services/loop.js.map +1 -0
- package/dist/setup.d.ts +4 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +118 -0
- package/dist/setup.js.map +1 -0
- package/dist/storage/database.d.ts +6 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/database.js +90 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/storage/graph-projects.d.ts +80 -0
- package/dist/storage/graph-projects.d.ts.map +1 -0
- package/dist/storage/graph-projects.js +154 -0
- package/dist/storage/graph-projects.js.map +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/kv-queries.d.ts +18 -0
- package/dist/storage/kv-queries.d.ts.map +1 -0
- package/dist/storage/kv-queries.js +70 -0
- package/dist/storage/kv-queries.js.map +1 -0
- package/dist/tools/graph.d.ts +9 -0
- package/dist/tools/graph.d.ts.map +1 -0
- package/dist/tools/graph.js +272 -0
- package/dist/tools/graph.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/loop.d.ts +21 -0
- package/dist/tools/loop.d.ts.map +1 -0
- package/dist/tools/loop.js +570 -0
- package/dist/tools/loop.js.map +1 -0
- package/dist/tools/plan-approval.d.ts +15 -0
- package/dist/tools/plan-approval.d.ts.map +1 -0
- package/dist/tools/plan-approval.js +203 -0
- package/dist/tools/plan-approval.js.map +1 -0
- package/dist/tools/plan-execute.d.ts +4 -0
- package/dist/tools/plan-execute.d.ts.map +1 -0
- package/dist/tools/plan-execute.js +85 -0
- package/dist/tools/plan-execute.js.map +1 -0
- package/dist/tools/plan-kv.d.ts +4 -0
- package/dist/tools/plan-kv.d.ts.map +1 -0
- package/dist/tools/plan-kv.js +107 -0
- package/dist/tools/plan-kv.js.map +1 -0
- package/dist/tools/review.d.ts +4 -0
- package/dist/tools/review.d.ts.map +1 -0
- package/dist/tools/review.js +90 -0
- package/dist/tools/review.js.map +1 -0
- package/dist/tools/sandbox-fs.d.ts +22 -0
- package/dist/tools/sandbox-fs.d.ts.map +1 -0
- package/dist/tools/sandbox-fs.js +83 -0
- package/dist/tools/sandbox-fs.js.map +1 -0
- package/dist/tools/types.d.ts +26 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +2061 -0
- package/dist/types.d.ts +124 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/git-branch.d.ts +11 -0
- package/dist/utils/git-branch.d.ts.map +1 -0
- package/dist/utils/git-branch.js +35 -0
- package/dist/utils/git-branch.js.map +1 -0
- package/dist/utils/graph-status-store.d.ts +72 -0
- package/dist/utils/graph-status-store.d.ts.map +1 -0
- package/dist/utils/graph-status-store.js +62 -0
- package/dist/utils/graph-status-store.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/loop-format.d.ts +5 -0
- package/dist/utils/loop-format.d.ts.map +1 -0
- package/dist/utils/loop-format.js +29 -0
- package/dist/utils/loop-format.js.map +1 -0
- package/dist/utils/loop-helpers.d.ts +9 -0
- package/dist/utils/loop-helpers.d.ts.map +1 -0
- package/dist/utils/loop-helpers.js +20 -0
- package/dist/utils/loop-helpers.js.map +1 -0
- package/dist/utils/loop-launch.d.ts +32 -0
- package/dist/utils/loop-launch.d.ts.map +1 -0
- package/dist/utils/loop-launch.js +162 -0
- package/dist/utils/loop-launch.js.map +1 -0
- package/dist/utils/model-fallback.d.ts +27 -0
- package/dist/utils/model-fallback.d.ts.map +1 -0
- package/dist/utils/model-fallback.js +33 -0
- package/dist/utils/model-fallback.js.map +1 -0
- package/dist/utils/partial-match.d.ts +7 -0
- package/dist/utils/partial-match.d.ts.map +1 -0
- package/dist/utils/partial-match.js +56 -0
- package/dist/utils/partial-match.js.map +1 -0
- package/dist/utils/plan-execution.d.ts +65 -0
- package/dist/utils/plan-execution.d.ts.map +1 -0
- package/dist/utils/plan-execution.js +107 -0
- package/dist/utils/plan-execution.js.map +1 -0
- package/dist/utils/session-stats.d.ts +36 -0
- package/dist/utils/session-stats.d.ts.map +1 -0
- package/dist/utils/session-stats.js +145 -0
- package/dist/utils/session-stats.js.map +1 -0
- package/dist/utils/tui-graph-status.d.ts +38 -0
- package/dist/utils/tui-graph-status.d.ts.map +1 -0
- package/dist/utils/tui-graph-status.js +95 -0
- package/dist/utils/tui-graph-status.js.map +1 -0
- package/dist/utils/tui-plan-store.d.ts +54 -0
- package/dist/utils/tui-plan-store.d.ts.map +1 -0
- package/dist/utils/tui-plan-store.js +168 -0
- package/dist/utils/tui-plan-store.js.map +1 -0
- package/dist/utils/tui-refresh-helpers.d.ts +44 -0
- package/dist/utils/tui-refresh-helpers.d.ts.map +1 -0
- package/dist/utils/tui-refresh-helpers.js +120 -0
- package/dist/utils/tui-refresh-helpers.js.map +1 -0
- package/dist/utils/upgrade.d.ts +23 -0
- package/dist/utils/upgrade.d.ts.map +1 -0
- package/dist/utils/upgrade.js +111 -0
- package/dist/utils/upgrade.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +92 -0
- package/scripts/build.ts +67 -0
- package/src/command/template/review.txt +101 -0
package/dist/tui.js
ADDED
|
@@ -0,0 +1,2061 @@
|
|
|
1
|
+
// src/tui.tsx
|
|
2
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
3
|
+
import { use as _$use } from "@opentui/solid";
|
|
4
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
5
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
6
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
7
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
8
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
9
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
10
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
11
|
+
import { createEffect, createMemo, createSignal, onCleanup, Show, For } from "solid-js";
|
|
12
|
+
import { SyntaxStyle } from "@opentui/core";
|
|
13
|
+
import { readFileSync, existsSync as existsSync6, writeFileSync } from "fs";
|
|
14
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
15
|
+
import { join as join6 } from "path";
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
import { Database as Database5 } from "bun:sqlite";
|
|
18
|
+
|
|
19
|
+
// src/version.ts
|
|
20
|
+
var VERSION = "0.1.5";
|
|
21
|
+
|
|
22
|
+
// src/storage/database.ts
|
|
23
|
+
import { mkdirSync, existsSync } from "fs";
|
|
24
|
+
import { homedir, platform } from "os";
|
|
25
|
+
import { join } from "path";
|
|
26
|
+
function resolveDataDir() {
|
|
27
|
+
const defaultBase = join(homedir(), platform() === "win32" ? "AppData" : ".local", "share");
|
|
28
|
+
const xdgDataHome = process.env["XDG_DATA_HOME"] || defaultBase;
|
|
29
|
+
const forgeDir = join(xdgDataHome, "opencode", "forge");
|
|
30
|
+
const legacyGraphDir = join(xdgDataHome, "opencode", "graph");
|
|
31
|
+
return existsSync(legacyGraphDir) && !existsSync(forgeDir) ? legacyGraphDir : forgeDir;
|
|
32
|
+
}
|
|
33
|
+
// src/storage/kv-queries.ts
|
|
34
|
+
function mapRow(row) {
|
|
35
|
+
return {
|
|
36
|
+
projectId: row.project_id,
|
|
37
|
+
key: row.key,
|
|
38
|
+
data: row.data,
|
|
39
|
+
expiresAt: row.expires_at,
|
|
40
|
+
createdAt: row.created_at,
|
|
41
|
+
updatedAt: row.updated_at
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var CLEANUP_INTERVAL_MS = 300000;
|
|
45
|
+
var lastCleanupAt = 0;
|
|
46
|
+
function createKvQuery(db) {
|
|
47
|
+
const getStmt = db.prepare(`SELECT project_id, key, data, expires_at, created_at, updated_at
|
|
48
|
+
FROM project_kv
|
|
49
|
+
WHERE project_id = ? AND key = ? AND expires_at > ?`);
|
|
50
|
+
const setStmt = db.prepare(`INSERT INTO project_kv (project_id, key, data, expires_at, created_at, updated_at)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
52
|
+
ON CONFLICT(project_id, key) DO UPDATE SET
|
|
53
|
+
data = excluded.data,
|
|
54
|
+
expires_at = excluded.expires_at,
|
|
55
|
+
updated_at = excluded.updated_at`);
|
|
56
|
+
const deleteStmt = db.prepare(`DELETE FROM project_kv WHERE project_id = ? AND key = ?`);
|
|
57
|
+
const listStmt = db.prepare(`SELECT project_id, key, data, expires_at, created_at, updated_at
|
|
58
|
+
FROM project_kv
|
|
59
|
+
WHERE project_id = ? AND expires_at > ?
|
|
60
|
+
ORDER BY updated_at DESC`);
|
|
61
|
+
const listByPrefixStmt = db.prepare(`SELECT project_id, key, data, expires_at, created_at, updated_at
|
|
62
|
+
FROM project_kv
|
|
63
|
+
WHERE project_id = ? AND key LIKE ? AND expires_at > ?
|
|
64
|
+
ORDER BY updated_at DESC`);
|
|
65
|
+
const deleteExpiredStmt = db.prepare(`DELETE FROM project_kv WHERE expires_at < ?`);
|
|
66
|
+
return {
|
|
67
|
+
get(projectId, key) {
|
|
68
|
+
const row = getStmt.get(projectId, key, Date.now());
|
|
69
|
+
return row ? mapRow(row) : undefined;
|
|
70
|
+
},
|
|
71
|
+
set(projectId, key, data, expiresAt) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
setStmt.run(projectId, key, data, expiresAt, now, now);
|
|
74
|
+
if (now - lastCleanupAt > CLEANUP_INTERVAL_MS) {
|
|
75
|
+
lastCleanupAt = now;
|
|
76
|
+
setImmediate(() => {
|
|
77
|
+
try {
|
|
78
|
+
deleteExpiredStmt.run(now);
|
|
79
|
+
} catch {}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
delete(projectId, key) {
|
|
84
|
+
deleteStmt.run(projectId, key);
|
|
85
|
+
},
|
|
86
|
+
list(projectId) {
|
|
87
|
+
const rows = listStmt.all(projectId, Date.now());
|
|
88
|
+
return rows.map(mapRow);
|
|
89
|
+
},
|
|
90
|
+
listByPrefix(projectId, prefix) {
|
|
91
|
+
const rows = listByPrefixStmt.all(projectId, `${prefix}%`, Date.now());
|
|
92
|
+
return rows.map(mapRow);
|
|
93
|
+
},
|
|
94
|
+
deleteExpired() {
|
|
95
|
+
const result = deleteExpiredStmt.run(Date.now());
|
|
96
|
+
return result.changes;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// src/utils/session-stats.ts
|
|
101
|
+
function extractActivity(parts) {
|
|
102
|
+
const toolCalls = [];
|
|
103
|
+
const textLines = [];
|
|
104
|
+
const toolLines = [];
|
|
105
|
+
const subtaskLines = [];
|
|
106
|
+
const reasoningLines = [];
|
|
107
|
+
for (const p of parts) {
|
|
108
|
+
if (p.type === "text" && typeof p.text === "string" && p.text.trim()) {
|
|
109
|
+
textLines.push(p.text.trim());
|
|
110
|
+
} else if (p.type === "tool" && p.tool && p.state) {
|
|
111
|
+
const s = p.state;
|
|
112
|
+
const name = p.tool;
|
|
113
|
+
const status = s.status;
|
|
114
|
+
if (status === "completed") {
|
|
115
|
+
const title = s.title ?? name;
|
|
116
|
+
toolCalls.push({ tool: name, title, status: "completed" });
|
|
117
|
+
toolLines.push(`[done] ${name}: ${title}`);
|
|
118
|
+
} else if (status === "running") {
|
|
119
|
+
const title = s.title ?? name;
|
|
120
|
+
toolCalls.push({ tool: name, title, status: "running" });
|
|
121
|
+
toolLines.push(`[running] ${name}: ${title}`);
|
|
122
|
+
} else if (status === "error") {
|
|
123
|
+
const msg = s.error ?? "error";
|
|
124
|
+
toolCalls.push({ tool: name, title: msg, status: "error" });
|
|
125
|
+
toolLines.push(`[error] ${name}: ${msg}`);
|
|
126
|
+
} else if (status === "pending") {
|
|
127
|
+
toolCalls.push({ tool: name, title: name, status: "pending" });
|
|
128
|
+
toolLines.push(`[pending] ${name}`);
|
|
129
|
+
}
|
|
130
|
+
} else if (p.type === "subtask" && p.description) {
|
|
131
|
+
const agentLabel = p.agent ? `${p.agent}: ` : "";
|
|
132
|
+
subtaskLines.push(`-> ${agentLabel}${p.description}`);
|
|
133
|
+
} else if (p.type === "reasoning" && typeof p.text === "string" && p.text.trim()) {
|
|
134
|
+
reasoningLines.push(p.text.trim());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let summary = "";
|
|
138
|
+
if (textLines.length > 0) {
|
|
139
|
+
summary = textLines.join(`
|
|
140
|
+
`);
|
|
141
|
+
} else if (toolLines.length > 0) {
|
|
142
|
+
summary = toolLines.join(`
|
|
143
|
+
`);
|
|
144
|
+
} else if (subtaskLines.length > 0) {
|
|
145
|
+
summary = subtaskLines.join(`
|
|
146
|
+
`);
|
|
147
|
+
} else if (reasoningLines.length > 0) {
|
|
148
|
+
summary = reasoningLines.join(`
|
|
149
|
+
`);
|
|
150
|
+
}
|
|
151
|
+
if (!summary && toolCalls.length === 0)
|
|
152
|
+
return null;
|
|
153
|
+
return { summary, toolCalls };
|
|
154
|
+
}
|
|
155
|
+
async function fetchSessionStats(api, sessionId, directory) {
|
|
156
|
+
if (!directory || !sessionId) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const messagesResult = await api.client.session.messages({
|
|
161
|
+
sessionID: sessionId,
|
|
162
|
+
directory
|
|
163
|
+
});
|
|
164
|
+
const messages = messagesResult.data ?? [];
|
|
165
|
+
const assistantMessages = messages.filter((m) => m.info.role === "assistant");
|
|
166
|
+
let lastActivity = null;
|
|
167
|
+
for (let i = assistantMessages.length - 1;i >= Math.max(0, assistantMessages.length - 3); i--) {
|
|
168
|
+
const result = extractActivity(assistantMessages[i].parts);
|
|
169
|
+
if (result) {
|
|
170
|
+
lastActivity = result;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
let totalInputTokens = 0;
|
|
175
|
+
let totalOutputTokens = 0;
|
|
176
|
+
let totalReasoningTokens = 0;
|
|
177
|
+
let totalCacheRead = 0;
|
|
178
|
+
let totalCacheWrite = 0;
|
|
179
|
+
let totalCost = 0;
|
|
180
|
+
for (const msg of messages) {
|
|
181
|
+
totalCost += msg.info.cost ?? 0;
|
|
182
|
+
const tokens = msg.info.tokens;
|
|
183
|
+
if (tokens) {
|
|
184
|
+
totalInputTokens += tokens.input ?? 0;
|
|
185
|
+
totalOutputTokens += tokens.output ?? 0;
|
|
186
|
+
totalReasoningTokens += tokens.reasoning ?? 0;
|
|
187
|
+
totalCacheRead += tokens.cache?.read ?? 0;
|
|
188
|
+
totalCacheWrite += tokens.cache?.write ?? 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const sessionResult = await api.client.session.get({
|
|
192
|
+
sessionID: sessionId,
|
|
193
|
+
directory
|
|
194
|
+
});
|
|
195
|
+
const session = sessionResult.data;
|
|
196
|
+
const fileChanges = session?.summary ? {
|
|
197
|
+
additions: session.summary.additions,
|
|
198
|
+
deletions: session.summary.deletions,
|
|
199
|
+
files: session.summary.files
|
|
200
|
+
} : null;
|
|
201
|
+
const timing = session?.time?.created && session?.time?.updated ? {
|
|
202
|
+
created: session.time.created,
|
|
203
|
+
updated: session.time.updated,
|
|
204
|
+
durationMs: new Date(session.time.updated).getTime() - new Date(session.time.created).getTime()
|
|
205
|
+
} : null;
|
|
206
|
+
return {
|
|
207
|
+
tokens: {
|
|
208
|
+
input: totalInputTokens,
|
|
209
|
+
output: totalOutputTokens,
|
|
210
|
+
reasoning: totalReasoningTokens,
|
|
211
|
+
cacheRead: totalCacheRead,
|
|
212
|
+
cacheWrite: totalCacheWrite,
|
|
213
|
+
total: totalInputTokens + totalOutputTokens + totalReasoningTokens + totalCacheRead + totalCacheWrite
|
|
214
|
+
},
|
|
215
|
+
cost: totalCost,
|
|
216
|
+
messages: {
|
|
217
|
+
total: messages.length,
|
|
218
|
+
assistant: assistantMessages.length
|
|
219
|
+
},
|
|
220
|
+
fileChanges,
|
|
221
|
+
timing,
|
|
222
|
+
lastActivity
|
|
223
|
+
};
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/utils/logger.ts
|
|
230
|
+
var MAX_LOG_FILE_SIZE = 10 * 1024 * 1024;
|
|
231
|
+
function slugify(text) {
|
|
232
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").substring(0, 50);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/utils/plan-execution.ts
|
|
236
|
+
var PLAN_EXECUTION_LABELS = [
|
|
237
|
+
"New session",
|
|
238
|
+
"Execute here",
|
|
239
|
+
"Loop (worktree)",
|
|
240
|
+
"Loop"
|
|
241
|
+
];
|
|
242
|
+
function extractPlanTitle(planContent) {
|
|
243
|
+
const headingMatch = planContent.match(/^#+\s+(.+)$/m);
|
|
244
|
+
if (headingMatch?.[1]) {
|
|
245
|
+
const title = headingMatch[1].trim();
|
|
246
|
+
return title.length > 60 ? `${title.substring(0, 57)}...` : title;
|
|
247
|
+
}
|
|
248
|
+
const firstLine = planContent.split(`
|
|
249
|
+
`)[0]?.trim();
|
|
250
|
+
if (firstLine) {
|
|
251
|
+
return firstLine.length > 60 ? `${firstLine.substring(0, 57)}...` : firstLine;
|
|
252
|
+
}
|
|
253
|
+
return "Implementation Plan";
|
|
254
|
+
}
|
|
255
|
+
function extractLoopName(planContent) {
|
|
256
|
+
const loopNameMatch = planContent.match(/^(?:\s*(?:-\s*)?)?(?:\*\*)?Loop Name(?:\*\*)?:\s*(.+)$/m);
|
|
257
|
+
if (loopNameMatch?.[1]) {
|
|
258
|
+
const name = loopNameMatch[1].trim();
|
|
259
|
+
return name.length > 60 ? name.substring(0, 60) : name;
|
|
260
|
+
}
|
|
261
|
+
const title = extractPlanTitle(planContent);
|
|
262
|
+
return title;
|
|
263
|
+
}
|
|
264
|
+
function extractLoopNames(planContent) {
|
|
265
|
+
const displayName = extractLoopName(planContent);
|
|
266
|
+
const executionName = sanitizeLoopName(displayName);
|
|
267
|
+
return { displayName, executionName };
|
|
268
|
+
}
|
|
269
|
+
function sanitizeLoopName(name) {
|
|
270
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 60) || "loop";
|
|
271
|
+
}
|
|
272
|
+
function normalizeModeLabel(label) {
|
|
273
|
+
return label.toLowerCase();
|
|
274
|
+
}
|
|
275
|
+
function matchExecutionLabel(input) {
|
|
276
|
+
const normalized = normalizeModeLabel(input);
|
|
277
|
+
for (const label of PLAN_EXECUTION_LABELS) {
|
|
278
|
+
if (normalized === label.toLowerCase() || normalized.startsWith(label.toLowerCase())) {
|
|
279
|
+
return label;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/utils/loop-launch.ts
|
|
286
|
+
import { Database } from "bun:sqlite";
|
|
287
|
+
import { existsSync as existsSync2 } from "fs";
|
|
288
|
+
import { join as join2 } from "path";
|
|
289
|
+
|
|
290
|
+
// src/constants/loop.ts
|
|
291
|
+
var LOOP_PERMISSION_RULESET = [
|
|
292
|
+
{ permission: "*", pattern: "*", action: "allow" },
|
|
293
|
+
{ permission: "external_directory", pattern: "*", action: "deny" },
|
|
294
|
+
{ permission: "bash", pattern: "git push *", action: "deny" }
|
|
295
|
+
];
|
|
296
|
+
// src/services/loop.ts
|
|
297
|
+
var DEFAULT_COMPLETION_SIGNAL = "ALL_PHASES_COMPLETE";
|
|
298
|
+
function buildCompletionSignalInstructions(signal) {
|
|
299
|
+
return `
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
**IMPORTANT - Completion Signal:** When you have completed ALL phases of this plan successfully, you MUST output the following phrase exactly: ${signal}
|
|
304
|
+
|
|
305
|
+
Before outputting the completion signal, you MUST:
|
|
306
|
+
1. Verify each phase's acceptance criteria are met
|
|
307
|
+
2. Run all verification commands listed in the plan and confirm they pass
|
|
308
|
+
3. If tests were required, confirm they exist AND pass
|
|
309
|
+
|
|
310
|
+
Do NOT output this phrase until every phase is truly complete and all verification steps pass. The loop will continue until this signal is detected.`;
|
|
311
|
+
}
|
|
312
|
+
function generateUniqueName(baseName, existingNames) {
|
|
313
|
+
const maxLength = 25;
|
|
314
|
+
const truncated = baseName.length > maxLength ? baseName.substring(0, maxLength) : baseName;
|
|
315
|
+
if (!existingNames.includes(truncated)) {
|
|
316
|
+
return truncated;
|
|
317
|
+
}
|
|
318
|
+
let counter = 1;
|
|
319
|
+
let candidate = `${truncated}-${counter}`;
|
|
320
|
+
while (existingNames.includes(candidate)) {
|
|
321
|
+
counter++;
|
|
322
|
+
candidate = `${truncated}-${counter}`;
|
|
323
|
+
}
|
|
324
|
+
return candidate;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/utils/loop-launch.ts
|
|
328
|
+
async function launchFreshLoop(options) {
|
|
329
|
+
const { planText, title, directory, projectId, isWorktree, api } = options;
|
|
330
|
+
const { displayName, executionName } = extractLoopNames(planText);
|
|
331
|
+
const dbPath = options.dbPath ?? join2(resolveDataDir(), "graph.db");
|
|
332
|
+
const existingNames = [];
|
|
333
|
+
if (existsSync2(dbPath)) {
|
|
334
|
+
let db = null;
|
|
335
|
+
try {
|
|
336
|
+
db = new Database(dbPath, { readonly: true });
|
|
337
|
+
const stmt = db.prepare("SELECT data FROM project_kv WHERE project_id = ? AND key LIKE ? AND expires_at > ?");
|
|
338
|
+
const rows = stmt.all(projectId, "loop:%", Date.now());
|
|
339
|
+
for (const row of rows) {
|
|
340
|
+
try {
|
|
341
|
+
const state = JSON.parse(row.data);
|
|
342
|
+
if (state?.worktreeName) {
|
|
343
|
+
existingNames.push(state.worktreeName);
|
|
344
|
+
}
|
|
345
|
+
} catch {}
|
|
346
|
+
}
|
|
347
|
+
} catch {} finally {
|
|
348
|
+
try {
|
|
349
|
+
db?.close();
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const uniqueWorktreeName = generateUniqueName(executionName, existingNames);
|
|
354
|
+
let sessionId;
|
|
355
|
+
let sessionDirectory;
|
|
356
|
+
let worktreeBranch;
|
|
357
|
+
if (isWorktree) {
|
|
358
|
+
const worktreeResult = await api.client.worktree.create({
|
|
359
|
+
worktreeCreateInput: { name: uniqueWorktreeName }
|
|
360
|
+
});
|
|
361
|
+
if (worktreeResult.error || !worktreeResult.data) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
sessionDirectory = worktreeResult.data.directory;
|
|
365
|
+
worktreeBranch = worktreeResult.data.branch;
|
|
366
|
+
const createResult = await api.client.session.create({
|
|
367
|
+
title: `Loop: ${title}`,
|
|
368
|
+
directory: sessionDirectory
|
|
369
|
+
});
|
|
370
|
+
if (createResult.error || !createResult.data) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
sessionId = createResult.data.id;
|
|
374
|
+
} else {
|
|
375
|
+
const createResult = await api.client.session.create({
|
|
376
|
+
title: `Loop: ${title}`,
|
|
377
|
+
directory
|
|
378
|
+
});
|
|
379
|
+
if (createResult.error || !createResult.data) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
sessionId = createResult.data.id;
|
|
383
|
+
sessionDirectory = directory;
|
|
384
|
+
}
|
|
385
|
+
const dbExists = existsSync2(dbPath);
|
|
386
|
+
if (dbExists) {
|
|
387
|
+
let db = null;
|
|
388
|
+
try {
|
|
389
|
+
db = new Database(dbPath);
|
|
390
|
+
const queries = createKvQuery(db);
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
const TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
393
|
+
queries.set(projectId, `plan:${uniqueWorktreeName}`, JSON.stringify(planText), now + TTL_MS);
|
|
394
|
+
const loopState = {
|
|
395
|
+
active: true,
|
|
396
|
+
sessionId,
|
|
397
|
+
worktreeName: uniqueWorktreeName,
|
|
398
|
+
worktreeDir: sessionDirectory,
|
|
399
|
+
worktreeBranch,
|
|
400
|
+
iteration: 1,
|
|
401
|
+
maxIterations: 0,
|
|
402
|
+
completionSignal: DEFAULT_COMPLETION_SIGNAL,
|
|
403
|
+
startedAt: new Date().toISOString(),
|
|
404
|
+
prompt: planText,
|
|
405
|
+
phase: "coding",
|
|
406
|
+
audit: true,
|
|
407
|
+
errorCount: 0,
|
|
408
|
+
auditCount: 0,
|
|
409
|
+
worktree: isWorktree
|
|
410
|
+
};
|
|
411
|
+
queries.set(projectId, `loop:${uniqueWorktreeName}`, JSON.stringify(loopState), now + TTL_MS);
|
|
412
|
+
queries.set(projectId, `loop-session:${sessionId}`, JSON.stringify(uniqueWorktreeName), now + TTL_MS);
|
|
413
|
+
} catch {} finally {
|
|
414
|
+
try {
|
|
415
|
+
db?.close();
|
|
416
|
+
} catch {}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
let promptText = planText;
|
|
420
|
+
if (DEFAULT_COMPLETION_SIGNAL) {
|
|
421
|
+
promptText += buildCompletionSignalInstructions(DEFAULT_COMPLETION_SIGNAL);
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
await api.client.session.promptAsync({
|
|
425
|
+
sessionID: sessionId,
|
|
426
|
+
directory: sessionDirectory,
|
|
427
|
+
parts: [{ type: "text", text: promptText }],
|
|
428
|
+
agent: "code"
|
|
429
|
+
});
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
sessionId,
|
|
435
|
+
loopName: displayName,
|
|
436
|
+
worktreeName: uniqueWorktreeName,
|
|
437
|
+
isWorktree,
|
|
438
|
+
worktreeDir: sessionDirectory,
|
|
439
|
+
worktreeBranch
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/utils/tui-plan-store.ts
|
|
444
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
445
|
+
import { existsSync as existsSync3 } from "fs";
|
|
446
|
+
import { join as join3 } from "path";
|
|
447
|
+
function getDbPath() {
|
|
448
|
+
return join3(resolveDataDir(), "graph.db");
|
|
449
|
+
}
|
|
450
|
+
function resolvePlanKey(projectId, sessionID, dbPathOverride) {
|
|
451
|
+
const dbPath = dbPathOverride || getDbPath();
|
|
452
|
+
if (!existsSync3(dbPath)) {
|
|
453
|
+
return `plan:${sessionID}`;
|
|
454
|
+
}
|
|
455
|
+
let db = null;
|
|
456
|
+
try {
|
|
457
|
+
db = new Database2(dbPath, { readonly: true });
|
|
458
|
+
const now = Date.now();
|
|
459
|
+
const mappingRow = db.prepare("SELECT data FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, `loop-session:${sessionID}`, now);
|
|
460
|
+
if (mappingRow) {
|
|
461
|
+
try {
|
|
462
|
+
const worktreeName = JSON.parse(mappingRow.data);
|
|
463
|
+
if (typeof worktreeName === "string" && worktreeName) {
|
|
464
|
+
return `plan:${worktreeName}`;
|
|
465
|
+
}
|
|
466
|
+
} catch {}
|
|
467
|
+
}
|
|
468
|
+
} catch {} finally {
|
|
469
|
+
try {
|
|
470
|
+
db?.close();
|
|
471
|
+
} catch {}
|
|
472
|
+
}
|
|
473
|
+
return `plan:${sessionID}`;
|
|
474
|
+
}
|
|
475
|
+
function readPlan(projectId, sessionID, dbPathOverride) {
|
|
476
|
+
const dbPath = dbPathOverride || getDbPath();
|
|
477
|
+
if (!existsSync3(dbPath))
|
|
478
|
+
return null;
|
|
479
|
+
let db = null;
|
|
480
|
+
try {
|
|
481
|
+
db = new Database2(dbPath, { readonly: true });
|
|
482
|
+
const now = Date.now();
|
|
483
|
+
const planKey = resolvePlanKey(projectId, sessionID, dbPath);
|
|
484
|
+
const row = db.prepare("SELECT data FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, planKey, now);
|
|
485
|
+
if (!row)
|
|
486
|
+
return null;
|
|
487
|
+
const data = row.data;
|
|
488
|
+
if (typeof data === "string" && data.startsWith('"')) {
|
|
489
|
+
try {
|
|
490
|
+
return JSON.parse(data);
|
|
491
|
+
} catch {
|
|
492
|
+
return data;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return data;
|
|
496
|
+
} catch {
|
|
497
|
+
return null;
|
|
498
|
+
} finally {
|
|
499
|
+
try {
|
|
500
|
+
db?.close();
|
|
501
|
+
} catch {}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function writePlan(projectId, sessionID, content, dbPathOverride) {
|
|
505
|
+
const dbPath = dbPathOverride || getDbPath();
|
|
506
|
+
if (!existsSync3(dbPath))
|
|
507
|
+
return false;
|
|
508
|
+
let db = null;
|
|
509
|
+
try {
|
|
510
|
+
db = new Database2(dbPath);
|
|
511
|
+
const now = Date.now();
|
|
512
|
+
const ttl = 7 * 24 * 60 * 60 * 1000;
|
|
513
|
+
const planKey = resolvePlanKey(projectId, sessionID, dbPath);
|
|
514
|
+
db.prepare("INSERT OR REPLACE INTO project_kv (project_id, key, data, expires_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(projectId, planKey, JSON.stringify(content), now + ttl, now, now);
|
|
515
|
+
return true;
|
|
516
|
+
} catch {
|
|
517
|
+
return false;
|
|
518
|
+
} finally {
|
|
519
|
+
try {
|
|
520
|
+
db?.close();
|
|
521
|
+
} catch {}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function deletePlan(projectId, sessionID, dbPathOverride) {
|
|
525
|
+
const dbPath = dbPathOverride || getDbPath();
|
|
526
|
+
if (!existsSync3(dbPath))
|
|
527
|
+
return false;
|
|
528
|
+
let db = null;
|
|
529
|
+
try {
|
|
530
|
+
db = new Database2(dbPath);
|
|
531
|
+
const planKey = resolvePlanKey(projectId, sessionID, dbPath);
|
|
532
|
+
const result = db.prepare("DELETE FROM project_kv WHERE project_id = ? AND key = ?").run(projectId, planKey);
|
|
533
|
+
return (result.changes || 0) > 0;
|
|
534
|
+
} catch {
|
|
535
|
+
return false;
|
|
536
|
+
} finally {
|
|
537
|
+
try {
|
|
538
|
+
db?.close();
|
|
539
|
+
} catch {}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/utils/tui-graph-status.ts
|
|
544
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
545
|
+
import { existsSync as existsSync4 } from "fs";
|
|
546
|
+
import { join as join4 } from "path";
|
|
547
|
+
function getDbPath2() {
|
|
548
|
+
return join4(resolveDataDir(), "graph.db");
|
|
549
|
+
}
|
|
550
|
+
function readGraphStatus(projectId, dbPathOverride) {
|
|
551
|
+
const dbPath = dbPathOverride || getDbPath2();
|
|
552
|
+
if (!existsSync4(dbPath))
|
|
553
|
+
return null;
|
|
554
|
+
let db = null;
|
|
555
|
+
try {
|
|
556
|
+
db = new Database3(dbPath, { readonly: true });
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
const row = db.prepare("SELECT data FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, "graph:status", now);
|
|
559
|
+
if (!row)
|
|
560
|
+
return null;
|
|
561
|
+
try {
|
|
562
|
+
return JSON.parse(row.data);
|
|
563
|
+
} catch {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
} catch {
|
|
567
|
+
return null;
|
|
568
|
+
} finally {
|
|
569
|
+
try {
|
|
570
|
+
db?.close();
|
|
571
|
+
} catch {}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function formatGraphStatus(status) {
|
|
575
|
+
if (!status) {
|
|
576
|
+
return { text: "unavailable", color: "textMuted" };
|
|
577
|
+
}
|
|
578
|
+
switch (status.state) {
|
|
579
|
+
case "ready":
|
|
580
|
+
if (status.stats) {
|
|
581
|
+
return {
|
|
582
|
+
text: `ready · ${status.stats.files} files`,
|
|
583
|
+
color: "success"
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
text: "ready",
|
|
588
|
+
color: "success"
|
|
589
|
+
};
|
|
590
|
+
case "indexing":
|
|
591
|
+
return { text: "indexing", color: "warning" };
|
|
592
|
+
case "initializing":
|
|
593
|
+
return { text: "initializing", color: "info" };
|
|
594
|
+
case "error":
|
|
595
|
+
return { text: "error", color: "error" };
|
|
596
|
+
case "unavailable":
|
|
597
|
+
default:
|
|
598
|
+
return { text: "unavailable", color: "textMuted" };
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/utils/tui-refresh-helpers.ts
|
|
603
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
604
|
+
import { existsSync as existsSync5 } from "fs";
|
|
605
|
+
import { join as join5 } from "path";
|
|
606
|
+
function getDbPath3() {
|
|
607
|
+
return join5(resolveDataDir(), "graph.db");
|
|
608
|
+
}
|
|
609
|
+
function readLoopStates(projectId, dbPathOverride) {
|
|
610
|
+
const dbPath = dbPathOverride || getDbPath3();
|
|
611
|
+
if (!existsSync5(dbPath))
|
|
612
|
+
return [];
|
|
613
|
+
let db = null;
|
|
614
|
+
try {
|
|
615
|
+
db = new Database4(dbPath, { readonly: true });
|
|
616
|
+
const now = Date.now();
|
|
617
|
+
const stmt = db.prepare("SELECT key, data FROM project_kv WHERE project_id = ? AND key LIKE ? AND expires_at > ?");
|
|
618
|
+
const rows = stmt.all(projectId, "loop:%", now);
|
|
619
|
+
const loops = [];
|
|
620
|
+
for (const row of rows) {
|
|
621
|
+
try {
|
|
622
|
+
const state = JSON.parse(row.data);
|
|
623
|
+
if (!state.worktreeName || !state.sessionId)
|
|
624
|
+
continue;
|
|
625
|
+
loops.push({
|
|
626
|
+
name: state.worktreeName,
|
|
627
|
+
phase: state.phase ?? "coding",
|
|
628
|
+
iteration: state.iteration ?? 0,
|
|
629
|
+
maxIterations: state.maxIterations ?? 0,
|
|
630
|
+
sessionId: state.sessionId,
|
|
631
|
+
active: state.active ?? false,
|
|
632
|
+
startedAt: state.startedAt,
|
|
633
|
+
completedAt: state.completedAt,
|
|
634
|
+
terminationReason: state.terminationReason,
|
|
635
|
+
worktreeBranch: state.worktreeBranch,
|
|
636
|
+
worktree: state.worktree ?? false,
|
|
637
|
+
worktreeDir: state.worktreeDir
|
|
638
|
+
});
|
|
639
|
+
} catch {}
|
|
640
|
+
}
|
|
641
|
+
return loops;
|
|
642
|
+
} catch {
|
|
643
|
+
return [];
|
|
644
|
+
} finally {
|
|
645
|
+
try {
|
|
646
|
+
db?.close();
|
|
647
|
+
} catch {}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function readLoopByName(projectId, loopName, dbPathOverride) {
|
|
651
|
+
const dbPath = dbPathOverride || getDbPath3();
|
|
652
|
+
if (!existsSync5(dbPath))
|
|
653
|
+
return null;
|
|
654
|
+
let db = null;
|
|
655
|
+
try {
|
|
656
|
+
db = new Database4(dbPath, { readonly: true });
|
|
657
|
+
const now = Date.now();
|
|
658
|
+
const key = `loop:${loopName}`;
|
|
659
|
+
const row = db.prepare("SELECT data FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, key, now);
|
|
660
|
+
if (!row)
|
|
661
|
+
return null;
|
|
662
|
+
const state = JSON.parse(row.data);
|
|
663
|
+
if (!state.worktreeName || !state.sessionId)
|
|
664
|
+
return null;
|
|
665
|
+
return {
|
|
666
|
+
name: state.worktreeName,
|
|
667
|
+
phase: state.phase ?? "coding",
|
|
668
|
+
iteration: state.iteration ?? 0,
|
|
669
|
+
maxIterations: state.maxIterations ?? 0,
|
|
670
|
+
sessionId: state.sessionId,
|
|
671
|
+
active: state.active ?? false,
|
|
672
|
+
startedAt: state.startedAt,
|
|
673
|
+
completedAt: state.completedAt,
|
|
674
|
+
terminationReason: state.terminationReason,
|
|
675
|
+
worktreeBranch: state.worktreeBranch,
|
|
676
|
+
worktree: state.worktree ?? false,
|
|
677
|
+
worktreeDir: state.worktreeDir
|
|
678
|
+
};
|
|
679
|
+
} catch {
|
|
680
|
+
return null;
|
|
681
|
+
} finally {
|
|
682
|
+
try {
|
|
683
|
+
db?.close();
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/tui.tsx
|
|
689
|
+
function loadTuiConfig() {
|
|
690
|
+
try {
|
|
691
|
+
const defaultBase = join6(homedir2(), platform2() === "win32" ? "AppData" : ".config");
|
|
692
|
+
const configDir = process.env["XDG_CONFIG_HOME"] || defaultBase;
|
|
693
|
+
const configRoot = join6(configDir, "opencode");
|
|
694
|
+
const configPath = existsSync6(join6(configRoot, "forge-config.jsonc")) ? join6(configRoot, "forge-config.jsonc") : existsSync6(join6(configRoot, "memory-config.jsonc")) ? join6(configRoot, "memory-config.jsonc") : join6(configRoot, "graph-config.jsonc");
|
|
695
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
696
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
697
|
+
const parsed = JSON.parse(stripped);
|
|
698
|
+
return parsed?.tui;
|
|
699
|
+
} catch {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function resolveProjectId(directory) {
|
|
704
|
+
const cachePath = join6(directory, ".git", "opencode");
|
|
705
|
+
if (existsSync6(cachePath)) {
|
|
706
|
+
try {
|
|
707
|
+
const id = readFileSync(cachePath, "utf-8").trim();
|
|
708
|
+
if (id)
|
|
709
|
+
return id;
|
|
710
|
+
} catch {}
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const output = execSync("git rev-list --max-parents=0 --all", {
|
|
714
|
+
cwd: directory,
|
|
715
|
+
encoding: "utf-8"
|
|
716
|
+
}).trim();
|
|
717
|
+
const commits = output.split(`
|
|
718
|
+
`).filter(Boolean).sort();
|
|
719
|
+
if (commits[0])
|
|
720
|
+
return commits[0];
|
|
721
|
+
} catch {}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function cancelLoop(projectId, loopName) {
|
|
725
|
+
const dbPath = join6(resolveDataDir(), "graph.db");
|
|
726
|
+
if (!existsSync6(dbPath))
|
|
727
|
+
return null;
|
|
728
|
+
let db = null;
|
|
729
|
+
try {
|
|
730
|
+
db = new Database5(dbPath);
|
|
731
|
+
const key = `loop:${loopName}`;
|
|
732
|
+
const now = Date.now();
|
|
733
|
+
const row = db.prepare("SELECT data, project_id FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, key, now);
|
|
734
|
+
if (!row)
|
|
735
|
+
return null;
|
|
736
|
+
const state = JSON.parse(row.data);
|
|
737
|
+
if (!state.active)
|
|
738
|
+
return null;
|
|
739
|
+
const updatedState = {
|
|
740
|
+
...state,
|
|
741
|
+
active: false,
|
|
742
|
+
completedAt: new Date().toISOString(),
|
|
743
|
+
terminationReason: "cancelled"
|
|
744
|
+
};
|
|
745
|
+
db.prepare("UPDATE project_kv SET data = ?, updated_at = ? WHERE project_id = ? AND key = ?").run(JSON.stringify(updatedState), now, projectId, key);
|
|
746
|
+
return state.sessionId ?? null;
|
|
747
|
+
} catch {
|
|
748
|
+
return null;
|
|
749
|
+
} finally {
|
|
750
|
+
try {
|
|
751
|
+
db?.close();
|
|
752
|
+
} catch {}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function restartLoop(projectId, loopName, api) {
|
|
756
|
+
const dbPath = join6(resolveDataDir(), "graph.db");
|
|
757
|
+
if (!existsSync6(dbPath))
|
|
758
|
+
return null;
|
|
759
|
+
let db = null;
|
|
760
|
+
try {
|
|
761
|
+
db = new Database5(dbPath);
|
|
762
|
+
const key = `loop:${loopName}`;
|
|
763
|
+
const now = Date.now();
|
|
764
|
+
const row = db.prepare("SELECT data, project_id FROM project_kv WHERE project_id = ? AND key = ? AND expires_at > ?").get(projectId, key, now);
|
|
765
|
+
if (!row)
|
|
766
|
+
return null;
|
|
767
|
+
const state = JSON.parse(row.data);
|
|
768
|
+
if (state.active) {
|
|
769
|
+
try {
|
|
770
|
+
await api.client.session.abort({
|
|
771
|
+
sessionID: state.sessionId
|
|
772
|
+
});
|
|
773
|
+
} catch {}
|
|
774
|
+
const oldSessionKey = `loop-session:${state.sessionId}`;
|
|
775
|
+
db.prepare("DELETE FROM project_kv WHERE project_id = ? AND key = ?").run(projectId, oldSessionKey);
|
|
776
|
+
}
|
|
777
|
+
const directory = state.worktreeDir;
|
|
778
|
+
if (!directory)
|
|
779
|
+
return null;
|
|
780
|
+
const createResult = await api.client.session.create({
|
|
781
|
+
directory,
|
|
782
|
+
title: loopName,
|
|
783
|
+
permission: LOOP_PERMISSION_RULESET
|
|
784
|
+
});
|
|
785
|
+
if (createResult.error || !createResult.data)
|
|
786
|
+
return null;
|
|
787
|
+
const newSessionId = createResult.data.id;
|
|
788
|
+
const sessionKey = `loop-session:${newSessionId}`;
|
|
789
|
+
const ttl = 30 * 24 * 60 * 60 * 1000;
|
|
790
|
+
db.prepare("INSERT OR REPLACE INTO project_kv (project_id, key, data, expires_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(projectId, sessionKey, JSON.stringify(loopName), now + ttl, now);
|
|
791
|
+
const newState = {
|
|
792
|
+
...state,
|
|
793
|
+
active: true,
|
|
794
|
+
sessionId: newSessionId,
|
|
795
|
+
phase: "coding",
|
|
796
|
+
errorCount: 0,
|
|
797
|
+
auditCount: 0,
|
|
798
|
+
startedAt: new Date().toISOString(),
|
|
799
|
+
completedAt: undefined,
|
|
800
|
+
terminationReason: undefined
|
|
801
|
+
};
|
|
802
|
+
db.prepare("UPDATE project_kv SET data = ?, updated_at = ? WHERE project_id = ? AND key = ?").run(JSON.stringify(newState), now, projectId, key);
|
|
803
|
+
let promptText = state.prompt ?? "";
|
|
804
|
+
if (state.completionSignal) {
|
|
805
|
+
const completionInstructions = `
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
**IMPORTANT - Completion Signal:** When you have completed ALL phases of this plan successfully, you MUST output the following phrase exactly: ${state.completionSignal}
|
|
810
|
+
|
|
811
|
+
Before outputting the completion signal, you MUST:
|
|
812
|
+
1. Verify each phase's acceptance criteria are met
|
|
813
|
+
2. Run all verification commands listed in the plan and confirm they pass
|
|
814
|
+
3. If tests were required, confirm they exist AND pass
|
|
815
|
+
|
|
816
|
+
Do NOT output this phrase until every phase is truly complete and all verification steps pass. The loop will continue until this signal is detected.`;
|
|
817
|
+
promptText += completionInstructions;
|
|
818
|
+
}
|
|
819
|
+
await api.client.session.promptAsync({
|
|
820
|
+
sessionID: newSessionId,
|
|
821
|
+
directory,
|
|
822
|
+
parts: [{
|
|
823
|
+
type: "text",
|
|
824
|
+
text: promptText
|
|
825
|
+
}],
|
|
826
|
+
agent: "code"
|
|
827
|
+
});
|
|
828
|
+
return newSessionId;
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
} finally {
|
|
832
|
+
try {
|
|
833
|
+
db?.close();
|
|
834
|
+
} catch {}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function formatTokens(n) {
|
|
838
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
|
|
839
|
+
}
|
|
840
|
+
function formatDuration(ms) {
|
|
841
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
842
|
+
const minutes = Math.floor(ms % (1000 * 60 * 60) / (1000 * 60));
|
|
843
|
+
const seconds = Math.floor(ms % (1000 * 60) / 1000);
|
|
844
|
+
if (hours > 0) {
|
|
845
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
846
|
+
}
|
|
847
|
+
if (minutes > 0) {
|
|
848
|
+
return `${minutes}m ${seconds}s`;
|
|
849
|
+
}
|
|
850
|
+
return `${seconds}s`;
|
|
851
|
+
}
|
|
852
|
+
function truncate(text, maxLength) {
|
|
853
|
+
if (text.length <= maxLength)
|
|
854
|
+
return text;
|
|
855
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
856
|
+
}
|
|
857
|
+
function truncateMiddle(text, maxLength) {
|
|
858
|
+
if (text.length <= maxLength)
|
|
859
|
+
return text;
|
|
860
|
+
const keep = maxLength - 5;
|
|
861
|
+
const start = Math.ceil(keep / 2);
|
|
862
|
+
const end = Math.floor(keep / 2);
|
|
863
|
+
return text.slice(0, start) + "....." + text.slice(text.length - end);
|
|
864
|
+
}
|
|
865
|
+
function PlanViewerDialog(props) {
|
|
866
|
+
const theme = () => props.api.theme.current;
|
|
867
|
+
const [editing, setEditing] = createSignal(false);
|
|
868
|
+
const [executing, setExecuting] = createSignal(false);
|
|
869
|
+
const [content, setContent] = createSignal(props.planContent);
|
|
870
|
+
let textareaRef;
|
|
871
|
+
const handleSave = () => {
|
|
872
|
+
const text = textareaRef?.plainText ?? content();
|
|
873
|
+
const saved = writePlan(props.projectId, props.sessionId, text);
|
|
874
|
+
props.api.ui.toast({
|
|
875
|
+
message: saved ? "Plan saved" : "Failed to save plan",
|
|
876
|
+
variant: saved ? "success" : "error",
|
|
877
|
+
duration: 3000
|
|
878
|
+
});
|
|
879
|
+
if (saved) {
|
|
880
|
+
setContent(text);
|
|
881
|
+
setEditing(false);
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
const handleExport = () => {
|
|
885
|
+
const planText = content();
|
|
886
|
+
const title = extractPlanTitle(planText);
|
|
887
|
+
const slugifiedTitle = slugify(title);
|
|
888
|
+
const directory = props.api.state.path.directory;
|
|
889
|
+
const filename = `${slugifiedTitle}.md`;
|
|
890
|
+
const filepath = join6(directory, filename);
|
|
891
|
+
try {
|
|
892
|
+
writeFileSync(filepath, planText, "utf-8");
|
|
893
|
+
props.api.ui.toast({
|
|
894
|
+
message: `Exported plan to ${filename}`,
|
|
895
|
+
variant: "success",
|
|
896
|
+
duration: 3000
|
|
897
|
+
});
|
|
898
|
+
} catch (error) {
|
|
899
|
+
props.api.ui.toast({
|
|
900
|
+
message: `Failed to export plan: ${error.message}`,
|
|
901
|
+
variant: "error",
|
|
902
|
+
duration: 3000
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
function getModeDescription(label) {
|
|
907
|
+
switch (label) {
|
|
908
|
+
case "New session":
|
|
909
|
+
return "Create a new session and send the plan to the code agent";
|
|
910
|
+
case "Execute here":
|
|
911
|
+
return "Execute the plan in the current session using the code agent";
|
|
912
|
+
case "Loop (worktree)":
|
|
913
|
+
return "Execute using iterative development loop in an isolated git worktree";
|
|
914
|
+
case "Loop":
|
|
915
|
+
return "Execute using iterative development loop in the current directory";
|
|
916
|
+
default:
|
|
917
|
+
return "";
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const handleExecuteMode = async (mode) => {
|
|
921
|
+
const planText = content();
|
|
922
|
+
const title = extractPlanTitle(planText);
|
|
923
|
+
const directory = props.api.state.path.directory;
|
|
924
|
+
const pid = resolveProjectId(directory);
|
|
925
|
+
if (!pid) {
|
|
926
|
+
props.api.ui.toast({
|
|
927
|
+
message: "Failed to resolve project ID",
|
|
928
|
+
variant: "error",
|
|
929
|
+
duration: 3000
|
|
930
|
+
});
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const matchedLabel = matchExecutionLabel(mode);
|
|
934
|
+
switch (matchedLabel) {
|
|
935
|
+
case "New session": {
|
|
936
|
+
props.api.ui.dialog.clear();
|
|
937
|
+
props.api.ui.toast({
|
|
938
|
+
message: "Creating new session for plan execution...",
|
|
939
|
+
variant: "info",
|
|
940
|
+
duration: 3000
|
|
941
|
+
});
|
|
942
|
+
try {
|
|
943
|
+
const createResult = await props.api.client.session.create({
|
|
944
|
+
title,
|
|
945
|
+
directory
|
|
946
|
+
});
|
|
947
|
+
if (createResult.error || !createResult.data) {
|
|
948
|
+
props.api.ui.toast({
|
|
949
|
+
message: "Failed to create new session",
|
|
950
|
+
variant: "error",
|
|
951
|
+
duration: 3000
|
|
952
|
+
});
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const newSessionId = createResult.data.id;
|
|
956
|
+
if (pid) {
|
|
957
|
+
deletePlan(pid, props.sessionId);
|
|
958
|
+
}
|
|
959
|
+
await props.api.client.session.promptAsync({
|
|
960
|
+
sessionID: newSessionId,
|
|
961
|
+
directory,
|
|
962
|
+
agent: "code",
|
|
963
|
+
parts: [{
|
|
964
|
+
type: "text",
|
|
965
|
+
text: planText
|
|
966
|
+
}]
|
|
967
|
+
});
|
|
968
|
+
props.api.ui.toast({
|
|
969
|
+
message: `New session created: ${title}`,
|
|
970
|
+
variant: "success",
|
|
971
|
+
duration: 3000
|
|
972
|
+
});
|
|
973
|
+
props.onRefresh?.();
|
|
974
|
+
try {
|
|
975
|
+
props.api.route.navigate("session", {
|
|
976
|
+
sessionID: newSessionId
|
|
977
|
+
});
|
|
978
|
+
} catch {}
|
|
979
|
+
} catch {
|
|
980
|
+
props.api.ui.toast({
|
|
981
|
+
message: "Failed to create new session",
|
|
982
|
+
variant: "error",
|
|
983
|
+
duration: 3000
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "Execute here": {
|
|
989
|
+
props.api.ui.dialog.clear();
|
|
990
|
+
props.api.ui.toast({
|
|
991
|
+
message: "Switching to code agent for plan execution...",
|
|
992
|
+
variant: "info",
|
|
993
|
+
duration: 3000
|
|
994
|
+
});
|
|
995
|
+
const inPlacePrompt = `The architect agent has created an implementation plan. You are now the code agent taking over this session. Your job is to execute the plan — edit files, run commands, create tests, and implement every phase. Do NOT just describe or summarize the changes. Actually make them.
|
|
996
|
+
|
|
997
|
+
Implementation Plan:
|
|
998
|
+
${planText}`;
|
|
999
|
+
try {
|
|
1000
|
+
await props.api.client.session.promptAsync({
|
|
1001
|
+
sessionID: props.sessionId,
|
|
1002
|
+
directory,
|
|
1003
|
+
agent: "code",
|
|
1004
|
+
parts: [{
|
|
1005
|
+
type: "text",
|
|
1006
|
+
text: inPlacePrompt
|
|
1007
|
+
}]
|
|
1008
|
+
});
|
|
1009
|
+
props.api.ui.toast({
|
|
1010
|
+
message: "Executing plan in current session",
|
|
1011
|
+
variant: "success",
|
|
1012
|
+
duration: 3000
|
|
1013
|
+
});
|
|
1014
|
+
props.onRefresh?.();
|
|
1015
|
+
} catch {
|
|
1016
|
+
props.api.ui.toast({
|
|
1017
|
+
message: "Failed to execute plan in current session",
|
|
1018
|
+
variant: "error",
|
|
1019
|
+
duration: 3000
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
case "Loop (worktree)":
|
|
1025
|
+
case "Loop": {
|
|
1026
|
+
const isWorktree = matchedLabel === "Loop (worktree)";
|
|
1027
|
+
props.api.ui.dialog.clear();
|
|
1028
|
+
props.api.ui.toast({
|
|
1029
|
+
message: isWorktree ? "Starting loop in worktree..." : "Starting loop in-place...",
|
|
1030
|
+
variant: "info",
|
|
1031
|
+
duration: 3000
|
|
1032
|
+
});
|
|
1033
|
+
try {
|
|
1034
|
+
const launchResult = await launchFreshLoop({
|
|
1035
|
+
planText,
|
|
1036
|
+
title,
|
|
1037
|
+
directory,
|
|
1038
|
+
projectId: pid,
|
|
1039
|
+
isWorktree,
|
|
1040
|
+
api: props.api
|
|
1041
|
+
});
|
|
1042
|
+
if (launchResult) {
|
|
1043
|
+
if (pid) {
|
|
1044
|
+
deletePlan(pid, props.sessionId);
|
|
1045
|
+
}
|
|
1046
|
+
props.api.ui.toast({
|
|
1047
|
+
message: isWorktree ? `Loop started in worktree: ${launchResult.loopName}` : `Loop started: ${launchResult.loopName}`,
|
|
1048
|
+
variant: "success",
|
|
1049
|
+
duration: 3000
|
|
1050
|
+
});
|
|
1051
|
+
props.onRefresh?.();
|
|
1052
|
+
}
|
|
1053
|
+
} catch {
|
|
1054
|
+
props.api.ui.toast({
|
|
1055
|
+
message: "Failed to start loop",
|
|
1056
|
+
variant: "error",
|
|
1057
|
+
duration: 3000
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
default: {
|
|
1063
|
+
props.api.ui.toast({
|
|
1064
|
+
message: "Unknown execution mode",
|
|
1065
|
+
variant: "error",
|
|
1066
|
+
duration: 3000
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
return (() => {
|
|
1072
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("b"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$0 = _$createElement("text"), _el$10 = _$createElement("text"), _el$21 = _$createElement("box"), _el$26 = _$createElement("text");
|
|
1073
|
+
_$insertNode(_el$, _el$2);
|
|
1074
|
+
_$insertNode(_el$, _el$21);
|
|
1075
|
+
_$setProp(_el$, "flexDirection", "column");
|
|
1076
|
+
_$setProp(_el$, "paddingX", 2);
|
|
1077
|
+
_$insertNode(_el$2, _el$3);
|
|
1078
|
+
_$insertNode(_el$2, _el$6);
|
|
1079
|
+
_$insertNode(_el$2, _el$8);
|
|
1080
|
+
_$insertNode(_el$2, _el$0);
|
|
1081
|
+
_$insertNode(_el$2, _el$10);
|
|
1082
|
+
_$setProp(_el$2, "flexShrink", 0);
|
|
1083
|
+
_$setProp(_el$2, "paddingBottom", 1);
|
|
1084
|
+
_$setProp(_el$2, "flexDirection", "row");
|
|
1085
|
+
_$setProp(_el$2, "gap", 2);
|
|
1086
|
+
_$insertNode(_el$3, _el$4);
|
|
1087
|
+
_$insertNode(_el$4, _$createTextNode(`Plan`));
|
|
1088
|
+
_$insertNode(_el$6, _$createTextNode(`[view]`));
|
|
1089
|
+
_$setProp(_el$6, "onMouseUp", () => {
|
|
1090
|
+
setEditing(false);
|
|
1091
|
+
setExecuting(false);
|
|
1092
|
+
});
|
|
1093
|
+
_$insertNode(_el$8, _$createTextNode(`[edit]`));
|
|
1094
|
+
_$setProp(_el$8, "onMouseUp", () => {
|
|
1095
|
+
setEditing(true);
|
|
1096
|
+
setExecuting(false);
|
|
1097
|
+
});
|
|
1098
|
+
_$insertNode(_el$0, _$createTextNode(`[execute]`));
|
|
1099
|
+
_$setProp(_el$0, "onMouseUp", () => {
|
|
1100
|
+
setEditing(false);
|
|
1101
|
+
setExecuting(true);
|
|
1102
|
+
});
|
|
1103
|
+
_$insertNode(_el$10, _$createTextNode(`[export]`));
|
|
1104
|
+
_$setProp(_el$10, "onMouseUp", handleExport);
|
|
1105
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
1106
|
+
get when() {
|
|
1107
|
+
return _$memo(() => !!!editing())() && !executing();
|
|
1108
|
+
},
|
|
1109
|
+
get children() {
|
|
1110
|
+
var _el$12 = _$createElement("scrollbox"), _el$13 = _$createElement("markdown");
|
|
1111
|
+
_$insertNode(_el$12, _el$13);
|
|
1112
|
+
_$setProp(_el$12, "minHeight", 20);
|
|
1113
|
+
_$setProp(_el$12, "maxHeight", "75%");
|
|
1114
|
+
_$setProp(_el$12, "borderStyle", "rounded");
|
|
1115
|
+
_$setProp(_el$12, "paddingX", 1);
|
|
1116
|
+
_$effect((_p$) => {
|
|
1117
|
+
var _v$ = theme().border, _v$2 = content(), _v$3 = SyntaxStyle.create(), _v$4 = theme().markdownText;
|
|
1118
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$12, "borderColor", _v$, _p$.e));
|
|
1119
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$13, "content", _v$2, _p$.t));
|
|
1120
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$13, "syntaxStyle", _v$3, _p$.a));
|
|
1121
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$13, "fg", _v$4, _p$.o));
|
|
1122
|
+
return _p$;
|
|
1123
|
+
}, {
|
|
1124
|
+
e: undefined,
|
|
1125
|
+
t: undefined,
|
|
1126
|
+
a: undefined,
|
|
1127
|
+
o: undefined
|
|
1128
|
+
});
|
|
1129
|
+
return _el$12;
|
|
1130
|
+
}
|
|
1131
|
+
}), _el$21);
|
|
1132
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
1133
|
+
get when() {
|
|
1134
|
+
return editing();
|
|
1135
|
+
},
|
|
1136
|
+
get children() {
|
|
1137
|
+
var _el$14 = _$createElement("textarea");
|
|
1138
|
+
_$use((value) => {
|
|
1139
|
+
textareaRef = value;
|
|
1140
|
+
}, _el$14);
|
|
1141
|
+
_$setProp(_el$14, "focused", true);
|
|
1142
|
+
_$setProp(_el$14, "minHeight", 20);
|
|
1143
|
+
_$setProp(_el$14, "maxHeight", "75%");
|
|
1144
|
+
_$setProp(_el$14, "paddingX", 1);
|
|
1145
|
+
_$effect((_$p) => _$setProp(_el$14, "initialValue", content(), _$p));
|
|
1146
|
+
return _el$14;
|
|
1147
|
+
}
|
|
1148
|
+
}), _el$21);
|
|
1149
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
1150
|
+
get when() {
|
|
1151
|
+
return executing();
|
|
1152
|
+
},
|
|
1153
|
+
get children() {
|
|
1154
|
+
var _el$15 = _$createElement("box"), _el$16 = _$createElement("box"), _el$17 = _$createElement("text"), _el$18 = _$createElement("b"), _el$20 = _$createElement("select");
|
|
1155
|
+
_$insertNode(_el$15, _el$16);
|
|
1156
|
+
_$insertNode(_el$15, _el$20);
|
|
1157
|
+
_$setProp(_el$15, "flexDirection", "column");
|
|
1158
|
+
_$setProp(_el$15, "paddingBottom", 1);
|
|
1159
|
+
_$setProp(_el$15, "gap", 1);
|
|
1160
|
+
_$setProp(_el$15, "minHeight", 20);
|
|
1161
|
+
_$setProp(_el$15, "maxHeight", "75%");
|
|
1162
|
+
_$insertNode(_el$16, _el$17);
|
|
1163
|
+
_$setProp(_el$16, "paddingBottom", 1);
|
|
1164
|
+
_$insertNode(_el$17, _el$18);
|
|
1165
|
+
_$insertNode(_el$18, _$createTextNode(`Select Execution Mode`));
|
|
1166
|
+
_$setProp(_el$20, "focused", true);
|
|
1167
|
+
_$setProp(_el$20, "onSelect", (_, option) => {
|
|
1168
|
+
if (option?.value) {
|
|
1169
|
+
handleExecuteMode(option.value);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
_$setProp(_el$20, "showDescription", false);
|
|
1173
|
+
_$setProp(_el$20, "itemSpacing", 1);
|
|
1174
|
+
_$setProp(_el$20, "wrapSelection", true);
|
|
1175
|
+
_$setProp(_el$20, "selectedTextColor", "#ffffff");
|
|
1176
|
+
_$setProp(_el$20, "minHeight", 12);
|
|
1177
|
+
_$setProp(_el$20, "flexGrow", 1);
|
|
1178
|
+
_$effect((_p$) => {
|
|
1179
|
+
var _v$5 = theme().text, _v$6 = PLAN_EXECUTION_LABELS.map((label) => ({
|
|
1180
|
+
name: label,
|
|
1181
|
+
description: getModeDescription(label),
|
|
1182
|
+
value: label
|
|
1183
|
+
})), _v$7 = theme().text, _v$8 = theme().text, _v$9 = theme().borderActive;
|
|
1184
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$17, "fg", _v$5, _p$.e));
|
|
1185
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$20, "options", _v$6, _p$.t));
|
|
1186
|
+
_v$7 !== _p$.a && (_p$.a = _$setProp(_el$20, "textColor", _v$7, _p$.a));
|
|
1187
|
+
_v$8 !== _p$.o && (_p$.o = _$setProp(_el$20, "focusedTextColor", _v$8, _p$.o));
|
|
1188
|
+
_v$9 !== _p$.i && (_p$.i = _$setProp(_el$20, "selectedBackgroundColor", _v$9, _p$.i));
|
|
1189
|
+
return _p$;
|
|
1190
|
+
}, {
|
|
1191
|
+
e: undefined,
|
|
1192
|
+
t: undefined,
|
|
1193
|
+
a: undefined,
|
|
1194
|
+
o: undefined,
|
|
1195
|
+
i: undefined
|
|
1196
|
+
});
|
|
1197
|
+
return _el$15;
|
|
1198
|
+
}
|
|
1199
|
+
}), _el$21);
|
|
1200
|
+
_$insertNode(_el$21, _el$26);
|
|
1201
|
+
_$setProp(_el$21, "paddingTop", 1);
|
|
1202
|
+
_$setProp(_el$21, "flexShrink", 0);
|
|
1203
|
+
_$setProp(_el$21, "flexDirection", "row");
|
|
1204
|
+
_$setProp(_el$21, "gap", 2);
|
|
1205
|
+
_$insert(_el$21, _$createComponent(Show, {
|
|
1206
|
+
get when() {
|
|
1207
|
+
return editing();
|
|
1208
|
+
},
|
|
1209
|
+
get children() {
|
|
1210
|
+
var _el$22 = _$createElement("text");
|
|
1211
|
+
_$insertNode(_el$22, _$createTextNode(`Save`));
|
|
1212
|
+
_$setProp(_el$22, "onMouseUp", handleSave);
|
|
1213
|
+
_$effect((_$p) => _$setProp(_el$22, "fg", theme().success, _$p));
|
|
1214
|
+
return _el$22;
|
|
1215
|
+
}
|
|
1216
|
+
}), _el$26);
|
|
1217
|
+
_$insert(_el$21, _$createComponent(Show, {
|
|
1218
|
+
get when() {
|
|
1219
|
+
return executing();
|
|
1220
|
+
},
|
|
1221
|
+
get children() {
|
|
1222
|
+
var _el$24 = _$createElement("text");
|
|
1223
|
+
_$insertNode(_el$24, _$createTextNode(`Back to plan`));
|
|
1224
|
+
_$setProp(_el$24, "onMouseUp", () => setExecuting(false));
|
|
1225
|
+
_$effect((_$p) => _$setProp(_el$24, "fg", theme().textMuted, _$p));
|
|
1226
|
+
return _el$24;
|
|
1227
|
+
}
|
|
1228
|
+
}), _el$26);
|
|
1229
|
+
_$insertNode(_el$26, _$createTextNode(`Close (esc)`));
|
|
1230
|
+
_$setProp(_el$26, "onMouseUp", () => props.api.ui.dialog.clear());
|
|
1231
|
+
_$effect((_p$) => {
|
|
1232
|
+
var _v$0 = theme().text, _v$1 = executing() ? theme().textMuted : editing() ? theme().text : theme().info, _v$10 = editing() ? theme().text : theme().textMuted, _v$11 = executing() ? theme().text : theme().textMuted, _v$12 = theme().textMuted, _v$13 = theme().textMuted;
|
|
1233
|
+
_v$0 !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$0, _p$.e));
|
|
1234
|
+
_v$1 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$1, _p$.t));
|
|
1235
|
+
_v$10 !== _p$.a && (_p$.a = _$setProp(_el$8, "fg", _v$10, _p$.a));
|
|
1236
|
+
_v$11 !== _p$.o && (_p$.o = _$setProp(_el$0, "fg", _v$11, _p$.o));
|
|
1237
|
+
_v$12 !== _p$.i && (_p$.i = _$setProp(_el$10, "fg", _v$12, _p$.i));
|
|
1238
|
+
_v$13 !== _p$.n && (_p$.n = _$setProp(_el$26, "fg", _v$13, _p$.n));
|
|
1239
|
+
return _p$;
|
|
1240
|
+
}, {
|
|
1241
|
+
e: undefined,
|
|
1242
|
+
t: undefined,
|
|
1243
|
+
a: undefined,
|
|
1244
|
+
o: undefined,
|
|
1245
|
+
i: undefined,
|
|
1246
|
+
n: undefined
|
|
1247
|
+
});
|
|
1248
|
+
return _el$;
|
|
1249
|
+
})();
|
|
1250
|
+
}
|
|
1251
|
+
function LoopDetailsDialog(props) {
|
|
1252
|
+
const theme = () => props.api.theme.current;
|
|
1253
|
+
const [currentLoop, setCurrentLoop] = createSignal(props.loop);
|
|
1254
|
+
const [stats, setStats] = createSignal(null);
|
|
1255
|
+
const [loading, setLoading] = createSignal(true);
|
|
1256
|
+
const directory = props.api.state.path.directory;
|
|
1257
|
+
const pid = resolveProjectId(directory);
|
|
1258
|
+
const refreshLoopState = () => {
|
|
1259
|
+
if (pid && currentLoop().name) {
|
|
1260
|
+
const freshLoop = readLoopByName(pid, currentLoop().name);
|
|
1261
|
+
if (freshLoop) {
|
|
1262
|
+
setCurrentLoop(freshLoop);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
refreshLoopState();
|
|
1267
|
+
createEffect(() => {
|
|
1268
|
+
const loop = currentLoop();
|
|
1269
|
+
if (loop.sessionId && directory) {
|
|
1270
|
+
setLoading(true);
|
|
1271
|
+
fetchSessionStats(props.api, loop.sessionId, directory).then((result) => {
|
|
1272
|
+
setStats(result);
|
|
1273
|
+
setLoading(false);
|
|
1274
|
+
}).catch(() => {
|
|
1275
|
+
setStats(null);
|
|
1276
|
+
setLoading(false);
|
|
1277
|
+
});
|
|
1278
|
+
} else {
|
|
1279
|
+
setLoading(false);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
const handleCancel = () => {
|
|
1283
|
+
props.api.ui.dialog.clear();
|
|
1284
|
+
const directory2 = props.api.state.path.directory;
|
|
1285
|
+
const pid2 = resolveProjectId(directory2);
|
|
1286
|
+
if (!pid2)
|
|
1287
|
+
return;
|
|
1288
|
+
const sessionId = cancelLoop(pid2, currentLoop().name);
|
|
1289
|
+
if (sessionId) {
|
|
1290
|
+
props.api.client.session.abort({
|
|
1291
|
+
sessionID: sessionId
|
|
1292
|
+
}).catch(() => {});
|
|
1293
|
+
}
|
|
1294
|
+
props.api.ui.toast({
|
|
1295
|
+
message: sessionId ? `Cancelled loop: ${currentLoop().name}` : `Loop ${currentLoop().name} is not active`,
|
|
1296
|
+
variant: sessionId ? "success" : "info",
|
|
1297
|
+
duration: 3000
|
|
1298
|
+
});
|
|
1299
|
+
props.onRefresh?.();
|
|
1300
|
+
};
|
|
1301
|
+
const handleRestart = async () => {
|
|
1302
|
+
props.api.ui.dialog.clear();
|
|
1303
|
+
const directory2 = props.api.state.path.directory;
|
|
1304
|
+
const pid2 = resolveProjectId(directory2);
|
|
1305
|
+
if (!pid2)
|
|
1306
|
+
return;
|
|
1307
|
+
const newSessionId = await restartLoop(pid2, currentLoop().name, props.api);
|
|
1308
|
+
const label = currentLoop().active ? "Force restarting" : "Restarting";
|
|
1309
|
+
props.api.ui.toast({
|
|
1310
|
+
message: newSessionId ? `${label} loop: ${currentLoop().name}` : `Failed to restart loop: ${currentLoop().name}`,
|
|
1311
|
+
variant: newSessionId ? "success" : "error",
|
|
1312
|
+
duration: 3000
|
|
1313
|
+
});
|
|
1314
|
+
props.onRefresh?.();
|
|
1315
|
+
};
|
|
1316
|
+
const statusBadge = () => {
|
|
1317
|
+
const loop = currentLoop();
|
|
1318
|
+
if (loop.active)
|
|
1319
|
+
return {
|
|
1320
|
+
text: loop.phase,
|
|
1321
|
+
color: loop.phase === "auditing" ? theme().warning : theme().success
|
|
1322
|
+
};
|
|
1323
|
+
if (loop.terminationReason === "completed")
|
|
1324
|
+
return {
|
|
1325
|
+
text: "completed",
|
|
1326
|
+
color: theme().success
|
|
1327
|
+
};
|
|
1328
|
+
if (loop.terminationReason === "cancelled" || loop.terminationReason === "user_aborted")
|
|
1329
|
+
return {
|
|
1330
|
+
text: "cancelled",
|
|
1331
|
+
color: theme().textMuted
|
|
1332
|
+
};
|
|
1333
|
+
return {
|
|
1334
|
+
text: "ended",
|
|
1335
|
+
color: theme().error
|
|
1336
|
+
};
|
|
1337
|
+
};
|
|
1338
|
+
return (() => {
|
|
1339
|
+
var _el$28 = _$createElement("box"), _el$29 = _$createElement("box"), _el$30 = _$createElement("box"), _el$31 = _$createElement("text"), _el$32 = _$createElement("b"), _el$33 = _$createElement("text"), _el$34 = _$createElement("b"), _el$35 = _$createTextNode(`[`), _el$36 = _$createTextNode(`]`), _el$37 = _$createElement("box"), _el$38 = _$createElement("text"), _el$39 = _$createTextNode(`Iteration `), _el$90 = _$createElement("box"), _el$99 = _$createElement("text");
|
|
1340
|
+
_$insertNode(_el$28, _el$29);
|
|
1341
|
+
_$insertNode(_el$28, _el$90);
|
|
1342
|
+
_$setProp(_el$28, "flexDirection", "column");
|
|
1343
|
+
_$setProp(_el$28, "paddingX", 2);
|
|
1344
|
+
_$insertNode(_el$29, _el$30);
|
|
1345
|
+
_$insertNode(_el$29, _el$37);
|
|
1346
|
+
_$setProp(_el$29, "flexDirection", "column");
|
|
1347
|
+
_$setProp(_el$29, "flexShrink", 0);
|
|
1348
|
+
_$insertNode(_el$30, _el$31);
|
|
1349
|
+
_$insertNode(_el$30, _el$33);
|
|
1350
|
+
_$setProp(_el$30, "flexDirection", "row");
|
|
1351
|
+
_$setProp(_el$30, "gap", 1);
|
|
1352
|
+
_$setProp(_el$30, "alignItems", "center");
|
|
1353
|
+
_$insertNode(_el$31, _el$32);
|
|
1354
|
+
_$insert(_el$32, () => currentLoop().name);
|
|
1355
|
+
_$insertNode(_el$33, _el$34);
|
|
1356
|
+
_$insertNode(_el$34, _el$35);
|
|
1357
|
+
_$insertNode(_el$34, _el$36);
|
|
1358
|
+
_$insert(_el$34, () => statusBadge().text, _el$36);
|
|
1359
|
+
_$insertNode(_el$37, _el$38);
|
|
1360
|
+
_$insertNode(_el$38, _el$39);
|
|
1361
|
+
_$insert(_el$38, () => currentLoop().iteration, null);
|
|
1362
|
+
_$insert(_el$38, (() => {
|
|
1363
|
+
var _c$ = _$memo(() => currentLoop().maxIterations > 0);
|
|
1364
|
+
return () => _c$() ? `/${currentLoop().maxIterations}` : "";
|
|
1365
|
+
})(), null);
|
|
1366
|
+
_$insert(_el$28, _$createComponent(Show, {
|
|
1367
|
+
get when() {
|
|
1368
|
+
return loading();
|
|
1369
|
+
},
|
|
1370
|
+
get children() {
|
|
1371
|
+
var _el$40 = _$createElement("box"), _el$41 = _$createElement("text");
|
|
1372
|
+
_$insertNode(_el$40, _el$41);
|
|
1373
|
+
_$setProp(_el$40, "paddingTop", 1);
|
|
1374
|
+
_$insertNode(_el$41, _$createTextNode(`Loading stats...`));
|
|
1375
|
+
_$effect((_$p) => _$setProp(_el$41, "fg", theme().textMuted, _$p));
|
|
1376
|
+
return _el$40;
|
|
1377
|
+
}
|
|
1378
|
+
}), _el$90);
|
|
1379
|
+
_$insert(_el$28, _$createComponent(Show, {
|
|
1380
|
+
get when() {
|
|
1381
|
+
return !loading();
|
|
1382
|
+
},
|
|
1383
|
+
get children() {
|
|
1384
|
+
var _el$43 = _$createElement("box");
|
|
1385
|
+
_$setProp(_el$43, "flexDirection", "column");
|
|
1386
|
+
_$setProp(_el$43, "paddingTop", 1);
|
|
1387
|
+
_$setProp(_el$43, "flexShrink", 0);
|
|
1388
|
+
_$insert(_el$43, _$createComponent(Show, {
|
|
1389
|
+
get when() {
|
|
1390
|
+
return stats();
|
|
1391
|
+
},
|
|
1392
|
+
get fallback() {
|
|
1393
|
+
return (() => {
|
|
1394
|
+
var _el$101 = _$createElement("box"), _el$102 = _$createElement("text");
|
|
1395
|
+
_$insertNode(_el$101, _el$102);
|
|
1396
|
+
_$insertNode(_el$102, _$createTextNode(`Session stats unavailable`));
|
|
1397
|
+
_$effect((_$p) => _$setProp(_el$102, "fg", theme().textMuted, _$p));
|
|
1398
|
+
return _el$101;
|
|
1399
|
+
})();
|
|
1400
|
+
},
|
|
1401
|
+
get children() {
|
|
1402
|
+
var _el$44 = _$createElement("box"), _el$45 = _$createElement("box"), _el$46 = _$createElement("text"), _el$47 = _$createElement("span"), _el$49 = _$createTextNode(`...`), _el$50 = _$createElement("box"), _el$51 = _$createElement("text"), _el$52 = _$createElement("span"), _el$54 = _$createElement("box"), _el$55 = _$createElement("text"), _el$56 = _$createElement("span"), _el$58 = _$createTextNode(` total (`), _el$59 = _$createTextNode(` assistant)`), _el$60 = _$createElement("box"), _el$61 = _$createElement("text"), _el$62 = _$createElement("span"), _el$64 = _$createTextNode(` in / `), _el$65 = _$createTextNode(` out / `), _el$66 = _$createTextNode(` reasoning`), _el$67 = _$createElement("box"), _el$68 = _$createElement("text"), _el$69 = _$createElement("span"), _el$71 = _$createTextNode(`$`);
|
|
1403
|
+
_$insertNode(_el$44, _el$45);
|
|
1404
|
+
_$insertNode(_el$44, _el$50);
|
|
1405
|
+
_$insertNode(_el$44, _el$54);
|
|
1406
|
+
_$insertNode(_el$44, _el$60);
|
|
1407
|
+
_$insertNode(_el$44, _el$67);
|
|
1408
|
+
_$setProp(_el$44, "flexDirection", "column");
|
|
1409
|
+
_$insertNode(_el$45, _el$46);
|
|
1410
|
+
_$insertNode(_el$46, _el$47);
|
|
1411
|
+
_$insertNode(_el$46, _el$49);
|
|
1412
|
+
_$insertNode(_el$47, _$createTextNode(`Session: `));
|
|
1413
|
+
_$insert(_el$46, () => currentLoop().sessionId.slice(0, 8), _el$49);
|
|
1414
|
+
_$insertNode(_el$50, _el$51);
|
|
1415
|
+
_$insertNode(_el$51, _el$52);
|
|
1416
|
+
_$insertNode(_el$52, _$createTextNode(`Phase: `));
|
|
1417
|
+
_$insert(_el$51, () => currentLoop().phase, null);
|
|
1418
|
+
_$insertNode(_el$54, _el$55);
|
|
1419
|
+
_$insertNode(_el$55, _el$56);
|
|
1420
|
+
_$insertNode(_el$55, _el$58);
|
|
1421
|
+
_$insertNode(_el$55, _el$59);
|
|
1422
|
+
_$insertNode(_el$56, _$createTextNode(`Messages: `));
|
|
1423
|
+
_$insert(_el$55, () => stats().messages.total, _el$58);
|
|
1424
|
+
_$insert(_el$55, () => stats().messages.assistant, _el$59);
|
|
1425
|
+
_$insertNode(_el$60, _el$61);
|
|
1426
|
+
_$insertNode(_el$61, _el$62);
|
|
1427
|
+
_$insertNode(_el$61, _el$64);
|
|
1428
|
+
_$insertNode(_el$61, _el$65);
|
|
1429
|
+
_$insertNode(_el$61, _el$66);
|
|
1430
|
+
_$insertNode(_el$62, _$createTextNode(`Tokens: `));
|
|
1431
|
+
_$insert(_el$61, () => formatTokens(stats().tokens.input), _el$64);
|
|
1432
|
+
_$insert(_el$61, () => formatTokens(stats().tokens.output), _el$65);
|
|
1433
|
+
_$insert(_el$61, () => formatTokens(stats().tokens.reasoning), _el$66);
|
|
1434
|
+
_$insertNode(_el$67, _el$68);
|
|
1435
|
+
_$insertNode(_el$68, _el$69);
|
|
1436
|
+
_$insertNode(_el$68, _el$71);
|
|
1437
|
+
_$insertNode(_el$69, _$createTextNode(`Cost: `));
|
|
1438
|
+
_$insert(_el$68, () => stats().cost.toFixed(4), null);
|
|
1439
|
+
_$insert(_el$44, _$createComponent(Show, {
|
|
1440
|
+
get when() {
|
|
1441
|
+
return stats().fileChanges;
|
|
1442
|
+
},
|
|
1443
|
+
get children() {
|
|
1444
|
+
var _el$72 = _$createElement("box"), _el$73 = _$createElement("text"), _el$74 = _$createElement("span"), _el$76 = _$createTextNode(` changed (+`), _el$77 = _$createTextNode(`/-`), _el$78 = _$createTextNode(`)`);
|
|
1445
|
+
_$insertNode(_el$72, _el$73);
|
|
1446
|
+
_$insertNode(_el$73, _el$74);
|
|
1447
|
+
_$insertNode(_el$73, _el$76);
|
|
1448
|
+
_$insertNode(_el$73, _el$77);
|
|
1449
|
+
_$insertNode(_el$73, _el$78);
|
|
1450
|
+
_$insertNode(_el$74, _$createTextNode(`Files: `));
|
|
1451
|
+
_$insert(_el$73, () => stats().fileChanges.files, _el$76);
|
|
1452
|
+
_$insert(_el$73, () => stats().fileChanges.additions, _el$77);
|
|
1453
|
+
_$insert(_el$73, () => stats().fileChanges.deletions, _el$78);
|
|
1454
|
+
_$effect((_p$) => {
|
|
1455
|
+
var _v$14 = theme().text, _v$15 = {
|
|
1456
|
+
fg: theme().textMuted
|
|
1457
|
+
};
|
|
1458
|
+
_v$14 !== _p$.e && (_p$.e = _$setProp(_el$73, "fg", _v$14, _p$.e));
|
|
1459
|
+
_v$15 !== _p$.t && (_p$.t = _$setProp(_el$74, "style", _v$15, _p$.t));
|
|
1460
|
+
return _p$;
|
|
1461
|
+
}, {
|
|
1462
|
+
e: undefined,
|
|
1463
|
+
t: undefined
|
|
1464
|
+
});
|
|
1465
|
+
return _el$72;
|
|
1466
|
+
}
|
|
1467
|
+
}), null);
|
|
1468
|
+
_$insert(_el$44, _$createComponent(Show, {
|
|
1469
|
+
get when() {
|
|
1470
|
+
return stats().timing;
|
|
1471
|
+
},
|
|
1472
|
+
get children() {
|
|
1473
|
+
var _el$79 = _$createElement("box"), _el$80 = _$createElement("text"), _el$81 = _$createElement("span");
|
|
1474
|
+
_$insertNode(_el$79, _el$80);
|
|
1475
|
+
_$insertNode(_el$80, _el$81);
|
|
1476
|
+
_$insertNode(_el$81, _$createTextNode(`Duration: `));
|
|
1477
|
+
_$insert(_el$80, () => formatDuration(stats().timing.durationMs), null);
|
|
1478
|
+
_$effect((_p$) => {
|
|
1479
|
+
var _v$16 = theme().text, _v$17 = {
|
|
1480
|
+
fg: theme().textMuted
|
|
1481
|
+
};
|
|
1482
|
+
_v$16 !== _p$.e && (_p$.e = _$setProp(_el$80, "fg", _v$16, _p$.e));
|
|
1483
|
+
_v$17 !== _p$.t && (_p$.t = _$setProp(_el$81, "style", _v$17, _p$.t));
|
|
1484
|
+
return _p$;
|
|
1485
|
+
}, {
|
|
1486
|
+
e: undefined,
|
|
1487
|
+
t: undefined
|
|
1488
|
+
});
|
|
1489
|
+
return _el$79;
|
|
1490
|
+
}
|
|
1491
|
+
}), null);
|
|
1492
|
+
_$effect((_p$) => {
|
|
1493
|
+
var _v$18 = theme().text, _v$19 = {
|
|
1494
|
+
fg: theme().textMuted
|
|
1495
|
+
}, _v$20 = theme().text, _v$21 = {
|
|
1496
|
+
fg: theme().textMuted
|
|
1497
|
+
}, _v$22 = theme().text, _v$23 = {
|
|
1498
|
+
fg: theme().textMuted
|
|
1499
|
+
}, _v$24 = theme().text, _v$25 = {
|
|
1500
|
+
fg: theme().textMuted
|
|
1501
|
+
}, _v$26 = theme().text, _v$27 = {
|
|
1502
|
+
fg: theme().textMuted
|
|
1503
|
+
};
|
|
1504
|
+
_v$18 !== _p$.e && (_p$.e = _$setProp(_el$46, "fg", _v$18, _p$.e));
|
|
1505
|
+
_v$19 !== _p$.t && (_p$.t = _$setProp(_el$47, "style", _v$19, _p$.t));
|
|
1506
|
+
_v$20 !== _p$.a && (_p$.a = _$setProp(_el$51, "fg", _v$20, _p$.a));
|
|
1507
|
+
_v$21 !== _p$.o && (_p$.o = _$setProp(_el$52, "style", _v$21, _p$.o));
|
|
1508
|
+
_v$22 !== _p$.i && (_p$.i = _$setProp(_el$55, "fg", _v$22, _p$.i));
|
|
1509
|
+
_v$23 !== _p$.n && (_p$.n = _$setProp(_el$56, "style", _v$23, _p$.n));
|
|
1510
|
+
_v$24 !== _p$.s && (_p$.s = _$setProp(_el$61, "fg", _v$24, _p$.s));
|
|
1511
|
+
_v$25 !== _p$.h && (_p$.h = _$setProp(_el$62, "style", _v$25, _p$.h));
|
|
1512
|
+
_v$26 !== _p$.r && (_p$.r = _$setProp(_el$68, "fg", _v$26, _p$.r));
|
|
1513
|
+
_v$27 !== _p$.d && (_p$.d = _$setProp(_el$69, "style", _v$27, _p$.d));
|
|
1514
|
+
return _p$;
|
|
1515
|
+
}, {
|
|
1516
|
+
e: undefined,
|
|
1517
|
+
t: undefined,
|
|
1518
|
+
a: undefined,
|
|
1519
|
+
o: undefined,
|
|
1520
|
+
i: undefined,
|
|
1521
|
+
n: undefined,
|
|
1522
|
+
s: undefined,
|
|
1523
|
+
h: undefined,
|
|
1524
|
+
r: undefined,
|
|
1525
|
+
d: undefined
|
|
1526
|
+
});
|
|
1527
|
+
return _el$44;
|
|
1528
|
+
}
|
|
1529
|
+
}));
|
|
1530
|
+
return _el$43;
|
|
1531
|
+
}
|
|
1532
|
+
}), _el$90);
|
|
1533
|
+
_$insert(_el$28, _$createComponent(Show, {
|
|
1534
|
+
get when() {
|
|
1535
|
+
return stats()?.lastActivity?.summary;
|
|
1536
|
+
},
|
|
1537
|
+
get children() {
|
|
1538
|
+
var _el$83 = _$createElement("box"), _el$84 = _$createElement("box"), _el$85 = _$createElement("text"), _el$86 = _$createElement("b"), _el$88 = _$createElement("scrollbox"), _el$89 = _$createElement("text");
|
|
1539
|
+
_$insertNode(_el$83, _el$84);
|
|
1540
|
+
_$insertNode(_el$83, _el$88);
|
|
1541
|
+
_$setProp(_el$83, "flexDirection", "column");
|
|
1542
|
+
_$setProp(_el$83, "paddingTop", 1);
|
|
1543
|
+
_$setProp(_el$83, "flexGrow", 1);
|
|
1544
|
+
_$setProp(_el$83, "flexShrink", 1);
|
|
1545
|
+
_$insertNode(_el$84, _el$85);
|
|
1546
|
+
_$setProp(_el$84, "flexShrink", 0);
|
|
1547
|
+
_$insertNode(_el$85, _el$86);
|
|
1548
|
+
_$insertNode(_el$86, _$createTextNode(`Latest Output`));
|
|
1549
|
+
_$insertNode(_el$88, _el$89);
|
|
1550
|
+
_$setProp(_el$88, "maxHeight", 12);
|
|
1551
|
+
_$setProp(_el$88, "borderStyle", "rounded");
|
|
1552
|
+
_$setProp(_el$88, "paddingX", 1);
|
|
1553
|
+
_$setProp(_el$89, "wrapMode", "word");
|
|
1554
|
+
_$insert(_el$89, () => truncate(stats().lastActivity.summary, 500));
|
|
1555
|
+
_$effect((_p$) => {
|
|
1556
|
+
var _v$28 = theme().text, _v$29 = theme().border, _v$30 = theme().textMuted;
|
|
1557
|
+
_v$28 !== _p$.e && (_p$.e = _$setProp(_el$85, "fg", _v$28, _p$.e));
|
|
1558
|
+
_v$29 !== _p$.t && (_p$.t = _$setProp(_el$88, "borderColor", _v$29, _p$.t));
|
|
1559
|
+
_v$30 !== _p$.a && (_p$.a = _$setProp(_el$89, "fg", _v$30, _p$.a));
|
|
1560
|
+
return _p$;
|
|
1561
|
+
}, {
|
|
1562
|
+
e: undefined,
|
|
1563
|
+
t: undefined,
|
|
1564
|
+
a: undefined
|
|
1565
|
+
});
|
|
1566
|
+
return _el$83;
|
|
1567
|
+
}
|
|
1568
|
+
}), _el$90);
|
|
1569
|
+
_$insertNode(_el$90, _el$99);
|
|
1570
|
+
_$setProp(_el$90, "paddingTop", 1);
|
|
1571
|
+
_$setProp(_el$90, "flexShrink", 0);
|
|
1572
|
+
_$setProp(_el$90, "flexDirection", "row");
|
|
1573
|
+
_$setProp(_el$90, "gap", 2);
|
|
1574
|
+
_$setProp(_el$90, "paddingY", 2);
|
|
1575
|
+
_$insert(_el$90, _$createComponent(Show, {
|
|
1576
|
+
get when() {
|
|
1577
|
+
return props.onBack;
|
|
1578
|
+
},
|
|
1579
|
+
get children() {
|
|
1580
|
+
var _el$91 = _$createElement("text");
|
|
1581
|
+
_$insertNode(_el$91, _$createTextNode(`Back`));
|
|
1582
|
+
_$setProp(_el$91, "onMouseUp", () => props.onBack());
|
|
1583
|
+
_$effect((_$p) => _$setProp(_el$91, "fg", theme().textMuted, _$p));
|
|
1584
|
+
return _el$91;
|
|
1585
|
+
}
|
|
1586
|
+
}), _el$99);
|
|
1587
|
+
_$insert(_el$90, _$createComponent(Show, {
|
|
1588
|
+
get when() {
|
|
1589
|
+
return currentLoop().active;
|
|
1590
|
+
},
|
|
1591
|
+
get children() {
|
|
1592
|
+
return [(() => {
|
|
1593
|
+
var _el$93 = _$createElement("text");
|
|
1594
|
+
_$insertNode(_el$93, _$createTextNode(`Force Restart`));
|
|
1595
|
+
_$setProp(_el$93, "onMouseUp", handleRestart);
|
|
1596
|
+
_$effect((_$p) => _$setProp(_el$93, "fg", theme().warning, _$p));
|
|
1597
|
+
return _el$93;
|
|
1598
|
+
})(), (() => {
|
|
1599
|
+
var _el$95 = _$createElement("text");
|
|
1600
|
+
_$insertNode(_el$95, _$createTextNode(`Cancel loop`));
|
|
1601
|
+
_$setProp(_el$95, "onMouseUp", handleCancel);
|
|
1602
|
+
_$effect((_$p) => _$setProp(_el$95, "fg", theme().error, _$p));
|
|
1603
|
+
return _el$95;
|
|
1604
|
+
})()];
|
|
1605
|
+
}
|
|
1606
|
+
}), _el$99);
|
|
1607
|
+
_$insert(_el$90, _$createComponent(Show, {
|
|
1608
|
+
get when() {
|
|
1609
|
+
return _$memo(() => !!!currentLoop().active)() && currentLoop().terminationReason !== "completed";
|
|
1610
|
+
},
|
|
1611
|
+
get children() {
|
|
1612
|
+
var _el$97 = _$createElement("text");
|
|
1613
|
+
_$insertNode(_el$97, _$createTextNode(`Restart`));
|
|
1614
|
+
_$setProp(_el$97, "onMouseUp", handleRestart);
|
|
1615
|
+
_$effect((_$p) => _$setProp(_el$97, "fg", theme().success, _$p));
|
|
1616
|
+
return _el$97;
|
|
1617
|
+
}
|
|
1618
|
+
}), _el$99);
|
|
1619
|
+
_$insertNode(_el$99, _$createTextNode(`Close (esc)`));
|
|
1620
|
+
_$setProp(_el$99, "onMouseUp", () => props.api.ui.dialog.clear());
|
|
1621
|
+
_$effect((_p$) => {
|
|
1622
|
+
var _v$31 = theme().text, _v$32 = statusBadge().color, _v$33 = theme().textMuted, _v$34 = theme().textMuted;
|
|
1623
|
+
_v$31 !== _p$.e && (_p$.e = _$setProp(_el$31, "fg", _v$31, _p$.e));
|
|
1624
|
+
_v$32 !== _p$.t && (_p$.t = _$setProp(_el$33, "fg", _v$32, _p$.t));
|
|
1625
|
+
_v$33 !== _p$.a && (_p$.a = _$setProp(_el$38, "fg", _v$33, _p$.a));
|
|
1626
|
+
_v$34 !== _p$.o && (_p$.o = _$setProp(_el$99, "fg", _v$34, _p$.o));
|
|
1627
|
+
return _p$;
|
|
1628
|
+
}, {
|
|
1629
|
+
e: undefined,
|
|
1630
|
+
t: undefined,
|
|
1631
|
+
a: undefined,
|
|
1632
|
+
o: undefined
|
|
1633
|
+
});
|
|
1634
|
+
return _el$28;
|
|
1635
|
+
})();
|
|
1636
|
+
}
|
|
1637
|
+
function Sidebar(props) {
|
|
1638
|
+
const [open, setOpen] = createSignal(true);
|
|
1639
|
+
const [loops, setLoops] = createSignal([]);
|
|
1640
|
+
const [hasPlan, setHasPlan] = createSignal(false);
|
|
1641
|
+
const [graphStatusFormatted, setGraphStatusFormatted] = createSignal(null);
|
|
1642
|
+
const theme = () => props.api.theme.current;
|
|
1643
|
+
const directory = props.api.state.path.directory;
|
|
1644
|
+
const pid = resolveProjectId(directory);
|
|
1645
|
+
const title = createMemo(() => {
|
|
1646
|
+
return props.opts.showVersion ? `Graph v${VERSION}` : "Graph";
|
|
1647
|
+
});
|
|
1648
|
+
const dot = (loop) => {
|
|
1649
|
+
if (!loop.active) {
|
|
1650
|
+
if (loop.terminationReason === "completed")
|
|
1651
|
+
return theme().success;
|
|
1652
|
+
if (loop.terminationReason === "cancelled" || loop.terminationReason === "user_aborted")
|
|
1653
|
+
return theme().textMuted;
|
|
1654
|
+
return theme().error;
|
|
1655
|
+
}
|
|
1656
|
+
if (loop.phase === "auditing")
|
|
1657
|
+
return theme().warning;
|
|
1658
|
+
return theme().success;
|
|
1659
|
+
};
|
|
1660
|
+
const statusText = (loop) => {
|
|
1661
|
+
const max = loop.maxIterations > 0 ? `/${loop.maxIterations}` : "";
|
|
1662
|
+
if (loop.active)
|
|
1663
|
+
return `${loop.phase} · iter ${loop.iteration}${max}`;
|
|
1664
|
+
if (loop.terminationReason === "completed")
|
|
1665
|
+
return `completed · ${loop.iteration} iter${loop.iteration !== 1 ? "s" : ""}`;
|
|
1666
|
+
return loop.terminationReason?.replace(/_/g, " ") ?? "ended";
|
|
1667
|
+
};
|
|
1668
|
+
function refreshSidebarData() {
|
|
1669
|
+
if (!pid)
|
|
1670
|
+
return;
|
|
1671
|
+
const states = readLoopStates(pid);
|
|
1672
|
+
const cutoff = Date.now() - 5 * 60 * 1000;
|
|
1673
|
+
const visible = states.filter((l) => l.active || l.completedAt && new Date(l.completedAt).getTime() > cutoff);
|
|
1674
|
+
visible.sort((a, b) => {
|
|
1675
|
+
if (a.active && !b.active)
|
|
1676
|
+
return -1;
|
|
1677
|
+
if (!a.active && b.active)
|
|
1678
|
+
return 1;
|
|
1679
|
+
const aTime = a.completedAt ?? a.startedAt ?? "";
|
|
1680
|
+
const bTime = b.completedAt ?? b.startedAt ?? "";
|
|
1681
|
+
return bTime.localeCompare(aTime);
|
|
1682
|
+
});
|
|
1683
|
+
setLoops(visible);
|
|
1684
|
+
if (props.sessionId) {
|
|
1685
|
+
const plan = readPlan(pid, props.sessionId);
|
|
1686
|
+
setHasPlan(plan !== null);
|
|
1687
|
+
}
|
|
1688
|
+
const status = readGraphStatus(pid);
|
|
1689
|
+
setGraphStatusFormatted(formatGraphStatus(status));
|
|
1690
|
+
}
|
|
1691
|
+
const unsub = props.api.event.on("session.status", () => {
|
|
1692
|
+
refreshSidebarData();
|
|
1693
|
+
});
|
|
1694
|
+
let pollTimer = null;
|
|
1695
|
+
function startPolling() {
|
|
1696
|
+
if (pollTimer)
|
|
1697
|
+
return;
|
|
1698
|
+
pollTimer = setInterval(() => {
|
|
1699
|
+
refreshSidebarData();
|
|
1700
|
+
}, 5000);
|
|
1701
|
+
}
|
|
1702
|
+
function stopPolling() {
|
|
1703
|
+
if (pollTimer) {
|
|
1704
|
+
clearInterval(pollTimer);
|
|
1705
|
+
pollTimer = null;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
refreshSidebarData();
|
|
1709
|
+
createEffect(() => {
|
|
1710
|
+
const hasActiveWorktreeLoops = loops().filter((l) => l.active && l.worktree).length > 0;
|
|
1711
|
+
if (hasActiveWorktreeLoops) {
|
|
1712
|
+
startPolling();
|
|
1713
|
+
} else {
|
|
1714
|
+
stopPolling();
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
onCleanup(() => {
|
|
1718
|
+
unsub();
|
|
1719
|
+
stopPolling();
|
|
1720
|
+
});
|
|
1721
|
+
const hasContent = createMemo(() => {
|
|
1722
|
+
if (hasPlan())
|
|
1723
|
+
return true;
|
|
1724
|
+
if (props.opts.showLoops && loops().length > 0)
|
|
1725
|
+
return true;
|
|
1726
|
+
if (graphStatusFormatted())
|
|
1727
|
+
return true;
|
|
1728
|
+
return false;
|
|
1729
|
+
});
|
|
1730
|
+
const activeCount = createMemo(() => {
|
|
1731
|
+
return loops().filter((l) => l.active).length;
|
|
1732
|
+
});
|
|
1733
|
+
return _$createComponent(Show, {
|
|
1734
|
+
get when() {
|
|
1735
|
+
return props.opts.sidebar;
|
|
1736
|
+
},
|
|
1737
|
+
get children() {
|
|
1738
|
+
var _el$104 = _$createElement("box"), _el$105 = _$createElement("box"), _el$107 = _$createElement("text"), _el$108 = _$createElement("b");
|
|
1739
|
+
_$insertNode(_el$104, _el$105);
|
|
1740
|
+
_$insertNode(_el$105, _el$107);
|
|
1741
|
+
_$setProp(_el$105, "flexDirection", "row");
|
|
1742
|
+
_$setProp(_el$105, "gap", 1);
|
|
1743
|
+
_$setProp(_el$105, "onMouseDown", () => hasContent() && setOpen((x) => !x));
|
|
1744
|
+
_$insert(_el$105, _$createComponent(Show, {
|
|
1745
|
+
get when() {
|
|
1746
|
+
return hasContent();
|
|
1747
|
+
},
|
|
1748
|
+
get children() {
|
|
1749
|
+
var _el$106 = _$createElement("text");
|
|
1750
|
+
_$insert(_el$106, () => open() ? "▼" : "▶");
|
|
1751
|
+
_$effect((_$p) => _$setProp(_el$106, "fg", theme().text, _$p));
|
|
1752
|
+
return _el$106;
|
|
1753
|
+
}
|
|
1754
|
+
}), _el$107);
|
|
1755
|
+
_$insertNode(_el$107, _el$108);
|
|
1756
|
+
_$insert(_el$108, title);
|
|
1757
|
+
_$insert(_el$107, (() => {
|
|
1758
|
+
var _c$2 = _$memo(() => !!(!open() && hasPlan()));
|
|
1759
|
+
return () => _c$2() ? (() => {
|
|
1760
|
+
var _el$118 = _$createElement("span");
|
|
1761
|
+
_$insertNode(_el$118, _$createTextNode(` · plan`));
|
|
1762
|
+
_$effect((_$p) => _$setProp(_el$118, "style", {
|
|
1763
|
+
fg: theme().info
|
|
1764
|
+
}, _$p));
|
|
1765
|
+
return _el$118;
|
|
1766
|
+
})() : "";
|
|
1767
|
+
})(), null);
|
|
1768
|
+
_$insert(_el$107, (() => {
|
|
1769
|
+
var _c$3 = _$memo(() => !!(!open() && graphStatusFormatted() && graphStatusFormatted().text.includes("ready")));
|
|
1770
|
+
return () => _c$3() ? (() => {
|
|
1771
|
+
var _el$120 = _$createElement("span");
|
|
1772
|
+
_$insertNode(_el$120, _$createTextNode(` · ready`));
|
|
1773
|
+
_$effect((_$p) => _$setProp(_el$120, "style", {
|
|
1774
|
+
fg: theme().success
|
|
1775
|
+
}, _$p));
|
|
1776
|
+
return _el$120;
|
|
1777
|
+
})() : "";
|
|
1778
|
+
})(), null);
|
|
1779
|
+
_$insert(_el$107, (() => {
|
|
1780
|
+
var _c$4 = _$memo(() => !!(!open() && activeCount() > 0));
|
|
1781
|
+
return () => _c$4() ? (() => {
|
|
1782
|
+
var _el$122 = _$createElement("span");
|
|
1783
|
+
_$insert(_el$122, () => ` (${activeCount()} active)`);
|
|
1784
|
+
_$effect((_$p) => _$setProp(_el$122, "style", {
|
|
1785
|
+
fg: theme().textMuted
|
|
1786
|
+
}, _$p));
|
|
1787
|
+
return _el$122;
|
|
1788
|
+
})() : "";
|
|
1789
|
+
})(), null);
|
|
1790
|
+
_$insert(_el$104, _$createComponent(Show, {
|
|
1791
|
+
get when() {
|
|
1792
|
+
return open();
|
|
1793
|
+
},
|
|
1794
|
+
get children() {
|
|
1795
|
+
return [_$createComponent(Show, {
|
|
1796
|
+
get when() {
|
|
1797
|
+
return hasPlan();
|
|
1798
|
+
},
|
|
1799
|
+
get children() {
|
|
1800
|
+
var _el$109 = _$createElement("box"), _el$110 = _$createElement("text"), _el$112 = _$createElement("text");
|
|
1801
|
+
_$insertNode(_el$109, _el$110);
|
|
1802
|
+
_$insertNode(_el$109, _el$112);
|
|
1803
|
+
_$setProp(_el$109, "flexDirection", "row");
|
|
1804
|
+
_$setProp(_el$109, "gap", 1);
|
|
1805
|
+
_$setProp(_el$109, "onMouseUp", () => {
|
|
1806
|
+
if (!pid || !props.sessionId)
|
|
1807
|
+
return;
|
|
1808
|
+
const plan = readPlan(pid, props.sessionId);
|
|
1809
|
+
if (!plan) {
|
|
1810
|
+
props.api.ui.toast({
|
|
1811
|
+
message: "Plan not found",
|
|
1812
|
+
variant: "info",
|
|
1813
|
+
duration: 3000
|
|
1814
|
+
});
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
const refreshSidebar = refreshSidebarData;
|
|
1818
|
+
props.api.ui.dialog.setSize("xlarge");
|
|
1819
|
+
props.api.ui.dialog.replace(() => _$createComponent(PlanViewerDialog, {
|
|
1820
|
+
get api() {
|
|
1821
|
+
return props.api;
|
|
1822
|
+
},
|
|
1823
|
+
planContent: plan,
|
|
1824
|
+
projectId: pid,
|
|
1825
|
+
get sessionId() {
|
|
1826
|
+
return props.sessionId;
|
|
1827
|
+
},
|
|
1828
|
+
onRefresh: refreshSidebar
|
|
1829
|
+
}));
|
|
1830
|
+
});
|
|
1831
|
+
_$insertNode(_el$110, _$createTextNode(`\uD83D\uDCCB`));
|
|
1832
|
+
_$setProp(_el$110, "flexShrink", 0);
|
|
1833
|
+
_$insertNode(_el$112, _$createTextNode(`Plan`));
|
|
1834
|
+
_$effect((_p$) => {
|
|
1835
|
+
var _v$35 = {
|
|
1836
|
+
fg: theme().info
|
|
1837
|
+
}, _v$36 = theme().text;
|
|
1838
|
+
_v$35 !== _p$.e && (_p$.e = _$setProp(_el$110, "style", _v$35, _p$.e));
|
|
1839
|
+
_v$36 !== _p$.t && (_p$.t = _$setProp(_el$112, "fg", _v$36, _p$.t));
|
|
1840
|
+
return _p$;
|
|
1841
|
+
}, {
|
|
1842
|
+
e: undefined,
|
|
1843
|
+
t: undefined
|
|
1844
|
+
});
|
|
1845
|
+
return _el$109;
|
|
1846
|
+
}
|
|
1847
|
+
}), _$createComponent(Show, {
|
|
1848
|
+
get when() {
|
|
1849
|
+
return graphStatusFormatted();
|
|
1850
|
+
},
|
|
1851
|
+
get children() {
|
|
1852
|
+
var _el$114 = _$createElement("box"), _el$115 = _$createElement("text"), _el$117 = _$createElement("text");
|
|
1853
|
+
_$insertNode(_el$114, _el$115);
|
|
1854
|
+
_$insertNode(_el$114, _el$117);
|
|
1855
|
+
_$setProp(_el$114, "flexDirection", "row");
|
|
1856
|
+
_$setProp(_el$114, "gap", 1);
|
|
1857
|
+
_$insertNode(_el$115, _$createTextNode(`•`));
|
|
1858
|
+
_$setProp(_el$115, "flexShrink", 0);
|
|
1859
|
+
_$setProp(_el$117, "wrapMode", "word");
|
|
1860
|
+
_$insert(_el$117, () => graphStatusFormatted().text);
|
|
1861
|
+
_$effect((_p$) => {
|
|
1862
|
+
var _v$37 = {
|
|
1863
|
+
fg: theme()[graphStatusFormatted().color]
|
|
1864
|
+
}, _v$38 = theme().text;
|
|
1865
|
+
_v$37 !== _p$.e && (_p$.e = _$setProp(_el$115, "style", _v$37, _p$.e));
|
|
1866
|
+
_v$38 !== _p$.t && (_p$.t = _$setProp(_el$117, "fg", _v$38, _p$.t));
|
|
1867
|
+
return _p$;
|
|
1868
|
+
}, {
|
|
1869
|
+
e: undefined,
|
|
1870
|
+
t: undefined
|
|
1871
|
+
});
|
|
1872
|
+
return _el$114;
|
|
1873
|
+
}
|
|
1874
|
+
}), _$createComponent(Show, {
|
|
1875
|
+
get when() {
|
|
1876
|
+
return _$memo(() => !!props.opts.showLoops)() && loops().length > 0;
|
|
1877
|
+
},
|
|
1878
|
+
get children() {
|
|
1879
|
+
return _$createComponent(For, {
|
|
1880
|
+
get each() {
|
|
1881
|
+
return loops();
|
|
1882
|
+
},
|
|
1883
|
+
children: (loop) => (() => {
|
|
1884
|
+
var _el$123 = _$createElement("box"), _el$124 = _$createElement("text"), _el$126 = _$createElement("text"), _el$127 = _$createTextNode(` `), _el$128 = _$createElement("span");
|
|
1885
|
+
_$insertNode(_el$123, _el$124);
|
|
1886
|
+
_$insertNode(_el$123, _el$126);
|
|
1887
|
+
_$setProp(_el$123, "flexDirection", "row");
|
|
1888
|
+
_$setProp(_el$123, "gap", 1);
|
|
1889
|
+
_$setProp(_el$123, "onMouseUp", () => {
|
|
1890
|
+
if (loop.worktree) {
|
|
1891
|
+
props.api.ui.dialog.setSize("medium");
|
|
1892
|
+
props.api.ui.dialog.replace(() => _$createComponent(LoopDetailsDialog, {
|
|
1893
|
+
get api() {
|
|
1894
|
+
return props.api;
|
|
1895
|
+
},
|
|
1896
|
+
loop,
|
|
1897
|
+
onRefresh: refreshSidebarData
|
|
1898
|
+
}));
|
|
1899
|
+
} else {
|
|
1900
|
+
props.api.route.navigate("session", {
|
|
1901
|
+
sessionID: loop.sessionId
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
_$insertNode(_el$124, _$createTextNode(`•`));
|
|
1906
|
+
_$setProp(_el$124, "flexShrink", 0);
|
|
1907
|
+
_$insertNode(_el$126, _el$127);
|
|
1908
|
+
_$insertNode(_el$126, _el$128);
|
|
1909
|
+
_$setProp(_el$126, "wrapMode", "word");
|
|
1910
|
+
_$insert(_el$126, () => truncateMiddle(loop.name, 25), _el$127);
|
|
1911
|
+
_$insert(_el$128, () => statusText(loop));
|
|
1912
|
+
_$effect((_p$) => {
|
|
1913
|
+
var _v$39 = {
|
|
1914
|
+
fg: dot(loop)
|
|
1915
|
+
}, _v$40 = theme().text, _v$41 = {
|
|
1916
|
+
fg: theme().textMuted
|
|
1917
|
+
};
|
|
1918
|
+
_v$39 !== _p$.e && (_p$.e = _$setProp(_el$124, "style", _v$39, _p$.e));
|
|
1919
|
+
_v$40 !== _p$.t && (_p$.t = _$setProp(_el$126, "fg", _v$40, _p$.t));
|
|
1920
|
+
_v$41 !== _p$.a && (_p$.a = _$setProp(_el$128, "style", _v$41, _p$.a));
|
|
1921
|
+
return _p$;
|
|
1922
|
+
}, {
|
|
1923
|
+
e: undefined,
|
|
1924
|
+
t: undefined,
|
|
1925
|
+
a: undefined
|
|
1926
|
+
});
|
|
1927
|
+
return _el$123;
|
|
1928
|
+
})()
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
})];
|
|
1932
|
+
}
|
|
1933
|
+
}), null);
|
|
1934
|
+
_$effect((_$p) => _$setProp(_el$107, "fg", theme().text, _$p));
|
|
1935
|
+
return _el$104;
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
var id = "oc-forge";
|
|
1940
|
+
var tui = async (api) => {
|
|
1941
|
+
const tuiConfig = loadTuiConfig();
|
|
1942
|
+
const opts = {
|
|
1943
|
+
sidebar: tuiConfig?.sidebar ?? true,
|
|
1944
|
+
showLoops: tuiConfig?.showLoops ?? true,
|
|
1945
|
+
showVersion: tuiConfig?.showVersion ?? true
|
|
1946
|
+
};
|
|
1947
|
+
if (!opts.sidebar)
|
|
1948
|
+
return;
|
|
1949
|
+
api.command.register(() => {
|
|
1950
|
+
const directory = api.state.path.directory;
|
|
1951
|
+
const pid = resolveProjectId(directory);
|
|
1952
|
+
if (!pid)
|
|
1953
|
+
return [];
|
|
1954
|
+
const states = readLoopStates(pid);
|
|
1955
|
+
if (states.length === 0)
|
|
1956
|
+
return [];
|
|
1957
|
+
return [{
|
|
1958
|
+
title: "Forge: Show loops",
|
|
1959
|
+
value: "forge.loops.show",
|
|
1960
|
+
description: `${states.length} loop${states.length !== 1 ? "s" : ""}`,
|
|
1961
|
+
category: "Forge",
|
|
1962
|
+
onSelect: () => {
|
|
1963
|
+
const worktreeLoops = states.filter((l) => l.worktree);
|
|
1964
|
+
const loopOptions = worktreeLoops.map((l) => {
|
|
1965
|
+
const status = l.active ? l.phase : l.terminationReason?.replace(/_/g, " ") ?? "ended";
|
|
1966
|
+
return {
|
|
1967
|
+
title: l.name,
|
|
1968
|
+
value: l.name,
|
|
1969
|
+
description: status
|
|
1970
|
+
};
|
|
1971
|
+
});
|
|
1972
|
+
const showLoopList = () => {
|
|
1973
|
+
api.ui.dialog.setSize("large");
|
|
1974
|
+
api.ui.dialog.replace(() => _$createComponent(api.ui.DialogSelect, {
|
|
1975
|
+
title: "Loops",
|
|
1976
|
+
options: loopOptions,
|
|
1977
|
+
onSelect: (opt) => {
|
|
1978
|
+
const loopName = opt.value;
|
|
1979
|
+
const freshLoop = pid ? readLoopByName(pid, loopName) : null;
|
|
1980
|
+
if (freshLoop) {
|
|
1981
|
+
api.ui.dialog.setSize("medium");
|
|
1982
|
+
api.ui.dialog.replace(() => _$createComponent(LoopDetailsDialog, {
|
|
1983
|
+
api,
|
|
1984
|
+
loop: freshLoop,
|
|
1985
|
+
onBack: showLoopList,
|
|
1986
|
+
onRefresh: () => {}
|
|
1987
|
+
}));
|
|
1988
|
+
} else {
|
|
1989
|
+
api.ui.dialog.clear();
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}));
|
|
1993
|
+
};
|
|
1994
|
+
showLoopList();
|
|
1995
|
+
}
|
|
1996
|
+
}];
|
|
1997
|
+
});
|
|
1998
|
+
api.command.register(() => {
|
|
1999
|
+
const route = api.route.current;
|
|
2000
|
+
if (route.name !== "session")
|
|
2001
|
+
return [];
|
|
2002
|
+
const directory = api.state.path.directory;
|
|
2003
|
+
const pid = resolveProjectId(directory);
|
|
2004
|
+
if (!pid)
|
|
2005
|
+
return [];
|
|
2006
|
+
const sessionID = route.params?.sessionID;
|
|
2007
|
+
if (!sessionID)
|
|
2008
|
+
return [];
|
|
2009
|
+
const plan = readPlan(pid, sessionID);
|
|
2010
|
+
if (!plan)
|
|
2011
|
+
return [];
|
|
2012
|
+
return [{
|
|
2013
|
+
title: "Forge: View plan",
|
|
2014
|
+
value: "forge.plan.view",
|
|
2015
|
+
description: "View cached plan for this session",
|
|
2016
|
+
category: "Forge",
|
|
2017
|
+
onSelect: () => {
|
|
2018
|
+
const freshPlan = readPlan(pid, sessionID);
|
|
2019
|
+
if (!freshPlan) {
|
|
2020
|
+
api.ui.toast({
|
|
2021
|
+
message: "No plan found for this session",
|
|
2022
|
+
variant: "info",
|
|
2023
|
+
duration: 3000
|
|
2024
|
+
});
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
api.ui.dialog.setSize("large");
|
|
2028
|
+
api.ui.dialog.replace(() => _$createComponent(PlanViewerDialog, {
|
|
2029
|
+
api,
|
|
2030
|
+
planContent: freshPlan,
|
|
2031
|
+
projectId: pid,
|
|
2032
|
+
sessionId: sessionID
|
|
2033
|
+
}));
|
|
2034
|
+
}
|
|
2035
|
+
}];
|
|
2036
|
+
});
|
|
2037
|
+
api.slots.register({
|
|
2038
|
+
order: 150,
|
|
2039
|
+
slots: {
|
|
2040
|
+
sidebar_content(_ctx, slotProps) {
|
|
2041
|
+
return _$createComponent(Sidebar, {
|
|
2042
|
+
api,
|
|
2043
|
+
opts,
|
|
2044
|
+
get sessionId() {
|
|
2045
|
+
return slotProps.session_id;
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
};
|
|
2052
|
+
var plugin = {
|
|
2053
|
+
id,
|
|
2054
|
+
tui
|
|
2055
|
+
};
|
|
2056
|
+
var tui_default = plugin;
|
|
2057
|
+
export {
|
|
2058
|
+
readLoopStates,
|
|
2059
|
+
readLoopByName,
|
|
2060
|
+
tui_default as default
|
|
2061
|
+
};
|