@foothill/agent-move 1.0.6

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 (163) hide show
  1. package/.github/screenshot.png +0 -0
  2. package/README.md +266 -0
  3. package/bin/cli.js +229 -0
  4. package/package.json +53 -0
  5. package/packages/client/dist/assets/BufferResource-Ddjob236.js +185 -0
  6. package/packages/client/dist/assets/CanvasRenderer-B0w6SYyW.js +1 -0
  7. package/packages/client/dist/assets/Filter-NcMGuiK-.js +1 -0
  8. package/packages/client/dist/assets/RenderTargetSystem-DgAzY5_U.js +172 -0
  9. package/packages/client/dist/assets/WebGLRenderer-DUWXDPIX.js +156 -0
  10. package/packages/client/dist/assets/WebGPURenderer-C1HbrllR.js +41 -0
  11. package/packages/client/dist/assets/browserAll-CaF1Fl0O.js +14 -0
  12. package/packages/client/dist/assets/index-CMmR_RuS.css +1 -0
  13. package/packages/client/dist/assets/index-Dh8yWoLP.js +711 -0
  14. package/packages/client/dist/assets/webworkerAll-BJ6UhC7r.js +83 -0
  15. package/packages/client/dist/favicon.svg +27 -0
  16. package/packages/client/dist/index.html +167 -0
  17. package/packages/client/package.json +19 -0
  18. package/packages/server/dist/config.d.ts +12 -0
  19. package/packages/server/dist/config.d.ts.map +1 -0
  20. package/packages/server/dist/config.js +19 -0
  21. package/packages/server/dist/config.js.map +1 -0
  22. package/packages/server/dist/hooks/hook-event-manager.d.ts +39 -0
  23. package/packages/server/dist/hooks/hook-event-manager.d.ts.map +1 -0
  24. package/packages/server/dist/hooks/hook-event-manager.js +161 -0
  25. package/packages/server/dist/hooks/hook-event-manager.js.map +1 -0
  26. package/packages/server/dist/hooks/hook-installer.d.ts +14 -0
  27. package/packages/server/dist/hooks/hook-installer.d.ts.map +1 -0
  28. package/packages/server/dist/hooks/hook-installer.js +179 -0
  29. package/packages/server/dist/hooks/hook-installer.js.map +1 -0
  30. package/packages/server/dist/index.d.ts +4 -0
  31. package/packages/server/dist/index.d.ts.map +1 -0
  32. package/packages/server/dist/index.js +3544 -0
  33. package/packages/server/dist/index.js.map +1 -0
  34. package/packages/server/dist/routes/api.d.ts +4 -0
  35. package/packages/server/dist/routes/api.d.ts.map +1 -0
  36. package/packages/server/dist/routes/api.js +27 -0
  37. package/packages/server/dist/routes/api.js.map +1 -0
  38. package/packages/server/dist/state/activity-processor.d.ts +31 -0
  39. package/packages/server/dist/state/activity-processor.d.ts.map +1 -0
  40. package/packages/server/dist/state/activity-processor.js +417 -0
  41. package/packages/server/dist/state/activity-processor.js.map +1 -0
  42. package/packages/server/dist/state/agent-state-manager.d.ts +107 -0
  43. package/packages/server/dist/state/agent-state-manager.d.ts.map +1 -0
  44. package/packages/server/dist/state/agent-state-manager.js +1622 -0
  45. package/packages/server/dist/state/agent-state-manager.js.map +1 -0
  46. package/packages/server/dist/state/anomaly-detector.d.ts +23 -0
  47. package/packages/server/dist/state/anomaly-detector.d.ts.map +1 -0
  48. package/packages/server/dist/state/anomaly-detector.js +272 -0
  49. package/packages/server/dist/state/anomaly-detector.js.map +1 -0
  50. package/packages/server/dist/state/identity-manager.d.ts +31 -0
  51. package/packages/server/dist/state/identity-manager.d.ts.map +1 -0
  52. package/packages/server/dist/state/identity-manager.js +63 -0
  53. package/packages/server/dist/state/identity-manager.js.map +1 -0
  54. package/packages/server/dist/state/role-resolver.d.ts +23 -0
  55. package/packages/server/dist/state/role-resolver.d.ts.map +1 -0
  56. package/packages/server/dist/state/role-resolver.js +43 -0
  57. package/packages/server/dist/state/role-resolver.js.map +1 -0
  58. package/packages/server/dist/state/task-graph-manager.d.ts +31 -0
  59. package/packages/server/dist/state/task-graph-manager.d.ts.map +1 -0
  60. package/packages/server/dist/state/task-graph-manager.js +191 -0
  61. package/packages/server/dist/state/task-graph-manager.js.map +1 -0
  62. package/packages/server/dist/state/tool-chain-tracker.d.ts +23 -0
  63. package/packages/server/dist/state/tool-chain-tracker.d.ts.map +1 -0
  64. package/packages/server/dist/state/tool-chain-tracker.js +113 -0
  65. package/packages/server/dist/state/tool-chain-tracker.js.map +1 -0
  66. package/packages/server/dist/watcher/agent-watcher.d.ts +16 -0
  67. package/packages/server/dist/watcher/agent-watcher.d.ts.map +1 -0
  68. package/packages/server/dist/watcher/agent-watcher.js +0 -0
  69. package/packages/server/dist/watcher/agent-watcher.js.map +1 -0
  70. package/packages/server/dist/watcher/claude-paths.d.ts +32 -0
  71. package/packages/server/dist/watcher/claude-paths.d.ts.map +1 -0
  72. package/packages/server/dist/watcher/claude-paths.js +104 -0
  73. package/packages/server/dist/watcher/claude-paths.js.map +1 -0
  74. package/packages/server/dist/watcher/file-watcher.d.ts +17 -0
  75. package/packages/server/dist/watcher/file-watcher.d.ts.map +1 -0
  76. package/packages/server/dist/watcher/file-watcher.js +347 -0
  77. package/packages/server/dist/watcher/file-watcher.js.map +1 -0
  78. package/packages/server/dist/watcher/git-info.d.ts +3 -0
  79. package/packages/server/dist/watcher/git-info.d.ts.map +1 -0
  80. package/packages/server/dist/watcher/git-info.js +35 -0
  81. package/packages/server/dist/watcher/git-info.js.map +1 -0
  82. package/packages/server/dist/watcher/jsonl-parser.d.ts +21 -0
  83. package/packages/server/dist/watcher/jsonl-parser.d.ts.map +1 -0
  84. package/packages/server/dist/watcher/jsonl-parser.js +95 -0
  85. package/packages/server/dist/watcher/jsonl-parser.js.map +1 -0
  86. package/packages/server/dist/watcher/opencode/opencode-parser.d.ts +58 -0
  87. package/packages/server/dist/watcher/opencode/opencode-parser.d.ts.map +1 -0
  88. package/packages/server/dist/watcher/opencode/opencode-parser.js +256 -0
  89. package/packages/server/dist/watcher/opencode/opencode-parser.js.map +1 -0
  90. package/packages/server/dist/watcher/opencode/opencode-paths.d.ts +20 -0
  91. package/packages/server/dist/watcher/opencode/opencode-paths.d.ts.map +1 -0
  92. package/packages/server/dist/watcher/opencode/opencode-paths.js +35 -0
  93. package/packages/server/dist/watcher/opencode/opencode-paths.js.map +1 -0
  94. package/packages/server/dist/watcher/opencode/opencode-watcher.d.ts +49 -0
  95. package/packages/server/dist/watcher/opencode/opencode-watcher.d.ts.map +1 -0
  96. package/packages/server/dist/watcher/opencode/opencode-watcher.js +1292 -0
  97. package/packages/server/dist/watcher/opencode/opencode-watcher.js.map +1 -0
  98. package/packages/server/dist/watcher/session-scanner.d.ts +7 -0
  99. package/packages/server/dist/watcher/session-scanner.d.ts.map +1 -0
  100. package/packages/server/dist/watcher/session-scanner.js +69 -0
  101. package/packages/server/dist/watcher/session-scanner.js.map +1 -0
  102. package/packages/server/dist/ws/broadcaster.d.ts +18 -0
  103. package/packages/server/dist/ws/broadcaster.d.ts.map +1 -0
  104. package/packages/server/dist/ws/broadcaster.js +152 -0
  105. package/packages/server/dist/ws/broadcaster.js.map +1 -0
  106. package/packages/server/dist/ws/ws-handler.d.ts +6 -0
  107. package/packages/server/dist/ws/ws-handler.d.ts.map +1 -0
  108. package/packages/server/dist/ws/ws-handler.js +55 -0
  109. package/packages/server/dist/ws/ws-handler.js.map +1 -0
  110. package/packages/server/package.json +25 -0
  111. package/packages/shared/dist/constants/colors.d.ts +46 -0
  112. package/packages/shared/dist/constants/colors.d.ts.map +1 -0
  113. package/packages/shared/dist/constants/colors.js +178 -0
  114. package/packages/shared/dist/constants/colors.js.map +1 -0
  115. package/packages/shared/dist/constants/names.d.ts +6 -0
  116. package/packages/shared/dist/constants/names.d.ts.map +1 -0
  117. package/packages/shared/dist/constants/names.js +28 -0
  118. package/packages/shared/dist/constants/names.js.map +1 -0
  119. package/packages/shared/dist/constants/tools.d.ts +15 -0
  120. package/packages/shared/dist/constants/tools.d.ts.map +1 -0
  121. package/packages/shared/dist/constants/tools.js +120 -0
  122. package/packages/shared/dist/constants/tools.js.map +1 -0
  123. package/packages/shared/dist/constants/zones.d.ts +37 -0
  124. package/packages/shared/dist/constants/zones.d.ts.map +1 -0
  125. package/packages/shared/dist/constants/zones.js +128 -0
  126. package/packages/shared/dist/constants/zones.js.map +1 -0
  127. package/packages/shared/dist/index.d.ts +15 -0
  128. package/packages/shared/dist/index.d.ts.map +1 -0
  129. package/packages/shared/dist/index.js +7 -0
  130. package/packages/shared/dist/index.js.map +1 -0
  131. package/packages/shared/dist/types/agent.d.ts +85 -0
  132. package/packages/shared/dist/types/agent.d.ts.map +1 -0
  133. package/packages/shared/dist/types/agent.js +2 -0
  134. package/packages/shared/dist/types/agent.js.map +1 -0
  135. package/packages/shared/dist/types/anomaly.d.ts +18 -0
  136. package/packages/shared/dist/types/anomaly.d.ts.map +1 -0
  137. package/packages/shared/dist/types/anomaly.js +6 -0
  138. package/packages/shared/dist/types/anomaly.js.map +1 -0
  139. package/packages/shared/dist/types/hooks.d.ts +51 -0
  140. package/packages/shared/dist/types/hooks.d.ts.map +1 -0
  141. package/packages/shared/dist/types/hooks.js +2 -0
  142. package/packages/shared/dist/types/hooks.js.map +1 -0
  143. package/packages/shared/dist/types/jsonl.d.ts +62 -0
  144. package/packages/shared/dist/types/jsonl.d.ts.map +1 -0
  145. package/packages/shared/dist/types/jsonl.js +3 -0
  146. package/packages/shared/dist/types/jsonl.js.map +1 -0
  147. package/packages/shared/dist/types/task-graph.d.ts +20 -0
  148. package/packages/shared/dist/types/task-graph.d.ts.map +1 -0
  149. package/packages/shared/dist/types/task-graph.js +2 -0
  150. package/packages/shared/dist/types/task-graph.js.map +1 -0
  151. package/packages/shared/dist/types/tool-chain.d.ts +17 -0
  152. package/packages/shared/dist/types/tool-chain.d.ts.map +1 -0
  153. package/packages/shared/dist/types/tool-chain.js +2 -0
  154. package/packages/shared/dist/types/tool-chain.js.map +1 -0
  155. package/packages/shared/dist/types/websocket.d.ts +132 -0
  156. package/packages/shared/dist/types/websocket.d.ts.map +1 -0
  157. package/packages/shared/dist/types/websocket.js +2 -0
  158. package/packages/shared/dist/types/websocket.js.map +1 -0
  159. package/packages/shared/dist/types/zone.d.ts +21 -0
  160. package/packages/shared/dist/types/zone.d.ts.map +1 -0
  161. package/packages/shared/dist/types/zone.js +2 -0
  162. package/packages/shared/dist/types/zone.js.map +1 -0
  163. package/packages/shared/package.json +15 -0
