awb-agent-manager 1.0.0

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -0
  3. package/dist/lib/agent-lockfile.d.ts +37 -0
  4. package/dist/lib/agent-lockfile.js +205 -0
  5. package/dist/lib/agent-lockfile.js.map +1 -0
  6. package/dist/lib/agent-manager-commands.d.ts +34 -0
  7. package/dist/lib/agent-manager-commands.js +531 -0
  8. package/dist/lib/agent-manager-commands.js.map +1 -0
  9. package/dist/lib/base-session-manager.d.ts +127 -0
  10. package/dist/lib/base-session-manager.js +667 -0
  11. package/dist/lib/base-session-manager.js.map +1 -0
  12. package/dist/lib/chat-attachment-prep.d.ts +35 -0
  13. package/dist/lib/chat-attachment-prep.js +180 -0
  14. package/dist/lib/chat-attachment-prep.js.map +1 -0
  15. package/dist/lib/chat-session-manager.d.ts +29 -0
  16. package/dist/lib/chat-session-manager.js +400 -0
  17. package/dist/lib/chat-session-manager.js.map +1 -0
  18. package/dist/lib/circuit-breaker.d.ts +34 -0
  19. package/dist/lib/circuit-breaker.js +146 -0
  20. package/dist/lib/circuit-breaker.js.map +1 -0
  21. package/dist/lib/cli-adapters/antigravity.d.ts +15 -0
  22. package/dist/lib/cli-adapters/antigravity.js +114 -0
  23. package/dist/lib/cli-adapters/antigravity.js.map +1 -0
  24. package/dist/lib/cli-adapters/base.d.ts +115 -0
  25. package/dist/lib/cli-adapters/base.js +111 -0
  26. package/dist/lib/cli-adapters/base.js.map +1 -0
  27. package/dist/lib/cli-adapters/claude.d.ts +19 -0
  28. package/dist/lib/cli-adapters/claude.js +233 -0
  29. package/dist/lib/cli-adapters/claude.js.map +1 -0
  30. package/dist/lib/cli-adapters/codex.d.ts +14 -0
  31. package/dist/lib/cli-adapters/codex.js +152 -0
  32. package/dist/lib/cli-adapters/codex.js.map +1 -0
  33. package/dist/lib/cli-adapters/deepseek.d.ts +15 -0
  34. package/dist/lib/cli-adapters/deepseek.js +70 -0
  35. package/dist/lib/cli-adapters/deepseek.js.map +1 -0
  36. package/dist/lib/cli-adapters/index.d.ts +8 -0
  37. package/dist/lib/cli-adapters/index.js +27 -0
  38. package/dist/lib/cli-adapters/index.js.map +1 -0
  39. package/dist/lib/cli-adapters/model-introspect.d.ts +3 -0
  40. package/dist/lib/cli-adapters/model-introspect.js +65 -0
  41. package/dist/lib/cli-adapters/model-introspect.js.map +1 -0
  42. package/dist/lib/cli-error-signatures.d.ts +9 -0
  43. package/dist/lib/cli-error-signatures.js +22 -0
  44. package/dist/lib/cli-error-signatures.js.map +1 -0
  45. package/dist/lib/cli-output-summary.d.ts +2 -0
  46. package/dist/lib/cli-output-summary.js +104 -0
  47. package/dist/lib/cli-output-summary.js.map +1 -0
  48. package/dist/lib/cli-resolver.d.ts +2 -0
  49. package/dist/lib/cli-resolver.js +177 -0
  50. package/dist/lib/cli-resolver.js.map +1 -0
  51. package/dist/lib/config.d.ts +13 -0
  52. package/dist/lib/config.js +124 -0
  53. package/dist/lib/config.js.map +1 -0
  54. package/dist/lib/constants.d.ts +36 -0
  55. package/dist/lib/constants.js +51 -0
  56. package/dist/lib/constants.js.map +1 -0
  57. package/dist/lib/environment-provisioner.d.ts +31 -0
  58. package/dist/lib/environment-provisioner.js +182 -0
  59. package/dist/lib/environment-provisioner.js.map +1 -0
  60. package/dist/lib/error-log-uploader.d.ts +19 -0
  61. package/dist/lib/error-log-uploader.js +138 -0
  62. package/dist/lib/error-log-uploader.js.map +1 -0
  63. package/dist/lib/event-dispatcher.d.ts +149 -0
  64. package/dist/lib/event-dispatcher.js +799 -0
  65. package/dist/lib/event-dispatcher.js.map +1 -0
  66. package/dist/lib/event-log-recorder.d.ts +11 -0
  67. package/dist/lib/event-log-recorder.js +77 -0
  68. package/dist/lib/event-log-recorder.js.map +1 -0
  69. package/dist/lib/event-stream.d.ts +26 -0
  70. package/dist/lib/event-stream.js +177 -0
  71. package/dist/lib/event-stream.js.map +1 -0
  72. package/dist/lib/fs-browser.d.ts +30 -0
  73. package/dist/lib/fs-browser.js +287 -0
  74. package/dist/lib/fs-browser.js.map +1 -0
  75. package/dist/lib/host-mcp/logs.d.ts +19 -0
  76. package/dist/lib/host-mcp/logs.js +102 -0
  77. package/dist/lib/host-mcp/logs.js.map +1 -0
  78. package/dist/lib/host-mcp/platform.d.ts +24 -0
  79. package/dist/lib/host-mcp/platform.js +133 -0
  80. package/dist/lib/host-mcp/platform.js.map +1 -0
  81. package/dist/lib/host-mcp/screens.d.ts +22 -0
  82. package/dist/lib/host-mcp/screens.js +172 -0
  83. package/dist/lib/host-mcp/screens.js.map +1 -0
  84. package/dist/lib/host-mcp/server.d.ts +3 -0
  85. package/dist/lib/host-mcp/server.js +233 -0
  86. package/dist/lib/host-mcp/server.js.map +1 -0
  87. package/dist/lib/host-mcp/tools.d.ts +172 -0
  88. package/dist/lib/host-mcp/tools.js +1241 -0
  89. package/dist/lib/host-mcp/tools.js.map +1 -0
  90. package/dist/lib/instance-heartbeat.d.ts +55 -0
  91. package/dist/lib/instance-heartbeat.js +118 -0
  92. package/dist/lib/instance-heartbeat.js.map +1 -0
  93. package/dist/lib/legacy-import.d.ts +17 -0
  94. package/dist/lib/legacy-import.js +70 -0
  95. package/dist/lib/legacy-import.js.map +1 -0
  96. package/dist/lib/logging.d.ts +3 -0
  97. package/dist/lib/logging.js +65 -0
  98. package/dist/lib/logging.js.map +1 -0
  99. package/dist/lib/managed-agent-context.d.ts +25 -0
  100. package/dist/lib/managed-agent-context.js +33 -0
  101. package/dist/lib/managed-agent-context.js.map +1 -0
  102. package/dist/lib/managed-agent-store.d.ts +34 -0
  103. package/dist/lib/managed-agent-store.js +160 -0
  104. package/dist/lib/managed-agent-store.js.map +1 -0
  105. package/dist/lib/managed-agents.d.ts +24 -0
  106. package/dist/lib/managed-agents.js +87 -0
  107. package/dist/lib/managed-agents.js.map +1 -0
  108. package/dist/lib/mcp-client.d.ts +8 -0
  109. package/dist/lib/mcp-client.js +122 -0
  110. package/dist/lib/mcp-client.js.map +1 -0
  111. package/dist/lib/orphan-cleanup.d.ts +6 -0
  112. package/dist/lib/orphan-cleanup.js +108 -0
  113. package/dist/lib/orphan-cleanup.js.map +1 -0
  114. package/dist/lib/presence-heartbeat.d.ts +8 -0
  115. package/dist/lib/presence-heartbeat.js +61 -0
  116. package/dist/lib/presence-heartbeat.js.map +1 -0
  117. package/dist/lib/prompts.d.ts +29 -0
  118. package/dist/lib/prompts.js +208 -0
  119. package/dist/lib/prompts.js.map +1 -0
  120. package/dist/lib/rest.d.ts +61 -0
  121. package/dist/lib/rest.js +307 -0
  122. package/dist/lib/rest.js.map +1 -0
  123. package/dist/lib/run-provisioner.d.ts +21 -0
  124. package/dist/lib/run-provisioner.js +115 -0
  125. package/dist/lib/run-provisioner.js.map +1 -0
  126. package/dist/lib/self-path.d.ts +7 -0
  127. package/dist/lib/self-path.js +29 -0
  128. package/dist/lib/self-path.js.map +1 -0
  129. package/dist/lib/self-update.d.ts +46 -0
  130. package/dist/lib/self-update.js +482 -0
  131. package/dist/lib/self-update.js.map +1 -0
  132. package/dist/lib/service-install.d.ts +23 -0
  133. package/dist/lib/service-install.js +673 -0
  134. package/dist/lib/service-install.js.map +1 -0
  135. package/dist/lib/setup.d.ts +16 -0
  136. package/dist/lib/setup.js +157 -0
  137. package/dist/lib/setup.js.map +1 -0
  138. package/dist/lib/subagent-manager.d.ts +84 -0
  139. package/dist/lib/subagent-manager.js +793 -0
  140. package/dist/lib/subagent-manager.js.map +1 -0
  141. package/dist/lib/subagent-monitor.d.ts +43 -0
  142. package/dist/lib/subagent-monitor.js +229 -0
  143. package/dist/lib/subagent-monitor.js.map +1 -0
  144. package/dist/lib/ticket-session-manager.d.ts +23 -0
  145. package/dist/lib/ticket-session-manager.js +471 -0
  146. package/dist/lib/ticket-session-manager.js.map +1 -0
  147. package/dist/lib/worktree-manager.d.ts +39 -0
  148. package/dist/lib/worktree-manager.js +226 -0
  149. package/dist/lib/worktree-manager.js.map +1 -0
  150. package/dist/main.d.ts +2 -0
  151. package/dist/main.js +670 -0
  152. package/dist/main.js.map +1 -0
  153. package/dist/package.json +57 -0
  154. package/package.json +57 -0
