chainlesschain 0.45.11 → 0.45.19

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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
  3. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
  4. package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
  6. package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
  7. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  8. package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
  9. package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
  10. package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
  14. package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
  15. package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
  16. package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
  17. package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
  18. package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
  19. package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
  20. package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
  21. package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
  22. package/src/assets/web-panel/index.html +2 -2
  23. package/src/commands/agent.js +7 -8
  24. package/src/commands/chat.js +9 -11
  25. package/src/commands/serve.js +11 -106
  26. package/src/commands/session.js +185 -18
  27. package/src/commands/ui.js +10 -151
  28. package/src/gateways/repl/agent-repl.js +1 -0
  29. package/src/gateways/repl/chat-repl.js +1 -0
  30. package/src/gateways/ui/web-ui-server.js +1 -0
  31. package/src/gateways/ws/action-protocol.js +83 -0
  32. package/src/gateways/ws/message-dispatcher.js +73 -0
  33. package/src/gateways/ws/session-protocol.js +396 -0
  34. package/src/gateways/ws/task-protocol.js +55 -0
  35. package/src/gateways/ws/worktree-protocol.js +315 -0
  36. package/src/gateways/ws/ws-server.js +4 -0
  37. package/src/gateways/ws/ws-session-gateway.js +1 -0
  38. package/src/harness/background-task-manager.js +506 -0
  39. package/src/harness/background-task-worker.js +48 -0
  40. package/src/harness/compression-telemetry.js +214 -0
  41. package/src/harness/feature-flags.js +157 -0
  42. package/src/harness/jsonl-session-store.js +452 -0
  43. package/src/harness/prompt-compressor.js +416 -0
  44. package/src/harness/worktree-isolator.js +845 -0
  45. package/src/lib/agent-core.js +246 -45
  46. package/src/lib/background-task-manager.js +1 -305
  47. package/src/lib/background-task-worker.js +1 -50
  48. package/src/lib/compression-telemetry.js +5 -0
  49. package/src/lib/feature-flags.js +7 -182
  50. package/src/lib/interaction-adapter.js +32 -6
  51. package/src/lib/jsonl-session-store.js +21 -237
  52. package/src/lib/prompt-compressor.js +10 -351
  53. package/src/lib/sub-agent-context.js +91 -0
  54. package/src/lib/worktree-isolator.js +13 -231
  55. package/src/lib/ws-agent-handler.js +1 -0
  56. package/src/lib/ws-server.js +155 -359
  57. package/src/lib/ws-session-manager.js +82 -1
  58. package/src/repl/agent-repl.js +114 -32
  59. package/src/runtime/agent-runtime.js +417 -0
  60. package/src/runtime/contracts/agent-turn.js +11 -0
  61. package/src/runtime/contracts/session-record.js +31 -0
  62. package/src/runtime/contracts/task-record.js +18 -0
  63. package/src/runtime/contracts/telemetry-record.js +23 -0
  64. package/src/runtime/contracts/worktree-record.js +14 -0
  65. package/src/runtime/index.js +13 -0
  66. package/src/runtime/policies/agent-policy.js +45 -0
  67. package/src/runtime/runtime-context.js +14 -0
  68. package/src/runtime/runtime-events.js +37 -0
  69. package/src/runtime/runtime-factory.js +50 -0
  70. package/src/tools/index.js +22 -0
  71. package/src/tools/legacy-agent-tools.js +171 -0
  72. package/src/tools/registry.js +141 -0
  73. package/src/tools/tool-context.js +28 -0
  74. package/src/tools/tool-permissions.js +28 -0
  75. package/src/tools/tool-telemetry.js +39 -0
  76. package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
  77. package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
  78. package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
  79. package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
  80. package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
  81. package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
@@ -11,6 +11,16 @@
11
11
  import crypto from "crypto";
12
12
  import { CLIContextEngineering } from "./cli-context-engineering.js";
13
13
  import { agentLoop, buildSystemPrompt, AGENT_TOOLS } from "./agent-core.js";
14
+ import { feature } from "./feature-flags.js";
15
+ import {
16
+ createWorktree,
17
+ removeWorktree,
18
+ isolateTask,
19
+ diffWorktree,
20
+ mergeWorktree,
21
+ worktreeLog,
22
+ } from "./worktree-isolator.js";
23
+ import { isGitRepo } from "./git-integration.js";
14
24
 
15
25
  // ─── Constants ──────────────────────────────────────────────────────────────
