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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 parnmanas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # awb-agent-manager
2
+
3
+ Standalone subagent runner for [AI Workflow Board](../../README.md). Connects to
4
+ an AWB server over SSE + REST, spawns CLI-driven agents (Claude, Codex, Antigravity,
5
+ or any custom binary that speaks MCP), and reports liveness back to the AWB
6
+ admin dashboard.
7
+
8
+ This package replaces the daemon that used to live inside the
9
+ `@parnmanas/awb` Claude plugin. The plugin (in
10
+ [`submodules/claude-plugins/ai-workflow-board/`](../../../claude-plugins/ai-workflow-board/))
11
+ is now a pure stdio↔HTTP MCP forwarder. agent-manager owns everything else:
12
+ SSE event delivery, persistent ticket/chat sessions, subagent supervision, fs
13
+ browser, instance heartbeats, and CLI lifecycle management.
14
+
15
+ ```
16
+ ┌─────────────────────────────────────────────────────────────────┐
17
+ │ AWB server (NestJS) │
18
+ │ ├── /api/agent-manager/* pairing, agent identity RPC │
19
+ │ ├── /api/admin/agent-manager/* instance dashboard, command │
20
+ │ └── SSE event stream ─────────────┐ │
21
+ └─────────────────────────────────────┬─┘ │
22
+ │ HTTP (Bearer key) │
23
+ ▼ │
24
+ ┌─────────────────────────────────────────────────────────────────┐│
25
+ │ awb-agent-manager (this package) ││
26
+ │ ├── EventStream SSE consumer + reconnect ││
27
+ │ ├── EventDispatcher route → ticket / chat / fs / command ││
28
+ │ ├── ManagedAgents spawn / stop / restart CLI children ││
29
+ │ └── InstanceHeartbeat per-process registry ping ──────────────┘
30
+ └─────────────────────────────────────────────────────────────────┘
31
+ │ stdio
32
+
33
+ claude / codex / antigravity / custom CLI
34
+ ```
35
+
36
+ ## Install
37
+
38
+ ### npm (recommended)
39
+
40
+ ```bash
41
+ npm i -g @awb/agent-manager
42
+ awb-agent-manager --version
43
+ ```
44
+
45
+ > The package is not yet published to the public npm registry. Until it ships,
46
+ > install from a local checkout (see "Development").
47
+
48
+ ### Docker
49
+
50
+ ```bash
51
+ docker run --rm -it \
52
+ -v "$HOME/.config/awb-agent-manager:/data" \
53
+ -e AWB_AGENT_MANAGER_HOME=/data \
54
+ ghcr.io/parnmanas/awb-agent-manager:latest
55
+ ```
56
+
57
+ The image bundles `node:22-alpine` plus the manager binary. Mount a host
58
+ directory for `AWB_AGENT_MANAGER_HOME` so config + lockfile survive container
59
+ restarts. Bind-mount each agent's working directory the same way (e.g.
60
+ `-v $HOME/repos:/repos`) and configure those paths inside AWB.
61
+
62
+ ## First run — pairing with an AWB server
63
+
64
+ The manager bootstraps from a one-time pairing token minted by an AWB admin.
65
+ After redeeming, the manager stores its API key and agent identity in
66
+ `$AWB_AGENT_MANAGER_HOME/config.json` (default
67
+ `~/.config/awb-agent-manager/config.json`).
68
+
69
+ 1. **Mint** — In the AWB admin UI: _Admin → Agent Manager → Pair manager…_.
70
+ The dialog returns a raw token (long-form) and a 6-char display code; copy
71
+ either. Both are shown only once. TTL 10 minutes, single-use.
72
+ 2. **Run the wizard** — On the host that will run the manager:
73
+
74
+ ```bash
75
+ awb-agent-manager setup
76
+ ```
77
+
78
+ You'll be prompted for:
79
+ - AWB server URL (e.g. `https://awb.example.com:7700`)
80
+ - Pairing token (paste from step 1)
81
+ - CLI to drive (`claude` / `codex` / `antigravity`, default `claude`)
82
+
83
+ The wizard calls `/api/agent-manager/pair/redeem`, then writes
84
+ `~/.config/awb-agent-manager/config.json` with mode 0600. Output:
85
+
86
+ ```
87
+ ✓ paired
88
+ agent_id <uuid>
89
+ workspace_id <uuid>
90
+ apiKey awb_abcd***xyz9
91
+ ✓ wrote ~/.config/awb-agent-manager/config.json (mode 0600)
92
+
93
+ Next: run `awb-agent-manager` to start the manager.
94
+ ```
95
+
96
+ Non-interactive form (CI / Ansible — fails fast on missing fields):
97
+
98
+ ```bash
99
+ awb-agent-manager setup \
100
+ --url https://awb.example.com:7700 \
101
+ --token ABCXYZ123 \
102
+ --cli claude \
103
+ --non-interactive
104
+ ```
105
+
106
+ `instance_id` defaults to `<hostname>-<rand6>` — pass `--instance-id <id>`
107
+ for a stable label across re-pairings on the same box. `--force`
108
+ overwrites an existing config.json.
109
+
110
+ 3. **Start** — `awb-agent-manager`. The process registers with the AWB
111
+ instance dashboard and starts listening for `agent_manager_command` SSE
112
+ events.
113
+
114
+ 4. **Add managed agents** — Back in AWB, _Agent Manager → Managed Agents →
115
+ Create_. Pick the CLI (`claude` / `codex` / `antigravity` / `custom`), point at
116
+ a working directory, and leave _Spawn on this manager after create_ on for
117
+ one-click setup. The manager provisions a per-agent apiKey, writes its
118
+ on-disk config + mcp-config.json, and starts routing matching ticket /
119
+ chat / mention events to subagents that run under that agent's identity.
120
+
121
+ On manager restart, agents previously spawned this way auto-rehydrate
122
+ from disk — no need to re-click Spawn.
123
+
124
+ ## Run as a background service
125
+
126
+ `awb-agent-manager service install` registers the manager so it starts on
127
+ boot/logon and auto-restarts on crash. The installer detects your host's
128
+ service manager and dispatches accordingly:
129
+
130
+ | Host | Backend | Default unit path |
131
+ |------------------------------|------------------------|----------------------------------------------------|
132
+ | Linux + systemd | systemd unit | `~/.config/systemd/user/awb-agent-manager.service` |
133
+ | Linux + Synology DSM | rc.d boot script | `/usr/local/etc/rc.d/awb-agent-manager.sh` |
134
+ | Linux without systemd | sysvinit | `/etc/init.d/awb-agent-manager` |
135
+ | macOS | launchd | `~/Library/LaunchAgents/com.awb.agent-manager.plist` |
136
+ | Windows | Task Scheduler | task `awb-agent-manager` (logon trigger) |
137
+
138
+ ```bash
139
+ # user scope (no admin/sudo) — runs at logon, recommended for laptops
140
+ awb-agent-manager service install
141
+
142
+ # system scope — runs at boot, requires sudo / Administrator shell
143
+ awb-agent-manager service install --system
144
+
145
+ # preview without writing or running registrar
146
+ awb-agent-manager service install --dry-run
147
+
148
+ # force a specific backend (e.g. testing sysvinit on a systemd host)
149
+ awb-agent-manager service install --platform sysvinit
150
+
151
+ # remove
152
+ awb-agent-manager service uninstall [--system]
153
+ ```
154
+
155
+ Notes:
156
+ - Linux user-mode systemd services stop at logout. Run
157
+ `sudo loginctl enable-linger $USER` to keep the manager running after the
158
+ installing user logs out.
159
+ - Synology DSM and bare sysvinit always install at system scope (the boot
160
+ directories are root-owned). The `--system` flag is implied.
161
+ - Windows user-mode tasks fire at logon only. Re-run with `--system` from
162
+ an elevated PowerShell for a boot-time task running as `LocalSystem`.
163
+ - macOS uses `launchctl bootstrap` on modern macOS and falls back to
164
+ `launchctl load -w` on older releases. Logs land in `/tmp/awb-agent-manager.log`.
165
+
166
+ ## Migration from the claude-plugin daemon (≤ v0.39)
167
+
168
+ The plugin daemon is gone as of plugin v0.40.0. Everything it owned moved
169
+ here. Migration is opt-in but easy:
170
+
171
+ - agent-manager auto-imports `~/.claude/channels/awb/{config,agent}.json` on
172
+ first run if `~/.config/awb-agent-manager/config.json` is missing. A
173
+ `MIGRATED-TO-AGENT-MANAGER.txt` marker is dropped next to the legacy files;
174
+ subsequent starts skip the import. Legacy files are never deleted — the
175
+ plugin's stdio MCP proxy still reads them.
176
+ - The first run will refuse to start while the legacy daemon's
177
+ `~/.claude/channels/awb/agent.lock` is owned by a live PID. Stop the old
178
+ Claude session (or `kill` the daemon) first, then start
179
+ `awb-agent-manager`. Pass `--force` to take over a stale lock owned by a
180
+ dead PID.
181
+
182
+ ## Configuration
183
+
184
+ | Source | Precedence |
185
+ |--------------------------------------------------|------------------|
186
+ | `--config <path>` flag | 1 (highest) |
187
+ | `$AWB_AGENT_MANAGER_HOME/config.json` | 2 |
188
+ | `$XDG_CONFIG_HOME/awb-agent-manager/config.json` | 3 (Linux) |
189
+ | `%APPDATA%\awb-agent-manager\config.json` | 3 (Windows) |
190
+ | `~/.config/awb-agent-manager/config.json` | 4 (fallback) |
191
+
192
+ Schema (`config.json`):
193
+
194
+ ```json
195
+ {
196
+ "url": "https://awb.example.com",
197
+ "apiKey": "<bearer key from pairing>",
198
+ "workspace_id": "<workspace uuid>",
199
+ "agent_id": "<manager agent uuid>",
200
+ "cli": "claude",
201
+ "delegation": {
202
+ "enabled": true,
203
+ "max_concurrent_subagents": 4
204
+ }
205
+ }
206
+ ```
207
+
208
+ CLI flags (`awb-agent-manager --help`):
209
+
210
+ | Flag | Meaning |
211
+ |-------------------------|---------------------------------------------------------|
212
+ | `-c, --config <path>` | Override config.json path |
213
+ | `-w, --workspace <id>` | Override `workspace_id` from config |
214
+ | `-f, --force` | Take over a lockfile owned by a stale or live owner |
215
+ | `--dry-run` | Load config, log what would happen, exit |
216
+ | `-h, --help` | Show full usage |
217
+ | `-v, --version` | Print version |
218
+
219
+ Signals:
220
+
221
+ | Signal | Behavior |
222
+ |--------------|-----------------------------------------------------------|
223
+ | `SIGTERM`/`SIGINT` | Graceful drain (stop subagents, release lock) |
224
+ | `SIGHUP` | Re-read `config.json` (delegation tunables hot-reload) |
225
+ | `SIGUSR1` | Self-update hook (currently a stub — upgrade via npm) |
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ # from this directory
231
+ npm install # workspace install at the repo root also works
232
+ npm run build # tsc → dist/
233
+ npm run dev # tsx watch src/main.ts
234
+ node dist/main.js -h
235
+ ```
236
+
237
+ The full AWB workspace builds via turbo from the repo root:
238
+
239
+ ```bash
240
+ cd ../.. # submodules/ai-workflow-board
241
+ npm install
242
+ npm run build # builds agent-manager + client + server
243
+ ```
244
+
245
+ For deep reference (config schema, SSE event types, security model, internals)
246
+ see [`docs/agent-manager.md`](../../docs/agent-manager.md).
@@ -0,0 +1,37 @@
1
+ export declare const LOCK_PATH: string;
2
+ export type LockRole = 'manager';
3
+ export interface LockPayload {
4
+ pid: number;
5
+ role: LockRole;
6
+ version: string;
7
+ started_at: string;
8
+ }
9
+ export interface LockHandle {
10
+ release(): void;
11
+ path: string;
12
+ payload: LockPayload;
13
+ }
14
+ interface ParsedLock {
15
+ pid: number;
16
+ role?: string;
17
+ started_at?: string;
18
+ version?: string;
19
+ }
20
+ interface AcquireOptions {
21
+ role: LockRole;
22
+ version: string;
23
+ force?: boolean;
24
+ }
25
+ export declare function acquireAgentLock(opts: AcquireOptions): LockHandle;
26
+ export declare function inspectAgentLock(): ParsedLock | null;
27
+ export interface LegacyLockState {
28
+ present: boolean;
29
+ alive: boolean;
30
+ pid: number | null;
31
+ role?: string;
32
+ version?: string;
33
+ started_at?: string;
34
+ path: string;
35
+ }
36
+ export declare function inspectLegacyAgentLock(): LegacyLockState;
37
+ export {};
@@ -0,0 +1,205 @@
1
+ import { writeFileSync, readFileSync, unlinkSync, mkdirSync, existsSync, } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { setTimeout as delay } from 'node:timers/promises';
4
+ import { AGENT_MANAGER_HOME, LEGACY_LOCK_PATH } from './constants.js';
5
+ import { join } from 'node:path';
6
+ import { log } from './logging.js';
7
+ export const LOCK_PATH = join(AGENT_MANAGER_HOME, 'agent.lock');
8
+ const FORCE_KILL_GRACE_MS = 1500;
9
+ function readLock() {
10
+ try {
11
+ const raw = readFileSync(LOCK_PATH, 'utf8');
12
+ const parsed = JSON.parse(raw);
13
+ const pid = Number.isFinite(parsed?.pid) ? parsed.pid : 0;
14
+ return pid > 0
15
+ ? {
16
+ pid,
17
+ role: parsed.role,
18
+ started_at: parsed.started_at,
19
+ version: parsed.version,
20
+ }
21
+ : null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function isPidAlive(pid) {
28
+ if (!Number.isFinite(pid) || pid <= 0)
29
+ return false;
30
+ try {
31
+ process.kill(pid, 0);
32
+ return true;
33
+ }
34
+ catch (err) {
35
+ return err?.code === 'EPERM';
36
+ }
37
+ }
38
+ function writeLockAtomic(payload) {
39
+ try {
40
+ mkdirSync(dirname(LOCK_PATH), { recursive: true });
41
+ }
42
+ catch {
43
+ }
44
+ writeFileSync(LOCK_PATH, JSON.stringify(payload, null, 2) + '\n', { flag: 'wx' });
45
+ }
46
+ function writeLockOverwrite(payload) {
47
+ try {
48
+ mkdirSync(dirname(LOCK_PATH), { recursive: true });
49
+ }
50
+ catch {
51
+ }
52
+ writeFileSync(LOCK_PATH, JSON.stringify(payload, null, 2) + '\n');
53
+ }
54
+ export function acquireAgentLock(opts) {
55
+ const role = opts?.role;
56
+ const version = opts?.version || 'unknown';
57
+ const force = opts?.force === true;
58
+ if (role !== 'manager') {
59
+ throw new Error(`acquireAgentLock: invalid role ${JSON.stringify(role)}`);
60
+ }
61
+ const payload = {
62
+ pid: process.pid,
63
+ role,
64
+ version,
65
+ started_at: new Date().toISOString(),
66
+ };
67
+ try {
68
+ writeLockAtomic(payload);
69
+ log(`[lockfile] acquired ${LOCK_PATH} (role=${role} pid=${process.pid})`);
70
+ return makeReleaseHandle(payload);
71
+ }
72
+ catch (err) {
73
+ if (err?.code !== 'EEXIST')
74
+ throw err;
75
+ }
76
+ const existing = readLock();
77
+ if (!existing) {
78
+ log(`[lockfile] removing unparseable lockfile at ${LOCK_PATH}`);
79
+ try {
80
+ unlinkSync(LOCK_PATH);
81
+ }
82
+ catch {
83
+ }
84
+ writeLockAtomic(payload);
85
+ log(`[lockfile] acquired after stale-cleanup (role=${role} pid=${process.pid})`);
86
+ return makeReleaseHandle(payload);
87
+ }
88
+ if (!isPidAlive(existing.pid)) {
89
+ log(`[lockfile] reusing stale lock (previous owner pid=${existing.pid} role=${existing.role || '?'} dead)`);
90
+ try {
91
+ unlinkSync(LOCK_PATH);
92
+ }
93
+ catch {
94
+ }
95
+ writeLockAtomic(payload);
96
+ log(`[lockfile] acquired after stale-cleanup (role=${role} pid=${process.pid})`);
97
+ return makeReleaseHandle(payload);
98
+ }
99
+ if (!force) {
100
+ const e = new Error(`AWB agent-manager lockfile held by pid=${existing.pid} role=${existing.role || '?'} ` +
101
+ `version=${existing.version || '?'} since ${existing.started_at || '?'}. ` +
102
+ `Stop it first, or pass --force to take over.`);
103
+ e.code = 'EAGENTLOCKED';
104
+ throw e;
105
+ }
106
+ log(`[lockfile] --force: SIGTERM previous owner pid=${existing.pid} role=${existing.role || '?'}`);
107
+ try {
108
+ process.kill(existing.pid, 'SIGTERM');
109
+ }
110
+ catch {
111
+ }
112
+ return forceTakeover(payload, existing.pid);
113
+ }
114
+ async function forceTakeoverAsync(payload, prevPid) {
115
+ const start = Date.now();
116
+ while (Date.now() - start < FORCE_KILL_GRACE_MS) {
117
+ if (!isPidAlive(prevPid))
118
+ break;
119
+ await delay(100);
120
+ }
121
+ try {
122
+ writeLockAtomic(payload);
123
+ }
124
+ catch (err) {
125
+ if (err?.code !== 'EEXIST')
126
+ throw err;
127
+ writeLockOverwrite(payload);
128
+ }
129
+ log(`[lockfile] --force: acquired by overwrite (role=${payload.role} pid=${process.pid})`);
130
+ }
131
+ function forceTakeover(payload, prevPid) {
132
+ try {
133
+ writeLockOverwrite(payload);
134
+ }
135
+ catch (err) {
136
+ log(`[lockfile] --force overwrite failed: ${err?.message ?? err}`);
137
+ throw err;
138
+ }
139
+ forceTakeoverAsync(payload, prevPid).catch((err) => log(`[lockfile] takeover poll: ${err?.message ?? err}`));
140
+ return makeReleaseHandle(payload);
141
+ }
142
+ function makeReleaseHandle(payload) {
143
+ let released = false;
144
+ process.on('exit', () => {
145
+ if (released)
146
+ return;
147
+ safeUnlinkOwn(payload.pid);
148
+ });
149
+ return {
150
+ release() {
151
+ if (released)
152
+ return;
153
+ released = true;
154
+ safeUnlinkOwn(payload.pid);
155
+ },
156
+ path: LOCK_PATH,
157
+ payload,
158
+ };
159
+ }
160
+ function safeUnlinkOwn(myPid) {
161
+ try {
162
+ const raw = readFileSync(LOCK_PATH, 'utf8');
163
+ const parsed = JSON.parse(raw);
164
+ if (parsed?.pid !== myPid)
165
+ return;
166
+ }
167
+ catch {
168
+ return;
169
+ }
170
+ try {
171
+ unlinkSync(LOCK_PATH);
172
+ log(`[lockfile] released ${LOCK_PATH}`);
173
+ }
174
+ catch {
175
+ }
176
+ }
177
+ export function inspectAgentLock() {
178
+ return readLock();
179
+ }
180
+ export function inspectLegacyAgentLock() {
181
+ if (!existsSync(LEGACY_LOCK_PATH)) {
182
+ return { present: false, alive: false, pid: null, path: LEGACY_LOCK_PATH };
183
+ }
184
+ try {
185
+ const raw = readFileSync(LEGACY_LOCK_PATH, 'utf8');
186
+ const parsed = JSON.parse(raw);
187
+ const pid = Number.isFinite(parsed?.pid) ? parsed.pid : 0;
188
+ if (pid <= 0) {
189
+ return { present: true, alive: false, pid: null, path: LEGACY_LOCK_PATH };
190
+ }
191
+ return {
192
+ present: true,
193
+ alive: isPidAlive(pid),
194
+ pid,
195
+ role: parsed.role,
196
+ version: parsed.version,
197
+ started_at: parsed.started_at,
198
+ path: LEGACY_LOCK_PATH,
199
+ };
200
+ }
201
+ catch {
202
+ return { present: true, alive: false, pid: null, path: LEGACY_LOCK_PATH };
203
+ }
204
+ }
205
+ //# sourceMappingURL=agent-lockfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-lockfile.js","sourceRoot":"","sources":["../../src/lib/agent-lockfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type AwbConfig } from './rest.js';
2
+ import type { ManagedAgentRegistry } from './managed-agents.js';
3
+ import type { ManagedAgentContextRegistry } from './managed-agent-context.js';
4
+ import type { BaseSessionManager } from './base-session-manager.js';
5
+ import type { SubagentManager } from './subagent-manager.js';
6
+ import type { CircuitBreaker } from './circuit-breaker.js';
7
+ type CommandKind = 'spawn_agent' | 'stop_agent' | 'restart_agent' | 'restart_all_agents' | 'set_working_dir' | 'reload_config' | 'update_plugins' | 'refresh_mcp_config' | 'pull_working_dir' | 'update_manager' | 'restart_manager';
8
+ export interface AgentManagerCommandPayload {
9
+ command_id: string;
10
+ instance_id: string;
11
+ agent_id?: string;
12
+ command: CommandKind | string;
13
+ args?: Record<string, any>;
14
+ issued_by?: string;
15
+ issued_at?: string;
16
+ timestamp?: string;
17
+ }
18
+ export interface CommandHandlerDeps {
19
+ getInstanceId(): string | null;
20
+ registry: ManagedAgentRegistry;
21
+ contextRegistry?: ManagedAgentContextRegistry | null;
22
+ chatSessionManager?: Pick<BaseSessionManager, 'stopForAgent'> | null;
23
+ ticketSessionManager?: Pick<BaseSessionManager, 'stopForAgent'> | null;
24
+ subagentManager?: Pick<SubagentManager, 'stopForAgent'> | null;
25
+ circuitBreaker?: CircuitBreaker | null;
26
+ reloadConfig?: () => Promise<string> | string;
27
+ requestStreamReconnect?: () => Promise<void> | void;
28
+ }
29
+ export declare class AgentManagerCommandHandler {
30
+ #private;
31
+ constructor(config: AwbConfig, deps: CommandHandlerDeps);
32
+ handle(raw: string): Promise<void>;
33
+ }
34
+ export {};