neural-loom 0.2.60 → 0.2.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +3 -0
- package/.next/build-manifest.json +3 -3
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/diagnostics/route-bundle-stats.json +2 -2
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +18 -0
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/configs/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/configs/route/build-manifest.json +9 -0
- package/.next/server/app/api/configs/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/configs/route.js +6 -0
- package/.next/server/app/api/configs/route.js.map +5 -0
- package/.next/server/app/api/configs/route.js.nft.json +1 -0
- package/.next/server/app/api/configs/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
- package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
- package/.next/server/app/api/context/route.js.nft.json +1 -1
- package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
- package/.next/server/app/api/recents/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/inject/route.js +2 -2
- package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/input/route.js +2 -2
- package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/launch/route.js +2 -2
- package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +2 -2
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/scope/route.js +2 -2
- package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/start/route.js +2 -2
- package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stats/route.js +2 -2
- package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stop/route.js +2 -2
- package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/upload-image/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/upload-image/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/upload-image/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/upload-image/route.js +7 -0
- package/.next/server/app/api/sessions/upload-image/route.js.map +5 -0
- package/.next/server/app/api/sessions/upload-image/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/upload-image/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sql/connections/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/query/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/schema/route.js.nft.json +1 -1
- package/.next/server/app/api/telemetry/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/telemetry/route/build-manifest.json +9 -0
- package/.next/server/app/api/telemetry/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/telemetry/route.js +6 -0
- package/.next/server/app/api/telemetry/route.js.map +5 -0
- package/.next/server/app/api/telemetry/route.js.nft.json +1 -0
- package/.next/server/app/api/telemetry/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page/react-loadable-manifest.json +5 -5
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +3 -0
- package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__00-sqou._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0thsozo._.js.map → [root-of-the-server]__00-sqou._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__01621nz._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__01621nz._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__097vtn1._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0em6a~~._.js.map → [root-of-the-server]__097vtn1._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0e03ite._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__10wwca8._.js.map → [root-of-the-server]__0e03ite._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0h.~hv6._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0s0is_9._.js.map → [root-of-the-server]__0h.~hv6._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js +33 -0
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0s1axek._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0i~01cv._.js.map → [root-of-the-server]__0s1axek._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0_o7_jj._.js.map → [root-of-the-server]__0tq0maq._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0viil6b._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__07zx04e._.js.map → [root-of-the-server]__0viil6b._.js.map} +1 -1
- package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js.map +1 -0
- package/.next/server/chunks/ssr/_0t7oqy6._.js +1 -1
- package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/07lj0adi0kt5p.css +1 -0
- package/.next/static/chunks/0xhaghhxllgzs.js +1 -0
- package/.next/static/chunks/128c-kly7grxg.js +4 -0
- package/.next/static/chunks/{0y~7jjqbr14.5.js → 15_baizhaiq8c.js} +10 -7
- package/.next/static/chunks/15sigqif_v.mu.js +1 -0
- package/.next/static/chunks/17ud2wmsyqknc.js +11 -0
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +4 -1
- package/.next/types/validator.ts +27 -0
- package/bin/cli.js +4 -4
- package/bin/fix-pwa-ubuntu.sh +50 -0
- package/package.json +1 -1
- package/src/app/api/configs/route.ts +40 -0
- package/src/app/api/sessions/launch/route.ts +3 -3
- package/src/app/api/sessions/route.ts +2 -2
- package/src/app/api/sessions/start/route.ts +2 -2
- package/src/app/api/sessions/upload-image/route.ts +57 -0
- package/src/app/api/sql/connections/route.ts +9 -6
- package/src/app/api/sql/mcp/route.ts +3 -2
- package/src/app/api/sql/query/route.ts +3 -2
- package/src/app/api/sql/schema/route.ts +3 -2
- package/src/app/api/telemetry/route.ts +18 -0
- package/src/app/components/AiderWizard.tsx +46 -2
- package/src/app/components/ClaudeWizard.tsx +30 -0
- package/src/app/components/IdeLayout.tsx +566 -70
- package/src/app/components/TerminalConsole.tsx +54 -0
- package/src/app/page.module.css +8 -2
- package/src/app/page.tsx +534 -401
- package/src/lib/agents/AgentRunner.ts +88 -5
- package/src/lib/agents/AiderRunner.ts +4 -18
- package/src/lib/agents/ClaudeRunner.ts +16 -26
- package/src/lib/agents/DockerRunner.ts +16 -26
- package/src/lib/agents/MockRunner.ts +4 -16
- package/src/lib/agents/SSHRunner.ts +16 -26
- package/src/lib/agents/SessionConfig.ts +79 -0
- package/src/lib/agents/SessionManager.ts +16 -16
- package/src/lib/agents/SessionPersister.ts +13 -9
- package/src/lib/agents/ShellRunner.ts +14 -21
- package/src/lib/agents/WebSocketServer.ts +2 -2
- package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__07zx04e._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +0 -33
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0em6a~~._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0i~01cv._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0s0is_9._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0thsozo._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__10wwca8._.js +0 -15
- package/.next/static/chunks/076_b43_kienv.js +0 -1
- package/.next/static/chunks/07r_au7dcyu8l.js +0 -11
- package/.next/static/chunks/0hoyc~vbuqe0t.css +0 -1
- package/.next/static/chunks/0k2co3uj4w81_.js +0 -1
- package/.next/static/chunks/0sqeixl35qoac.js +0 -1
- /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → b48zoI5QUg8m7v1asTiCM}/_buildManifest.js +0 -0
- /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → b48zoI5QUg8m7v1asTiCM}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → b48zoI5QUg8m7v1asTiCM}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
1
4
|
export interface RunnerStatus {
|
|
2
5
|
id: string;
|
|
3
6
|
name: string;
|
|
@@ -12,6 +15,19 @@ export interface RunnerStatus {
|
|
|
12
15
|
agentState?: 'idle' | 'thinking' | 'complete' | 'needs-input';
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
export interface TokenTransaction {
|
|
19
|
+
timestamp: string;
|
|
20
|
+
sessionId: string;
|
|
21
|
+
sessionName: string;
|
|
22
|
+
type: string;
|
|
23
|
+
inputTokens: number;
|
|
24
|
+
outputTokens: number;
|
|
25
|
+
cost: number;
|
|
26
|
+
totalInput: number;
|
|
27
|
+
totalOutput: number;
|
|
28
|
+
totalCost: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
export abstract class AgentRunner {
|
|
16
32
|
abstract id: string;
|
|
17
33
|
abstract name: string;
|
|
@@ -25,6 +41,57 @@ export abstract class AgentRunner {
|
|
|
25
41
|
tokenUsage: { input: number; output: number; cost: number } = { input: 0, output: 0, cost: 0 };
|
|
26
42
|
agentState: 'idle' | 'thinking' | 'complete' | 'needs-input' = 'idle';
|
|
27
43
|
private stateTimer: NodeJS.Timeout | null = null;
|
|
44
|
+
private lastLoggedUsage = { input: 0, output: 0, cost: 0 };
|
|
45
|
+
|
|
46
|
+
protected logTokenUsageUpdate(): void {
|
|
47
|
+
if (
|
|
48
|
+
this.tokenUsage.input === this.lastLoggedUsage.input &&
|
|
49
|
+
this.tokenUsage.output === this.lastLoggedUsage.output &&
|
|
50
|
+
this.tokenUsage.cost === this.lastLoggedUsage.cost
|
|
51
|
+
) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const diffInput = this.tokenUsage.input - this.lastLoggedUsage.input;
|
|
56
|
+
const diffOutput = this.tokenUsage.output - this.lastLoggedUsage.output;
|
|
57
|
+
const diffCost = this.tokenUsage.cost - this.lastLoggedUsage.cost;
|
|
58
|
+
|
|
59
|
+
if (diffInput > 0 || diffOutput > 0 || diffCost > 0) {
|
|
60
|
+
try {
|
|
61
|
+
const configDir = path.join(process.cwd(), ".ai", "neural-loom");
|
|
62
|
+
if (!fs.existsSync(configDir)) {
|
|
63
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
const historyFile = path.join(configDir, "token-history.json");
|
|
66
|
+
let history: TokenTransaction[] = [];
|
|
67
|
+
if (fs.existsSync(historyFile)) {
|
|
68
|
+
try {
|
|
69
|
+
history = JSON.parse(fs.readFileSync(historyFile, "utf8")) || [];
|
|
70
|
+
} catch {
|
|
71
|
+
history = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
history.push({
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
sessionId: this.id,
|
|
78
|
+
sessionName: this.name,
|
|
79
|
+
type: this.type,
|
|
80
|
+
inputTokens: diffInput > 0 ? diffInput : 0,
|
|
81
|
+
outputTokens: diffOutput > 0 ? diffOutput : 0,
|
|
82
|
+
cost: diffCost > 0 ? diffCost : 0,
|
|
83
|
+
totalInput: this.tokenUsage.input,
|
|
84
|
+
totalOutput: this.tokenUsage.output,
|
|
85
|
+
totalCost: this.tokenUsage.cost
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf8");
|
|
89
|
+
this.lastLoggedUsage = { ...this.tokenUsage };
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error("[AgentRunner] Failed to log token usage transaction:", err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
28
95
|
|
|
29
96
|
protected parseTokenUsage(data: string): void {
|
|
30
97
|
const cleanData = data.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI colors
|
|
@@ -68,14 +135,30 @@ export abstract class AgentRunner {
|
|
|
68
135
|
if (this.tokenUsage.cost === 0 && (this.tokenUsage.input > 0 || this.tokenUsage.output > 0)) {
|
|
69
136
|
this.tokenUsage.cost = (this.tokenUsage.input * 0.000003) + (this.tokenUsage.output * 0.000015);
|
|
70
137
|
}
|
|
138
|
+
this.logTokenUsageUpdate();
|
|
71
139
|
}
|
|
72
140
|
|
|
73
|
-
abstract start(): Promise<void>;
|
|
141
|
+
abstract start(resume?: boolean): Promise<void>;
|
|
74
142
|
abstract stop(): Promise<void>;
|
|
75
143
|
abstract sendInput(text: string, isRaw?: boolean): Promise<void>;
|
|
76
|
-
|
|
144
|
+
getStatus(): RunnerStatus {
|
|
145
|
+
this.analyzeState();
|
|
146
|
+
return {
|
|
147
|
+
id: this.id,
|
|
148
|
+
name: this.name,
|
|
149
|
+
type: this.type,
|
|
150
|
+
status: this.status,
|
|
151
|
+
createdAt: this.createdAt,
|
|
152
|
+
logsCount: this.logs.length,
|
|
153
|
+
workspaceRoot: this.workspaceRoot || (this.type === 'shell' ? (process.env.ORIGINAL_CWD || process.cwd()) : undefined),
|
|
154
|
+
scopedDirs: this.scopedDirs,
|
|
155
|
+
lastSyncedWorkspaceFolders: this.lastSyncedWorkspaceFolders,
|
|
156
|
+
tokenUsage: this.tokenUsage,
|
|
157
|
+
agentState: this.agentState
|
|
158
|
+
};
|
|
159
|
+
}
|
|
77
160
|
|
|
78
|
-
resize(
|
|
161
|
+
resize(_cols: number, _rows: number): void {
|
|
79
162
|
// No-op by default, overridden by PTY runners
|
|
80
163
|
}
|
|
81
164
|
|
|
@@ -129,14 +212,14 @@ export abstract class AgentRunner {
|
|
|
129
212
|
}, 800);
|
|
130
213
|
}
|
|
131
214
|
|
|
132
|
-
|
|
215
|
+
protected analyzeState(): void {
|
|
133
216
|
if (this.status !== 'running') {
|
|
134
217
|
this.agentState = 'idle';
|
|
135
218
|
return;
|
|
136
219
|
}
|
|
137
220
|
|
|
138
221
|
const tailLines = this.logs.slice(-5).join("");
|
|
139
|
-
const cleanTail = tailLines.replace(/\x1b
|
|
222
|
+
const cleanTail = tailLines.replace(/\x1b(?:\[[0-9;]*[a-zA-Z]|\([a-zA-Z]|\)[a-zA-Z])/g, "").trim();
|
|
140
223
|
|
|
141
224
|
const isQuestion =
|
|
142
225
|
/(\[y\/n\]|\(y\/n\)|\?|confirm|allow|should I|would you|press enter|proceed)/i.test(cleanTail);
|
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { exec } from "child_process";
|
|
4
4
|
import { getConfigDir } from "../config";
|
|
5
5
|
import type { IPty } from "node-pty";
|
|
6
|
-
import { AgentRunner
|
|
6
|
+
import { AgentRunner } from "./AgentRunner";
|
|
7
7
|
import { SessionPersister } from "./SessionPersister";
|
|
8
8
|
|
|
9
9
|
function isWindows(): boolean {
|
|
@@ -71,7 +71,7 @@ function getFilesForAider(dir: string): string {
|
|
|
71
71
|
export class AiderRunner extends AgentRunner {
|
|
72
72
|
id: string;
|
|
73
73
|
name: string;
|
|
74
|
-
type: 'aider' | 'aider-docker';
|
|
74
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
|
|
75
75
|
status: 'idle' | 'running' | 'stopped' = 'idle';
|
|
76
76
|
createdAt: string;
|
|
77
77
|
logs: string[] = [];
|
|
@@ -97,7 +97,7 @@ export class AiderRunner extends AgentRunner {
|
|
|
97
97
|
SessionPersister.saveSession(this);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
async start(): Promise<void> {
|
|
100
|
+
async start(resume?: boolean): Promise<void> {
|
|
101
101
|
if (this.status === 'running') return;
|
|
102
102
|
|
|
103
103
|
this.status = 'running';
|
|
@@ -304,19 +304,5 @@ export class AiderRunner extends AgentRunner {
|
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
-
|
|
308
|
-
getStatus(): RunnerStatus {
|
|
309
|
-
return {
|
|
310
|
-
id: this.id,
|
|
311
|
-
name: this.name,
|
|
312
|
-
type: this.type,
|
|
313
|
-
status: this.status,
|
|
314
|
-
createdAt: this.createdAt,
|
|
315
|
-
logsCount: this.logs.length,
|
|
316
|
-
workspaceRoot: this.workspaceRoot,
|
|
317
|
-
scopedDirs: this.scopedDirs,
|
|
318
|
-
tokenUsage: this.tokenUsage,
|
|
319
|
-
agentState: this.agentState
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
307
|
}
|
|
308
|
+
|
|
@@ -4,7 +4,7 @@ import os from "os";
|
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { getConfigDir } from "../config";
|
|
6
6
|
import type { IPty } from "node-pty";
|
|
7
|
-
import { AgentRunner
|
|
7
|
+
import { AgentRunner } from "./AgentRunner";
|
|
8
8
|
import { SessionPersister } from "./SessionPersister";
|
|
9
9
|
|
|
10
10
|
function isWindows(): boolean {
|
|
@@ -32,7 +32,7 @@ function findGitBash(): string | null {
|
|
|
32
32
|
export class ClaudeRunner extends AgentRunner {
|
|
33
33
|
id: string;
|
|
34
34
|
name: string;
|
|
35
|
-
type: 'mock' | 'claude' = 'claude';
|
|
35
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell' = 'claude';
|
|
36
36
|
status: 'idle' | 'running' | 'stopped' = 'idle';
|
|
37
37
|
createdAt: string;
|
|
38
38
|
logs: string[] = [];
|
|
@@ -57,7 +57,7 @@ export class ClaudeRunner extends AgentRunner {
|
|
|
57
57
|
SessionPersister.saveSession(this);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async start(): Promise<void> {
|
|
60
|
+
async start(resume?: boolean): Promise<void> {
|
|
61
61
|
if (this.status === 'running') return;
|
|
62
62
|
|
|
63
63
|
// Detect Git Bash on Windows
|
|
@@ -87,15 +87,19 @@ export class ClaudeRunner extends AgentRunner {
|
|
|
87
87
|
|
|
88
88
|
// Load Claude configuration to check for extraFlags (like --resume)
|
|
89
89
|
let extraFlags = "";
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
if (resume) {
|
|
91
|
+
extraFlags = "--resume";
|
|
92
|
+
} else {
|
|
93
|
+
const claudeConfigPath = path.join(getConfigDir(), "claude.json");
|
|
94
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
95
|
+
try {
|
|
96
|
+
const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf8"));
|
|
97
|
+
if (config.extraFlags) {
|
|
98
|
+
extraFlags = config.extraFlags;
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error("Failed to parse Claude configuration:", err);
|
|
96
102
|
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
console.error("Failed to parse Claude configuration:", err);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -194,19 +198,5 @@ export class ClaudeRunner extends AgentRunner {
|
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
|
|
198
|
-
getStatus(): RunnerStatus {
|
|
199
|
-
return {
|
|
200
|
-
id: this.id,
|
|
201
|
-
name: this.name,
|
|
202
|
-
type: this.type,
|
|
203
|
-
status: this.status,
|
|
204
|
-
createdAt: this.createdAt,
|
|
205
|
-
logsCount: this.logs.length,
|
|
206
|
-
workspaceRoot: this.workspaceRoot,
|
|
207
|
-
scopedDirs: this.scopedDirs,
|
|
208
|
-
tokenUsage: this.tokenUsage,
|
|
209
|
-
agentState: this.agentState
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
201
|
}
|
|
202
|
+
|
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { getConfigDir } from "../config";
|
|
4
4
|
import type { IPty } from "node-pty";
|
|
5
|
-
import { AgentRunner
|
|
5
|
+
import { AgentRunner } from "./AgentRunner";
|
|
6
6
|
import { SessionPersister } from "./SessionPersister";
|
|
7
7
|
|
|
8
8
|
function isWindows(): boolean {
|
|
@@ -30,7 +30,7 @@ function findGitBash(): string | null {
|
|
|
30
30
|
export class DockerRunner extends AgentRunner {
|
|
31
31
|
id: string;
|
|
32
32
|
name: string;
|
|
33
|
-
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' = 'claude-docker';
|
|
33
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell' = 'claude-docker';
|
|
34
34
|
status: 'idle' | 'running' | 'stopped' = 'idle';
|
|
35
35
|
createdAt: string;
|
|
36
36
|
logs: string[] = [];
|
|
@@ -55,7 +55,7 @@ export class DockerRunner extends AgentRunner {
|
|
|
55
55
|
SessionPersister.saveSession(this);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async start(): Promise<void> {
|
|
58
|
+
async start(resume?: boolean): Promise<void> {
|
|
59
59
|
if (this.status === 'running') return;
|
|
60
60
|
|
|
61
61
|
this.status = 'running';
|
|
@@ -123,15 +123,19 @@ export class DockerRunner extends AgentRunner {
|
|
|
123
123
|
|
|
124
124
|
// Load Claude configuration to check for extraFlags (like --resume)
|
|
125
125
|
let extraFlags = "";
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
if (resume) {
|
|
127
|
+
extraFlags = "--resume";
|
|
128
|
+
} else {
|
|
129
|
+
const claudeConfigPath = path.join(getConfigDir(), "claude.json");
|
|
130
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
131
|
+
try {
|
|
132
|
+
const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf8"));
|
|
133
|
+
if (config.extraFlags) {
|
|
134
|
+
extraFlags = config.extraFlags;
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("Failed to parse Claude configuration:", err);
|
|
132
138
|
}
|
|
133
|
-
} catch (err) {
|
|
134
|
-
console.error("Failed to parse Claude configuration:", err);
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -198,19 +202,5 @@ export class DockerRunner extends AgentRunner {
|
|
|
198
202
|
}
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
|
-
|
|
202
|
-
getStatus(): RunnerStatus {
|
|
203
|
-
return {
|
|
204
|
-
id: this.id,
|
|
205
|
-
name: this.name,
|
|
206
|
-
type: this.type,
|
|
207
|
-
status: this.status,
|
|
208
|
-
createdAt: this.createdAt,
|
|
209
|
-
logsCount: this.logs.length,
|
|
210
|
-
workspaceRoot: this.workspaceRoot,
|
|
211
|
-
scopedDirs: this.scopedDirs,
|
|
212
|
-
tokenUsage: this.tokenUsage,
|
|
213
|
-
agentState: this.agentState
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
205
|
}
|
|
206
|
+
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { AgentRunner
|
|
1
|
+
import { AgentRunner } from "./AgentRunner";
|
|
2
2
|
import { SessionPersister } from "./SessionPersister";
|
|
3
3
|
|
|
4
4
|
export class MockRunner extends AgentRunner {
|
|
5
5
|
id: string;
|
|
6
6
|
name: string;
|
|
7
|
-
type: 'mock' | 'claude' = 'mock';
|
|
7
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell' = 'mock';
|
|
8
8
|
status: 'idle' | 'running' | 'stopped' = 'idle';
|
|
9
9
|
createdAt: string;
|
|
10
10
|
logs: string[] = [];
|
|
@@ -33,7 +33,7 @@ export class MockRunner extends AgentRunner {
|
|
|
33
33
|
SessionPersister.saveSession(this);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async start(): Promise<void> {
|
|
36
|
+
async start(resume?: boolean): Promise<void> {
|
|
37
37
|
if (this.status === 'running') return;
|
|
38
38
|
this.status = 'running';
|
|
39
39
|
|
|
@@ -111,17 +111,5 @@ export class MockRunner extends AgentRunner {
|
|
|
111
111
|
}
|
|
112
112
|
}, 400);
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
getStatus(): RunnerStatus {
|
|
116
|
-
return {
|
|
117
|
-
id: this.id,
|
|
118
|
-
name: this.name,
|
|
119
|
-
type: this.type,
|
|
120
|
-
status: this.status,
|
|
121
|
-
createdAt: this.createdAt,
|
|
122
|
-
logsCount: this.logs.length,
|
|
123
|
-
tokenUsage: this.tokenUsage,
|
|
124
|
-
agentState: this.agentState
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
114
|
}
|
|
115
|
+
|
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { getConfigDir } from "../config";
|
|
4
4
|
import type { IPty } from "node-pty";
|
|
5
|
-
import { AgentRunner
|
|
5
|
+
import { AgentRunner } from "./AgentRunner";
|
|
6
6
|
import { SessionPersister } from "./SessionPersister";
|
|
7
7
|
|
|
8
8
|
function isWindows(): boolean {
|
|
@@ -37,7 +37,7 @@ interface SSHConfig {
|
|
|
37
37
|
export class SSHRunner extends AgentRunner {
|
|
38
38
|
id: string;
|
|
39
39
|
name: string;
|
|
40
|
-
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' = 'claude-ssh';
|
|
40
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell' = 'claude-ssh';
|
|
41
41
|
status: 'idle' | 'running' | 'stopped' = 'idle';
|
|
42
42
|
createdAt: string;
|
|
43
43
|
logs: string[] = [];
|
|
@@ -62,7 +62,7 @@ export class SSHRunner extends AgentRunner {
|
|
|
62
62
|
SessionPersister.saveSession(this);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async start(): Promise<void> {
|
|
65
|
+
async start(resume?: boolean): Promise<void> {
|
|
66
66
|
if (this.status === 'running') return;
|
|
67
67
|
|
|
68
68
|
this.status = 'running';
|
|
@@ -121,15 +121,19 @@ export class SSHRunner extends AgentRunner {
|
|
|
121
121
|
|
|
122
122
|
// Load Claude configuration to check for extraFlags (like --resume)
|
|
123
123
|
let extraFlags = "";
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
if (resume) {
|
|
125
|
+
extraFlags = "--resume";
|
|
126
|
+
} else {
|
|
127
|
+
const claudeConfigPath = path.join(getConfigDir(), "claude.json");
|
|
128
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf8"));
|
|
131
|
+
if (config.extraFlags) {
|
|
132
|
+
extraFlags = config.extraFlags;
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error("Failed to parse Claude configuration:", err);
|
|
130
136
|
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.error("Failed to parse Claude configuration:", err);
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -196,19 +200,5 @@ export class SSHRunner extends AgentRunner {
|
|
|
196
200
|
}
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
|
-
|
|
200
|
-
getStatus(): RunnerStatus {
|
|
201
|
-
return {
|
|
202
|
-
id: this.id,
|
|
203
|
-
name: this.name,
|
|
204
|
-
type: this.type,
|
|
205
|
-
status: this.status,
|
|
206
|
-
createdAt: this.createdAt,
|
|
207
|
-
logsCount: this.logs.length,
|
|
208
|
-
workspaceRoot: this.workspaceRoot,
|
|
209
|
-
scopedDirs: this.scopedDirs,
|
|
210
|
-
tokenUsage: this.tokenUsage,
|
|
211
|
-
agentState: this.agentState
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
203
|
}
|
|
204
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getConfigDir } from "../config";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
|
|
6
|
+
export interface SessionConfig {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
|
|
10
|
+
workspaceRoot?: string;
|
|
11
|
+
scopedDirs?: string[];
|
|
12
|
+
extraFlags?: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SessionConfigManager {
|
|
17
|
+
private static getConfigFile(): string {
|
|
18
|
+
return path.join(getConfigDir(), "configs.json");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static loadAllConfigs(): SessionConfig[] {
|
|
22
|
+
try {
|
|
23
|
+
const file = this.getConfigFile();
|
|
24
|
+
if (!fs.existsSync(file)) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
28
|
+
return JSON.parse(raw) || [];
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error("[SessionConfigManager] Failed to load configs:", err);
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static saveConfig(config: Omit<SessionConfig, "id" | "createdAt"> & { id?: string }): SessionConfig {
|
|
36
|
+
const configs = this.loadAllConfigs();
|
|
37
|
+
const id = config.id || crypto.randomUUID();
|
|
38
|
+
const newConfig: SessionConfig = {
|
|
39
|
+
id,
|
|
40
|
+
name: config.name,
|
|
41
|
+
type: config.type,
|
|
42
|
+
workspaceRoot: config.workspaceRoot,
|
|
43
|
+
scopedDirs: config.scopedDirs,
|
|
44
|
+
extraFlags: config.extraFlags,
|
|
45
|
+
createdAt: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const idx = configs.findIndex(c => c.id === id);
|
|
49
|
+
if (idx !== -1) {
|
|
50
|
+
configs[idx] = newConfig;
|
|
51
|
+
} else {
|
|
52
|
+
configs.push(newConfig);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(this.getConfigFile(), JSON.stringify(configs, null, 2), "utf8");
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error("[SessionConfigManager] Failed to save config to disk:", err);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return newConfig;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static deleteConfig(id: string): boolean {
|
|
65
|
+
const configs = this.loadAllConfigs();
|
|
66
|
+
const filtered = configs.filter(c => c.id !== id);
|
|
67
|
+
if (configs.length === filtered.length) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(this.getConfigFile(), JSON.stringify(filtered, null, 2), "utf8");
|
|
73
|
+
return true;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("[SessionConfigManager] Failed to delete config from disk:", err);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -33,15 +33,7 @@ export class SessionManager {
|
|
|
33
33
|
try {
|
|
34
34
|
const saved = SessionPersister.loadAllSessions();
|
|
35
35
|
for (const data of saved) {
|
|
36
|
-
//
|
|
37
|
-
if (data.status === "stopped") {
|
|
38
|
-
const createdTime = new Date(data.createdAt).getTime();
|
|
39
|
-
const ageHours = (Date.now() - createdTime) / (1000 * 60 * 60);
|
|
40
|
-
if (ageHours > 24) {
|
|
41
|
-
SessionPersister.deleteSession(data.id);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
36
|
+
// Preserved persisted session
|
|
45
37
|
|
|
46
38
|
let runner: AgentRunner;
|
|
47
39
|
if (data.type === "mock") {
|
|
@@ -52,6 +44,8 @@ export class SessionManager {
|
|
|
52
44
|
runner = new SSHRunner(data.id, data.name);
|
|
53
45
|
} else if (data.type === "aider" || data.type === "aider-docker") {
|
|
54
46
|
runner = new AiderRunner(data.id, data.type as 'aider' | 'aider-docker', data.name);
|
|
47
|
+
} else if (data.type === "shell") {
|
|
48
|
+
runner = new ShellRunner(data.id, data.name, data.workspaceRoot);
|
|
55
49
|
} else {
|
|
56
50
|
runner = new ClaudeRunner(data.id, data.name);
|
|
57
51
|
}
|
|
@@ -73,8 +67,9 @@ export class SessionManager {
|
|
|
73
67
|
public static getInstance(): SessionManager {
|
|
74
68
|
if (!globalThis.sessionManager) {
|
|
75
69
|
globalThis.sessionManager = new SessionManager();
|
|
76
|
-
// Initialize WebSocket gateway server
|
|
77
|
-
|
|
70
|
+
// Initialize WebSocket gateway server
|
|
71
|
+
const wsPort = Number(process.env.WS_PORT) || 9601;
|
|
72
|
+
initWebSocketServer(wsPort, (id) => globalThis.sessionManager!.getSession(id));
|
|
78
73
|
}
|
|
79
74
|
return globalThis.sessionManager;
|
|
80
75
|
}
|
|
@@ -84,12 +79,15 @@ export class SessionManager {
|
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
public async createSession(
|
|
87
|
-
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker',
|
|
82
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell',
|
|
88
83
|
name?: string,
|
|
89
84
|
workspaceRoot?: string,
|
|
90
|
-
scopedDirs?: string[]
|
|
85
|
+
scopedDirs?: string[],
|
|
86
|
+
parentSessionId?: string
|
|
91
87
|
): Promise<AgentRunner> {
|
|
92
|
-
const id =
|
|
88
|
+
const id = (type === 'shell' && parentSessionId)
|
|
89
|
+
? `shell-${parentSessionId}-${crypto.randomUUID()}`
|
|
90
|
+
: this.generateId();
|
|
93
91
|
let runner: AgentRunner;
|
|
94
92
|
|
|
95
93
|
if (type === 'mock') {
|
|
@@ -100,6 +98,8 @@ export class SessionManager {
|
|
|
100
98
|
runner = new SSHRunner(id, name);
|
|
101
99
|
} else if (type === 'aider' || type === 'aider-docker') {
|
|
102
100
|
runner = new AiderRunner(id, type, name);
|
|
101
|
+
} else if (type === 'shell') {
|
|
102
|
+
runner = new ShellRunner(id, name, workspaceRoot);
|
|
103
103
|
} else {
|
|
104
104
|
runner = new ClaudeRunner(id, name);
|
|
105
105
|
}
|
|
@@ -177,10 +177,10 @@ export class SessionManager {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
public async startSession(id: string): Promise<boolean> {
|
|
180
|
+
public async startSession(id: string, resume?: boolean): Promise<boolean> {
|
|
181
181
|
const runner = this.sessions.get(id);
|
|
182
182
|
if (!runner) return false;
|
|
183
|
-
await runner.start();
|
|
183
|
+
await runner.start(resume);
|
|
184
184
|
SessionPersister.saveSession(runner, true);
|
|
185
185
|
return true;
|
|
186
186
|
}
|
|
@@ -94,16 +94,20 @@ export class SessionPersister {
|
|
|
94
94
|
for (const file of files) {
|
|
95
95
|
if (file.endsWith(".json")) {
|
|
96
96
|
const filePath = path.join(dir, file);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
data.status
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
try {
|
|
98
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
99
|
+
const data: PersistedSession = JSON.parse(raw);
|
|
100
|
+
// On server restart, active PTY processes (which are dead) are marked stopped
|
|
101
|
+
if (data.status === "running" && data.type !== "mock") {
|
|
102
|
+
data.status = "stopped";
|
|
103
|
+
data.logs.push("\r\n\x1b[1;31m[System] Server restarted. Terminal connection lost.\x1b[0m\r\n");
|
|
104
|
+
// Save the updated status back to disk
|
|
105
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
106
|
+
}
|
|
107
|
+
sessions.push(data);
|
|
108
|
+
} catch (fileErr) {
|
|
109
|
+
console.error(`[SessionPersister] Failed to load session file ${file}:`, fileErr);
|
|
105
110
|
}
|
|
106
|
-
sessions.push(data);
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
return sessions;
|