package/dist/main.js ADDED
@@ -0,0 +1,670 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, resolve, join as pathJoin } from 'node:path';
6
+ import { AGENT_MANAGER_HOME, CONFIG_PATH, LEGACY_CONFIG_PATH, MANAGED_AGENTS_DIR, } from './lib/constants.js';
7
+ import { loadConfig, resolveAgentId } from './lib/config.js';
8
+ import { installCrashHandlers, log } from './lib/logging.js';
9
+ import { acquireAgentLock, inspectLegacyAgentLock, } from './lib/agent-lockfile.js';
10
+ import { importLegacyConfig } from './lib/legacy-import.js';
11
+ import { isSystemdReExecPending, runSelfUpdate, UpdateChecker } from './lib/self-update.js';
12
+ import { runSetup } from './lib/setup.js';
13
+ import { installService, uninstallService } from './lib/service-install.js';
14
+ import { PresenceHeartbeat } from './lib/presence-heartbeat.js';
15
+ import { InstanceHeartbeat } from './lib/instance-heartbeat.js';
16
+ import { EventStream } from './lib/event-stream.js';
17
+ import { SubagentManager } from './lib/subagent-manager.js';
18
+ import { ChatSessionManager } from './lib/chat-session-manager.js';
19
+ import { TicketSessionManager } from './lib/ticket-session-manager.js';
20
+ import { CircuitBreaker } from './lib/circuit-breaker.js';
21
+ import { uploadIfNewErrors } from './lib/error-log-uploader.js';
22
+ import { onFlushThreshold } from './lib/event-log-recorder.js';
23
+ import { cleanupOrphanSubagents } from './lib/orphan-cleanup.js';
24
+ import { FsBrowser } from './lib/fs-browser.js';
25
+ import { SubagentMonitor } from './lib/subagent-monitor.js';
26
+ import { KNOWN_ADAPTER_CLI_TYPES, createAdapter } from './lib/cli-adapters/index.js';
27
+ import { promptComposer } from './lib/prompts.js';
28
+ import { ManagedAgentRegistry } from './lib/managed-agents.js';
29
+ import { ManagedAgentContextRegistry } from './lib/managed-agent-context.js';
30
+ import { WorktreeManager, worktreeSlug } from './lib/worktree-manager.js';
31
+ import { EnvironmentProvisioner } from './lib/environment-provisioner.js';
32
+ import { AgentManagerCommandHandler } from './lib/agent-manager-commands.js';
33
+ import { listManagedAgentDirs, readManagedAgentConfig, readApiKey, readAgentCredential, mcpConfigPathFor, subagentLogPathFor, cliHomeDirFor, ensureCliHomeDir, } from './lib/managed-agent-store.js';
34
+ const __dirname = dirname(fileURLToPath(import.meta.url));
35
+ function readPkgVersion() {
36
+ try {
37
+ const pkgPath = resolve(__dirname, '..', 'package.json');
38
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
39
+ return pkg.version ?? '0.0.0';
40
+ }
41
+ catch {
42
+ return '0.0.0';
43
+ }
44
+ }
45
+ function parseFlags(argv) {
46
+ let values;
47
+ try {
48
+ ({ values } = parseArgs({
49
+ args: argv,
50
+ options: {
51
+ config: { type: 'string', short: 'c' },
52
+ workspace: { type: 'string', short: 'w' },
53
+ 'dry-run': { type: 'boolean' },
54
+ help: { type: 'boolean', short: 'h' },
55
+ version: { type: 'boolean', short: 'v' },
56
+ force: { type: 'boolean', short: 'f' },
57
+ },
58
+ allowPositionals: false,
59
+ strict: true,
60
+ }));
61
+ }
62
+ catch (err) {
63
+ process.stderr.write(`\n ✗ ${err?.message ?? err}\n\n`);
64
+ process.stderr.write(` Run \`awb-agent-manager --help\` for usage.\n\n`);
65
+ process.exit(2);
66
+ }
67
+ return {
68
+ config: values.config,
69
+ workspace: values.workspace,
70
+ dryRun: Boolean(values['dry-run']),
71
+ help: Boolean(values.help),
72
+ version: Boolean(values.version),
73
+ force: Boolean(values.force),
74
+ };
75
+ }
76
+ function printHelp() {
77
+ process.stdout.write(`awb-agent-manager — standalone AWB subagent runner
78
+
79
+ Usage:
80
+ awb-agent-manager start the manager (uses saved config)
81
+ awb-agent-manager setup [opts] first-run pairing wizard
82
+ awb-agent-manager service install [..] register as a background service (auto-detects host)
83
+ awb-agent-manager service uninstall remove the registered service
84
+ awb-agent-manager mcp-host run the host-tools MCP server over stdio
85
+ (spawned by managed agents; not for direct use)
86
+ awb-agent-manager [options] start with overrides
87
+
88
+ Options:
89
+ -c, --config <path> Path to config.json (default: ${CONFIG_PATH})
90
+ -w, --workspace <id> Override workspace_id from config
91
+ -f, --force Take over the lockfile from a stale or running owner
92
+ --dry-run Load config and exit without starting runtime
93
+ -h, --help Show this help text
94
+ -v, --version Print version
95
+
96
+ Setup options (\`awb-agent-manager setup ...\`):
97
+ --url <url> AWB server base URL (skip prompt)
98
+ --token <token> Pairing token from AWB Admin → Agent Manager
99
+ --instance-id <id> Stable id reported on heartbeats (default <hostname>-<rand>)
100
+ --non-interactive Fail fast on missing fields instead of prompting
101
+ --force Overwrite an existing config.json
102
+
103
+ Service options (\`awb-agent-manager service install ...\`):
104
+ --system Install at system scope (sudo / admin, runs at boot)
105
+ Default: user scope (no admin, runs at logon)
106
+ --platform <p> Force a specific service backend instead of auto-detect:
107
+ auto (default) | systemd | sysvinit | synology | launchd | windows
108
+ --exec-path <path> Override path to dist/main.js (default: this binary's location)
109
+ --dry-run Print the unit/plist/script without writing or running registrar
110
+ --unit-only Write the unit file but skip daemon-reload / load / register
111
+
112
+ Platform mapping (auto):
113
+ linux + systemd → ~/.config/systemd/user/ (user) | /etc/systemd/system/ (--system)
114
+ linux + Synology DSM → /usr/local/etc/rc.d/awb-agent-manager.sh (always system)
115
+ linux without systemd → /etc/init.d/awb-agent-manager (always system, sysvinit)
116
+ darwin (macOS) → ~/Library/LaunchAgents/ (user) | /Library/LaunchDaemons/ (--system)
117
+ win32 → Task Scheduler task 'awb-agent-manager' (logon | --system boot)
118
+
119
+ Config search order:
120
+ 1. --config flag
121
+ 2. $AWB_AGENT_MANAGER_HOME/config.json
122
+ 3. $XDG_CONFIG_HOME/awb-agent-manager/config.json (or %APPDATA% on Windows)
123
+ 4. ~/.config/awb-agent-manager/config.json
124
+
125
+ Legacy import:
126
+ On first run, ${LEGACY_CONFIG_PATH} is copied into the new
127
+ config home if no config.json is present yet. A marker file is placed in
128
+ the legacy directory; subsequent runs skip the import. Existing legacy
129
+ files are NEVER deleted — the claude-plugin stdio MCP proxy may still use
130
+ them.
131
+
132
+ Signals:
133
+ SIGTERM/SIGINT graceful drain + exit
134
+ SIGHUP re-read config.json (delegation tunables hot-reload)
135
+ SIGUSR1 self-update: git pull + npm install + npm run build, then
136
+ re-exec with --force so the new build adopts the lockfile.
137
+ No-op when the manager is not running from a git checkout.
138
+ `);
139
+ }
140
+ function parseSetupArgs(argv) {
141
+ let values;
142
+ try {
143
+ ({ values } = parseArgs({
144
+ args: argv,
145
+ options: {
146
+ config: { type: 'string', short: 'c' },
147
+ url: { type: 'string' },
148
+ token: { type: 'string' },
149
+ 'instance-id': { type: 'string' },
150
+ 'non-interactive': { type: 'boolean' },
151
+ force: { type: 'boolean', short: 'f' },
152
+ },
153
+ allowPositionals: false,
154
+ strict: true,
155
+ }));
156
+ }
157
+ catch (err) {
158
+ process.stderr.write(`\n ✗ setup: ${err?.message ?? err}\n\n`);
159
+ process.stderr.write(` Run \`awb-agent-manager --help\` for setup options.\n\n`);
160
+ process.exit(2);
161
+ }
162
+ return {
163
+ configPath: values.config || undefined,
164
+ url: values.url || undefined,
165
+ token: values.token || undefined,
166
+ instanceId: values['instance-id'] || undefined,
167
+ nonInteractive: Boolean(values['non-interactive']) || !process.stdin.isTTY,
168
+ force: Boolean(values.force),
169
+ };
170
+ }
171
+ async function main() {
172
+ const argv = process.argv.slice(2);
173
+ if (argv[0] === 'mcp-host') {
174
+ try {
175
+ const { runHostMcpServerOverStdio } = await import('./lib/host-mcp/server.js');
176
+ await runHostMcpServerOverStdio();
177
+ process.exit(0);
178
+ }
179
+ catch (err) {
180
+ process.stderr.write(`\n ✗ mcp-host failed: ${err?.stack ?? err?.message ?? err}\n\n`);
181
+ process.exit(1);
182
+ }
183
+ }
184
+ if (argv[0] === 'setup') {
185
+ try {
186
+ await runSetup(parseSetupArgs(argv.slice(1)));
187
+ process.exit(0);
188
+ }
189
+ catch (err) {
190
+ process.stderr.write(`\n ✗ setup failed: ${err?.message ?? err}\n\n`);
191
+ process.exit(1);
192
+ }
193
+ }
194
+ if (argv[0] === 'service') {
195
+ const sub = argv[1] || '';
196
+ if (sub !== 'install' && sub !== 'uninstall') {
197
+ process.stderr.write(`\n ✗ service: unknown subcommand '${sub}' (expected: install | uninstall)\n\n` +
198
+ ` Usage: awb-agent-manager service <install|uninstall> [--system] [--platform <p>] [--exec-path <p>] [--dry-run] [--unit-only]\n\n`);
199
+ process.exit(2);
200
+ }
201
+ let serviceValues;
202
+ try {
203
+ ({ values: serviceValues } = parseArgs({
204
+ args: argv.slice(2),
205
+ options: {
206
+ system: { type: 'boolean' },
207
+ 'dry-run': { type: 'boolean' },
208
+ 'unit-only': { type: 'boolean' },
209
+ 'exec-path': { type: 'string' },
210
+ platform: { type: 'string' },
211
+ },
212
+ allowPositionals: false,
213
+ strict: true,
214
+ }));
215
+ }
216
+ catch (err) {
217
+ process.stderr.write(`\n ✗ service ${sub}: ${err?.message ?? err}\n\n`);
218
+ process.exit(2);
219
+ }
220
+ const isSystem = Boolean(serviceValues.system);
221
+ const dryRun = Boolean(serviceValues['dry-run']);
222
+ const unitOnly = Boolean(serviceValues['unit-only']);
223
+ const execPath = serviceValues['exec-path'];
224
+ const platformRaw = serviceValues.platform;
225
+ const validPlatforms = ['auto', 'systemd', 'sysvinit', 'synology', 'launchd', 'windows'];
226
+ if (platformRaw && !validPlatforms.includes(platformRaw)) {
227
+ process.stderr.write(`\n ✗ service ${sub}: invalid --platform '${platformRaw}' ` +
228
+ `(expected: ${validPlatforms.join(' | ')})\n\n`);
229
+ process.exit(2);
230
+ }
231
+ const platform = (platformRaw || 'auto');
232
+ try {
233
+ if (sub === 'install') {
234
+ await installService({ system: isSystem, execPath, dryRun, unitOnly, platform });
235
+ }
236
+ else {
237
+ await uninstallService({ system: isSystem, platform });
238
+ }
239
+ process.exit(0);
240
+ }
241
+ catch (err) {
242
+ process.stderr.write(`\n ✗ service ${sub} failed: ${err?.message ?? err}\n\n`);
243
+ process.exit(1);
244
+ }
245
+ }
246
+ const flags = parseFlags(argv);
247
+ if (flags.help) {
248
+ printHelp();
249
+ return;
250
+ }
251
+ if (flags.version) {
252
+ process.stdout.write(`${readPkgVersion()}\n`);
253
+ return;
254
+ }
255
+ installCrashHandlers();
256
+ const version = readPkgVersion();
257
+ process.stdout.write(`awb-agent-manager v${version}\n`);
258
+ process.stdout.write(` home: ${AGENT_MANAGER_HOME}\n`);
259
+ if (!flags.config) {
260
+ const importResult = importLegacyConfig();
261
+ if (importResult.imported) {
262
+ process.stdout.write(` imported: ${importResult.source.config} -> ${importResult.target.config}\n`);
263
+ }
264
+ }
265
+ const configPath = flags.config ?? CONFIG_PATH;
266
+ let config = loadConfig(configPath);
267
+ if (!config) {
268
+ process.stdout.write(` config: not found at ${configPath}\n`);
269
+ if (existsSync(LEGACY_CONFIG_PATH)) {
270
+ process.stdout.write(` hint: legacy plugin config exists at ${LEGACY_CONFIG_PATH} ` +
271
+ `but a previous import already ran. Copy it to ${configPath} manually if you want to re-import.\n`);
272
+ }
273
+ else {
274
+ process.stdout.write(`\n No config yet. Run the pairing wizard to set one up:\n\n` +
275
+ ` awb-agent-manager setup\n\n` +
276
+ ` Or non-interactively (CI / Ansible):\n\n` +
277
+ ` awb-agent-manager setup --url <awb-url> --token <pairing-token>\n\n` +
278
+ ` The token comes from AWB Admin → Agent Manager → "Pair manager…".\n`);
279
+ }
280
+ if (flags.dryRun) {
281
+ log('--dry-run: exiting after config load (config=missing)');
282
+ return;
283
+ }
284
+ log('No config — exiting. Run `awb-agent-manager setup` first.');
285
+ process.exit(1);
286
+ }
287
+ if (flags.workspace) {
288
+ config.workspace_id = flags.workspace;
289
+ }
290
+ process.stdout.write(` config: ${configPath}\n`);
291
+ process.stdout.write(` url: ${config.url}\n`);
292
+ process.stdout.write(` workspace: ${config.workspace_id ?? '(none)'}\n`);
293
+ if (flags.dryRun) {
294
+ log('--dry-run: exiting after config load (config=loaded)');
295
+ return;
296
+ }
297
+ await runRuntime(config, version, flags, argv);
298
+ }
299
+ async function runRuntime(config, version, flags, argv) {
300
+ void argv;
301
+ const legacyLock = inspectLegacyAgentLock();
302
+ if (legacyLock.alive && legacyLock.pid) {
303
+ if (flags.force) {
304
+ log(`[legacy-lock] --force: ignoring live plugin daemon at pid=${legacyLock.pid} ` +
305
+ `(role=${legacyLock.role || '?'}, version=${legacyLock.version || '?'}). ` +
306
+ `Stop it manually if you see double-processed events.`);
307
+ }
308
+ else {
309
+ log(`[legacy-lock] claude-plugin daemon is still running ` +
310
+ `(pid=${legacyLock.pid}, role=${legacyLock.role || '?'}, ` +
311
+ `version=${legacyLock.version || '?'}, started_at=${legacyLock.started_at || '?'}). ` +
312
+ `Stop it before launching agent-manager, or pass --force to start anyway.`);
313
+ process.exit(3);
314
+ }
315
+ }
316
+ else if (legacyLock.present) {
317
+ log(`[legacy-lock] found stale lockfile at ${legacyLock.path} — ignoring.`);
318
+ }
319
+ let lock;
320
+ try {
321
+ lock = acquireAgentLock({ role: 'manager', version, force: flags.force });
322
+ }
323
+ catch (err) {
324
+ if (err?.code === 'EAGENTLOCKED') {
325
+ log(`agent-manager: ${err.message}`);
326
+ process.exit(2);
327
+ }
328
+ throw err;
329
+ }
330
+ log(`agent-manager starting (server=${config.url} version=${version})`);
331
+ log(`Delegation: maxConcurrent=${config.delegation.maxConcurrent} ttl=${config.delegation.ttlMinutes}min idle=${config.delegation.idleMinutes}min cliBin=${config.delegation.claudeBin}`);
332
+ const agentIdReady = resolveAgentId(config).then((id) => {
333
+ if (id)
334
+ log(`Agent identity: ${id.slice(0, 8)}…`);
335
+ else
336
+ log('Agent identity: not resolved — presence + error-log upload disabled until pairing writes agent.json');
337
+ return id;
338
+ });
339
+ const presenceHeartbeat = { _real: null };
340
+ const kickPresencePing = () => {
341
+ presenceHeartbeat._real?.pingNow().catch(() => { });
342
+ };
343
+ const instanceHeartbeat = { _real: null };
344
+ const updateChecker = new UpdateChecker({ log });
345
+ updateChecker.start();
346
+ cleanupOrphanSubagents()
347
+ .then((r) => {
348
+ if (r.scanned > 0)
349
+ log(`Orphan subagent cleanup: scanned=${r.scanned} reaped=${r.reaped} skipped=${r.skipped ?? 0}`);
350
+ })
351
+ .catch((err) => log(`Orphan subagent cleanup failed: ${err?.message ?? err}`));
352
+ const circuitBreaker = new CircuitBreaker();
353
+ const subagentManager = new SubagentManager(config, circuitBreaker);
354
+ subagentManager.init().catch((err) => log(`SubagentManager init failed: ${err?.message ?? err}`));
355
+ const managedAgents = new ManagedAgentRegistry();
356
+ const managedAgentContexts = new ManagedAgentContextRegistry();
357
+ const worktreeManager = new WorktreeManager({
358
+ enabled: config?.delegation?.worktreeIsolation !== false,
359
+ });
360
+ const environmentProvisioner = new EnvironmentProvisioner();
361
+ const chatSessionManager = new ChatSessionManager(config);
362
+ const ticketSessionManager = new TicketSessionManager(config, circuitBreaker);
363
+ let eventStreamRef = null;
364
+ const commandHandler = new AgentManagerCommandHandler(config, {
365
+ registry: managedAgents,
366
+ contextRegistry: managedAgentContexts,
367
+ chatSessionManager,
368
+ ticketSessionManager,
369
+ subagentManager,
370
+ circuitBreaker,
371
+ getInstanceId: () => instanceHeartbeat._real?.instanceId ?? null,
372
+ requestStreamReconnect: () => eventStreamRef?.reconnect(),
373
+ reloadConfig: async () => {
374
+ const next = loadConfig();
375
+ if (!next?.url || !next?.apiKey)
376
+ return 'reload skipped: config missing';
377
+ const disruptive = next.url !== config.url ||
378
+ next.apiKey !== config.apiKey;
379
+ Object.assign(config, next);
380
+ return disruptive ? 'reloaded (disruptive — server/apiKey need restart)' : 'reloaded';
381
+ },
382
+ });
383
+ const fsBrowser = new FsBrowser(config, config.fs_browser || null);
384
+ log('fs_browser: always-on (ST-7) — construction OK, ready to handle fs_request events');
385
+ const subagentMonitor = new SubagentMonitor(config, null);
386
+ subagentManager.setMonitor(subagentMonitor);
387
+ chatSessionManager.setMonitor(subagentMonitor);
388
+ ticketSessionManager.setMonitor(subagentMonitor);
389
+ subagentManager.onExit = ({ record, code, signal, durationSec }) => {
390
+ const label = record.kind === 'chat' ? 'Chat Subagent' : 'Subagent';
391
+ let msg;
392
+ if (signal === 'SIGTERM' || signal === 'SIGKILL') {
393
+ msg = `[AWB ${label}] ticket=${record.ticket_id || '-'} TIMED OUT after ${durationSec}s`;
394
+ }
395
+ else if (code === 0) {
396
+ msg = `[AWB ${label}] ticket=${record.ticket_id || '-'} completed (duration=${durationSec}s)`;
397
+ }
398
+ else {
399
+ msg = `[AWB ${label}] ticket=${record.ticket_id || '-'} FAILED (exit=${code}, duration=${durationSec}s)`;
400
+ }
401
+ log(msg);
402
+ };
403
+ try {
404
+ const dirs = await listManagedAgentDirs();
405
+ let rehydrated = 0;
406
+ let skipped = 0;
407
+ for (const id of dirs) {
408
+ const cfg = await readManagedAgentConfig(id);
409
+ const apiKey = await readApiKey(id);
410
+ if (!cfg || !apiKey || !cfg.working_dir) {
411
+ skipped++;
412
+ continue;
413
+ }
414
+ await ensureCliHomeDir(id);
415
+ const credential = await readAgentCredential(id);
416
+ let extraEnv = {};
417
+ try {
418
+ const prep = await createAdapter(cfg.cli).prepareCliHome(cliHomeDirFor(id), credential, { url: config.url, apiKey }, cfg.model || null);
419
+ extraEnv = prep?.extraEnv ?? {};
420
+ }
421
+ catch (err) {
422
+ log(`rehydrate: cli-home prep failed for agent=${id.slice(0, 8)} cli=${cfg.cli}: ${err?.message ?? err}`);
423
+ }
424
+ const credentialKind = !credential
425
+ ? 'operator_home'
426
+ : credential.provider.endsWith('_subscription')
427
+ ? 'subscription'
428
+ : credential.provider.endsWith('_api_key')
429
+ ? 'api_key'
430
+ : 'subscription';
431
+ managedAgentContexts.upsert({
432
+ agent_id: id,
433
+ name: cfg.name,
434
+ cli: cfg.cli,
435
+ working_dir: cfg.working_dir,
436
+ mcp_config_path: mcpConfigPathFor(id),
437
+ api_key: apiKey,
438
+ subagent_log_path: subagentLogPathFor(id),
439
+ cli_home_dir: cliHomeDirFor(id),
440
+ model: cfg.model || null,
441
+ extra_env: extraEnv,
442
+ credential_provider: credential?.provider ?? null,
443
+ credential_kind: credentialKind,
444
+ registered_at: new Date().toISOString(),
445
+ });
446
+ managedAgents.upsert({ agent_id: id, name: cfg.name, cli: cfg.cli, working_dir: cfg.working_dir });
447
+ managedAgents.markRunning(id, process.pid);
448
+ rehydrated++;
449
+ }
450
+ if (rehydrated || skipped) {
451
+ log(`Managed-agent rehydrate: rehydrated=${rehydrated} skipped=${skipped} (of ${dirs.length} on-disk dirs)`);
452
+ }
453
+ }
454
+ catch (err) {
455
+ log(`Managed-agent rehydrate failed: ${err?.message ?? err}`);
456
+ }
457
+ const eventStream = new EventStream({
458
+ config,
459
+ deps: {
460
+ subagentManager,
461
+ chatSessionManager,
462
+ ticketSessionManager,
463
+ fsBrowser,
464
+ prompts: promptComposer,
465
+ agentManagerCommandHandler: commandHandler,
466
+ managedAgentContexts,
467
+ worktreeManager,
468
+ environmentProvisioner,
469
+ },
470
+ pluginVersion: version,
471
+ onConnect: kickPresencePing,
472
+ });
473
+ eventStreamRef = eventStream;
474
+ eventStream.start();
475
+ log('SSE event stream started');
476
+ let uploadTimer = null;
477
+ let worktreeSweepTimer = null;
478
+ const sweepWorktrees = async () => {
479
+ if (!worktreeManager.enabled)
480
+ return;
481
+ try {
482
+ const activeKeys = new Set();
483
+ for (const s of ticketSessionManager._snapshot()) {
484
+ if (s.ticketId && s.role)
485
+ activeKeys.add(worktreeSlug(s.ticketId, s.role));
486
+ }
487
+ for (const s of subagentManager._snapshot()) {
488
+ if (s.ticket_id && s.role)
489
+ activeKeys.add(worktreeSlug(s.ticket_id, s.role));
490
+ }
491
+ let total = 0;
492
+ const seenRoots = new Set();
493
+ for (const ctx of managedAgentContexts.list()) {
494
+ if (!ctx.working_dir)
495
+ continue;
496
+ const worktreesRoot = pathJoin(MANAGED_AGENTS_DIR, ctx.agent_id, 'worktrees');
497
+ const dedupeKey = `${ctx.working_dir}${worktreesRoot}`;
498
+ if (seenRoots.has(dedupeKey))
499
+ continue;
500
+ seenRoots.add(dedupeKey);
501
+ total += await worktreeManager.sweep({
502
+ baseWorkingDir: ctx.working_dir,
503
+ worktreesRoot,
504
+ activeKeys,
505
+ });
506
+ }
507
+ if (total > 0)
508
+ log(`[worktree] sweep reclaimed ${total} idle clean worktree(s)`);
509
+ }
510
+ catch (err) {
511
+ log(`[worktree] sweep failed: ${err?.message ?? err}`);
512
+ }
513
+ };
514
+ worktreeSweepTimer = setInterval(() => void sweepWorktrees(), 10 * 60 * 1000);
515
+ worktreeSweepTimer.unref?.();
516
+ agentIdReady.then(async (agentId) => {
517
+ if (!agentId)
518
+ return;
519
+ presenceHeartbeat._real = new PresenceHeartbeat(config, agentId);
520
+ presenceHeartbeat._real.start();
521
+ const availableModels = {};
522
+ await Promise.all(KNOWN_ADAPTER_CLI_TYPES.map(async (cli) => {
523
+ try {
524
+ const models = await createAdapter(cli).listModels();
525
+ if (Array.isArray(models) && models.length)
526
+ availableModels[cli] = models;
527
+ }
528
+ catch (err) {
529
+ log(`listModels failed for cli=${cli}: ${err?.message ?? err}`);
530
+ }
531
+ }));
532
+ instanceHeartbeat._real = new InstanceHeartbeat(config, agentId, {
533
+ mode: 'manager',
534
+ version,
535
+ cli: 'mixed',
536
+ cliAdapters: KNOWN_ADAPTER_CLI_TYPES.slice(),
537
+ availableModels,
538
+ managedAgents,
539
+ updateChecker,
540
+ agentCredentialMetaProvider: async () => {
541
+ const out = [];
542
+ for (const ctx of managedAgentContexts.list()) {
543
+ if (ctx.credential_kind === 'api_key') {
544
+ out.push({
545
+ agent_id: ctx.agent_id,
546
+ cli: ctx.cli,
547
+ kind: 'api_key',
548
+ expires_at_ms: null,
549
+ refresh_token_present: false,
550
+ });
551
+ continue;
552
+ }
553
+ let meta = null;
554
+ try {
555
+ meta = await createAdapter(ctx.cli).readCredentialMeta(ctx.cli_home_dir);
556
+ }
557
+ catch (err) {
558
+ log(`agentCredentialMetaProvider: read failed for agent=${ctx.agent_id.slice(0, 8)} cli=${ctx.cli}: ${err?.message ?? err}`);
559
+ meta = null;
560
+ }
561
+ if (!meta) {
562
+ out.push({
563
+ agent_id: ctx.agent_id,
564
+ cli: ctx.cli,
565
+ kind: ctx.credential_kind === 'operator_home' ? 'operator_home' : 'missing',
566
+ expires_at_ms: null,
567
+ refresh_token_present: false,
568
+ });
569
+ continue;
570
+ }
571
+ const kind = ctx.credential_kind === 'operator_home' && meta.kind === 'subscription'
572
+ ? 'operator_home'
573
+ : meta.kind;
574
+ out.push({
575
+ agent_id: ctx.agent_id,
576
+ cli: ctx.cli,
577
+ kind,
578
+ expires_at_ms: meta.expires_at_ms,
579
+ refresh_token_present: meta.refresh_token_present,
580
+ });
581
+ }
582
+ return out;
583
+ },
584
+ });
585
+ instanceHeartbeat._real.start();
586
+ const fireUpload = () => {
587
+ uploadIfNewErrors(config, agentId, version).catch(() => { });
588
+ };
589
+ fireUpload();
590
+ uploadTimer = setInterval(fireUpload, 30 * 1000);
591
+ uploadTimer.unref?.();
592
+ onFlushThreshold(fireUpload);
593
+ });
594
+ const shutdown = async (signal) => {
595
+ log(`agent-manager received ${signal} — terminating subagents`);
596
+ presenceHeartbeat._real?.stop();
597
+ instanceHeartbeat._real?.stop();
598
+ updateChecker.stop();
599
+ if (uploadTimer) {
600
+ clearInterval(uploadTimer);
601
+ uploadTimer = null;
602
+ }
603
+ if (worktreeSweepTimer) {
604
+ clearInterval(worktreeSweepTimer);
605
+ worktreeSweepTimer = null;
606
+ }
607
+ eventStream.stop();
608
+ try {
609
+ await subagentManager.stop();
610
+ }
611
+ catch (err) {
612
+ log(`shutdown: ${err?.message ?? err}`);
613
+ }
614
+ try {
615
+ await chatSessionManager.stop();
616
+ }
617
+ catch (err) {
618
+ log(`shutdown (chat): ${err?.message ?? err}`);
619
+ }
620
+ try {
621
+ await ticketSessionManager.stop();
622
+ }
623
+ catch (err) {
624
+ log(`shutdown (ticket): ${err?.message ?? err}`);
625
+ }
626
+ try {
627
+ subagentMonitor.stop();
628
+ }
629
+ catch (err) {
630
+ log(`shutdown (monitor): ${err?.message ?? err}`);
631
+ }
632
+ try {
633
+ lock.release();
634
+ }
635
+ catch (err) {
636
+ log(`shutdown (lockfile): ${err?.message ?? err}`);
637
+ }
638
+ process.exit(isSystemdReExecPending() ? 1 : 0);
639
+ };
640
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
641
+ process.once('SIGINT', () => void shutdown('SIGINT'));
642
+ process.on('SIGUSR1', async () => {
643
+ try {
644
+ const result = await runSelfUpdate({ log });
645
+ log(`Self-update: ${result.summary}`);
646
+ }
647
+ catch (err) {
648
+ log(`Self-update failed: ${err?.stack || err?.message || err}`);
649
+ }
650
+ });
651
+ process.on('SIGHUP', () => {
652
+ const next = loadConfig();
653
+ if (!next?.url || !next?.apiKey) {
654
+ log('SIGHUP: config.json missing or unparseable — keeping previous config');
655
+ return;
656
+ }
657
+ const disruptive = next.url !== config.url ||
658
+ next.apiKey !== config.apiKey;
659
+ Object.assign(config, next);
660
+ log(`SIGHUP: config reloaded (delegation.maxConcurrent=${config.delegation.maxConcurrent} ` +
661
+ `ttl=${config.delegation.ttlMinutes}min idle=${config.delegation.idleMinutes}min)` +
662
+ (disruptive ? ' — server/apiKey changes need a manager restart to take effect' : ''));
663
+ });
664
+ log('agent-manager ready');
665
+ }
666
+ main().catch((err) => {
667
+ process.stderr.write(`agent-manager: fatal: ${err?.stack ?? err}\n`);
668
+ process.exit(1);
669
+ });
670
+ //# sourceMappingURL=main.js.map