16
26
 
@@ -38,6 +48,7 @@ export class SubAgentContext {
38
48
  * @param {object} [options.permanentMemory] - Permanent memory instance
39
49
  * @param {object} [options.llmOptions] - LLM provider/model/key options
40
50
  * @param {string} [options.cwd] - Working directory
51
+ * @param {boolean} [options.useWorktree] - Force worktree isolation (overrides flag)
41
52
  * @returns {SubAgentContext}
42
53
  */
43
54
  static create(options = {}) {
@@ -59,6 +70,12 @@ export class SubAgentContext {
59
70
  this.createdAt = new Date().toISOString();
60
71
  this.completedAt = null;
61
72
 
73
+ // Worktree isolation state
74
+ this._useWorktree = options.useWorktree ?? feature("WORKTREE_ISOLATION");
75
+ this._worktreePath = null;
76
+ this._worktreeBranch = null;
77
+ this._repoDir = this.cwd;
78
+
62
79
  // ── Isolated state ──────────────────────────────────────────────
63
80
  // Independent message history — never shared with parent
64
81
  this.messages = [];
@@ -110,6 +127,77 @@ export class SubAgentContext {
110
127
  );
111
128
  }
112
129
 
130
+ // If worktree isolation is enabled, wrap execution in isolated worktree
131
+ if (this._useWorktree && isGitRepo(this._repoDir)) {
132
+ return this._runInWorktree(userPrompt, loopOptions);
133
+ }
134
+
135
+ return this._runCore(userPrompt, loopOptions);
136
+ }
137
+
138
+ /**
139
+ * Run in an isolated git worktree. Creates worktree → runs → cleans up.
140
+ */
141
+ async _runInWorktree(userPrompt, loopOptions = {}) {
142
+ const taskId = `${this.role}-${this.id.slice(4)}`;
143
+ try {
144
+ const { result, branch, worktreePath, hasChanges } = await isolateTask(
145
+ this._repoDir,
146
+ taskId,
147
+ async (wtPath) => {
148
+ this._worktreePath = wtPath;
149
+ this._worktreeBranch = `agent/${taskId}`;
150
+ // Override cwd to worktree for tool execution
151
+ this.cwd = wtPath;
152
+ return this._runCore(userPrompt, loopOptions);
153
+ },
154
+ );
155
+
156
+ // Annotate result with worktree info + diff preview
157
+ if (result) {
158
+ let diffInfo = null;
159
+ let commits = [];
160
+ if (
161
+ hasChanges ||
162
+ worktreeLog(this._repoDir, `agent/${taskId}`).length > 0
163
+ ) {
164
+ try {
165
+ diffInfo = diffWorktree(this._repoDir, `agent/${taskId}`);
166
+ commits = worktreeLog(this._repoDir, `agent/${taskId}`);
167
+ } catch (_e) {
168
+ // Non-critical — diff preview is optional
169
+ }
170
+ }
171
+ result.worktree = {
172
+ branch,
173
+ path: worktreePath,
174
+ hasChanges,
175
+ diff: diffInfo,
176
+ commits,
177
+ merge: (options = {}) =>
178
+ mergeWorktree(this._repoDir, branch, options),
179
+ };
180
+ }
181
+ return result;
182
+ } catch (err) {
183
+ // If worktree creation fails (e.g. not a git repo), fall back to direct
184
+ this.status = "failed";
185
+ this.completedAt = new Date().toISOString();
186
+ this.result = {
187
+ summary: `Worktree isolation failed: ${err.message}`,
188
+ artifacts: [],
189
+ tokenCount: this._tokenCount,
190
+ toolsUsed: [...new Set(this._toolsUsed)],
191
+ iterationCount: this._iterationCount,
192
+ };
193
+ return this.result;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Core agent loop execution (shared by direct and worktree paths).
199
+ */
200
+ async _runCore(userPrompt, loopOptions = {}) {
113
201
  // Add user message
114
202
  this.messages.push({ role: "user", content: userPrompt });
115
203
 
@@ -291,6 +379,9 @@ export class SubAgentContext {
291
379
  iterationCount: this._iterationCount,
292
380
  createdAt: this.createdAt,
293
381
  completedAt: this.completedAt,
382
+ worktree: this._worktreePath
383
+ ? { path: this._worktreePath, branch: this._worktreeBranch }
384
+ : null,
294
385
  };
295
386
  }
296
387
  }
@@ -1,231 +1,13 @@
1
- /**
2
- * Worktree Isolator — git worktree-based task isolation.
3
- *
4
- * Creates temporary git worktrees for parallel agent tasks,
5
- * ensuring file operations don't interfere with the main working tree.
6
- *
7
- * Feature-flag gated: WORKTREE_ISOLATION
8
- */
9
-
10
- import { execSync } from "node:child_process";
11
- import { existsSync, rmSync } from "node:fs";
12
- import { join, resolve } from "node:path";
13
- import { isGitRepo, gitExec } from "./git-integration.js";
14
-
15
- const WORKTREE_DIR = ".worktrees";
16
-
17
- // ── Low-level worktree operations ───────────────────────────────────────
18
-
19
- /**
20
- * Create a new git worktree with a new branch.
21
- * @param {string} repoDir — Root of the git repository
22
- * @param {string} branchName — Branch name (e.g. "agent/task-123")
23
- * @param {string} [baseBranch] — Base branch (default: HEAD)
24
- * @returns {{ path: string, branch: string }}
25
- */
26
- export function createWorktree(repoDir, branchName, baseBranch) {
27
- if (!isGitRepo(repoDir)) {
28
- throw new Error("Not a git repository");
29
- }
30
-
31
- const worktreePath = resolve(
32
- repoDir,
33
- WORKTREE_DIR,
34
- branchName.replace(/\//g, "-"),
35
- );
36
-
37
- if (existsSync(worktreePath)) {
38
- throw new Error(`Worktree already exists: ${worktreePath}`);
39
- }
40
-
41
- const base = baseBranch || "HEAD";
42
- gitExec(`worktree add "${worktreePath}" -b "${branchName}" ${base}`, repoDir);
43
-
44
- return { path: worktreePath, branch: branchName };
45
- }
46
-
47
- /**
48
- * Remove a git worktree and its branch.
49
- * @param {string} repoDir
50
- * @param {string} worktreePath — Absolute path to the worktree
51
- * @param {object} [options]
52
- * @param {boolean} [options.deleteBranch=true] — Also delete the branch
53
- */
54
- export function removeWorktree(repoDir, worktreePath, options = {}) {
55
- const deleteBranch = options.deleteBranch !== false;
56
-
57
- // Get branch name before removal
58
- let branch = null;
59
- if (deleteBranch) {
60
- try {
61
- branch = execSync("git rev-parse --abbrev-ref HEAD", {
62
- cwd: worktreePath,
63
- encoding: "utf-8",
64
- }).trim();
65
- } catch (_e) {
66
- // Can't determine branch — skip branch deletion
67
- }
68
- }
69
-
70
- try {
71
- gitExec(`worktree remove "${worktreePath}" --force`, repoDir);
72
- } catch (_e) {
73
- // If git worktree remove fails, try manual cleanup
74
- if (existsSync(worktreePath)) {
75
- rmSync(worktreePath, { recursive: true, force: true });
76
- }
77
- gitExec("worktree prune", repoDir);
78
- }
79
-
80
- // Delete the branch
81
- if (
82
- deleteBranch &&
83
- branch &&
84
- branch !== "HEAD" &&
85
- !branch.startsWith("main") &&
86
- !branch.startsWith("master")
87
- ) {
88
- try {
89
- gitExec(`branch -D "${branch}"`, repoDir);
90
- } catch (_e) {
91
- // Branch might already be deleted
92
- }
93
- }
94
- }
95
-
96
- /**
97
- * List all worktrees.
98
- * @param {string} repoDir
99
- * @returns {Array<{path: string, branch: string, head: string}>}
100
- */
101
- export function listWorktrees(repoDir) {
102
- if (!isGitRepo(repoDir)) return [];
103
-
104
- try {
105
- const output = gitExec("worktree list --porcelain", repoDir);
106
- const worktrees = [];
107
- let current = {};
108
-
109
- for (const line of output.split("\n")) {
110
- if (line.startsWith("worktree ")) {
111
- if (current.path) worktrees.push(current);
112
- current = { path: line.slice(9) };
113
- } else if (line.startsWith("HEAD ")) {
114
- current.head = line.slice(5);
115
- } else if (line.startsWith("branch ")) {
116
- current.branch = line.slice(7).replace("refs/heads/", "");
117
- } else if (line === "bare") {
118
- current.bare = true;
119
- }
120
- }
121
- if (current.path) worktrees.push(current);
122
-
123
- return worktrees;
124
- } catch (_e) {
125
- return [];
126
- }
127
- }
128
-
129
- /**
130
- * Prune stale worktrees (where directory no longer exists).
131
- * @param {string} repoDir
132
- * @returns {number} Number pruned
133
- */
134
- export function pruneWorktrees(repoDir) {
135
- if (!isGitRepo(repoDir)) return 0;
136
-
137
- const before = listWorktrees(repoDir).length;
138
- gitExec("worktree prune", repoDir);
139
- const after = listWorktrees(repoDir).length;
140
- return before - after;
141
- }
142
-
143
- // ── High-level isolation API ────────────────────────────────────────────
144
-
145
- /**
146
- * Run a function in an isolated git worktree.
147
- *
148
- * Creates a worktree → executes fn(worktreePath) → cleans up.
149
- * If fn throws, the worktree is still cleaned up.
150
- *
151
- * @param {string} repoDir — Root of the git repository
152
- * @param {string} taskId — Used to generate branch name: agent/{taskId}
153
- * @param {Function} fn — async (worktreePath) => result
154
- * @returns {Promise<{result, branch, merged}>}
155
- */
156
- export async function isolateTask(repoDir, taskId, fn) {
157
- const branchName = `agent/${taskId}`;
158
- const { path: worktreePath } = createWorktree(repoDir, branchName);
159
-
160
- try {
161
- const result = await fn(worktreePath);
162
-
163
- // Check if the worktree has any changes
164
- const hasChanges = _hasUncommittedChanges(worktreePath);
165
-
166
- return {
167
- result,
168
- branch: branchName,
169
- worktreePath,
170
- hasChanges,
171
- };
172
- } finally {
173
- // Always clean up the worktree (but keep branch if there are commits)
174
- try {
175
- const hasCommits = _hasBranchCommits(repoDir, branchName);
176
- removeWorktree(repoDir, worktreePath, {
177
- deleteBranch: !hasCommits,
178
- });
179
- } catch (_e) {
180
- // Best-effort cleanup
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * Clean up all agent worktrees (e.g. after crash recovery).
187
- * @param {string} repoDir
188
- * @returns {number} Number of worktrees cleaned
189
- */
190
- export function cleanupAgentWorktrees(repoDir) {
191
- const worktrees = listWorktrees(repoDir);
192
- let cleaned = 0;
193
-
194
- for (const wt of worktrees) {
195
- if (wt.branch && wt.branch.startsWith("agent/")) {
196
- try {
197
- removeWorktree(repoDir, wt.path, { deleteBranch: true });
198
- cleaned++;
199
- } catch (_e) {
200
- // Skip if can't clean
201
- }
202
- }
203
- }
204
-
205
- pruneWorktrees(repoDir);
206
- return cleaned;
207
- }
208
-
209
- // ── Internal helpers ────────────────────────────────────────────────────
210
-
211
- function _hasUncommittedChanges(worktreePath) {
212
- try {
213
- const output = execSync("git status --porcelain", {
214
- cwd: worktreePath,
215
- encoding: "utf-8",
216
- });
217
- return output.trim().length > 0;
218
- } catch (_e) {
219
- return false;
220
- }
221
- }
222
-
223
- function _hasBranchCommits(repoDir, branchName) {
224
- try {
225
- // Check if branch has commits not in HEAD
226
- const output = gitExec(`log HEAD..${branchName} --oneline`, repoDir);
227
- return output.trim().length > 0;
228
- } catch (_e) {
229
- return false;
230
- }
231
- }
1
+ export {
2
+ createWorktree,
3
+ removeWorktree,
4
+ listWorktrees,
5
+ pruneWorktrees,
6
+ isolateTask,
7
+ cleanupAgentWorktrees,
8
+ diffWorktree,
9
+ previewWorktreeMerge,
10
+ applyWorktreeAutomationCandidate,
11
+ mergeWorktree,
12
+ worktreeLog,
13
+ } from "../harness/worktree-isolator.js";
@@ -83,6 +83,7 @@ export class WSAgentHandler {
83
83
  contextEngine: session.contextEngine,
84
84
  hookDb: this.db,
85
85
  cwd: session.projectRoot,
86
+ hostManagedToolPolicy: session.hostManagedToolPolicy || null,
86
87
  slotFiller,
87
88
  interaction: this.interaction,
88
89
  };