@@ -0,0 +1,3544 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // dist/index.js
9
+ import { fileURLToPath } from "url";
10
+ import { dirname, join as join6 } from "path";
11
+ import Fastify from "fastify";
12
+ import cors from "@fastify/cors";
13
+ import websocket from "@fastify/websocket";
14
+ import fastifyStatic from "@fastify/static";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ import chokidar from "chokidar";
18
+ import { stat as stat2, open } from "fs/promises";
19
+ import { join as join4, basename } from "path";
20
+ import { existsSync } from "fs";
21
+ import { join as join2 } from "path";
22
+ import { readdir, stat } from "fs/promises";
23
+ import { join as join3 } from "path";
24
+ import chokidar2 from "chokidar";
25
+ import { homedir as homedir2 } from "os";
26
+ import { join as join5 } from "path";
27
+ import { existsSync as existsSync2 } from "fs";
28
+ import { EventEmitter as EventEmitter2 } from "events";
29
+ import { EventEmitter } from "events";
30
+ import { execSync } from "child_process";
31
+ import { EventEmitter as EventEmitter3 } from "events";
32
+ import { randomUUID } from "crypto";
33
+ var __create = Object.create;
34
+ var __defProp = Object.defineProperty;
35
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
36
+ var __getOwnPropNames = Object.getOwnPropertyNames;
37
+ var __getProtoOf = Object.getPrototypeOf;
38
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
39
+ var __require2 = /* @__PURE__ */ ((x) => typeof __require !== "undefined" ? __require : typeof Proxy !== "undefined" ? new Proxy(x, {
40
+ get: (a, b) => (typeof __require !== "undefined" ? __require : a)[b]
41
+ }) : x)(function(x) {
42
+ if (typeof __require !== "undefined") return __require.apply(this, arguments);
43
+ throw Error('Dynamic require of "' + x + '" is not supported');
44
+ });
45
+ var __commonJS = (cb, mod) => function __require22() {
46
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
47
+ };
48
+ var __copyProps = (to, from, except, desc) => {
49
+ if (from && typeof from === "object" || typeof from === "function") {
50
+ for (let key of __getOwnPropNames(from))
51
+ if (!__hasOwnProp.call(to, key) && key !== except)
52
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
53
+ }
54
+ return to;
55
+ };
56
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
57
+ // If the importer is in node compatibility mode or this is not an ESM
58
+ // file that has been converted to a CommonJS file using a Babel-
59
+ // compatible transform (i.e. "__esModule" has not been set), then set
60
+ // "default" to the CommonJS "module.exports" for node compatibility.
61
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
62
+ mod
63
+ ));
64
+ var require_util = __commonJS({
65
+ "../../node_modules/better-sqlite3/lib/util.js"(exports) {
66
+ "use strict";
67
+ exports.getBooleanOption = (options, key) => {
68
+ let value = false;
69
+ if (key in options && typeof (value = options[key]) !== "boolean") {
70
+ throw new TypeError(`Expected the "${key}" option to be a boolean`);
71
+ }
72
+ return value;
73
+ };
74
+ exports.cppdb = /* @__PURE__ */ Symbol();
75
+ exports.inspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
76
+ }
77
+ });
78
+ var require_sqlite_error = __commonJS({
79
+ "../../node_modules/better-sqlite3/lib/sqlite-error.js"(exports, module) {
80
+ "use strict";
81
+ var descriptor = { value: "SqliteError", writable: true, enumerable: false, configurable: true };
82
+ function SqliteError(message, code) {
83
+ if (new.target !== SqliteError) {
84
+ return new SqliteError(message, code);
85
+ }
86
+ if (typeof code !== "string") {
87
+ throw new TypeError("Expected second argument to be a string");
88
+ }
89
+ Error.call(this, message);
90
+ descriptor.value = "" + message;
91
+ Object.defineProperty(this, "message", descriptor);
92
+ Error.captureStackTrace(this, SqliteError);
93
+ this.code = code;
94
+ }
95
+ Object.setPrototypeOf(SqliteError, Error);
96
+ Object.setPrototypeOf(SqliteError.prototype, Error.prototype);
97
+ Object.defineProperty(SqliteError.prototype, "name", descriptor);
98
+ module.exports = SqliteError;
99
+ }
100
+ });
101
+ var require_file_uri_to_path = __commonJS({
102
+ "../../node_modules/file-uri-to-path/index.js"(exports, module) {
103
+ var sep = __require2("path").sep || "/";
104
+ module.exports = fileUriToPath;
105
+ function fileUriToPath(uri) {
106
+ if ("string" != typeof uri || uri.length <= 7 || "file://" != uri.substring(0, 7)) {
107
+ throw new TypeError("must pass in a file:// URI to convert to a file path");
108
+ }
109
+ var rest = decodeURI(uri.substring(7));
110
+ var firstSlash = rest.indexOf("/");
111
+ var host = rest.substring(0, firstSlash);
112
+ var path = rest.substring(firstSlash + 1);
113
+ if ("localhost" == host) host = "";
114
+ if (host) {
115
+ host = sep + sep + host;
116
+ }
117
+ path = path.replace(/^(.+)\|/, "$1:");
118
+ if (sep == "\\") {
119
+ path = path.replace(/\//g, "\\");
120
+ }
121
+ if (/^.+\:/.test(path)) {
122
+ } else {
123
+ path = sep + path;
124
+ }
125
+ return host + path;
126
+ }
127
+ }
128
+ });
129
+ var require_bindings = __commonJS({
130
+ "../../node_modules/bindings/bindings.js"(exports, module) {
131
+ var fs = __require2("fs");
132
+ var path = __require2("path");
133
+ var fileURLToPath2 = require_file_uri_to_path();
134
+ var join7 = path.join;
135
+ var dirname2 = path.dirname;
136
+ var exists = fs.accessSync && function(path2) {
137
+ try {
138
+ fs.accessSync(path2);
139
+ } catch (e) {
140
+ return false;
141
+ }
142
+ return true;
143
+ } || fs.existsSync || path.existsSync;
144
+ var defaults = {
145
+ arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
146
+ compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
147
+ platform: process.platform,
148
+ arch: process.arch,
149
+ nodePreGyp: "node-v" + process.versions.modules + "-" + process.platform + "-" + process.arch,
150
+ version: process.versions.node,
151
+ bindings: "bindings.node",
152
+ try: [
153
+ // node-gyp's linked version in the "build" dir
154
+ ["module_root", "build", "bindings"],
155
+ // node-waf and gyp_addon (a.k.a node-gyp)
156
+ ["module_root", "build", "Debug", "bindings"],
157
+ ["module_root", "build", "Release", "bindings"],
158
+ // Debug files, for development (legacy behavior, remove for node v0.9)
159
+ ["module_root", "out", "Debug", "bindings"],
160
+ ["module_root", "Debug", "bindings"],
161
+ // Release files, but manually compiled (legacy behavior, remove for node v0.9)
162
+ ["module_root", "out", "Release", "bindings"],
163
+ ["module_root", "Release", "bindings"],
164
+ // Legacy from node-waf, node <= 0.4.x
165
+ ["module_root", "build", "default", "bindings"],
166
+ // Production "Release" buildtype binary (meh...)
167
+ ["module_root", "compiled", "version", "platform", "arch", "bindings"],
168
+ // node-qbs builds
169
+ ["module_root", "addon-build", "release", "install-root", "bindings"],
170
+ ["module_root", "addon-build", "debug", "install-root", "bindings"],
171
+ ["module_root", "addon-build", "default", "install-root", "bindings"],
172
+ // node-pre-gyp path ./lib/binding/{node_abi}-{platform}-{arch}
173
+ ["module_root", "lib", "binding", "nodePreGyp", "bindings"]
174
+ ]
175
+ };
176
+ function bindings(opts) {
177
+ if (typeof opts == "string") {
178
+ opts = { bindings: opts };
179
+ } else if (!opts) {
180
+ opts = {};
181
+ }
182
+ Object.keys(defaults).map(function(i2) {
183
+ if (!(i2 in opts)) opts[i2] = defaults[i2];
184
+ });
185
+ if (!opts.module_root) {
186
+ opts.module_root = exports.getRoot(exports.getFileName());
187
+ }
188
+ if (path.extname(opts.bindings) != ".node") {
189
+ opts.bindings += ".node";
190
+ }
191
+ var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require2;
192
+ var tries = [], i = 0, l = opts.try.length, n, b, err;
193
+ for (; i < l; i++) {
194
+ n = join7.apply(
195
+ null,
196
+ opts.try[i].map(function(p) {
197
+ return opts[p] || p;
198
+ })
199
+ );
200
+ tries.push(n);
201
+ try {
202
+ b = opts.path ? requireFunc.resolve(n) : requireFunc(n);
203
+ if (!opts.path) {
204
+ b.path = n;
205
+ }
206
+ return b;
207
+ } catch (e) {
208
+ if (e.code !== "MODULE_NOT_FOUND" && e.code !== "QUALIFIED_PATH_RESOLUTION_FAILED" && !/not find/i.test(e.message)) {
209
+ throw e;
210
+ }
211
+ }
212
+ }
213
+ err = new Error(
214
+ "Could not locate the bindings file. Tried:\n" + tries.map(function(a) {
215
+ return opts.arrow + a;
216
+ }).join("\n")
217
+ );
218
+ err.tries = tries;
219
+ throw err;
220
+ }
221
+ module.exports = exports = bindings;
222
+ exports.getFileName = function getFileName(calling_file) {
223
+ var origPST = Error.prepareStackTrace, origSTL = Error.stackTraceLimit, dummy = {}, fileName;
224
+ Error.stackTraceLimit = 10;
225
+ Error.prepareStackTrace = function(e, st) {
226
+ for (var i = 0, l = st.length; i < l; i++) {
227
+ fileName = st[i].getFileName();
228
+ if (fileName !== __filename) {
229
+ if (calling_file) {
230
+ if (fileName !== calling_file) {
231
+ return;
232
+ }
233
+ } else {
234
+ return;
235
+ }
236
+ }
237
+ }
238
+ };
239
+ Error.captureStackTrace(dummy);
240
+ dummy.stack;
241
+ Error.prepareStackTrace = origPST;
242
+ Error.stackTraceLimit = origSTL;
243
+ var fileSchema = "file://";
244
+ if (fileName.indexOf(fileSchema) === 0) {
245
+ fileName = fileURLToPath2(fileName);
246
+ }
247
+ return fileName;
248
+ };
249
+ exports.getRoot = function getRoot(file) {
250
+ var dir = dirname2(file), prev;
251
+ while (true) {
252
+ if (dir === ".") {
253
+ dir = process.cwd();
254
+ }
255
+ if (exists(join7(dir, "package.json")) || exists(join7(dir, "node_modules"))) {
256
+ return dir;
257
+ }
258
+ if (prev === dir) {
259
+ throw new Error(
260
+ 'Could not find module root given file: "' + file + '". Do you have a `package.json` file? '
261
+ );
262
+ }
263
+ prev = dir;
264
+ dir = join7(dir, "..");
265
+ }
266
+ };
267
+ }
268
+ });
269
+ var require_wrappers = __commonJS({
270
+ "../../node_modules/better-sqlite3/lib/methods/wrappers.js"(exports) {
271
+ "use strict";
272
+ var { cppdb } = require_util();
273
+ exports.prepare = function prepare(sql) {
274
+ return this[cppdb].prepare(sql, this, false);
275
+ };
276
+ exports.exec = function exec(sql) {
277
+ this[cppdb].exec(sql);
278
+ return this;
279
+ };
280
+ exports.close = function close() {
281
+ this[cppdb].close();
282
+ return this;
283
+ };
284
+ exports.loadExtension = function loadExtension(...args) {
285
+ this[cppdb].loadExtension(...args);
286
+ return this;
287
+ };
288
+ exports.defaultSafeIntegers = function defaultSafeIntegers(...args) {
289
+ this[cppdb].defaultSafeIntegers(...args);
290
+ return this;
291
+ };
292
+ exports.unsafeMode = function unsafeMode(...args) {
293
+ this[cppdb].unsafeMode(...args);
294
+ return this;
295
+ };
296
+ exports.getters = {
297
+ name: {
298
+ get: function name() {
299
+ return this[cppdb].name;
300
+ },
301
+ enumerable: true
302
+ },
303
+ open: {
304
+ get: function open2() {
305
+ return this[cppdb].open;
306
+ },
307
+ enumerable: true
308
+ },
309
+ inTransaction: {
310
+ get: function inTransaction() {
311
+ return this[cppdb].inTransaction;
312
+ },
313
+ enumerable: true
314
+ },
315
+ readonly: {
316
+ get: function readonly() {
317
+ return this[cppdb].readonly;
318
+ },
319
+ enumerable: true
320
+ },
321
+ memory: {
322
+ get: function memory() {
323
+ return this[cppdb].memory;
324
+ },
325
+ enumerable: true
326
+ }
327
+ };
328
+ }
329
+ });
330
+ var require_transaction = __commonJS({
331
+ "../../node_modules/better-sqlite3/lib/methods/transaction.js"(exports, module) {
332
+ "use strict";
333
+ var { cppdb } = require_util();
334
+ var controllers = /* @__PURE__ */ new WeakMap();
335
+ module.exports = function transaction(fn) {
336
+ if (typeof fn !== "function") throw new TypeError("Expected first argument to be a function");
337
+ const db = this[cppdb];
338
+ const controller = getController(db, this);
339
+ const { apply } = Function.prototype;
340
+ const properties = {
341
+ default: { value: wrapTransaction(apply, fn, db, controller.default) },
342
+ deferred: { value: wrapTransaction(apply, fn, db, controller.deferred) },
343
+ immediate: { value: wrapTransaction(apply, fn, db, controller.immediate) },
344
+ exclusive: { value: wrapTransaction(apply, fn, db, controller.exclusive) },
345
+ database: { value: this, enumerable: true }
346
+ };
347
+ Object.defineProperties(properties.default.value, properties);
348
+ Object.defineProperties(properties.deferred.value, properties);
349
+ Object.defineProperties(properties.immediate.value, properties);
350
+ Object.defineProperties(properties.exclusive.value, properties);
351
+ return properties.default.value;
352
+ };
353
+ var getController = (db, self) => {
354
+ let controller = controllers.get(db);
355
+ if (!controller) {
356
+ const shared = {
357
+ commit: db.prepare("COMMIT", self, false),
358
+ rollback: db.prepare("ROLLBACK", self, false),
359
+ savepoint: db.prepare("SAVEPOINT ` _bs3. `", self, false),
360
+ release: db.prepare("RELEASE ` _bs3. `", self, false),
361
+ rollbackTo: db.prepare("ROLLBACK TO ` _bs3. `", self, false)
362
+ };
363
+ controllers.set(db, controller = {
364
+ default: Object.assign({ begin: db.prepare("BEGIN", self, false) }, shared),
365
+ deferred: Object.assign({ begin: db.prepare("BEGIN DEFERRED", self, false) }, shared),
366
+ immediate: Object.assign({ begin: db.prepare("BEGIN IMMEDIATE", self, false) }, shared),
367
+ exclusive: Object.assign({ begin: db.prepare("BEGIN EXCLUSIVE", self, false) }, shared)
368
+ });
369
+ }
370
+ return controller;
371
+ };
372
+ var wrapTransaction = (apply, fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) => function sqliteTransaction() {
373
+ let before, after, undo;
374
+ if (db.inTransaction) {
375
+ before = savepoint;
376
+ after = release;
377
+ undo = rollbackTo;
378
+ } else {
379
+ before = begin;
380
+ after = commit;
381
+ undo = rollback;
382
+ }
383
+ before.run();
384
+ try {
385
+ const result = apply.call(fn, this, arguments);
386
+ if (result && typeof result.then === "function") {
387
+ throw new TypeError("Transaction function cannot return a promise");
388
+ }
389
+ after.run();
390
+ return result;
391
+ } catch (ex) {
392
+ if (db.inTransaction) {
393
+ undo.run();
394
+ if (undo !== rollback) after.run();
395
+ }
396
+ throw ex;
397
+ }
398
+ };
399
+ }
400
+ });
401
+ var require_pragma = __commonJS({
402
+ "../../node_modules/better-sqlite3/lib/methods/pragma.js"(exports, module) {
403
+ "use strict";
404
+ var { getBooleanOption, cppdb } = require_util();
405
+ module.exports = function pragma(source, options) {
406
+ if (options == null) options = {};
407
+ if (typeof source !== "string") throw new TypeError("Expected first argument to be a string");
408
+ if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
409
+ const simple = getBooleanOption(options, "simple");
410
+ const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true);
411
+ return simple ? stmt.pluck().get() : stmt.all();
412
+ };
413
+ }
414
+ });
415
+ var require_backup = __commonJS({
416
+ "../../node_modules/better-sqlite3/lib/methods/backup.js"(exports, module) {
417
+ "use strict";
418
+ var fs = __require2("fs");
419
+ var path = __require2("path");
420
+ var { promisify } = __require2("util");
421
+ var { cppdb } = require_util();
422
+ var fsAccess = promisify(fs.access);
423
+ module.exports = async function backup(filename, options) {
424
+ if (options == null) options = {};
425
+ if (typeof filename !== "string") throw new TypeError("Expected first argument to be a string");
426
+ if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
427
+ filename = filename.trim();
428
+ const attachedName = "attached" in options ? options.attached : "main";
429
+ const handler = "progress" in options ? options.progress : null;
430
+ if (!filename) throw new TypeError("Backup filename cannot be an empty string");
431
+ if (filename === ":memory:") throw new TypeError('Invalid backup filename ":memory:"');
432
+ if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
433
+ if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
434
+ if (handler != null && typeof handler !== "function") throw new TypeError('Expected the "progress" option to be a function');
435
+ await fsAccess(path.dirname(filename)).catch(() => {
436
+ throw new TypeError("Cannot save backup because the directory does not exist");
437
+ });
438
+ const isNewFile = await fsAccess(filename).then(() => false, () => true);
439
+ return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
440
+ };
441
+ var runBackup = (backup, handler) => {
442
+ let rate = 0;
443
+ let useDefault = true;
444
+ return new Promise((resolve, reject) => {
445
+ setImmediate(function step() {
446
+ try {
447
+ const progress = backup.transfer(rate);
448
+ if (!progress.remainingPages) {
449
+ backup.close();
450
+ resolve(progress);
451
+ return;
452
+ }
453
+ if (useDefault) {
454
+ useDefault = false;
455
+ rate = 100;
456
+ }
457
+ if (handler) {
458
+ const ret = handler(progress);
459
+ if (ret !== void 0) {
460
+ if (typeof ret === "number" && ret === ret) rate = Math.max(0, Math.min(2147483647, Math.round(ret)));
461
+ else throw new TypeError("Expected progress callback to return a number or undefined");
462
+ }
463
+ }
464
+ setImmediate(step);
465
+ } catch (err) {
466
+ backup.close();
467
+ reject(err);
468
+ }
469
+ });
470
+ });
471
+ };
472
+ }
473
+ });
474
+ var require_serialize = __commonJS({
475
+ "../../node_modules/better-sqlite3/lib/methods/serialize.js"(exports, module) {
476
+ "use strict";
477
+ var { cppdb } = require_util();
478
+ module.exports = function serialize(options) {
479
+ if (options == null) options = {};
480
+ if (typeof options !== "object") throw new TypeError("Expected first argument to be an options object");
481
+ const attachedName = "attached" in options ? options.attached : "main";
482
+ if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
483
+ if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
484
+ return this[cppdb].serialize(attachedName);
485
+ };
486
+ }
487
+ });
488
+ var require_function = __commonJS({
489
+ "../../node_modules/better-sqlite3/lib/methods/function.js"(exports, module) {
490
+ "use strict";
491
+ var { getBooleanOption, cppdb } = require_util();
492
+ module.exports = function defineFunction(name, options, fn) {
493
+ if (options == null) options = {};
494
+ if (typeof options === "function") {
495
+ fn = options;
496
+ options = {};
497
+ }
498
+ if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
499
+ if (typeof fn !== "function") throw new TypeError("Expected last argument to be a function");
500
+ if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
501
+ if (!name) throw new TypeError("User-defined function name cannot be an empty string");
502
+ const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
503
+ const deterministic = getBooleanOption(options, "deterministic");
504
+ const directOnly = getBooleanOption(options, "directOnly");
505
+ const varargs = getBooleanOption(options, "varargs");
506
+ let argCount = -1;
507
+ if (!varargs) {
508
+ argCount = fn.length;
509
+ if (!Number.isInteger(argCount) || argCount < 0) throw new TypeError("Expected function.length to be a positive integer");
510
+ if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
511
+ }
512
+ this[cppdb].function(fn, name, argCount, safeIntegers, deterministic, directOnly);
513
+ return this;
514
+ };
515
+ }
516
+ });
517
+ var require_aggregate = __commonJS({
518
+ "../../node_modules/better-sqlite3/lib/methods/aggregate.js"(exports, module) {
519
+ "use strict";
520
+ var { getBooleanOption, cppdb } = require_util();
521
+ module.exports = function defineAggregate(name, options) {
522
+ if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
523
+ if (typeof options !== "object" || options === null) throw new TypeError("Expected second argument to be an options object");
524
+ if (!name) throw new TypeError("User-defined function name cannot be an empty string");
525
+ const start = "start" in options ? options.start : null;
526
+ const step = getFunctionOption(options, "step", true);
527
+ const inverse = getFunctionOption(options, "inverse", false);
528
+ const result = getFunctionOption(options, "result", false);
529
+ const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
530
+ const deterministic = getBooleanOption(options, "deterministic");
531
+ const directOnly = getBooleanOption(options, "directOnly");
532
+ const varargs = getBooleanOption(options, "varargs");
533
+ let argCount = -1;
534
+ if (!varargs) {
535
+ argCount = Math.max(getLength(step), inverse ? getLength(inverse) : 0);
536
+ if (argCount > 0) argCount -= 1;
537
+ if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
538
+ }
539
+ this[cppdb].aggregate(start, step, inverse, result, name, argCount, safeIntegers, deterministic, directOnly);
540
+ return this;
541
+ };
542
+ var getFunctionOption = (options, key, required) => {
543
+ const value = key in options ? options[key] : null;
544
+ if (typeof value === "function") return value;
545
+ if (value != null) throw new TypeError(`Expected the "${key}" option to be a function`);
546
+ if (required) throw new TypeError(`Missing required option "${key}"`);
547
+ return null;
548
+ };
549
+ var getLength = ({ length }) => {
550
+ if (Number.isInteger(length) && length >= 0) return length;
551
+ throw new TypeError("Expected function.length to be a positive integer");
552
+ };
553
+ }
554
+ });
555
+ var require_table = __commonJS({
556
+ "../../node_modules/better-sqlite3/lib/methods/table.js"(exports, module) {
557
+ "use strict";
558
+ var { cppdb } = require_util();
559
+ module.exports = function defineTable(name, factory) {
560
+ if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
561
+ if (!name) throw new TypeError("Virtual table module name cannot be an empty string");
562
+ let eponymous = false;
563
+ if (typeof factory === "object" && factory !== null) {
564
+ eponymous = true;
565
+ factory = defer(parseTableDefinition(factory, "used", name));
566
+ } else {
567
+ if (typeof factory !== "function") throw new TypeError("Expected second argument to be a function or a table definition object");
568
+ factory = wrapFactory(factory);
569
+ }
570
+ this[cppdb].table(factory, name, eponymous);
571
+ return this;
572
+ };
573
+ function wrapFactory(factory) {
574
+ return function virtualTableFactory(moduleName, databaseName, tableName, ...args) {
575
+ const thisObject = {
576
+ module: moduleName,
577
+ database: databaseName,
578
+ table: tableName
579
+ };
580
+ const def = apply.call(factory, thisObject, args);
581
+ if (typeof def !== "object" || def === null) {
582
+ throw new TypeError(`Virtual table module "${moduleName}" did not return a table definition object`);
583
+ }
584
+ return parseTableDefinition(def, "returned", moduleName);
585
+ };
586
+ }
587
+ function parseTableDefinition(def, verb, moduleName) {
588
+ if (!hasOwnProperty.call(def, "rows")) {
589
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "rows" property`);
590
+ }
591
+ if (!hasOwnProperty.call(def, "columns")) {
592
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "columns" property`);
593
+ }
594
+ const rows = def.rows;
595
+ if (typeof rows !== "function" || Object.getPrototypeOf(rows) !== GeneratorFunctionPrototype) {
596
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "rows" property (should be a generator function)`);
597
+ }
598
+ let columns = def.columns;
599
+ if (!Array.isArray(columns) || !(columns = [...columns]).every((x) => typeof x === "string")) {
600
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "columns" property (should be an array of strings)`);
601
+ }
602
+ if (columns.length !== new Set(columns).size) {
603
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate column names`);
604
+ }
605
+ if (!columns.length) {
606
+ throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with zero columns`);
607
+ }
608
+ let parameters;
609
+ if (hasOwnProperty.call(def, "parameters")) {
610
+ parameters = def.parameters;
611
+ if (!Array.isArray(parameters) || !(parameters = [...parameters]).every((x) => typeof x === "string")) {
612
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "parameters" property (should be an array of strings)`);
613
+ }
614
+ } else {
615
+ parameters = inferParameters(rows);
616
+ }
617
+ if (parameters.length !== new Set(parameters).size) {
618
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate parameter names`);
619
+ }
620
+ if (parameters.length > 32) {
621
+ throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with more than the maximum number of 32 parameters`);
622
+ }
623
+ for (const parameter of parameters) {
624
+ if (columns.includes(parameter)) {
625
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with column "${parameter}" which was ambiguously defined as both a column and parameter`);
626
+ }
627
+ }
628
+ let safeIntegers = 2;
629
+ if (hasOwnProperty.call(def, "safeIntegers")) {
630
+ const bool = def.safeIntegers;
631
+ if (typeof bool !== "boolean") {
632
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "safeIntegers" property (should be a boolean)`);
633
+ }
634
+ safeIntegers = +bool;
635
+ }
636
+ let directOnly = false;
637
+ if (hasOwnProperty.call(def, "directOnly")) {
638
+ directOnly = def.directOnly;
639
+ if (typeof directOnly !== "boolean") {
640
+ throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "directOnly" property (should be a boolean)`);
641
+ }
642
+ }
643
+ const columnDefinitions = [
644
+ ...parameters.map(identifier).map((str) => `${str} HIDDEN`),
645
+ ...columns.map(identifier)
646
+ ];
647
+ return [
648
+ `CREATE TABLE x(${columnDefinitions.join(", ")});`,
649
+ wrapGenerator(rows, new Map(columns.map((x, i) => [x, parameters.length + i])), moduleName),
650
+ parameters,
651
+ safeIntegers,
652
+ directOnly
653
+ ];
654
+ }
655
+ function wrapGenerator(generator, columnMap, moduleName) {
656
+ return function* virtualTable(...args) {
657
+ const output = args.map((x) => Buffer.isBuffer(x) ? Buffer.from(x) : x);
658
+ for (let i = 0; i < columnMap.size; ++i) {
659
+ output.push(null);
660
+ }
661
+ for (const row of generator(...args)) {
662
+ if (Array.isArray(row)) {
663
+ extractRowArray(row, output, columnMap.size, moduleName);
664
+ yield output;
665
+ } else if (typeof row === "object" && row !== null) {
666
+ extractRowObject(row, output, columnMap, moduleName);
667
+ yield output;
668
+ } else {
669
+ throw new TypeError(`Virtual table module "${moduleName}" yielded something that isn't a valid row object`);
670
+ }
671
+ }
672
+ };
673
+ }
674
+ function extractRowArray(row, output, columnCount, moduleName) {
675
+ if (row.length !== columnCount) {
676
+ throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an incorrect number of columns`);
677
+ }
678
+ const offset = output.length - columnCount;
679
+ for (let i = 0; i < columnCount; ++i) {
680
+ output[i + offset] = row[i];
681
+ }
682
+ }
683
+ function extractRowObject(row, output, columnMap, moduleName) {
684
+ let count = 0;
685
+ for (const key of Object.keys(row)) {
686
+ const index = columnMap.get(key);
687
+ if (index === void 0) {
688
+ throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an undeclared column "${key}"`);
689
+ }
690
+ output[index] = row[key];
691
+ count += 1;
692
+ }
693
+ if (count !== columnMap.size) {
694
+ throw new TypeError(`Virtual table module "${moduleName}" yielded a row with missing columns`);
695
+ }
696
+ }
697
+ function inferParameters({ length }) {
698
+ if (!Number.isInteger(length) || length < 0) {
699
+ throw new TypeError("Expected function.length to be a positive integer");
700
+ }
701
+ const params = [];
702
+ for (let i = 0; i < length; ++i) {
703
+ params.push(`$${i + 1}`);
704
+ }
705
+ return params;
706
+ }
707
+ var { hasOwnProperty } = Object.prototype;
708
+ var { apply } = Function.prototype;
709
+ var GeneratorFunctionPrototype = Object.getPrototypeOf(function* () {
710
+ });
711
+ var identifier = (str) => `"${str.replace(/"/g, '""')}"`;
712
+ var defer = (x) => () => x;
713
+ }
714
+ });
715
+ var require_inspect = __commonJS({
716
+ "../../node_modules/better-sqlite3/lib/methods/inspect.js"(exports, module) {
717
+ "use strict";
718
+ var DatabaseInspection = function Database2() {
719
+ };
720
+ module.exports = function inspect(depth, opts) {
721
+ return Object.assign(new DatabaseInspection(), this);
722
+ };
723
+ }
724
+ });
725
+ var require_database = __commonJS({
726
+ "../../node_modules/better-sqlite3/lib/database.js"(exports, module) {
727
+ "use strict";
728
+ var fs = __require2("fs");
729
+ var path = __require2("path");
730
+ var util = require_util();
731
+ var SqliteError = require_sqlite_error();
732
+ var DEFAULT_ADDON;
733
+ function Database2(filenameGiven, options) {
734
+ if (new.target == null) {
735
+ return new Database2(filenameGiven, options);
736
+ }
737
+ let buffer;
738
+ if (Buffer.isBuffer(filenameGiven)) {
739
+ buffer = filenameGiven;
740
+ filenameGiven = ":memory:";
741
+ }
742
+ if (filenameGiven == null) filenameGiven = "";
743
+ if (options == null) options = {};
744
+ if (typeof filenameGiven !== "string") throw new TypeError("Expected first argument to be a string");
745
+ if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
746
+ if ("readOnly" in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"');
747
+ if ("memory" in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)');
748
+ const filename = filenameGiven.trim();
749
+ const anonymous = filename === "" || filename === ":memory:";
750
+ const readonly = util.getBooleanOption(options, "readonly");
751
+ const fileMustExist = util.getBooleanOption(options, "fileMustExist");
752
+ const timeout = "timeout" in options ? options.timeout : 5e3;
753
+ const verbose = "verbose" in options ? options.verbose : null;
754
+ const nativeBinding = "nativeBinding" in options ? options.nativeBinding : null;
755
+ if (readonly && anonymous && !buffer) throw new TypeError("In-memory/temporary databases cannot be readonly");
756
+ if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer');
757
+ if (timeout > 2147483647) throw new RangeError('Option "timeout" cannot be greater than 2147483647');
758
+ if (verbose != null && typeof verbose !== "function") throw new TypeError('Expected the "verbose" option to be a function');
759
+ if (nativeBinding != null && typeof nativeBinding !== "string" && typeof nativeBinding !== "object") throw new TypeError('Expected the "nativeBinding" option to be a string or addon object');
760
+ let addon;
761
+ if (nativeBinding == null) {
762
+ addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
763
+ } else if (typeof nativeBinding === "string") {
764
+ const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : __require2;
765
+ addon = requireFunc(path.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
766
+ } else {
767
+ addon = nativeBinding;
768
+ }
769
+ if (!addon.isInitialized) {
770
+ addon.setErrorConstructor(SqliteError);
771
+ addon.isInitialized = true;
772
+ }
773
+ if (!anonymous && !filename.startsWith("file:") && !fs.existsSync(path.dirname(filename))) {
774
+ throw new TypeError("Cannot open database because the directory does not exist");
775
+ }
776
+ Object.defineProperties(this, {
777
+ [util.cppdb]: { value: new addon.Database(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) },
778
+ ...wrappers.getters
779
+ });
780
+ }
781
+ var wrappers = require_wrappers();
782
+ Database2.prototype.prepare = wrappers.prepare;
783
+ Database2.prototype.transaction = require_transaction();
784
+ Database2.prototype.pragma = require_pragma();
785
+ Database2.prototype.backup = require_backup();
786
+ Database2.prototype.serialize = require_serialize();
787
+ Database2.prototype.function = require_function();
788
+ Database2.prototype.aggregate = require_aggregate();
789
+ Database2.prototype.table = require_table();
790
+ Database2.prototype.loadExtension = wrappers.loadExtension;
791
+ Database2.prototype.exec = wrappers.exec;
792
+ Database2.prototype.close = wrappers.close;
793
+ Database2.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
794
+ Database2.prototype.unsafeMode = wrappers.unsafeMode;
795
+ Database2.prototype[util.inspect] = require_inspect();
796
+ module.exports = Database2;
797
+ }
798
+ });
799
+ var require_lib = __commonJS({
800
+ "../../node_modules/better-sqlite3/lib/index.js"(exports, module) {
801
+ "use strict";
802
+ module.exports = require_database();
803
+ module.exports.SqliteError = require_sqlite_error();
804
+ }
805
+ });
806
+ var config = {
807
+ port: parseInt(process.env.AGENT_MOVE_PORT || "3333", 10),
808
+ claudeHome: join(homedir(), ".claude"),
809
+ idleTimeoutMs: 45e3,
810
+ /** How long after going idle before an agent is automatically shutdown/removed */
811
+ shutdownTimeoutMs: 30 * 60 * 1e3,
812
+ // 30 minutes
813
+ /** How recently a session file must be modified to be considered "active" on startup */
814
+ activeThresholdMs: 10 * 60 * 1e3,
815
+ // 10 minutes
816
+ /** Enable OpenCode session watching (auto-detected if storage dir exists) */
817
+ enableOpenCode: process.env.AGENT_MOVE_OPENCODE !== "false"
818
+ };
819
+ var JsonlParser = class {
820
+ parseLine(line) {
821
+ try {
822
+ const msg = JSON.parse(line);
823
+ return this.extractActivity(msg);
824
+ } catch {
825
+ return null;
826
+ }
827
+ }
828
+ extractActivity(msg) {
829
+ const sessionId = msg.sessionId;
830
+ if (msg.toolUseResult?.routing?.sender) {
831
+ const agentName = msg.toolUseResult.routing.sender;
832
+ if (msg.message?.role === "user" || msg.type === "user") {
833
+ return {
834
+ type: "text",
835
+ text: void 0,
836
+ agentName,
837
+ sessionId
838
+ };
839
+ }
840
+ }
841
+ if ((msg.message?.role === "user" || msg.type === "user") && msg.message?.content) {
842
+ const content2 = typeof msg.message.content === "string" ? msg.message.content : Array.isArray(msg.message.content) ? msg.message.content.map((b) => b.text ?? "").join("") : "";
843
+ if (content2.includes("<teammate-message")) {
844
+ const senderMatch = content2.match(/<teammate-message\s+teammate_id="([^"]+)"/);
845
+ if (senderMatch) {
846
+ return {
847
+ type: "text",
848
+ text: void 0,
849
+ messageSender: senderMatch[1],
850
+ sessionId
851
+ };
852
+ }
853
+ }
854
+ }
855
+ if (!msg.message?.content || !Array.isArray(msg.message.content)) {
856
+ return null;
857
+ }
858
+ if (msg.message.role !== "assistant") {
859
+ return null;
860
+ }
861
+ const content = msg.message.content;
862
+ for (const block of content) {
863
+ if (block.type === "tool_use") {
864
+ const toolBlock = block;
865
+ return {
866
+ type: "tool_use",
867
+ toolName: toolBlock.name,
868
+ toolInput: toolBlock.input,
869
+ model: msg.message.model,
870
+ sessionId,
871
+ inputTokens: msg.message.usage?.input_tokens,
872
+ outputTokens: msg.message.usage?.output_tokens,
873
+ cacheReadTokens: msg.message.usage?.cache_read_input_tokens,
874
+ cacheCreationTokens: msg.message.usage?.cache_creation_input_tokens
875
+ };
876
+ }
877
+ }
878
+ for (const block of content) {
879
+ if (block.type === "text") {
880
+ const textBlock = block;
881
+ const text = textBlock.text.trim();
882
+ if (text.length > 0 && text.length < 200) {
883
+ return {
884
+ type: "text",
885
+ text,
886
+ model: msg.message.model,
887
+ sessionId,
888
+ inputTokens: msg.message.usage?.input_tokens,
889
+ outputTokens: msg.message.usage?.output_tokens,
890
+ cacheReadTokens: msg.message.usage?.cache_read_input_tokens,
891
+ cacheCreationTokens: msg.message.usage?.cache_creation_input_tokens
892
+ };
893
+ }
894
+ }
895
+ }
896
+ if (msg.message.usage) {
897
+ return {
898
+ type: "token_usage",
899
+ inputTokens: msg.message.usage.input_tokens,
900
+ outputTokens: msg.message.usage.output_tokens,
901
+ cacheReadTokens: msg.message.usage.cache_read_input_tokens,
902
+ cacheCreationTokens: msg.message.usage.cache_creation_input_tokens,
903
+ model: msg.message.model,
904
+ sessionId
905
+ };
906
+ }
907
+ return null;
908
+ }
909
+ };
910
+ var ClaudePaths = class {
911
+ /**
912
+ * Parse a JSONL session file path to extract project info.
913
+ * Paths look like: ~/.claude/projects/{encoded-project-path}/{sessionId}.jsonl
914
+ * Subagent paths may be nested deeper.
915
+ */
916
+ parseSessionPath(filePath) {
917
+ const normalized = filePath.replace(/\\/g, "/");
918
+ const parts = normalized.split("/");
919
+ const projectsIdx = parts.indexOf("projects");
920
+ if (projectsIdx === -1 || projectsIdx + 1 >= parts.length) {
921
+ return {
922
+ projectPath: "unknown",
923
+ projectName: "Unknown",
924
+ isSubagent: false,
925
+ projectDir: "unknown",
926
+ parentSessionId: null
927
+ };
928
+ }
929
+ const encodedProjectName = parts[projectsIdx + 1];
930
+ const projectName = this.decodeProjectName(encodedProjectName);
931
+ const depthAfterProject = parts.length - projectsIdx - 2;
932
+ const isSubagent = depthAfterProject > 1;
933
+ const parentSessionId = isSubagent ? parts[projectsIdx + 2] : null;
934
+ return {
935
+ projectPath: encodedProjectName,
936
+ projectName,
937
+ isSubagent,
938
+ projectDir: encodedProjectName,
939
+ parentSessionId
940
+ };
941
+ }
942
+ /**
943
+ * Decode Claude Code's encoded project path format.
944
+ * Resolves against the filesystem to correctly handle dashes in folder names.
945
+ * e.g., "C--projects-fts-temp-agent-move" → "agent-move"
946
+ */
947
+ decodeProjectName(encoded) {
948
+ const resolved = this.resolveToFolderName(encoded);
949
+ if (resolved)
950
+ return resolved;
951
+ const parts = encoded.split("-").filter((p) => p.length > 0);
952
+ if (parts.length <= 2)
953
+ return parts.join("/");
954
+ return parts.slice(-2).join("/");
955
+ }
956
+ /**
957
+ * Greedily resolve the encoded path against the filesystem.
958
+ * Tries each dash-segment as a directory, joining multiple segments
959
+ * when a single one doesn't exist (to handle dashes in folder names).
960
+ */
961
+ resolveToFolderName(encoded) {
962
+ try {
963
+ let root;
964
+ let rest;
965
+ const driveMatch = encoded.match(/^([A-Za-z])--(.*)/);
966
+ const unixMatch = !driveMatch && encoded.match(/^-(.*)/);
967
+ if (driveMatch) {
968
+ root = driveMatch[1] + ":/";
969
+ rest = driveMatch[2];
970
+ } else if (unixMatch) {
971
+ root = "/";
972
+ rest = unixMatch[1];
973
+ } else {
974
+ return null;
975
+ }
976
+ const parts = rest.split("-").filter(Boolean);
977
+ let currentPath = root;
978
+ let lastName = "";
979
+ let i = 0;
980
+ while (i < parts.length) {
981
+ let found = false;
982
+ const maxLen = Math.min(parts.length - i, 6);
983
+ for (let len = 1; len <= maxLen; len++) {
984
+ const segment = parts.slice(i, i + len).join("-");
985
+ for (const prefix of ["", "."]) {
986
+ const testPath = join2(currentPath, prefix + segment);
987
+ if (existsSync(testPath)) {
988
+ currentPath = testPath;
989
+ lastName = prefix + segment;
990
+ i += len;
991
+ found = true;
992
+ break;
993
+ }
994
+ }
995
+ if (found)
996
+ break;
997
+ }
998
+ if (!found)
999
+ break;
1000
+ }
1001
+ return lastName || null;
1002
+ } catch {
1003
+ return null;
1004
+ }
1005
+ }
1006
+ };
1007
+ var claudePaths = new ClaudePaths();
1008
+ var SessionScanner = class {
1009
+ claudeHome;
1010
+ constructor(claudeHome) {
1011
+ this.claudeHome = claudeHome;
1012
+ }
1013
+ /** Find all recently active JSONL session files */
1014
+ async scan() {
1015
+ const results = [];
1016
+ const projectsDir = join3(this.claudeHome, "projects");
1017
+ try {
1018
+ const projects = await readdir(projectsDir);
1019
+ const now = Date.now();
1020
+ for (const project of projects) {
1021
+ const projectDir = join3(projectsDir, project);
1022
+ try {
1023
+ const projectStat = await stat(projectDir);
1024
+ if (!projectStat.isDirectory())
1025
+ continue;
1026
+ const files = await readdir(projectDir);
1027
+ let newestFile = null;
1028
+ let newestMtime = 0;
1029
+ for (const file of files) {
1030
+ if (!file.endsWith(".jsonl"))
1031
+ continue;
1032
+ const filePath = join3(projectDir, file);
1033
+ try {
1034
+ const fileStat = await stat(filePath);
1035
+ if (now - fileStat.mtimeMs < config.activeThresholdMs) {
1036
+ if (fileStat.mtimeMs > newestMtime) {
1037
+ newestMtime = fileStat.mtimeMs;
1038
+ newestFile = filePath;
1039
+ }
1040
+ }
1041
+ } catch {
1042
+ }
1043
+ }
1044
+ if (newestFile) {
1045
+ results.push(newestFile);
1046
+ }
1047
+ } catch {
1048
+ }
1049
+ }
1050
+ } catch {
1051
+ console.log("No projects directory found \u2014 will wait for new sessions");
1052
+ }
1053
+ return results;
1054
+ }
1055
+ };
1056
+ var FileWatcher = class {
1057
+ claudeHome;
1058
+ stateManager;
1059
+ watcher = null;
1060
+ byteOffsets = /* @__PURE__ */ new Map();
1061
+ parser = new JsonlParser();
1062
+ /** Per-file lock to prevent concurrent processFile calls for the same file */
1063
+ fileLocks = /* @__PURE__ */ new Map();
1064
+ constructor(claudeHome, stateManager) {
1065
+ this.claudeHome = claudeHome;
1066
+ this.stateManager = stateManager;
1067
+ }
1068
+ async start() {
1069
+ const scanner = new SessionScanner(this.claudeHome);
1070
+ const existingFiles = await scanner.scan();
1071
+ for (const file of existingFiles) {
1072
+ await this.processFile(file);
1073
+ }
1074
+ const pattern = join4(this.claudeHome, "projects", "**", "*.jsonl");
1075
+ this.watcher = chokidar.watch(pattern, {
1076
+ persistent: true,
1077
+ ignoreInitial: true,
1078
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
1079
+ });
1080
+ this.watcher.on("add", (filePath) => {
1081
+ console.log(`New session file: ${filePath}`);
1082
+ this.processFile(filePath);
1083
+ });
1084
+ this.watcher.on("change", (filePath) => {
1085
+ this.processFile(filePath);
1086
+ });
1087
+ console.log(`Watching for JSONL files in ${this.claudeHome}/projects/`);
1088
+ }
1089
+ stop() {
1090
+ this.watcher?.close();
1091
+ this.byteOffsets.clear();
1092
+ this.fileLocks.clear();
1093
+ }
1094
+ processFile(filePath) {
1095
+ const prev = this.fileLocks.get(filePath) ?? Promise.resolve();
1096
+ const next = prev.then(() => this.doProcessFile(filePath)).catch(() => {
1097
+ }).finally(() => {
1098
+ if (this.fileLocks.get(filePath) === next) {
1099
+ this.fileLocks.delete(filePath);
1100
+ }
1101
+ });
1102
+ this.fileLocks.set(filePath, next);
1103
+ }
1104
+ async doProcessFile(filePath) {
1105
+ try {
1106
+ const fileStats = await stat2(filePath);
1107
+ const currentOffset = this.byteOffsets.get(filePath) ?? 0;
1108
+ if (fileStats.size <= currentOffset)
1109
+ return;
1110
+ const handle = await open(filePath, "r");
1111
+ try {
1112
+ const buffer = Buffer.alloc(fileStats.size - currentOffset);
1113
+ await handle.read(buffer, 0, buffer.length, currentOffset);
1114
+ this.byteOffsets.set(filePath, fileStats.size);
1115
+ const newContent = buffer.toString("utf-8");
1116
+ const lines = newContent.split("\n").filter((l) => l.trim());
1117
+ const sessionId = basename(filePath, ".jsonl");
1118
+ const sessionInfo = claudePaths.parseSessionPath(filePath);
1119
+ let hadParsedActivity = false;
1120
+ for (const line of lines) {
1121
+ const parsed = this.parser.parseLine(line);
1122
+ if (parsed) {
1123
+ hadParsedActivity = true;
1124
+ this.stateManager.processMessage(sessionId, parsed, sessionInfo);
1125
+ }
1126
+ }
1127
+ if (!hadParsedActivity && lines.length > 0) {
1128
+ this.stateManager.heartbeat(sessionId);
1129
+ }
1130
+ } finally {
1131
+ await handle.close();
1132
+ }
1133
+ } catch (err) {
1134
+ if (err.code !== "ENOENT") {
1135
+ console.error(`Error processing ${filePath}:`, err);
1136
+ }
1137
+ }
1138
+ }
1139
+ };
1140
+ var import_better_sqlite3 = __toESM(require_lib(), 1);
1141
+ function getOpenCodeDbPath() {
1142
+ const home = homedir2();
1143
+ const candidates = [
1144
+ join5(home, ".local", "share", "opencode", "opencode.db"),
1145
+ // Windows fallback via LOCALAPPDATA
1146
+ ...process.env.LOCALAPPDATA ? [join5(process.env.LOCALAPPDATA, "opencode", "opencode.db")] : [],
1147
+ join5(home, ".opencode", "opencode.db")
1148
+ ];
1149
+ for (const candidate of candidates) {
1150
+ if (existsSync2(candidate))
1151
+ return candidate;
1152
+ }
1153
+ return null;
1154
+ }
1155
+ function parseOpenCodeSession(row) {
1156
+ const segments = row.directory.replace(/\\/g, "/").split("/").filter(Boolean);
1157
+ const projectName = segments[segments.length - 1] || "opencode";
1158
+ return {
1159
+ // Use the actual directory as projectPath so getGitBranch() gets a valid cwd.
1160
+ // projectDir uses the project_id hash to group agents belonging to the same project.
1161
+ projectPath: row.directory || row.project_id,
1162
+ projectName,
1163
+ isSubagent: !!row.parent_id,
1164
+ projectDir: row.project_id,
1165
+ parentSessionId: row.parent_id ? `oc:${row.parent_id}` : null
1166
+ };
1167
+ }
1168
+ var DEFAULT_ANOMALY_CONFIG = {
1169
+ tokenVelocityThreshold: 1e5,
1170
+ retryLoopThreshold: 5,
1171
+ stuckTimeoutSeconds: 120
1172
+ };
1173
+ var TOOL_ZONE_MAP = {
1174
+ // Files zone
1175
+ Read: "files",
1176
+ Write: "files",
1177
+ Edit: "files",
1178
+ Patch: "files",
1179
+ Glob: "files",
1180
+ NotebookEdit: "files",
1181
+ // Terminal zone
1182
+ Bash: "terminal",
1183
+ // Search zone
1184
+ Grep: "search",
1185
+ WebSearch: "search",
1186
+ // Web zone
1187
+ WebFetch: "web",
1188
+ mcp__chrome_devtools__navigate_page: "web",
1189
+ mcp__chrome_devtools__click: "web",
1190
+ mcp__chrome_devtools__fill: "web",
1191
+ mcp__chrome_devtools__take_screenshot: "web",
1192
+ mcp__chrome_devtools__take_snapshot: "web",
1193
+ mcp__chrome_devtools__evaluate_script: "web",
1194
+ // Thinking zone
1195
+ EnterPlanMode: "thinking",
1196
+ ExitPlanMode: "thinking",
1197
+ AskUserQuestion: "thinking",
1198
+ // Messaging zone
1199
+ SendMessage: "messaging",
1200
+ // Tasks zone
1201
+ TaskCreate: "tasks",
1202
+ TaskUpdate: "tasks",
1203
+ TaskList: "tasks",
1204
+ TaskGet: "tasks",
1205
+ TodoRead: "tasks",
1206
+ TodoWrite: "tasks",
1207
+ // Spawn zone
1208
+ Agent: "spawn",
1209
+ TeamCreate: "spawn",
1210
+ TeamDelete: "spawn"
1211
+ };
1212
+ function getZoneForTool(toolName) {
1213
+ if (toolName.startsWith("mcp__"))
1214
+ return "web";
1215
+ return TOOL_ZONE_MAP[toolName] ?? "thinking";
1216
+ }
1217
+ var TOOL_NAME_MAP = {
1218
+ // OpenCode lowercase → canonical PascalCase
1219
+ read: "Read",
1220
+ write: "Write",
1221
+ edit: "Edit",
1222
+ patch: "Patch",
1223
+ glob: "Glob",
1224
+ bash: "Bash",
1225
+ grep: "Grep",
1226
+ websearch: "WebSearch",
1227
+ webfetch: "WebFetch",
1228
+ todoread: "TodoRead",
1229
+ todowrite: "TodoWrite"
1230
+ };
1231
+ function normalizeToolName(name) {
1232
+ return TOOL_NAME_MAP[name] ?? name;
1233
+ }
1234
+ function normalizeToolInput(input) {
1235
+ if (!("filePath" in input) && !("oldString" in input) && !("newString" in input) && !("replaceAll" in input)) {
1236
+ return input;
1237
+ }
1238
+ const out = { ...input };
1239
+ if ("filePath" in out) {
1240
+ out.file_path = out.filePath;
1241
+ delete out.filePath;
1242
+ }
1243
+ if ("oldString" in out) {
1244
+ out.old_string = out.oldString;
1245
+ delete out.oldString;
1246
+ }
1247
+ if ("newString" in out) {
1248
+ out.new_string = out.newString;
1249
+ delete out.newString;
1250
+ }
1251
+ if ("replaceAll" in out) {
1252
+ out.replace_all = out.replaceAll;
1253
+ delete out.replaceAll;
1254
+ }
1255
+ return out;
1256
+ }
1257
+ var ZONES = [
1258
+ // Row 0
1259
+ {
1260
+ id: "search",
1261
+ label: "Search",
1262
+ description: "Grep, WebSearch \u2014 Research & lookup",
1263
+ icon: "\u{1F4DA}",
1264
+ color: 15381256,
1265
+ colStart: 0,
1266
+ colSpan: 5,
1267
+ rowStart: 0,
1268
+ rowSpan: 1,
1269
+ x: 0,
1270
+ y: 0,
1271
+ width: 0,
1272
+ height: 0
1273
+ },
1274
+ {
1275
+ id: "terminal",
1276
+ label: "Terminal",
1277
+ description: "Bash commands \u2014 Server room",
1278
+ icon: "\u{1F4BB}",
1279
+ color: 2278750,
1280
+ colStart: 5,
1281
+ colSpan: 3,
1282
+ rowStart: 0,
1283
+ rowSpan: 1,
1284
+ x: 0,
1285
+ y: 0,
1286
+ width: 0,
1287
+ height: 0
1288
+ },
1289
+ {
1290
+ id: "web",
1291
+ label: "Web",
1292
+ description: "WebFetch, Browser \u2014 Network hub",
1293
+ icon: "\u{1F310}",
1294
+ color: 9133302,
1295
+ colStart: 8,
1296
+ colSpan: 4,
1297
+ rowStart: 0,
1298
+ rowSpan: 1,
1299
+ x: 0,
1300
+ y: 0,
1301
+ width: 0,
1302
+ height: 0
1303
+ },
1304
+ // Row 1
1305
+ {
1306
+ id: "files",
1307
+ label: "Files",
1308
+ description: "Read, Write, Edit, Glob \u2014 File storage",
1309
+ icon: "\u{1F4C1}",
1310
+ color: 3900150,
1311
+ colStart: 0,
1312
+ colSpan: 4,
1313
+ rowStart: 1,
1314
+ rowSpan: 1,
1315
+ x: 0,
1316
+ y: 0,
1317
+ width: 0,
1318
+ height: 0
1319
+ },
1320
+ {
1321
+ id: "thinking",
1322
+ label: "Thinking",
1323
+ description: "Planning, Questions \u2014 Conference area",
1324
+ icon: "\u{1F4AD}",
1325
+ color: 16347926,
1326
+ colStart: 4,
1327
+ colSpan: 5,
1328
+ rowStart: 1,
1329
+ rowSpan: 1,
1330
+ x: 0,
1331
+ y: 0,
1332
+ width: 0,
1333
+ height: 0
1334
+ },
1335
+ {
1336
+ id: "messaging",
1337
+ label: "Messaging",
1338
+ description: "SendMessage, Teams \u2014 Chat & relax",
1339
+ icon: "\u{1F4AC}",
1340
+ color: 15485081,
1341
+ colStart: 9,
1342
+ colSpan: 3,
1343
+ rowStart: 1,
1344
+ rowSpan: 1,
1345
+ x: 0,
1346
+ y: 0,
1347
+ width: 0,
1348
+ height: 0
1349
+ },
1350
+ // Row 2
1351
+ {
1352
+ id: "spawn",
1353
+ label: "Spawn",
1354
+ description: "Agent spawn/despawn \u2014 Entry portal",
1355
+ icon: "\u{1F300}",
1356
+ color: 11032055,
1357
+ colStart: 0,
1358
+ colSpan: 3,
1359
+ rowStart: 2,
1360
+ rowSpan: 1,
1361
+ x: 0,
1362
+ y: 0,
1363
+ width: 0,
1364
+ height: 0
1365
+ },
1366
+ {
1367
+ id: "idle",
1368
+ label: "Idle",
1369
+ description: "Idle agents rest here \u2014 Kitchen & lounge",
1370
+ icon: "\u2615",
1371
+ color: 7041664,
1372
+ colStart: 3,
1373
+ colSpan: 5,
1374
+ rowStart: 2,
1375
+ rowSpan: 1,
1376
+ x: 0,
1377
+ y: 0,
1378
+ width: 0,
1379
+ height: 0
1380
+ },
1381
+ {
1382
+ id: "tasks",
1383
+ label: "Tasks",
1384
+ description: "TaskCreate, TaskUpdate \u2014 Kanban & planning",
1385
+ icon: "\u{1F4CB}",
1386
+ color: 1357990,
1387
+ colStart: 8,
1388
+ colSpan: 4,
1389
+ rowStart: 2,
1390
+ rowSpan: 1,
1391
+ x: 0,
1392
+ y: 0,
1393
+ width: 0,
1394
+ height: 0
1395
+ }
1396
+ ];
1397
+ var ZONE_MAP = new Map(ZONES.map((z) => [z.id, z]));
1398
+ var AGENT_PALETTES = [
1399
+ { name: "blue", body: 4886745, outline: 2906762, highlight: 8042741, eye: 16777215, skin: 16767916 },
1400
+ { name: "green", body: 5025616, outline: 3046706, highlight: 8505220, eye: 16777215, skin: 16767916 },
1401
+ { name: "red", body: 15037299, outline: 12986408, highlight: 16764370, eye: 16777215, skin: 16767916 },
1402
+ { name: "purple", body: 11225020, outline: 6953882, highlight: 13538264, eye: 16777215, skin: 16767916 },
1403
+ { name: "orange", body: 16750592, outline: 15094016, highlight: 16764032, eye: 16777215, skin: 16767916 },
1404
+ { name: "cyan", body: 2541274, outline: 33679, highlight: 8445674, eye: 16777215, skin: 16767916 },
1405
+ { name: "pink", body: 15753874, outline: 12720219, highlight: 16301008, eye: 16777215, skin: 16767916 },
1406
+ { name: "teal", body: 2533018, outline: 26972, highlight: 8440772, eye: 16777215, skin: 16767916 },
1407
+ { name: "amber", body: 16761095, outline: 16748288, highlight: 16769154, eye: 3355443, skin: 16767916 },
1408
+ { name: "indigo", body: 6056896, outline: 2635155, highlight: 10463450, eye: 16777215, skin: 16767916 },
1409
+ { name: "lime", body: 10275941, outline: 5606191, highlight: 12968357, eye: 3355443, skin: 16767916 },
1410
+ { name: "brown", body: 9268835, outline: 5125166, highlight: 12364452, eye: 16777215, skin: 16767916 }
1411
+ ];
1412
+ function getProjectColorIndex(projectPath) {
1413
+ let hash = 0;
1414
+ for (let i = 0; i < projectPath.length; i++) {
1415
+ hash = (hash << 5) - hash + projectPath.charCodeAt(i) | 0;
1416
+ }
1417
+ return Math.abs(hash) % AGENT_PALETTES.length;
1418
+ }
1419
+ var OpenCodeParser = class {
1420
+ /**
1421
+ * Convert an OpenCode part JSON object into a ParsedActivity.
1422
+ * All tool names and input field names are normalized to canonical form
1423
+ * so downstream code (activity-processor, TOOL_ZONE_MAP, etc.) needs no
1424
+ * agent-specific branches.
1425
+ *
1426
+ * Returns null for parts that carry no actionable activity.
1427
+ */
1428
+ parsePart(partData, messageData) {
1429
+ if (partData.type === "tool") {
1430
+ const tool = partData;
1431
+ if (tool.state.status === "pending")
1432
+ return null;
1433
+ const rawInput = tool.state.input ?? {};
1434
+ return {
1435
+ type: "tool_use",
1436
+ toolName: normalizeToolName(tool.tool),
1437
+ toolInput: normalizeToolInput(rawInput),
1438
+ agentName: messageData?.agent,
1439
+ model: messageData?.modelID
1440
+ };
1441
+ }
1442
+ if (partData.type === "text") {
1443
+ const text = partData;
1444
+ if (text.synthetic)
1445
+ return null;
1446
+ const trimmed = text.text?.trim() ?? "";
1447
+ if (trimmed.length === 0 || trimmed.length >= 200)
1448
+ return null;
1449
+ return {
1450
+ type: "text",
1451
+ text: trimmed,
1452
+ agentName: messageData?.agent,
1453
+ model: messageData?.modelID
1454
+ };
1455
+ }
1456
+ if (partData.type === "reasoning") {
1457
+ const reasoning = partData;
1458
+ const trimmed = reasoning.text?.trim() ?? "";
1459
+ return {
1460
+ type: "tool_use",
1461
+ toolName: "thinking",
1462
+ toolInput: trimmed.length > 0 ? { thought: trimmed.slice(0, 120) } : void 0,
1463
+ agentName: messageData?.agent,
1464
+ model: messageData?.modelID
1465
+ };
1466
+ }
1467
+ return null;
1468
+ }
1469
+ /**
1470
+ * Emit token usage from an assistant message row.
1471
+ * Called once per assistant message.
1472
+ */
1473
+ parseTokenUsage(messageData) {
1474
+ if (messageData.role !== "assistant")
1475
+ return null;
1476
+ const t = messageData.tokens;
1477
+ if (!t?.input && !t?.output)
1478
+ return null;
1479
+ return {
1480
+ type: "token_usage",
1481
+ inputTokens: t.input,
1482
+ outputTokens: t.output,
1483
+ cacheReadTokens: t.cache?.read,
1484
+ cacheCreationTokens: t.cache?.write,
1485
+ model: messageData.modelID,
1486
+ agentName: messageData.agent
1487
+ };
1488
+ }
1489
+ };
1490
+ var OpenCodeWatcher = class {
1491
+ stateManager;
1492
+ watcher = null;
1493
+ db = null;
1494
+ parser = new OpenCodeParser();
1495
+ /** Timestamp watermark for incremental polling (ms) */
1496
+ lastMessageTs = 0;
1497
+ /** Tracks time_created (not time_updated) so each part is processed exactly once */
1498
+ lastPartCreatedTs = 0;
1499
+ /** sessionId → SessionInfo cache */
1500
+ sessions = /* @__PURE__ */ new Map();
1501
+ /** messageId → parsed message data cache */
1502
+ messages = /* @__PURE__ */ new Map();
1503
+ /** callID → true — deduplicates tool_use per tool invocation */
1504
+ seenCallIds = /* @__PURE__ */ new Set();
1505
+ /** row id → true — deduplicates text/token events */
1506
+ seenIds = /* @__PURE__ */ new Set();
1507
+ // Prepared statements (initialised after DB opens)
1508
+ stmtAllSessions;
1509
+ stmtRecentSessions;
1510
+ stmtMessagesBySession;
1511
+ stmtPartsBySession;
1512
+ stmtNewMessages;
1513
+ stmtNewParts;
1514
+ constructor(stateManager) {
1515
+ this.stateManager = stateManager;
1516
+ }
1517
+ async start() {
1518
+ const activeThresholdMs = config.activeThresholdMs;
1519
+ const dbPath = getOpenCodeDbPath();
1520
+ if (!dbPath) {
1521
+ console.log("[opencode] No database found \u2014 OpenCode not installed or not yet used");
1522
+ return;
1523
+ }
1524
+ console.log(`[opencode] Database found at ${dbPath}`);
1525
+ try {
1526
+ this.db = new import_better_sqlite3.default(dbPath, { readonly: true, fileMustExist: true });
1527
+ this.prepareStatements();
1528
+ } catch (err) {
1529
+ console.error("[opencode] Failed to open database:", err);
1530
+ return;
1531
+ }
1532
+ this.loadAllSessions();
1533
+ this.replayRecentSessions(activeThresholdMs);
1534
+ if (this.lastMessageTs === 0)
1535
+ this.lastMessageTs = Date.now() - 5e3;
1536
+ if (this.lastPartCreatedTs === 0)
1537
+ this.lastPartCreatedTs = Date.now() - 5e3;
1538
+ const walPath = dbPath + "-wal";
1539
+ this.watcher = chokidar2.watch(walPath, {
1540
+ persistent: true,
1541
+ ignoreInitial: true,
1542
+ usePolling: true,
1543
+ interval: 500,
1544
+ disableGlobbing: true
1545
+ });
1546
+ this.watcher.on("change", () => this.poll());
1547
+ this.watcher.on("add", () => this.poll());
1548
+ console.log("[opencode] Watching for new activity");
1549
+ }
1550
+ stop() {
1551
+ this.watcher?.close();
1552
+ this.db?.close();
1553
+ this.sessions.clear();
1554
+ this.messages.clear();
1555
+ this.seenCallIds.clear();
1556
+ this.seenIds.clear();
1557
+ }
1558
+ // ── Prepared statements ────────────────────────────────────────────────────
1559
+ prepareStatements() {
1560
+ const db = this.db;
1561
+ this.stmtAllSessions = db.prepare("SELECT id, directory, parent_id, title, project_id FROM session");
1562
+ this.stmtRecentSessions = db.prepare("SELECT id, directory, parent_id, title, project_id FROM session WHERE time_updated > ?");
1563
+ this.stmtMessagesBySession = db.prepare("SELECT id, session_id, time_updated, data FROM message WHERE session_id = ? ORDER BY time_created");
1564
+ this.stmtPartsBySession = db.prepare("SELECT id, message_id, session_id, time_created, time_updated, data FROM part WHERE session_id = ? ORDER BY time_created");
1565
+ this.stmtNewMessages = db.prepare("SELECT id, session_id, time_updated, data FROM message WHERE time_updated > ? ORDER BY time_updated");
1566
+ this.stmtNewParts = db.prepare("SELECT id, message_id, session_id, time_created, time_updated, data FROM part WHERE time_created > ? ORDER BY time_created");
1567
+ }
1568
+ // ── Session cache ──────────────────────────────────────────────────────────
1569
+ loadAllSessions() {
1570
+ const rows = this.stmtAllSessions.all();
1571
+ for (const row of rows) {
1572
+ this.sessions.set(row.id, parseOpenCodeSession(row));
1573
+ }
1574
+ }
1575
+ // ── Startup replay ─────────────────────────────────────────────────────────
1576
+ replayRecentSessions(activeThresholdMs) {
1577
+ const cutoff = Date.now() - activeThresholdMs;
1578
+ const recent = this.stmtRecentSessions.all(cutoff);
1579
+ if (recent.length === 0)
1580
+ return;
1581
+ console.log(`[opencode] Replaying ${recent.length} recent session(s)`);
1582
+ for (const session of recent) {
1583
+ this.sessions.set(session.id, parseOpenCodeSession(session));
1584
+ this.replaySession(session.id);
1585
+ }
1586
+ }
1587
+ replaySession(sessionId) {
1588
+ const messages = this.stmtMessagesBySession.all(sessionId);
1589
+ for (const msg of messages) {
1590
+ this.processMessageRow(msg);
1591
+ if (msg.time_updated > this.lastMessageTs)
1592
+ this.lastMessageTs = msg.time_updated;
1593
+ }
1594
+ const parts = this.stmtPartsBySession.all(sessionId);
1595
+ for (const part of parts) {
1596
+ this.processPartRow(part);
1597
+ if (part.time_created > this.lastPartCreatedTs)
1598
+ this.lastPartCreatedTs = part.time_created;
1599
+ }
1600
+ }
1601
+ // ── Live polling ───────────────────────────────────────────────────────────
1602
+ poll() {
1603
+ if (!this.db)
1604
+ return;
1605
+ try {
1606
+ const newSessions = this.stmtRecentSessions.all(Date.now() - 6e4);
1607
+ for (const row of newSessions) {
1608
+ if (!this.sessions.has(row.id)) {
1609
+ this.sessions.set(row.id, parseOpenCodeSession(row));
1610
+ console.log(`[opencode] New session: ${row.id.slice(0, 20)} (${row.directory})`);
1611
+ }
1612
+ }
1613
+ const newMessages = this.stmtNewMessages.all(this.lastMessageTs);
1614
+ for (const msg of newMessages) {
1615
+ this.processMessageRow(msg);
1616
+ if (msg.time_updated > this.lastMessageTs)
1617
+ this.lastMessageTs = msg.time_updated;
1618
+ }
1619
+ const newParts = this.stmtNewParts.all(this.lastPartCreatedTs);
1620
+ for (const part of newParts) {
1621
+ this.processPartRow(part);
1622
+ if (part.time_created > this.lastPartCreatedTs)
1623
+ this.lastPartCreatedTs = part.time_created;
1624
+ }
1625
+ } catch (err) {
1626
+ console.error("[opencode] Poll error:", err);
1627
+ }
1628
+ }
1629
+ // ── Row processors ─────────────────────────────────────────────────────────
1630
+ processMessageRow(row) {
1631
+ let data;
1632
+ try {
1633
+ data = JSON.parse(row.data);
1634
+ } catch {
1635
+ return;
1636
+ }
1637
+ this.messages.set(row.id, data);
1638
+ const seenKey = "msg:" + row.id;
1639
+ if (this.seenIds.has(seenKey))
1640
+ return;
1641
+ this.seenIds.add(seenKey);
1642
+ const activity = this.parser.parseTokenUsage(data);
1643
+ if (!activity)
1644
+ return;
1645
+ const sessionInfo = this.getSessionInfo(row.session_id);
1646
+ this.stateManager.processMessage(this.prefixed(row.session_id), activity, sessionInfo);
1647
+ }
1648
+ processPartRow(row) {
1649
+ let data;
1650
+ try {
1651
+ data = JSON.parse(row.data);
1652
+ } catch {
1653
+ return;
1654
+ }
1655
+ const messageData = this.messages.get(row.message_id);
1656
+ if (data.type === "step-start" || data.type === "step-finish") {
1657
+ if (this.seenIds.has(row.id))
1658
+ return;
1659
+ this.seenIds.add(row.id);
1660
+ this.stateManager.heartbeat(this.prefixed(row.session_id));
1661
+ return;
1662
+ }
1663
+ if (data.type === "tool" && data.callID) {
1664
+ if (this.seenCallIds.has(data.callID))
1665
+ return;
1666
+ this.seenCallIds.add(data.callID);
1667
+ } else {
1668
+ if (this.seenIds.has(row.id))
1669
+ return;
1670
+ this.seenIds.add(row.id);
1671
+ }
1672
+ const activity = this.parser.parsePart(data, messageData);
1673
+ if (!activity)
1674
+ return;
1675
+ const sessionInfo = this.getSessionInfo(row.session_id);
1676
+ this.stateManager.processMessage(this.prefixed(row.session_id), activity, sessionInfo);
1677
+ }
1678
+ // ── Helpers ────────────────────────────────────────────────────────────────
1679
+ getSessionInfo(sessionId) {
1680
+ const cached = this.sessions.get(sessionId);
1681
+ if (cached)
1682
+ return cached;
1683
+ this.loadAllSessions();
1684
+ return this.sessions.get(sessionId) ?? this.fallbackSession();
1685
+ }
1686
+ prefixed(id) {
1687
+ return id.startsWith("oc:") ? id : `oc:${id}`;
1688
+ }
1689
+ fallbackSession() {
1690
+ return {
1691
+ projectPath: "opencode",
1692
+ projectName: "opencode",
1693
+ isSubagent: false,
1694
+ projectDir: "opencode",
1695
+ parentSessionId: null
1696
+ };
1697
+ }
1698
+ };
1699
+ var COOLDOWN_MS = 6e4;
1700
+ var TOKEN_WINDOW_MS = 6e4;
1701
+ var anomalyIdCounter = 0;
1702
+ var AnomalyDetector = class extends EventEmitter {
1703
+ agents = /* @__PURE__ */ new Map();
1704
+ agentNames = /* @__PURE__ */ new Map();
1705
+ config;
1706
+ stuckInterval = null;
1707
+ constructor(config2) {
1708
+ super();
1709
+ this.config = { ...DEFAULT_ANOMALY_CONFIG, ...config2 };
1710
+ }
1711
+ getState(agentId) {
1712
+ let state = this.agents.get(agentId);
1713
+ if (!state) {
1714
+ state = {
1715
+ lastTools: [],
1716
+ tokenSamples: [],
1717
+ lastActivityAt: Date.now(),
1718
+ lastAlertAt: /* @__PURE__ */ new Map()
1719
+ };
1720
+ this.agents.set(agentId, state);
1721
+ }
1722
+ return state;
1723
+ }
1724
+ setAgentName(agentId, name) {
1725
+ this.agentNames.set(agentId, name);
1726
+ }
1727
+ checkToolUse(agentId, toolName) {
1728
+ const state = this.getState(agentId);
1729
+ state.lastActivityAt = Date.now();
1730
+ state.lastTools.push(toolName);
1731
+ if (state.lastTools.length > 20)
1732
+ state.lastTools.shift();
1733
+ const threshold = this.config.retryLoopThreshold;
1734
+ if (state.lastTools.length >= threshold) {
1735
+ const recent = state.lastTools.slice(-threshold);
1736
+ if (recent.every((t) => t === recent[0])) {
1737
+ this.alert(agentId, "retry-loop", `${recent[0]} called ${threshold}+ times consecutively`, "warning", {
1738
+ tool: recent[0],
1739
+ consecutiveCount: threshold
1740
+ });
1741
+ }
1742
+ }
1743
+ }
1744
+ checkTokenUsage(agentId, inputTokens, outputTokens) {
1745
+ const state = this.getState(agentId);
1746
+ state.lastActivityAt = Date.now();
1747
+ const now = Date.now();
1748
+ const total = inputTokens + outputTokens;
1749
+ state.tokenSamples.push({ ts: now, total });
1750
+ const cutoff = now - TOKEN_WINDOW_MS;
1751
+ while (state.tokenSamples.length > 0 && state.tokenSamples[0].ts < cutoff) {
1752
+ state.tokenSamples.shift();
1753
+ }
1754
+ if (state.tokenSamples.length >= 2) {
1755
+ const totalTokens = state.tokenSamples.reduce((sum, s) => sum + s.total, 0);
1756
+ const elapsed = (state.tokenSamples[state.tokenSamples.length - 1].ts - state.tokenSamples[0].ts) / 6e4;
1757
+ if (elapsed > 0.1) {
1758
+ const velocity = totalTokens / elapsed;
1759
+ if (velocity >= this.config.tokenVelocityThreshold) {
1760
+ this.alert(agentId, "token-spike", `Token velocity ${Math.round(velocity)}/min exceeds threshold`, "critical", {
1761
+ velocity: Math.round(velocity),
1762
+ threshold: this.config.tokenVelocityThreshold
1763
+ });
1764
+ }
1765
+ }
1766
+ }
1767
+ }
1768
+ checkStuck(agentId, lastActivityAt) {
1769
+ const state = this.getState(agentId);
1770
+ const elapsed = (Date.now() - lastActivityAt) / 1e3;
1771
+ if (elapsed >= this.config.stuckTimeoutSeconds) {
1772
+ this.alert(agentId, "stuck-agent", `No activity for ${Math.round(elapsed)}s`, "warning", {
1773
+ idleSeconds: Math.round(elapsed),
1774
+ threshold: this.config.stuckTimeoutSeconds
1775
+ });
1776
+ }
1777
+ state.lastActivityAt = lastActivityAt;
1778
+ }
1779
+ startStuckDetection(getActiveAgents) {
1780
+ if (this.stuckInterval)
1781
+ return;
1782
+ this.stuckInterval = setInterval(() => {
1783
+ for (const agent of getActiveAgents()) {
1784
+ if (!agent.isIdle) {
1785
+ this.checkStuck(agent.id, agent.lastActivityAt);
1786
+ }
1787
+ }
1788
+ }, 3e4);
1789
+ }
1790
+ removeAgent(agentId) {
1791
+ this.agents.delete(agentId);
1792
+ this.agentNames.delete(agentId);
1793
+ }
1794
+ alert(agentId, kind, message, severity, details) {
1795
+ const state = this.getState(agentId);
1796
+ const now = Date.now();
1797
+ const lastAlert = state.lastAlertAt.get(kind) ?? 0;
1798
+ if (now - lastAlert < COOLDOWN_MS)
1799
+ return;
1800
+ state.lastAlertAt.set(kind, now);
1801
+ const anomaly = {
1802
+ id: `anomaly-${++anomalyIdCounter}`,
1803
+ agentId,
1804
+ agentName: this.agentNames.get(agentId) ?? agentId.slice(0, 10),
1805
+ kind,
1806
+ message,
1807
+ severity,
1808
+ timestamp: now,
1809
+ details
1810
+ };
1811
+ this.emit("anomaly", anomaly);
1812
+ }
1813
+ dispose() {
1814
+ if (this.stuckInterval) {
1815
+ clearInterval(this.stuckInterval);
1816
+ this.stuckInterval = null;
1817
+ }
1818
+ }
1819
+ };
1820
+ var MAX_DURATION_SAMPLES = 50;
1821
+ var ToolChainTracker = class {
1822
+ lastTool = /* @__PURE__ */ new Map();
1823
+ transitions = /* @__PURE__ */ new Map();
1824
+ toolCounts = /* @__PURE__ */ new Map();
1825
+ // Hook-sourced outcome tracking
1826
+ toolSuccesses = /* @__PURE__ */ new Map();
1827
+ toolFailures = /* @__PURE__ */ new Map();
1828
+ toolDurationSamples = /* @__PURE__ */ new Map();
1829
+ /** Per-agent pending tool start: agentId → {tool, startTime} */
1830
+ pendingStart = /* @__PURE__ */ new Map();
1831
+ recordToolUse(agentId, toolName) {
1832
+ this.toolCounts.set(toolName, (this.toolCounts.get(toolName) ?? 0) + 1);
1833
+ const prev = this.lastTool.get(agentId);
1834
+ this.lastTool.set(agentId, toolName);
1835
+ if (prev && prev !== toolName) {
1836
+ const key = `${prev}\u2192${toolName}`;
1837
+ this.transitions.set(key, (this.transitions.get(key) ?? 0) + 1);
1838
+ }
1839
+ }
1840
+ /** Called from hookPreToolUse — records when a tool started for duration tracking */
1841
+ recordToolStart(agentId, toolName) {
1842
+ this.pendingStart.set(agentId, { tool: toolName, startTime: Date.now() });
1843
+ }
1844
+ /** Called from hookPostToolUse — records outcome and duration */
1845
+ recordToolOutcome(agentId, success) {
1846
+ const pending = this.pendingStart.get(agentId);
1847
+ if (!pending)
1848
+ return;
1849
+ this.pendingStart.delete(agentId);
1850
+ const { tool, startTime } = pending;
1851
+ const duration = Date.now() - startTime;
1852
+ if (success) {
1853
+ this.toolSuccesses.set(tool, (this.toolSuccesses.get(tool) ?? 0) + 1);
1854
+ } else {
1855
+ this.toolFailures.set(tool, (this.toolFailures.get(tool) ?? 0) + 1);
1856
+ }
1857
+ let samples = this.toolDurationSamples.get(tool);
1858
+ if (!samples) {
1859
+ samples = [];
1860
+ this.toolDurationSamples.set(tool, samples);
1861
+ }
1862
+ samples.push(duration);
1863
+ if (samples.length > MAX_DURATION_SAMPLES)
1864
+ samples.shift();
1865
+ }
1866
+ resetAgent(agentId) {
1867
+ this.lastTool.delete(agentId);
1868
+ this.pendingStart.delete(agentId);
1869
+ }
1870
+ /** Migrate per-agent state from an old ID to a new canonical ID (used on agent merge). */
1871
+ migrateAgent(fromId, toId) {
1872
+ const last = this.lastTool.get(fromId);
1873
+ if (last !== void 0) {
1874
+ if (!this.lastTool.has(toId))
1875
+ this.lastTool.set(toId, last);
1876
+ this.lastTool.delete(fromId);
1877
+ }
1878
+ const pending = this.pendingStart.get(fromId);
1879
+ if (pending !== void 0) {
1880
+ if (!this.pendingStart.has(toId))
1881
+ this.pendingStart.set(toId, pending);
1882
+ this.pendingStart.delete(fromId);
1883
+ }
1884
+ }
1885
+ reset() {
1886
+ this.lastTool.clear();
1887
+ this.transitions.clear();
1888
+ this.toolCounts.clear();
1889
+ this.toolSuccesses.clear();
1890
+ this.toolFailures.clear();
1891
+ this.toolDurationSamples.clear();
1892
+ this.pendingStart.clear();
1893
+ }
1894
+ hasActiveAgents() {
1895
+ return this.lastTool.size > 0;
1896
+ }
1897
+ getSnapshot() {
1898
+ const transitions = [];
1899
+ for (const [key, count] of this.transitions) {
1900
+ const [from, to] = key.split("\u2192");
1901
+ transitions.push({ from, to, count });
1902
+ }
1903
+ transitions.sort((a, b) => b.count - a.count);
1904
+ const toolCounts = {};
1905
+ for (const [tool, count] of this.toolCounts)
1906
+ toolCounts[tool] = count;
1907
+ const toolSuccesses = {};
1908
+ for (const [tool, count] of this.toolSuccesses)
1909
+ toolSuccesses[tool] = count;
1910
+ const toolFailures = {};
1911
+ for (const [tool, count] of this.toolFailures)
1912
+ toolFailures[tool] = count;
1913
+ const toolAvgDuration = {};
1914
+ for (const [tool, samples] of this.toolDurationSamples) {
1915
+ if (samples.length > 0) {
1916
+ toolAvgDuration[tool] = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);
1917
+ }
1918
+ }
1919
+ return {
1920
+ transitions,
1921
+ tools: Array.from(this.toolCounts.keys()).sort(),
1922
+ toolCounts,
1923
+ toolSuccesses,
1924
+ toolFailures,
1925
+ toolAvgDuration
1926
+ };
1927
+ }
1928
+ };
1929
+ var TaskGraphManager = class {
1930
+ tasks = /* @__PURE__ */ new Map();
1931
+ /** Per-agent-root counters to match Claude's sequential task IDs per team */
1932
+ counters = /* @__PURE__ */ new Map();
1933
+ /**
1934
+ * Build a scoped key from rootSessionId + short task ID.
1935
+ * This prevents cross-team ID collisions in the global map.
1936
+ */
1937
+ scopedKey(root, shortId) {
1938
+ return `${root}::${shortId}`;
1939
+ }
1940
+ /**
1941
+ * Process a tool use that may affect the task graph.
1942
+ * Returns true if the graph changed.
1943
+ */
1944
+ processToolUse(agentId, agentName, toolName, toolInput, projectName, rootSessionId) {
1945
+ const root = rootSessionId ?? agentId;
1946
+ if (toolName === "TaskCreate") {
1947
+ return this.handleCreate(agentId, agentName, toolInput, projectName, root);
1948
+ }
1949
+ if (toolName === "TaskUpdate") {
1950
+ return this.handleUpdate(agentId, agentName, toolInput, projectName, root);
1951
+ }
1952
+ return false;
1953
+ }
1954
+ handleCreate(agentId, agentName, toolInput, projectName, root) {
1955
+ const input = toolInput;
1956
+ if (!input)
1957
+ return false;
1958
+ const count = (this.counters.get(root) ?? 0) + 1;
1959
+ this.counters.set(root, count);
1960
+ const shortId = String(count);
1961
+ const key = this.scopedKey(root, shortId);
1962
+ const subject = input.subject ?? `Task ${shortId}`;
1963
+ const description = input.description;
1964
+ const node = {
1965
+ id: shortId,
1966
+ subject,
1967
+ description,
1968
+ status: "pending",
1969
+ owner: void 0,
1970
+ agentId,
1971
+ agentName,
1972
+ projectName,
1973
+ blocks: [],
1974
+ blockedBy: [],
1975
+ timestamp: Date.now(),
1976
+ _rootKey: key
1977
+ };
1978
+ this.tasks.set(key, node);
1979
+ return true;
1980
+ }
1981
+ handleUpdate(agentId, agentName, toolInput, projectName, root) {
1982
+ const input = toolInput;
1983
+ if (!input)
1984
+ return false;
1985
+ const taskId = input.taskId;
1986
+ if (!taskId)
1987
+ return false;
1988
+ const key = this.scopedKey(root, taskId);
1989
+ let task = this.tasks.get(key);
1990
+ if (!task) {
1991
+ task = {
1992
+ id: taskId,
1993
+ subject: `Task ${taskId}`,
1994
+ status: "pending",
1995
+ agentId,
1996
+ agentName,
1997
+ projectName,
1998
+ blocks: [],
1999
+ blockedBy: [],
2000
+ timestamp: Date.now(),
2001
+ _rootKey: key
2002
+ };
2003
+ this.tasks.set(key, task);
2004
+ }
2005
+ let changed = false;
2006
+ if (input.status !== void 0) {
2007
+ const newStatus = input.status;
2008
+ if (["pending", "in_progress", "completed", "deleted"].includes(newStatus)) {
2009
+ task.status = newStatus;
2010
+ changed = true;
2011
+ }
2012
+ }
2013
+ if (input.owner !== void 0) {
2014
+ task.owner = input.owner;
2015
+ changed = true;
2016
+ }
2017
+ if (input.subject !== void 0) {
2018
+ task.subject = input.subject;
2019
+ changed = true;
2020
+ }
2021
+ if (input.description !== void 0) {
2022
+ task.description = input.description;
2023
+ changed = true;
2024
+ }
2025
+ if (input.addBlocks) {
2026
+ const blocks = input.addBlocks;
2027
+ for (const id of blocks) {
2028
+ if (!task.blocks.includes(id)) {
2029
+ task.blocks.push(id);
2030
+ changed = true;
2031
+ }
2032
+ const otherKey = this.scopedKey(root, id);
2033
+ const other = this.tasks.get(otherKey);
2034
+ if (other && !other.blockedBy.includes(taskId)) {
2035
+ other.blockedBy.push(taskId);
2036
+ changed = true;
2037
+ }
2038
+ }
2039
+ }
2040
+ if (input.addBlockedBy) {
2041
+ const blockedBy = input.addBlockedBy;
2042
+ for (const id of blockedBy) {
2043
+ if (!task.blockedBy.includes(id)) {
2044
+ task.blockedBy.push(id);
2045
+ changed = true;
2046
+ }
2047
+ const otherKey = this.scopedKey(root, id);
2048
+ const other = this.tasks.get(otherKey);
2049
+ if (other && !other.blocks.includes(taskId)) {
2050
+ other.blocks.push(taskId);
2051
+ changed = true;
2052
+ }
2053
+ }
2054
+ }
2055
+ return changed;
2056
+ }
2057
+ /**
2058
+ * Mark a task as completed (hook-sourced — TaskCompleted event).
2059
+ * Returns true if the status actually changed.
2060
+ */
2061
+ processTaskCompleted(taskId, root) {
2062
+ const key = this.scopedKey(root, taskId);
2063
+ const task = this.tasks.get(key);
2064
+ if (!task || task.status === "completed")
2065
+ return false;
2066
+ task.status = "completed";
2067
+ return true;
2068
+ }
2069
+ /**
2070
+ * Remove all tasks created by a given agent.
2071
+ * Cleans up dependency references from remaining tasks.
2072
+ * Returns true if any tasks were removed.
2073
+ */
2074
+ removeAgentTasks(agentId) {
2075
+ const toRemove = /* @__PURE__ */ new Set();
2076
+ for (const [key, task] of this.tasks) {
2077
+ if (task.agentId === agentId)
2078
+ toRemove.add(key);
2079
+ }
2080
+ if (toRemove.size === 0)
2081
+ return false;
2082
+ const removedKeys = /* @__PURE__ */ new Set();
2083
+ for (const key of toRemove) {
2084
+ removedKeys.add(key);
2085
+ this.tasks.delete(key);
2086
+ }
2087
+ for (const [taskKey, task] of this.tasks) {
2088
+ const root = taskKey.split("::")[0];
2089
+ task.blocks = task.blocks.filter((id) => !removedKeys.has(`${root}::${id}`));
2090
+ task.blockedBy = task.blockedBy.filter((id) => !removedKeys.has(`${root}::${id}`));
2091
+ }
2092
+ return true;
2093
+ }
2094
+ getSnapshot() {
2095
+ const live = Array.from(this.tasks.values()).filter((t) => t.status !== "deleted");
2096
+ const rootOf = (key) => key.split("::")[0];
2097
+ const shortToRoot = /* @__PURE__ */ new Map();
2098
+ for (const t of live) {
2099
+ const root = rootOf(t._rootKey);
2100
+ if (!shortToRoot.has(root))
2101
+ shortToRoot.set(root, /* @__PURE__ */ new Map());
2102
+ shortToRoot.get(root).set(t.id, t._rootKey);
2103
+ }
2104
+ return {
2105
+ tasks: live.map((t) => {
2106
+ const lookup = shortToRoot.get(rootOf(t._rootKey));
2107
+ return {
2108
+ ...t,
2109
+ blocks: t.blocks.map((id) => lookup?.get(id) ?? id),
2110
+ blockedBy: t.blockedBy.map((id) => lookup?.get(id) ?? id)
2111
+ };
2112
+ })
2113
+ };
2114
+ }
2115
+ };
2116
+ function resolveAgentId(sessionToAgent, sessionId) {
2117
+ return sessionToAgent.get(sessionId) ?? sessionId;
2118
+ }
2119
+ function transferAndRemove(deps, sourceId, target, redirectSessionId) {
2120
+ const { agents, hiddenAgents, sessionToAgent, toolChainTracker, clearTimers } = deps;
2121
+ const source = agents.get(sourceId);
2122
+ if (source) {
2123
+ target.totalInputTokens += source.totalInputTokens;
2124
+ target.totalOutputTokens += source.totalOutputTokens;
2125
+ target.cacheReadTokens += source.cacheReadTokens;
2126
+ target.cacheCreationTokens += source.cacheCreationTokens;
2127
+ }
2128
+ agents.delete(sourceId);
2129
+ hiddenAgents.delete(sourceId);
2130
+ clearTimers(sourceId);
2131
+ toolChainTracker.migrateAgent(sourceId, target.id);
2132
+ sessionToAgent.set(redirectSessionId, target.id);
2133
+ target.isIdle = false;
2134
+ target.isDone = false;
2135
+ target.lastActivityAt = Date.now();
2136
+ }
2137
+ function mergeIntoNamed(deps, hiddenId, agentName) {
2138
+ const { agents, namedAgentMap } = deps;
2139
+ const hidden = agents.get(hiddenId);
2140
+ if (!hidden)
2141
+ return null;
2142
+ const key = `${hidden.rootSessionId}:${agentName}`;
2143
+ const existingId = namedAgentMap.get(key);
2144
+ if (!existingId || existingId === hiddenId)
2145
+ return null;
2146
+ const existing = agents.get(existingId);
2147
+ if (!existing)
2148
+ return null;
2149
+ transferAndRemove(deps, hiddenId, existing, hiddenId);
2150
+ console.log(`Merged session ${hiddenId.slice(0, 12)}\u2026 into agent "${agentName}" (${existingId.slice(0, 12)}\u2026)`);
2151
+ return existing;
2152
+ }
2153
+ function promoteAgent(deps, agentId) {
2154
+ const { agents, hiddenAgents, identityTimers, recordTimeline, emit } = deps;
2155
+ hiddenAgents.delete(agentId);
2156
+ const idTimer = identityTimers.get(agentId);
2157
+ if (idTimer)
2158
+ clearTimeout(idTimer);
2159
+ identityTimers.delete(agentId);
2160
+ const agent = agents.get(agentId);
2161
+ if (!agent)
2162
+ return;
2163
+ console.log(`Promoting hidden agent ${agentId.slice(0, 12)}\u2026 (name: ${agent.agentName ?? "unknown"})`);
2164
+ const now = Date.now();
2165
+ const spawnEvent = { type: "agent:spawn", agent: { ...agent }, timestamp: now };
2166
+ recordTimeline(spawnEvent);
2167
+ emit("agent:spawn", spawnEvent);
2168
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: now };
2169
+ recordTimeline(updateEvent);
2170
+ emit("agent:update", updateEvent);
2171
+ }
2172
+ function determineRole(_activity, sessionInfo) {
2173
+ if (sessionInfo.isSubagent)
2174
+ return "subagent";
2175
+ return "main";
2176
+ }
2177
+ function determineRoleForNamed(_parentId, pendingTeam) {
2178
+ if (pendingTeam)
2179
+ return "team-member";
2180
+ return "subagent";
2181
+ }
2182
+ function findParentId(agents, projectDir) {
2183
+ for (const [id, agent] of agents) {
2184
+ if (agent.projectPath === projectDir && agent.role === "team-lead") {
2185
+ return id;
2186
+ }
2187
+ }
2188
+ for (const [id, agent] of agents) {
2189
+ if (agent.projectPath === projectDir && agent.role === "main") {
2190
+ return id;
2191
+ }
2192
+ }
2193
+ for (const [id, agent] of agents) {
2194
+ if (agent.projectPath === projectDir && agent.role !== "subagent" && agent.role !== "team-member") {
2195
+ return id;
2196
+ }
2197
+ }
2198
+ return null;
2199
+ }
2200
+ function getParentTeamName(agents, rootSessionId) {
2201
+ for (const agent of agents.values()) {
2202
+ if (agent.rootSessionId === rootSessionId && agent.teamName) {
2203
+ return agent.teamName;
2204
+ }
2205
+ }
2206
+ return null;
2207
+ }
2208
+ var branchCache = /* @__PURE__ */ new Map();
2209
+ var CACHE_TTL = 3e4;
2210
+ var MAX_CACHE_SIZE = 100;
2211
+ function getGitBranch(projectPath) {
2212
+ const cached = branchCache.get(projectPath);
2213
+ if (cached && Date.now() - cached.ts < CACHE_TTL)
2214
+ return cached.branch;
2215
+ try {
2216
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
2217
+ cwd: projectPath,
2218
+ timeout: 5e3,
2219
+ encoding: "utf-8",
2220
+ stdio: ["ignore", "pipe", "ignore"]
2221
+ }).trim() || null;
2222
+ branchCache.set(projectPath, { branch, ts: Date.now() });
2223
+ pruneCache();
2224
+ return branch;
2225
+ } catch {
2226
+ branchCache.set(projectPath, { branch: null, ts: Date.now() });
2227
+ return null;
2228
+ }
2229
+ }
2230
+ function pruneCache() {
2231
+ if (branchCache.size <= MAX_CACHE_SIZE)
2232
+ return;
2233
+ const entries = [...branchCache.entries()].sort((a, b) => a[1].ts - b[1].ts);
2234
+ const toRemove = entries.slice(0, branchCache.size - MAX_CACHE_SIZE);
2235
+ for (const [key] of toRemove)
2236
+ branchCache.delete(key);
2237
+ }
2238
+ var LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set([
2239
+ "Bash",
2240
+ "Agent",
2241
+ "WebFetch",
2242
+ "WebSearch",
2243
+ // Tools that block waiting for user input
2244
+ "AskUserQuestion",
2245
+ // Reasoning / extended thinking pseudo-tool (emitted for OpenCode reasoning parts)
2246
+ "thinking",
2247
+ // Browser/playwright tools that wait for navigation or network
2248
+ "mcp__playwright__browser_navigate",
2249
+ "mcp__playwright__browser_wait_for",
2250
+ "mcp__playwright__browser_run_code",
2251
+ "mcp__chrome-devtools__navigate_page",
2252
+ "mcp__chrome-devtools__wait_for",
2253
+ "mcp__chrome-devtools__evaluate_script",
2254
+ "mcp__chrome-devtools__performance_start_trace",
2255
+ "mcp__chrome-devtools__performance_stop_trace"
2256
+ ]);
2257
+ var USER_BLOCKING_TOOLS = /* @__PURE__ */ new Set([
2258
+ "AskUserQuestion"
2259
+ ]);
2260
+ function processToolUseActivity(deps, agent, activity, now) {
2261
+ const { pendingTool, anomalyDetector, toolChainTracker, taskGraphManager, namedAgentMap, addHistory, summarizeToolInput, queuePendingInfo, queueRecipient, emit } = deps;
2262
+ const agentId = agent.id;
2263
+ const toolName = activity.toolName ?? "";
2264
+ if (LONG_RUNNING_TOOLS.has(toolName)) {
2265
+ pendingTool.add(agentId);
2266
+ } else {
2267
+ pendingTool.delete(agentId);
2268
+ }
2269
+ agent.isWaitingForUser = USER_BLOCKING_TOOLS.has(toolName);
2270
+ const prevZone = agent.currentZone;
2271
+ agent.currentTool = activity.toolName ?? null;
2272
+ agent.currentActivity = summarizeToolInput(activity.toolInput) || null;
2273
+ agent.currentZone = getZoneForTool(activity.toolName ?? "");
2274
+ agent.toolUseCount++;
2275
+ anomalyDetector.setAgentName(agentId, agent.agentName ?? agent.projectName ?? agentId.slice(0, 10));
2276
+ anomalyDetector.checkToolUse(agentId, toolName);
2277
+ toolChainTracker.recordToolUse(agentId, toolName);
2278
+ if (toolName === "TaskCreate" || toolName === "TaskUpdate") {
2279
+ const graphChanged = taskGraphManager.processToolUse(agentId, agent.agentName ?? agentId.slice(0, 10), toolName, activity.toolInput, agent.projectName, agent.rootSessionId);
2280
+ if (graphChanged) {
2281
+ emit("taskgraph:changed", { data: taskGraphManager.getSnapshot(), timestamp: Date.now() });
2282
+ }
2283
+ }
2284
+ if (agent.projectPath) {
2285
+ agent.gitBranch = getGitBranch(agent.projectPath);
2286
+ }
2287
+ if (activity.toolInput) {
2288
+ const input = activity.toolInput;
2289
+ const filePath = input.file_path;
2290
+ if (filePath) {
2291
+ agent.recentFiles = [filePath, ...agent.recentFiles.filter((f) => f !== filePath)].slice(0, 10);
2292
+ }
2293
+ }
2294
+ if (activity.toolName === "EnterPlanMode") {
2295
+ agent.isPlanning = true;
2296
+ } else if (activity.toolName === "ExitPlanMode") {
2297
+ agent.isPlanning = false;
2298
+ }
2299
+ if (activity.toolName === "TeamCreate" && activity.toolInput) {
2300
+ agent.teamName = activity.toolInput.team_name ?? null;
2301
+ agent.role = "team-lead";
2302
+ if (!agent.agentName) {
2303
+ agent.agentName = "team-lead";
2304
+ }
2305
+ namedAgentMap.set(`${agent.rootSessionId}:team-lead`, agentId);
2306
+ }
2307
+ if (activity.toolName === "SendMessage") {
2308
+ agent.currentZone = "messaging";
2309
+ if (activity.toolInput) {
2310
+ const input = activity.toolInput;
2311
+ const recipient = input.recipient;
2312
+ agent.messageTarget = recipient ?? null;
2313
+ const senderIdentity = agent.agentName || (agent.role === "team-lead" ? "team-lead" : null);
2314
+ if (recipient && senderIdentity) {
2315
+ queueRecipient(agent.rootSessionId, senderIdentity, recipient);
2316
+ }
2317
+ }
2318
+ } else {
2319
+ agent.messageTarget = null;
2320
+ }
2321
+ if (activity.toolName === "Agent" && activity.toolInput) {
2322
+ const input = activity.toolInput;
2323
+ const desc = input.description ?? input.prompt ?? "";
2324
+ const name = input.name ?? null;
2325
+ const teamName = input.team_name ?? null;
2326
+ queuePendingInfo(agentId, {
2327
+ name,
2328
+ task: desc ? desc.length > 80 ? desc.slice(0, 77) + "..." : desc : null,
2329
+ team: teamName
2330
+ });
2331
+ }
2332
+ let diffData;
2333
+ if (activity.toolName === "Edit" && activity.toolInput) {
2334
+ const input = activity.toolInput;
2335
+ const fp = input.file_path ?? "";
2336
+ const oldStr = input.old_string ?? "";
2337
+ const newStr = input.new_string ?? "";
2338
+ if (fp && (oldStr || newStr)) {
2339
+ diffData = {
2340
+ filePath: fp,
2341
+ oldText: oldStr.length > 500 ? oldStr.slice(0, 500) + "..." : oldStr,
2342
+ newText: newStr.length > 500 ? newStr.slice(0, 500) + "..." : newStr
2343
+ };
2344
+ }
2345
+ }
2346
+ if (diffData) {
2347
+ agent.recentDiffs.unshift({ ...diffData, timestamp: now });
2348
+ if (agent.recentDiffs.length > 10)
2349
+ agent.recentDiffs.pop();
2350
+ }
2351
+ addHistory(agentId, {
2352
+ timestamp: now,
2353
+ kind: "tool",
2354
+ tool: activity.toolName ?? void 0,
2355
+ toolArgs: summarizeToolInput(activity.toolInput),
2356
+ zone: agent.currentZone,
2357
+ diff: diffData
2358
+ });
2359
+ if (prevZone !== agent.currentZone) {
2360
+ addHistory(agentId, {
2361
+ timestamp: now,
2362
+ kind: "zone-change",
2363
+ zone: agent.currentZone,
2364
+ prevZone
2365
+ });
2366
+ }
2367
+ if (activity.inputTokens !== void 0 || activity.outputTokens !== void 0) {
2368
+ agent.totalInputTokens += activity.inputTokens ?? 0;
2369
+ agent.totalOutputTokens += activity.outputTokens ?? 0;
2370
+ agent.cacheReadTokens += activity.cacheReadTokens ?? 0;
2371
+ agent.cacheCreationTokens += activity.cacheCreationTokens ?? 0;
2372
+ if (activity.inputTokens !== void 0) {
2373
+ agent.contextTokens = activity.inputTokens + (activity.cacheReadTokens ?? 0);
2374
+ agent.contextCacheTokens = activity.cacheReadTokens ?? 0;
2375
+ }
2376
+ }
2377
+ }
2378
+ function processToolActivity(deps, agent, activity, now) {
2379
+ const { pendingTool, anomalyDetector, addHistory } = deps;
2380
+ const agentId = agent.id;
2381
+ switch (activity.type) {
2382
+ case "tool_use": {
2383
+ processToolUseActivity(deps, agent, activity, now);
2384
+ break;
2385
+ }
2386
+ case "text":
2387
+ pendingTool.delete(agentId);
2388
+ agent.isWaitingForUser = false;
2389
+ if (activity.text) {
2390
+ agent.speechText = activity.text;
2391
+ agent.currentActivity = activity.text;
2392
+ if (!agent.taskDescription) {
2393
+ agent.taskDescription = activity.text;
2394
+ }
2395
+ addHistory(agentId, {
2396
+ timestamp: now,
2397
+ kind: "text",
2398
+ text: activity.text
2399
+ });
2400
+ }
2401
+ if (activity.inputTokens !== void 0 || activity.outputTokens !== void 0) {
2402
+ agent.totalInputTokens += activity.inputTokens ?? 0;
2403
+ agent.totalOutputTokens += activity.outputTokens ?? 0;
2404
+ agent.cacheReadTokens += activity.cacheReadTokens ?? 0;
2405
+ agent.cacheCreationTokens += activity.cacheCreationTokens ?? 0;
2406
+ if (activity.inputTokens !== void 0) {
2407
+ agent.contextTokens = activity.inputTokens + (activity.cacheReadTokens ?? 0);
2408
+ agent.contextCacheTokens = activity.cacheReadTokens ?? 0;
2409
+ }
2410
+ }
2411
+ break;
2412
+ case "token_usage":
2413
+ pendingTool.delete(agentId);
2414
+ agent.isWaitingForUser = false;
2415
+ agent.totalInputTokens += activity.inputTokens ?? 0;
2416
+ agent.totalOutputTokens += activity.outputTokens ?? 0;
2417
+ agent.cacheReadTokens += activity.cacheReadTokens ?? 0;
2418
+ agent.cacheCreationTokens += activity.cacheCreationTokens ?? 0;
2419
+ if (activity.inputTokens !== void 0) {
2420
+ agent.contextTokens = activity.inputTokens + (activity.cacheReadTokens ?? 0);
2421
+ agent.contextCacheTokens = activity.cacheReadTokens ?? 0;
2422
+ }
2423
+ anomalyDetector.checkTokenUsage(agentId, activity.inputTokens ?? 0, activity.outputTokens ?? 0);
2424
+ addHistory(agentId, {
2425
+ timestamp: now,
2426
+ kind: "tokens",
2427
+ inputTokens: activity.inputTokens ?? 0,
2428
+ outputTokens: activity.outputTokens ?? 0
2429
+ });
2430
+ break;
2431
+ }
2432
+ }
2433
+ var MAX_HISTORY_PER_AGENT = 500;
2434
+ var MAX_HISTORY_AGE_MS = 30 * 60 * 1e3;
2435
+ var MAX_TIMELINE_EVENTS = 5e3;
2436
+ var IDENTITY_TIMEOUT_MS = 15e3;
2437
+ var AgentStateManager = class extends EventEmitter2 {
2438
+ agents = /* @__PURE__ */ new Map();
2439
+ idleTimers = /* @__PURE__ */ new Map();
2440
+ shutdownTimers = /* @__PURE__ */ new Map();
2441
+ /** Agents with a pending tool call (waiting for result) */
2442
+ pendingTool = /* @__PURE__ */ new Set();
2443
+ activityHistory = /* @__PURE__ */ new Map();
2444
+ timelineBuffer = [];
2445
+ /** Compound queue of subagent metadata from Agent tool calls, keyed by parent agent ID */
2446
+ pendingSubagentInfo = /* @__PURE__ */ new Map();
2447
+ /** Maps sessionId → canonical agent ID (for merging multiple sessions into one agent) */
2448
+ sessionToAgent = /* @__PURE__ */ new Map();
2449
+ /** Maps rootSessionId:agentName → canonical agent ID (scoped per terminal session) */
2450
+ namedAgentMap = /* @__PURE__ */ new Map();
2451
+ /** Agents that are hidden pending identity confirmation — no events emitted */
2452
+ hiddenAgents = /* @__PURE__ */ new Set();
2453
+ /** Timers to promote hidden agents after timeout */
2454
+ identityTimers = /* @__PURE__ */ new Map();
2455
+ /** Queued recipient names from SendMessage calls: rootSessionId → [{sender, recipient}] */
2456
+ pendingRecipients = /* @__PURE__ */ new Map();
2457
+ /** Anomaly detection for token spikes, retry loops, stuck agents */
2458
+ anomalyDetector = new AnomalyDetector();
2459
+ /** Tool chain transition tracking */
2460
+ toolChainTracker = new ToolChainTracker();
2461
+ /** Task dependency graph tracking */
2462
+ taskGraphManager = new TaskGraphManager();
2463
+ constructor() {
2464
+ super();
2465
+ this.anomalyDetector.startStuckDetection(() => Array.from(this.agents.values()).filter((a) => !this.hiddenAgents.has(a.id)).map((a) => ({ id: a.id, lastActivityAt: a.lastActivityAt, isIdle: a.isIdle })));
2466
+ }
2467
+ getAll() {
2468
+ return Array.from(this.agents.values()).filter((a) => !this.hiddenAgents.has(a.id));
2469
+ }
2470
+ get(id) {
2471
+ return this.agents.get(id);
2472
+ }
2473
+ /** Flush pending subagent/recipient queues — call after initial replay to prevent stale matches */
2474
+ flushPendingQueues() {
2475
+ const infoCount = Array.from(this.pendingSubagentInfo.values()).reduce((n, q) => n + q.length, 0);
2476
+ const recipientCount = Array.from(this.pendingRecipients.values()).reduce((n, q) => n + q.length, 0);
2477
+ this.pendingSubagentInfo.clear();
2478
+ this.pendingRecipients.clear();
2479
+ if (infoCount + recipientCount > 0) {
2480
+ console.log(`Flushed ${infoCount} pending subagent infos, ${recipientCount} pending recipients from replay`);
2481
+ }
2482
+ }
2483
+ getHistory(agentId) {
2484
+ return this.activityHistory.get(agentId) ?? [];
2485
+ }
2486
+ getTimeline() {
2487
+ return this.timelineBuffer;
2488
+ }
2489
+ getToolChainSnapshot() {
2490
+ return this.toolChainTracker.getSnapshot();
2491
+ }
2492
+ getTaskGraphSnapshot() {
2493
+ return this.taskGraphManager.getSnapshot();
2494
+ }
2495
+ /** Check if any named agents exist for a given root session */
2496
+ hasNamedAgentsForRoot(rootSessionId) {
2497
+ for (const key of this.namedAgentMap.keys()) {
2498
+ if (key.startsWith(rootSessionId + ":"))
2499
+ return true;
2500
+ }
2501
+ return false;
2502
+ }
2503
+ recordTimeline(event) {
2504
+ this.timelineBuffer.push({
2505
+ type: event.type,
2506
+ agent: { ...event.agent },
2507
+ timestamp: event.timestamp
2508
+ });
2509
+ const cutoff = Date.now() - MAX_HISTORY_AGE_MS;
2510
+ while (this.timelineBuffer.length > 0 && this.timelineBuffer[0].timestamp < cutoff) {
2511
+ this.timelineBuffer.shift();
2512
+ }
2513
+ while (this.timelineBuffer.length > MAX_TIMELINE_EVENTS) {
2514
+ this.timelineBuffer.shift();
2515
+ }
2516
+ }
2517
+ addHistory(agentId, entry) {
2518
+ let entries = this.activityHistory.get(agentId);
2519
+ if (!entries) {
2520
+ entries = [];
2521
+ this.activityHistory.set(agentId, entries);
2522
+ }
2523
+ entries.push(entry);
2524
+ const cutoff = Date.now() - MAX_HISTORY_AGE_MS;
2525
+ while (entries.length > 0 && entries[0].timestamp < cutoff) {
2526
+ entries.shift();
2527
+ }
2528
+ while (entries.length > MAX_HISTORY_PER_AGENT) {
2529
+ entries.shift();
2530
+ }
2531
+ }
2532
+ summarizeToolInput(input) {
2533
+ if (!input)
2534
+ return "";
2535
+ try {
2536
+ const obj = input;
2537
+ const summary = obj.command ?? obj.file_path ?? obj.pattern ?? obj.query ?? obj.url ?? obj.content;
2538
+ if (typeof summary === "string") {
2539
+ return summary.length > 120 ? summary.slice(0, 117) + "..." : summary;
2540
+ }
2541
+ const json = JSON.stringify(input);
2542
+ return json.length > 120 ? json.slice(0, 117) + "..." : json;
2543
+ } catch {
2544
+ return "";
2545
+ }
2546
+ }
2547
+ /**
2548
+ * Resolve a sessionId to its canonical agent ID.
2549
+ */
2550
+ resolveAgentId(sessionId) {
2551
+ return resolveAgentId(this.sessionToAgent, sessionId);
2552
+ }
2553
+ /**
2554
+ * Compute the rootSessionId for a new agent.
2555
+ * - Main agents are their own root.
2556
+ * - Subagents inherit from their parent (or fallback to parentId/own sessionId).
2557
+ */
2558
+ computeRootSessionId(sessionId, parentId, sessionInfo) {
2559
+ if (!sessionInfo.isSubagent)
2560
+ return sessionId;
2561
+ if (parentId) {
2562
+ const parent = this.agents.get(parentId);
2563
+ return parent?.rootSessionId ?? parentId;
2564
+ }
2565
+ return sessionId;
2566
+ }
2567
+ /** Build the deps object required by identity-manager functions. */
2568
+ get identityDeps() {
2569
+ return {
2570
+ agents: this.agents,
2571
+ hiddenAgents: this.hiddenAgents,
2572
+ sessionToAgent: this.sessionToAgent,
2573
+ namedAgentMap: this.namedAgentMap,
2574
+ identityTimers: this.identityTimers,
2575
+ toolChainTracker: this.toolChainTracker,
2576
+ clearTimers: (id) => this.clearTimers(id),
2577
+ recordTimeline: (e) => this.recordTimeline(e),
2578
+ emit: (event, ...args) => this.emit(event, ...args)
2579
+ };
2580
+ }
2581
+ /** Build the deps object required by activity-processor functions. */
2582
+ get activityDeps() {
2583
+ return {
2584
+ pendingTool: this.pendingTool,
2585
+ anomalyDetector: this.anomalyDetector,
2586
+ toolChainTracker: this.toolChainTracker,
2587
+ taskGraphManager: this.taskGraphManager,
2588
+ namedAgentMap: this.namedAgentMap,
2589
+ addHistory: (id, entry) => this.addHistory(id, entry),
2590
+ summarizeToolInput: (input) => this.summarizeToolInput(input),
2591
+ queuePendingInfo: (parentId, info) => this.queuePendingInfo(parentId, info),
2592
+ queueRecipient: (rootSessionId, sender, recipient) => this.queueRecipient(rootSessionId, sender, recipient),
2593
+ emit: (event, ...args) => this.emit(event, ...args)
2594
+ };
2595
+ }
2596
+ /**
2597
+ * Called when ANY new bytes appear in a session's JSONL file,
2598
+ * even if the parser doesn't extract a meaningful activity.
2599
+ * This keeps the agent alive during long-running tool executions
2600
+ * (e.g. Bash commands) where the tool result writes to the file
2601
+ * but doesn't produce a ParsedActivity.
2602
+ */
2603
+ heartbeat(sessionId) {
2604
+ const canonicalId = this.resolveAgentId(sessionId);
2605
+ const agent = this.agents.get(canonicalId);
2606
+ if (!agent || this.hiddenAgents.has(canonicalId))
2607
+ return;
2608
+ if (this.pendingTool.has(canonicalId)) {
2609
+ agent.lastActivityAt = Date.now();
2610
+ this.resetIdleTimer(canonicalId);
2611
+ }
2612
+ }
2613
+ processMessage(sessionId, activity, sessionInfo) {
2614
+ const canonicalId = this.resolveAgentId(sessionId);
2615
+ if (canonicalId !== sessionId) {
2616
+ const agent2 = this.agents.get(canonicalId);
2617
+ if (agent2) {
2618
+ this.applyActivity(agent2, activity, Date.now(), sessionInfo);
2619
+ return;
2620
+ }
2621
+ }
2622
+ let agent = this.agents.get(canonicalId);
2623
+ const now = Date.now();
2624
+ if (agent && this.hiddenAgents.has(canonicalId)) {
2625
+ let discoveredName = activity.agentName ?? null;
2626
+ if (!discoveredName && activity.messageSender) {
2627
+ discoveredName = this.popRecipientBySender(agent.rootSessionId, activity.messageSender);
2628
+ }
2629
+ if (discoveredName) {
2630
+ const merged = mergeIntoNamed(this.identityDeps, canonicalId, discoveredName);
2631
+ if (merged) {
2632
+ this.applyActivity(merged, activity, now, sessionInfo);
2633
+ return;
2634
+ }
2635
+ agent.agentName = discoveredName;
2636
+ const role = determineRoleForNamed(agent.parentId, null);
2637
+ agent.role = role;
2638
+ agent.teamName = role === "team-member" ? agent.teamName || getParentTeamName(this.agents, agent.rootSessionId) : null;
2639
+ this.namedAgentMap.set(`${agent.rootSessionId}:${discoveredName}`, canonicalId);
2640
+ promoteAgent(this.identityDeps, canonicalId);
2641
+ this.applyActivity(agent, activity, now, sessionInfo);
2642
+ return;
2643
+ }
2644
+ this.applyActivitySilent(agent, activity, now, sessionInfo);
2645
+ return;
2646
+ }
2647
+ if (agent && activity.agentName && !agent.agentName) {
2648
+ const key = `${agent.rootSessionId}:${activity.agentName}`;
2649
+ const existingId = this.namedAgentMap.get(key);
2650
+ if (existingId && existingId !== canonicalId) {
2651
+ const existing = this.agents.get(existingId);
2652
+ if (existing) {
2653
+ transferAndRemove(this.identityDeps, canonicalId, existing, sessionId);
2654
+ this.emit("agent:shutdown", {
2655
+ type: "agent:shutdown",
2656
+ agent: { id: canonicalId },
2657
+ timestamp: now
2658
+ });
2659
+ console.log(`Late-merged agent ${canonicalId.slice(0, 12)}\u2026 into "${activity.agentName}" (${existingId.slice(0, 12)}\u2026)`);
2660
+ this.applyActivity(existing, activity, now, sessionInfo);
2661
+ return;
2662
+ }
2663
+ }
2664
+ agent.agentName = activity.agentName;
2665
+ const role = determineRoleForNamed(agent.parentId, null);
2666
+ agent.role = role;
2667
+ agent.teamName = role === "team-member" ? agent.teamName || getParentTeamName(this.agents, agent.rootSessionId) : agent.teamName;
2668
+ this.namedAgentMap.set(key, canonicalId);
2669
+ console.log(`Agent ${canonicalId.slice(0, 12)}\u2026 identified as "${activity.agentName}"`);
2670
+ }
2671
+ if (!agent) {
2672
+ const parentId = sessionInfo.isSubagent ? sessionInfo.parentSessionId ? this.resolveAgentId(sessionInfo.parentSessionId) : findParentId(this.agents, sessionInfo.projectDir) : null;
2673
+ const rootSessionId = this.computeRootSessionId(sessionId, parentId, sessionInfo);
2674
+ const pendingInfo = parentId ? this.popPendingInfo(parentId) : null;
2675
+ const taskDescription = pendingInfo?.task ?? null;
2676
+ const pendingTeam = pendingInfo?.team ?? null;
2677
+ let agentName = pendingInfo?.name ?? activity.agentName ?? null;
2678
+ if (!agentName && activity.messageSender) {
2679
+ agentName = this.popRecipientBySender(rootSessionId, activity.messageSender);
2680
+ }
2681
+ if (agentName) {
2682
+ const key = `${rootSessionId}:${agentName}`;
2683
+ const existingId = this.namedAgentMap.get(key);
2684
+ if (existingId) {
2685
+ const existing = this.agents.get(existingId);
2686
+ if (existing) {
2687
+ this.sessionToAgent.set(sessionId, existingId);
2688
+ existing.isIdle = false;
2689
+ existing.isDone = false;
2690
+ existing.lastActivityAt = now;
2691
+ console.log(`Immediate merge: session ${sessionId.slice(0, 12)}\u2026 \u2192 agent "${agentName}" (${existingId.slice(0, 12)}\u2026)`);
2692
+ this.applyActivity(existing, activity, now, sessionInfo);
2693
+ return;
2694
+ }
2695
+ }
2696
+ }
2697
+ const role = agentName ? determineRoleForNamed(parentId, pendingTeam) : determineRole(activity, sessionInfo);
2698
+ const teamName = role === "team-member" ? pendingTeam || getParentTeamName(this.agents, rootSessionId) : null;
2699
+ agent = {
2700
+ id: sessionId,
2701
+ sessionId,
2702
+ rootSessionId,
2703
+ projectPath: sessionInfo.projectPath,
2704
+ projectName: sessionInfo.projectName,
2705
+ agentName,
2706
+ role,
2707
+ parentId,
2708
+ teamName,
2709
+ currentZone: "spawn",
2710
+ currentTool: null,
2711
+ currentActivity: null,
2712
+ messageTarget: null,
2713
+ taskDescription,
2714
+ speechText: null,
2715
+ lastActivityAt: now,
2716
+ spawnedAt: now,
2717
+ isIdle: false,
2718
+ isDone: false,
2719
+ isPlanning: false,
2720
+ isWaitingForUser: false,
2721
+ phase: "running",
2722
+ lastToolOutcome: null,
2723
+ totalInputTokens: 0,
2724
+ totalOutputTokens: 0,
2725
+ cacheReadTokens: 0,
2726
+ cacheCreationTokens: 0,
2727
+ contextTokens: 0,
2728
+ contextCacheTokens: 0,
2729
+ model: activity.model ?? null,
2730
+ colorIndex: getProjectColorIndex(sessionId),
2731
+ toolUseCount: 0,
2732
+ gitBranch: null,
2733
+ recentFiles: [],
2734
+ recentDiffs: []
2735
+ };
2736
+ this.agents.set(sessionId, agent);
2737
+ this.sessionToAgent.set(sessionId, sessionId);
2738
+ const shouldHide = sessionInfo.isSubagent && !agentName && this.hasNamedAgentsForRoot(rootSessionId);
2739
+ if (shouldHide) {
2740
+ this.hiddenAgents.add(sessionId);
2741
+ console.log(`Hiding new session ${sessionId.slice(0, 12)}\u2026 (pending identity)`);
2742
+ const timer = setTimeout(() => {
2743
+ if (this.hiddenAgents.has(sessionId)) {
2744
+ console.log(`Identity timeout for ${sessionId.slice(0, 12)}\u2026 \u2014 promoting with fallback name`);
2745
+ promoteAgent(this.identityDeps, sessionId);
2746
+ }
2747
+ }, IDENTITY_TIMEOUT_MS);
2748
+ this.identityTimers.set(sessionId, timer);
2749
+ this.applyActivitySilent(agent, activity, now, sessionInfo);
2750
+ return;
2751
+ }
2752
+ if (agentName) {
2753
+ this.namedAgentMap.set(`${rootSessionId}:${agentName}`, sessionId);
2754
+ }
2755
+ this.addHistory(sessionId, { timestamp: now, kind: "spawn", zone: "spawn" });
2756
+ const spawnEvent = { type: "agent:spawn", agent: { ...agent }, timestamp: now };
2757
+ this.recordTimeline(spawnEvent);
2758
+ this.emit("agent:spawn", spawnEvent);
2759
+ }
2760
+ this.applyActivity(agent, activity, now, sessionInfo);
2761
+ }
2762
+ /** Apply activity and emit update event (for visible agents) */
2763
+ applyActivity(agent, activity, now, sessionInfo) {
2764
+ this.mutateAgentState(agent, activity, now, sessionInfo);
2765
+ this.resetIdleTimer(agent.id);
2766
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: now };
2767
+ this.recordTimeline(updateEvent);
2768
+ this.emit("agent:update", updateEvent);
2769
+ }
2770
+ /** Apply activity silently — no events emitted (for hidden agents) */
2771
+ applyActivitySilent(agent, activity, now, sessionInfo) {
2772
+ this.mutateAgentState(agent, activity, now, sessionInfo);
2773
+ }
2774
+ /** Mutate agent state based on activity (shared between silent and loud paths) */
2775
+ mutateAgentState(agent, activity, now, _sessionInfo) {
2776
+ agent.lastActivityAt = now;
2777
+ agent.isIdle = false;
2778
+ agent.isDone = false;
2779
+ if (agent.phase === "idle")
2780
+ agent.phase = "running";
2781
+ if (activity.model) {
2782
+ agent.model = activity.model;
2783
+ }
2784
+ processToolActivity(this.activityDeps, agent, activity, now);
2785
+ }
2786
+ queuePendingInfo(parentId, info) {
2787
+ let queue = this.pendingSubagentInfo.get(parentId);
2788
+ if (!queue) {
2789
+ queue = [];
2790
+ this.pendingSubagentInfo.set(parentId, queue);
2791
+ }
2792
+ queue.push(info);
2793
+ }
2794
+ popPendingInfo(parentId) {
2795
+ const queue = this.pendingSubagentInfo.get(parentId);
2796
+ if (!queue || queue.length === 0)
2797
+ return null;
2798
+ return queue.shift();
2799
+ }
2800
+ queueRecipient(rootSessionId, sender, recipient) {
2801
+ let queue = this.pendingRecipients.get(rootSessionId);
2802
+ if (!queue) {
2803
+ queue = [];
2804
+ this.pendingRecipients.set(rootSessionId, queue);
2805
+ }
2806
+ queue.push({ sender, recipient });
2807
+ if (queue.length > 50)
2808
+ queue.shift();
2809
+ }
2810
+ /** Pop a queued recipient name matching a specific sender */
2811
+ popRecipientBySender(rootSessionId, sender) {
2812
+ const queue = this.pendingRecipients.get(rootSessionId);
2813
+ if (!queue)
2814
+ return null;
2815
+ const idx = queue.findIndex((e) => e.sender === sender);
2816
+ if (idx === -1)
2817
+ return null;
2818
+ const entry = queue.splice(idx, 1)[0];
2819
+ return entry.recipient;
2820
+ }
2821
+ clearTimers(agentId) {
2822
+ const idle = this.idleTimers.get(agentId);
2823
+ if (idle)
2824
+ clearTimeout(idle);
2825
+ this.idleTimers.delete(agentId);
2826
+ const shutdown = this.shutdownTimers.get(agentId);
2827
+ if (shutdown)
2828
+ clearTimeout(shutdown);
2829
+ this.shutdownTimers.delete(agentId);
2830
+ const identity = this.identityTimers.get(agentId);
2831
+ if (identity)
2832
+ clearTimeout(identity);
2833
+ this.identityTimers.delete(agentId);
2834
+ }
2835
+ resetIdleTimer(agentId) {
2836
+ const existing = this.idleTimers.get(agentId);
2837
+ if (existing)
2838
+ clearTimeout(existing);
2839
+ const existingShutdown = this.shutdownTimers.get(agentId);
2840
+ if (existingShutdown)
2841
+ clearTimeout(existingShutdown);
2842
+ const timer = setTimeout(() => {
2843
+ const agent = this.agents.get(agentId);
2844
+ if (agent && !this.hiddenAgents.has(agentId)) {
2845
+ if (this.pendingTool.has(agentId)) {
2846
+ this.resetIdleTimer(agentId);
2847
+ return;
2848
+ }
2849
+ agent.isIdle = true;
2850
+ agent.isPlanning = false;
2851
+ agent.isWaitingForUser = false;
2852
+ agent.phase = "idle";
2853
+ agent.currentZone = "idle";
2854
+ agent.currentTool = null;
2855
+ agent.currentActivity = null;
2856
+ agent.speechText = null;
2857
+ const ts = Date.now();
2858
+ this.addHistory(agentId, { timestamp: ts, kind: "idle", zone: "idle" });
2859
+ const idleEvent = {
2860
+ type: "agent:idle",
2861
+ agent: { ...agent },
2862
+ timestamp: ts
2863
+ };
2864
+ this.recordTimeline(idleEvent);
2865
+ this.emit("agent:idle", idleEvent);
2866
+ this.startShutdownTimer(agentId);
2867
+ }
2868
+ }, config.idleTimeoutMs);
2869
+ this.idleTimers.set(agentId, timer);
2870
+ }
2871
+ startShutdownTimer(agentId) {
2872
+ const existing = this.shutdownTimers.get(agentId);
2873
+ if (existing)
2874
+ clearTimeout(existing);
2875
+ const timer = setTimeout(() => {
2876
+ const agent = this.agents.get(agentId);
2877
+ if (agent && agent.isIdle && !agent.isDone) {
2878
+ agent.isDone = true;
2879
+ console.log(`Agent marked done: ${agentId} (idle for ${config.shutdownTimeoutMs / 1e3}s)`);
2880
+ const ts = Date.now();
2881
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: ts };
2882
+ this.recordTimeline(updateEvent);
2883
+ this.emit("agent:update", updateEvent);
2884
+ }
2885
+ }, config.shutdownTimeoutMs);
2886
+ this.shutdownTimers.set(agentId, timer);
2887
+ }
2888
+ removeDone() {
2889
+ const removed = [];
2890
+ const doneIds = [...this.agents.entries()].filter(([, a]) => a.isDone).map(([id]) => id);
2891
+ for (const id of doneIds) {
2892
+ if (this.agents.has(id)) {
2893
+ removed.push(id);
2894
+ this.shutdown(id);
2895
+ }
2896
+ }
2897
+ return removed;
2898
+ }
2899
+ shutdown(sessionId) {
2900
+ const childIds = [...this.agents.entries()].filter(([id, a]) => a.parentId === sessionId && id !== sessionId).map(([id]) => id);
2901
+ this.clearTimers(sessionId);
2902
+ this.hiddenAgents.delete(sessionId);
2903
+ this.pendingTool.delete(sessionId);
2904
+ const ts = Date.now();
2905
+ this.addHistory(sessionId, { timestamp: ts, kind: "shutdown" });
2906
+ const agent = this.agents.get(sessionId);
2907
+ const shutdownEvent = {
2908
+ type: "agent:shutdown",
2909
+ agent: agent ? { ...agent } : { id: sessionId },
2910
+ timestamp: ts
2911
+ };
2912
+ this.recordTimeline(shutdownEvent);
2913
+ this.emit("agent:shutdown", shutdownEvent);
2914
+ if (agent?.agentName) {
2915
+ const key = `${agent.rootSessionId}:${agent.agentName}`;
2916
+ if (this.namedAgentMap.get(key) === sessionId) {
2917
+ this.namedAgentMap.delete(key);
2918
+ }
2919
+ }
2920
+ for (const [sid, target] of this.sessionToAgent) {
2921
+ if (target === sessionId) {
2922
+ this.sessionToAgent.delete(sid);
2923
+ }
2924
+ }
2925
+ this.agents.delete(sessionId);
2926
+ this.activityHistory.delete(sessionId);
2927
+ this.pendingSubagentInfo.delete(sessionId);
2928
+ this.anomalyDetector.removeAgent(sessionId);
2929
+ this.toolChainTracker.resetAgent(sessionId);
2930
+ if (!this.toolChainTracker.hasActiveAgents() && this.agents.size === 0) {
2931
+ this.toolChainTracker.reset();
2932
+ }
2933
+ this.emit("toolchain:changed", { data: this.toolChainTracker.getSnapshot(), timestamp: Date.now() });
2934
+ if (agent?.rootSessionId) {
2935
+ const rootId = agent.rootSessionId;
2936
+ const hasRelated = [...this.agents.values()].some((a) => a.rootSessionId === rootId);
2937
+ if (!hasRelated) {
2938
+ this.pendingRecipients.delete(rootId);
2939
+ }
2940
+ }
2941
+ if (this.taskGraphManager.removeAgentTasks(sessionId)) {
2942
+ this.emit("taskgraph:changed", { data: this.taskGraphManager.getSnapshot(), timestamp: Date.now() });
2943
+ }
2944
+ for (const childId of childIds) {
2945
+ if (this.agents.has(childId)) {
2946
+ console.log(`Cascading shutdown: child ${childId.slice(0, 12)}\u2026 (parent ${sessionId.slice(0, 12)}\u2026 removed)`);
2947
+ this.shutdown(childId);
2948
+ }
2949
+ }
2950
+ }
2951
+ // ---------------------------------------------------------------------------
2952
+ // Hook-based lifecycle methods (Phase B: merge event sources)
2953
+ // ---------------------------------------------------------------------------
2954
+ /** Hook: new Claude Code session started */
2955
+ hookSessionStart(sessionId, _cwd) {
2956
+ const canonicalId = this.resolveAgentId(sessionId);
2957
+ const agent = this.agents.get(canonicalId);
2958
+ if (agent) {
2959
+ agent.isIdle = false;
2960
+ agent.isDone = false;
2961
+ agent.phase = "running";
2962
+ agent.lastActivityAt = Date.now();
2963
+ this.resetIdleTimer(canonicalId);
2964
+ }
2965
+ }
2966
+ /** Hook: session ended — definitively shut down the agent */
2967
+ hookSessionEnd(sessionId) {
2968
+ const canonicalId = this.resolveAgentId(sessionId);
2969
+ if (this.agents.has(canonicalId)) {
2970
+ this.shutdown(canonicalId);
2971
+ }
2972
+ }
2973
+ /** Hook: user submitted a prompt — agent is now running */
2974
+ hookUserPromptSubmit(sessionId) {
2975
+ this.setPhase(sessionId, "running");
2976
+ }
2977
+ /** Hook: agent finished responding (Stop event) */
2978
+ hookStop(sessionId, lastMessage) {
2979
+ const canonicalId = this.resolveAgentId(sessionId);
2980
+ const agent = this.agents.get(canonicalId);
2981
+ if (!agent || this.hiddenAgents.has(canonicalId))
2982
+ return;
2983
+ agent.phase = "idle";
2984
+ agent.isIdle = true;
2985
+ agent.isPlanning = false;
2986
+ agent.isWaitingForUser = false;
2987
+ agent.currentZone = "idle";
2988
+ agent.currentTool = null;
2989
+ agent.currentActivity = null;
2990
+ if (lastMessage) {
2991
+ agent.speechText = lastMessage.length > 200 ? lastMessage.slice(0, 197) + "..." : lastMessage;
2992
+ }
2993
+ const now = Date.now();
2994
+ this.addHistory(canonicalId, { timestamp: now, kind: "idle", zone: "idle" });
2995
+ const idleEvent = { type: "agent:idle", agent: { ...agent }, timestamp: now };
2996
+ this.recordTimeline(idleEvent);
2997
+ this.emit("agent:idle", idleEvent);
2998
+ this.resetIdleTimer(canonicalId);
2999
+ }
3000
+ /** Hook: context compaction is starting */
3001
+ hookPreCompact(sessionId) {
3002
+ this.setPhase(sessionId, "compacting");
3003
+ }
3004
+ /** Hook: tool is about to execute (PreToolUse) */
3005
+ hookPreToolUse(sessionId, toolName, toolInput, _toolUseId) {
3006
+ const canonicalId = this.resolveAgentId(sessionId);
3007
+ const agent = this.agents.get(canonicalId);
3008
+ if (!agent || this.hiddenAgents.has(canonicalId))
3009
+ return;
3010
+ agent.phase = "running";
3011
+ agent.lastToolOutcome = null;
3012
+ agent.currentTool = toolName;
3013
+ agent.currentZone = getZoneForTool(toolName);
3014
+ if (toolInput) {
3015
+ agent.currentActivity = this.summarizeToolInput(toolInput);
3016
+ }
3017
+ agent.lastActivityAt = Date.now();
3018
+ this.resetIdleTimer(canonicalId);
3019
+ this.toolChainTracker.recordToolStart(canonicalId, toolName);
3020
+ const now = Date.now();
3021
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: now };
3022
+ this.recordTimeline(updateEvent);
3023
+ this.emit("agent:update", updateEvent);
3024
+ }
3025
+ /** Hook: background task completed (TaskCompleted event) */
3026
+ hookTaskCompleted(sessionId, taskId, taskSubject) {
3027
+ const canonicalId = this.resolveAgentId(sessionId);
3028
+ const agent = this.agents.get(canonicalId);
3029
+ const root = agent?.rootSessionId ?? canonicalId;
3030
+ const changed = this.taskGraphManager.processTaskCompleted(taskId, root);
3031
+ if (changed) {
3032
+ this.emit("taskgraph:changed", { data: this.taskGraphManager.getSnapshot(), timestamp: Date.now() });
3033
+ }
3034
+ this.emit("task:completed", {
3035
+ taskId,
3036
+ taskSubject: taskSubject ?? `Task ${taskId}`,
3037
+ agentId: canonicalId
3038
+ });
3039
+ }
3040
+ /** Hook: tool completed (PostToolUse = success, PostToolUseFailure = failure) */
3041
+ hookPostToolUse(sessionId, _toolName, _toolUseId, success) {
3042
+ const canonicalId = this.resolveAgentId(sessionId);
3043
+ const agent = this.agents.get(canonicalId);
3044
+ if (!agent || this.hiddenAgents.has(canonicalId))
3045
+ return;
3046
+ agent.lastToolOutcome = success ? "success" : "failure";
3047
+ this.toolChainTracker.recordToolOutcome(canonicalId, success);
3048
+ agent.lastActivityAt = Date.now();
3049
+ this.resetIdleTimer(canonicalId);
3050
+ const now = Date.now();
3051
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: now };
3052
+ this.recordTimeline(updateEvent);
3053
+ this.emit("agent:update", updateEvent);
3054
+ }
3055
+ setPhase(sessionId, phase) {
3056
+ const canonicalId = this.resolveAgentId(sessionId);
3057
+ const agent = this.agents.get(canonicalId);
3058
+ if (!agent || this.hiddenAgents.has(canonicalId))
3059
+ return;
3060
+ agent.phase = phase;
3061
+ if (phase === "running" || phase === "compacting") {
3062
+ agent.isIdle = false;
3063
+ agent.isDone = false;
3064
+ agent.lastActivityAt = Date.now();
3065
+ this.resetIdleTimer(canonicalId);
3066
+ }
3067
+ const now = Date.now();
3068
+ const updateEvent = { type: "agent:update", agent: { ...agent }, timestamp: now };
3069
+ this.recordTimeline(updateEvent);
3070
+ this.emit("agent:update", updateEvent);
3071
+ }
3072
+ dispose() {
3073
+ this.anomalyDetector.dispose();
3074
+ this.clearAllTimers();
3075
+ }
3076
+ clearAllTimers() {
3077
+ for (const timer of this.idleTimers.values())
3078
+ clearTimeout(timer);
3079
+ this.idleTimers.clear();
3080
+ for (const timer of this.shutdownTimers.values())
3081
+ clearTimeout(timer);
3082
+ this.shutdownTimers.clear();
3083
+ for (const timer of this.identityTimers.values())
3084
+ clearTimeout(timer);
3085
+ this.identityTimers.clear();
3086
+ }
3087
+ };
3088
+ var Broadcaster = class {
3089
+ stateManager;
3090
+ clients = /* @__PURE__ */ new Set();
3091
+ boundListeners = [];
3092
+ constructor(stateManager, hookManager) {
3093
+ this.stateManager = stateManager;
3094
+ const track = (emitter, event, fn) => {
3095
+ emitter.on(event, fn);
3096
+ this.boundListeners.push({ emitter, event, fn });
3097
+ };
3098
+ if (hookManager) {
3099
+ track(hookManager, "permission:request", (permission) => {
3100
+ this.broadcast({
3101
+ type: "permission:request",
3102
+ permission,
3103
+ timestamp: Date.now()
3104
+ });
3105
+ });
3106
+ track(hookManager, "permission:resolved", (payload) => {
3107
+ this.broadcast({
3108
+ type: "permission:resolved",
3109
+ permissionId: payload.permissionId,
3110
+ decision: payload.decision,
3111
+ timestamp: Date.now()
3112
+ });
3113
+ });
3114
+ }
3115
+ for (const eventType of ["agent:spawn", "agent:update", "agent:idle", "agent:shutdown"]) {
3116
+ track(stateManager, eventType, (event) => {
3117
+ if (eventType === "agent:shutdown") {
3118
+ this.broadcast({
3119
+ type: "agent:shutdown",
3120
+ agentId: event.agent.id,
3121
+ timestamp: event.timestamp
3122
+ });
3123
+ } else {
3124
+ this.broadcast({
3125
+ type: eventType,
3126
+ agent: event.agent,
3127
+ timestamp: event.timestamp
3128
+ });
3129
+ }
3130
+ });
3131
+ }
3132
+ track(stateManager.anomalyDetector, "anomaly", (anomaly) => {
3133
+ this.broadcast({
3134
+ type: "anomaly:alert",
3135
+ anomaly,
3136
+ timestamp: Date.now()
3137
+ });
3138
+ });
3139
+ track(stateManager, "toolchain:changed", (payload) => {
3140
+ this.broadcast({
3141
+ type: "toolchain:snapshot",
3142
+ data: payload.data,
3143
+ timestamp: payload.timestamp
3144
+ });
3145
+ });
3146
+ track(stateManager, "taskgraph:changed", (payload) => {
3147
+ this.broadcast({
3148
+ type: "taskgraph:snapshot",
3149
+ data: payload.data,
3150
+ timestamp: payload.timestamp
3151
+ });
3152
+ });
3153
+ track(stateManager, "task:completed", (payload) => {
3154
+ this.broadcast({
3155
+ type: "task:completed",
3156
+ taskId: payload.taskId,
3157
+ taskSubject: payload.taskSubject,
3158
+ agentId: payload.agentId,
3159
+ timestamp: Date.now()
3160
+ });
3161
+ });
3162
+ }
3163
+ addClient(ws) {
3164
+ this.clients.add(ws);
3165
+ ws.on("close", () => {
3166
+ this.clients.delete(ws);
3167
+ });
3168
+ ws.on("error", () => {
3169
+ this.clients.delete(ws);
3170
+ });
3171
+ if (ws.readyState === 1) {
3172
+ try {
3173
+ const fullState = {
3174
+ type: "full_state",
3175
+ agents: this.stateManager.getAll(),
3176
+ timestamp: Date.now()
3177
+ };
3178
+ ws.send(JSON.stringify(fullState));
3179
+ const timeline = {
3180
+ type: "timeline:snapshot",
3181
+ events: this.stateManager.getTimeline(),
3182
+ timestamp: Date.now()
3183
+ };
3184
+ ws.send(JSON.stringify(timeline));
3185
+ const toolchain = {
3186
+ type: "toolchain:snapshot",
3187
+ data: this.stateManager.getToolChainSnapshot(),
3188
+ timestamp: Date.now()
3189
+ };
3190
+ ws.send(JSON.stringify(toolchain));
3191
+ const taskgraph = {
3192
+ type: "taskgraph:snapshot",
3193
+ data: this.stateManager.getTaskGraphSnapshot(),
3194
+ timestamp: Date.now()
3195
+ };
3196
+ ws.send(JSON.stringify(taskgraph));
3197
+ } catch {
3198
+ this.clients.delete(ws);
3199
+ }
3200
+ }
3201
+ }
3202
+ /** Broadcast that hook events are being received */
3203
+ broadcastHooksStatus() {
3204
+ this.broadcast({ type: "hooks:status", timestamp: Date.now() });
3205
+ }
3206
+ /** Send a message to a specific client */
3207
+ sendToClient(ws, message) {
3208
+ if (ws.readyState === 1) {
3209
+ try {
3210
+ ws.send(JSON.stringify(message));
3211
+ } catch {
3212
+ this.clients.delete(ws);
3213
+ }
3214
+ }
3215
+ }
3216
+ dispose() {
3217
+ for (const { emitter, event, fn } of this.boundListeners) {
3218
+ emitter.removeListener(event, fn);
3219
+ }
3220
+ this.boundListeners = [];
3221
+ this.clients.clear();
3222
+ }
3223
+ broadcast(message) {
3224
+ const data = JSON.stringify(message);
3225
+ for (const client of this.clients) {
3226
+ if (client.readyState === 1) {
3227
+ try {
3228
+ client.send(data);
3229
+ } catch {
3230
+ this.clients.delete(client);
3231
+ }
3232
+ }
3233
+ }
3234
+ }
3235
+ };
3236
+ function registerWsHandler(app, stateManager, broadcaster, hookManager) {
3237
+ app.get("/ws", { websocket: true }, (socket, _req) => {
3238
+ console.log("WebSocket client connected");
3239
+ broadcaster.addClient(socket);
3240
+ socket.on("message", (data) => {
3241
+ try {
3242
+ const msg = JSON.parse(data.toString());
3243
+ if (msg.type === "ping") {
3244
+ socket.send(JSON.stringify({ type: "pong" }));
3245
+ } else if (msg.type === "request:history") {
3246
+ const agentId = msg.agentId;
3247
+ const entries = stateManager.getHistory(agentId);
3248
+ socket.send(JSON.stringify({
3249
+ type: "agent:history",
3250
+ agentId,
3251
+ entries,
3252
+ timestamp: Date.now()
3253
+ }));
3254
+ } else if (msg.type === "request:toolchain") {
3255
+ broadcaster.sendToClient(socket, {
3256
+ type: "toolchain:snapshot",
3257
+ data: stateManager.getToolChainSnapshot(),
3258
+ timestamp: Date.now()
3259
+ });
3260
+ } else if (msg.type === "request:taskgraph") {
3261
+ broadcaster.sendToClient(socket, {
3262
+ type: "taskgraph:snapshot",
3263
+ data: stateManager.getTaskGraphSnapshot(),
3264
+ timestamp: Date.now()
3265
+ });
3266
+ } else if (hookManager && msg.type === "permission:approve") {
3267
+ hookManager.resolvePermission(msg.permissionId, {
3268
+ behavior: "allow",
3269
+ updatedInput: msg.updatedInput
3270
+ });
3271
+ } else if (hookManager && msg.type === "permission:deny") {
3272
+ hookManager.resolvePermission(msg.permissionId, { behavior: "deny" });
3273
+ } else if (hookManager && msg.type === "permission:approve-always") {
3274
+ hookManager.resolvePermission(msg.permissionId, {
3275
+ behavior: "allow",
3276
+ updatedPermissions: msg.rules
3277
+ });
3278
+ }
3279
+ } catch {
3280
+ }
3281
+ });
3282
+ socket.on("close", () => {
3283
+ console.log("WebSocket client disconnected");
3284
+ });
3285
+ });
3286
+ }
3287
+ function registerApiRoutes(app, stateManager) {
3288
+ app.get("/api/health", async () => {
3289
+ return { status: "ok", timestamp: Date.now() };
3290
+ });
3291
+ app.get("/api/state", async () => {
3292
+ return {
3293
+ agents: stateManager.getAll(),
3294
+ timestamp: Date.now()
3295
+ };
3296
+ });
3297
+ app.post("/api/agents/clean-done", async () => {
3298
+ const removed = stateManager.removeDone();
3299
+ return { removed, count: removed.length };
3300
+ });
3301
+ app.post("/api/agents/:id/shutdown", async (req, reply) => {
3302
+ const agent = stateManager.getAll().find((a) => a.id === req.params.id);
3303
+ if (!agent) {
3304
+ return reply.status(404).send({ error: "Agent not found" });
3305
+ }
3306
+ stateManager.shutdown(req.params.id);
3307
+ return { removed: req.params.id };
3308
+ });
3309
+ }
3310
+ var PERMISSION_TIMEOUT_MS = 5 * 60 * 1e3;
3311
+ var HookEventManager = class extends EventEmitter3 {
3312
+ stateManager;
3313
+ pending = /* @__PURE__ */ new Map();
3314
+ constructor(stateManager) {
3315
+ super();
3316
+ this.stateManager = stateManager;
3317
+ }
3318
+ /**
3319
+ * Main entry point for incoming hook events.
3320
+ *
3321
+ * - For PermissionRequest: returns a Promise that resolves when the user
3322
+ * decides. Fastify holds the HTTP connection open while awaiting.
3323
+ * - For all other events: processes state updates and returns null immediately
3324
+ * (fire-and-forget from the HTTP handler's perspective).
3325
+ */
3326
+ async handleEvent(event) {
3327
+ if (event.hook_event_name === "PermissionRequest") {
3328
+ return this.handlePermissionRequest(event);
3329
+ }
3330
+ this.processLifecycleEvent(event);
3331
+ return null;
3332
+ }
3333
+ /**
3334
+ * Called by the WS handler when a client approves a permission.
3335
+ */
3336
+ resolvePermission(permissionId, decision) {
3337
+ const deferred = this.pending.get(permissionId);
3338
+ if (!deferred)
3339
+ return;
3340
+ clearTimeout(deferred.timeout);
3341
+ this.pending.delete(permissionId);
3342
+ deferred.resolve(decision);
3343
+ this.emit("permission:resolved", { permissionId, decision: decision.behavior });
3344
+ }
3345
+ /** All currently pending permission requests (for sending on new WS connect) */
3346
+ getPendingPermissions() {
3347
+ return [];
3348
+ }
3349
+ dispose() {
3350
+ for (const deferred of this.pending.values()) {
3351
+ clearTimeout(deferred.timeout);
3352
+ }
3353
+ this.pending.clear();
3354
+ this.removeAllListeners();
3355
+ }
3356
+ // ---------------------------------------------------------------------------
3357
+ // Private
3358
+ // ---------------------------------------------------------------------------
3359
+ async handlePermissionRequest(event) {
3360
+ const permissionId = randomUUID();
3361
+ const permission = {
3362
+ permissionId,
3363
+ sessionId: event.session_id,
3364
+ toolName: event.tool_name ?? "",
3365
+ toolInput: event.tool_input ?? null,
3366
+ toolUseId: event.tool_use_id ?? "",
3367
+ permissionSuggestions: event.permission_suggestions,
3368
+ timestamp: Date.now()
3369
+ };
3370
+ this.emit("permission:request", permission);
3371
+ return new Promise((resolve) => {
3372
+ const timeout = setTimeout(() => {
3373
+ this.pending.delete(permissionId);
3374
+ this.emit("permission:resolved", { permissionId, decision: "deny" });
3375
+ resolve({
3376
+ statusCode: 403,
3377
+ body: buildResponse({ behavior: "deny" })
3378
+ });
3379
+ }, PERMISSION_TIMEOUT_MS);
3380
+ this.pending.set(permissionId, {
3381
+ timeout,
3382
+ resolve: (decision) => {
3383
+ if (decision.behavior === "deny") {
3384
+ resolve({ statusCode: 403, body: buildResponse(decision) });
3385
+ } else {
3386
+ resolve({ statusCode: 200, body: buildResponse(decision) });
3387
+ }
3388
+ }
3389
+ });
3390
+ });
3391
+ }
3392
+ processLifecycleEvent(event) {
3393
+ const { hook_event_name, session_id } = event;
3394
+ switch (hook_event_name) {
3395
+ case "SessionStart":
3396
+ this.stateManager.hookSessionStart(session_id, event.cwd ?? "");
3397
+ break;
3398
+ case "SessionEnd":
3399
+ this.stateManager.hookSessionEnd(session_id);
3400
+ break;
3401
+ case "UserPromptSubmit":
3402
+ this.stateManager.hookUserPromptSubmit(session_id);
3403
+ break;
3404
+ case "Stop":
3405
+ this.stateManager.hookStop(session_id, event.last_assistant_message);
3406
+ break;
3407
+ case "PreCompact":
3408
+ this.stateManager.hookPreCompact(session_id);
3409
+ break;
3410
+ case "PreToolUse":
3411
+ if (event.tool_name) {
3412
+ this.stateManager.hookPreToolUse(session_id, event.tool_name, event.tool_input, event.tool_use_id ?? "");
3413
+ }
3414
+ break;
3415
+ case "PostToolUse":
3416
+ if (event.tool_name) {
3417
+ this.stateManager.hookPostToolUse(session_id, event.tool_name, event.tool_use_id ?? "", true);
3418
+ }
3419
+ break;
3420
+ case "PostToolUseFailure":
3421
+ if (event.tool_name) {
3422
+ this.stateManager.hookPostToolUse(session_id, event.tool_name, event.tool_use_id ?? "", false);
3423
+ }
3424
+ break;
3425
+ case "TaskCompleted":
3426
+ if (event.task_id) {
3427
+ this.stateManager.hookTaskCompleted(session_id, event.task_id, event.task_subject);
3428
+ }
3429
+ break;
3430
+ case "SubagentStart":
3431
+ break;
3432
+ case "SubagentStop":
3433
+ if (event.agent_id) {
3434
+ this.stateManager.hookSessionEnd(event.agent_id);
3435
+ }
3436
+ break;
3437
+ case "Notification":
3438
+ break;
3439
+ case "TeammateIdle":
3440
+ this.emit("teammate:idle", {
3441
+ sessionId: session_id,
3442
+ agentId: event.agent_id,
3443
+ message: event.message
3444
+ });
3445
+ break;
3446
+ case "WorktreeCreate":
3447
+ console.log(`[hook] Worktree created: session=${session_id}`);
3448
+ break;
3449
+ case "WorktreeRemove":
3450
+ console.log(`[hook] Worktree removed: session=${session_id}`);
3451
+ break;
3452
+ default:
3453
+ break;
3454
+ }
3455
+ }
3456
+ };
3457
+ function buildResponse(decision) {
3458
+ return {
3459
+ hookSpecificOutput: {
3460
+ hookEventName: "PermissionRequest",
3461
+ decision
3462
+ }
3463
+ };
3464
+ }
3465
+ var __dirname = dirname(fileURLToPath(import.meta.url));
3466
+ async function main() {
3467
+ const app = Fastify({ logger: { level: "info" } });
3468
+ await app.register(cors, { origin: true });
3469
+ await app.register(websocket);
3470
+ const clientDist = join6(__dirname, "..", "..", "client", "dist");
3471
+ await app.register(fastifyStatic, {
3472
+ root: clientDist,
3473
+ prefix: "/",
3474
+ wildcard: false
3475
+ });
3476
+ const stateManager = new AgentStateManager();
3477
+ const hookManager = new HookEventManager(stateManager);
3478
+ const broadcaster = new Broadcaster(stateManager, hookManager);
3479
+ registerWsHandler(app, stateManager, broadcaster, hookManager);
3480
+ registerApiRoutes(app, stateManager);
3481
+ app.post("/hook", {
3482
+ config: { rawBody: false }
3483
+ }, async (req, reply) => {
3484
+ const event = req.body;
3485
+ if (!event?.hook_event_name || !event?.session_id) {
3486
+ console.warn("[hook] Received invalid hook payload:", JSON.stringify(req.body).slice(0, 200));
3487
+ return reply.status(400).send({ error: "Invalid hook event" });
3488
+ }
3489
+ console.log(`[hook] ${event.hook_event_name} | session=${event.session_id.slice(0, 12)} | tool=${event.tool_name ?? "-"}`);
3490
+ broadcaster.broadcastHooksStatus();
3491
+ const result = await hookManager.handleEvent(event);
3492
+ if (result) {
3493
+ return reply.status(result.statusCode).send(result.body);
3494
+ }
3495
+ return reply.status(200).send({ ok: true });
3496
+ });
3497
+ app.setNotFoundHandler((_req, reply) => {
3498
+ reply.sendFile("index.html");
3499
+ });
3500
+ const watchers = [
3501
+ new FileWatcher(config.claudeHome, stateManager),
3502
+ ...config.enableOpenCode ? [new OpenCodeWatcher(stateManager)] : []
3503
+ ];
3504
+ for (const w of watchers) {
3505
+ await w.start();
3506
+ }
3507
+ stateManager.flushPendingQueues();
3508
+ let actualPort = config.port;
3509
+ for (let attempt = 0; attempt < 10; attempt++) {
3510
+ try {
3511
+ await app.listen({ port: actualPort, host: "127.0.0.1" });
3512
+ break;
3513
+ } catch (err) {
3514
+ if (err.code === "EADDRINUSE" && attempt < 9) {
3515
+ actualPort++;
3516
+ continue;
3517
+ }
3518
+ throw err;
3519
+ }
3520
+ }
3521
+ console.log(`Server listening on http://localhost:${actualPort}`);
3522
+ const shutdown = async () => {
3523
+ console.log("Shutting down...");
3524
+ for (const w of watchers)
3525
+ w.stop();
3526
+ hookManager.dispose();
3527
+ broadcaster.dispose();
3528
+ stateManager.dispose();
3529
+ await app.close();
3530
+ process.exit(0);
3531
+ };
3532
+ process.on("SIGINT", shutdown);
3533
+ process.on("SIGTERM", shutdown);
3534
+ return { port: actualPort };
3535
+ }
3536
+ if (!process.env.__AGENT_MOVE_CLI) {
3537
+ main().catch((err) => {
3538
+ console.error("Failed to start server:", err);
3539
+ process.exit(1);
3540
+ });
3541
+ }
3542
+ export {
3543
+ main
3544
+ };