@agentmeshhq/agent 0.4.12 → 0.4.14

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 (51) hide show
  1. package/dist/__tests__/context-template.test.js +9 -0
  2. package/dist/__tests__/context-template.test.js.map +1 -1
  3. package/dist/__tests__/daemon-hub-resilience.test.d.ts +12 -0
  4. package/dist/__tests__/daemon-hub-resilience.test.js +144 -0
  5. package/dist/__tests__/daemon-hub-resilience.test.js.map +1 -0
  6. package/dist/__tests__/inbox-poll.test.d.ts +15 -0
  7. package/dist/__tests__/inbox-poll.test.js +93 -0
  8. package/dist/__tests__/inbox-poll.test.js.map +1 -0
  9. package/dist/__tests__/injector.test.js +49 -2
  10. package/dist/__tests__/injector.test.js.map +1 -1
  11. package/dist/__tests__/loader.test.js +35 -3
  12. package/dist/__tests__/loader.test.js.map +1 -1
  13. package/dist/__tests__/tmux-send.test.js +9 -0
  14. package/dist/__tests__/tmux-send.test.js.map +1 -1
  15. package/dist/__tests__/token-rejection-recovery.test.d.ts +16 -0
  16. package/dist/__tests__/token-rejection-recovery.test.js +241 -0
  17. package/dist/__tests__/token-rejection-recovery.test.js.map +1 -0
  18. package/dist/__tests__/watcher-401-recovery.test.d.ts +1 -0
  19. package/dist/__tests__/watcher-401-recovery.test.js +146 -0
  20. package/dist/__tests__/watcher-401-recovery.test.js.map +1 -0
  21. package/dist/cli/attach.js +55 -0
  22. package/dist/cli/attach.js.map +1 -1
  23. package/dist/cli/index.js +23 -11
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/watcher.d.ts +7 -0
  26. package/dist/cli/watcher.js +239 -25
  27. package/dist/cli/watcher.js.map +1 -1
  28. package/dist/core/daemon/context-template.d.ts +2 -0
  29. package/dist/core/daemon/context-template.js +2 -2
  30. package/dist/core/daemon/context-template.js.map +1 -1
  31. package/dist/core/daemon.d.ts +1 -0
  32. package/dist/core/daemon.js +92 -60
  33. package/dist/core/daemon.js.map +1 -1
  34. package/dist/core/heartbeat.d.ts +3 -1
  35. package/dist/core/heartbeat.js +5 -6
  36. package/dist/core/heartbeat.js.map +1 -1
  37. package/dist/core/injector.d.ts +1 -1
  38. package/dist/core/injector.js +20 -8
  39. package/dist/core/injector.js.map +1 -1
  40. package/dist/core/project-watcher-loop.d.ts +4 -0
  41. package/dist/core/project-watcher-loop.js +11 -1
  42. package/dist/core/project-watcher-loop.js.map +1 -1
  43. package/dist/core/tmux.js +3 -0
  44. package/dist/core/tmux.js.map +1 -1
  45. package/dist/core/token-lifecycle.d.ts +7 -0
  46. package/dist/core/token-lifecycle.js +14 -0
  47. package/dist/core/token-lifecycle.js.map +1 -1
  48. package/dist/core/watcher-reauth.d.ts +18 -0
  49. package/dist/core/watcher-reauth.js +29 -0
  50. package/dist/core/watcher-reauth.js.map +1 -0
  51. package/package.json +1 -1
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Tests for token rejection self-healing (#490)
3
+ *
4
+ * Covers the scenario where the hub restarts with a new JWT secret, making
5
+ * all existing tokens invalid. Agents must detect the 401 and immediately
6
+ * re-register without waiting for token expiry.
7
+ *
8
+ * Tests:
9
+ * 1. notifyRejected() triggers re-register when token is rejected by hub
10
+ * 2. notifyRejected() is a no-op if refresh is already in progress (no concurrent attempts)
11
+ * 3. notifyRejected() is a no-op after stop()
12
+ * 4. Heartbeat 401 response calls notifyRejected() on lifecycle manager
13
+ * 5. Re-registered token is accepted by subsequent heartbeats
14
+ * 6. notifyRejected() → refresh endpoint also 401 → falls through to re-register
15
+ */
16
+ import { afterEach, describe, expect, it, vi } from "vitest";
17
+ import { Heartbeat } from "../core/heartbeat.js";
18
+ import { TokenLifecycleManager } from "../core/token-lifecycle.js";
19
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
20
+ /** Build a non-expiring JWT stub (exp = year 2099) */
21
+ function makeToken(sub = "agent-1") {
22
+ const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
23
+ const payload = Buffer.from(JSON.stringify({ sub, exp: Math.floor(new Date("2099-01-01").getTime() / 1000) })).toString("base64url");
24
+ return `${header}.${payload}.sig`;
25
+ }
26
+ const BOOTSTRAP = {
27
+ apiKey: "test-api-key",
28
+ agentId: "agent-1",
29
+ displayName: "test-agent",
30
+ model: "claude-sonnet-4-6",
31
+ workspace: "test-ws",
32
+ };
33
+ // ─── TokenLifecycleManager.notifyRejected() ──────────────────────────────────
34
+ describe("TokenLifecycleManager.notifyRejected()", () => {
35
+ it("immediately triggers re-register when token is rejected by hub", async () => {
36
+ const newToken = makeToken("agent-new");
37
+ const fetchMock = vi.fn().mockImplementation((url) => {
38
+ if (url.includes("/token/refresh")) {
39
+ // Refresh endpoint also returns 401 (hub has new secret)
40
+ return Promise.resolve({ ok: false, status: 401, json: async () => ({}) });
41
+ }
42
+ if (url.includes("/agents/register")) {
43
+ return Promise.resolve({
44
+ ok: true,
45
+ status: 200,
46
+ json: async () => ({ token: newToken, expires_at: "2099-01-01T00:00:00Z" }),
47
+ });
48
+ }
49
+ return Promise.resolve({ ok: true, status: 200, json: async () => ({}) });
50
+ });
51
+ const onTokenUpdate = vi.fn();
52
+ const manager = new TokenLifecycleManager({
53
+ hubUrl: "https://hub.test",
54
+ initialToken: makeToken(),
55
+ bootstrap: BOOTSTRAP,
56
+ onTokenUpdate,
57
+ fetch: fetchMock,
58
+ checkIntervalMs: 60_000, // long interval — should not fire during test
59
+ minReregisterIntervalMs: 0, // allow immediate re-register
60
+ });
61
+ manager.start();
62
+ manager.notifyRejected();
63
+ // Give the async chain time to complete
64
+ await vi.waitFor(() => expect(onTokenUpdate).toHaveBeenCalledWith(newToken, "2099-01-01T00:00:00Z"), {
65
+ timeout: 2000,
66
+ });
67
+ expect(manager.getToken()).toBe(newToken);
68
+ manager.stop();
69
+ });
70
+ it("does not start a concurrent refresh if one is already in progress", async () => {
71
+ let registerCallCount = 0;
72
+ let resolveRegister;
73
+ const registerPromise = new Promise((r) => {
74
+ resolveRegister = r;
75
+ });
76
+ const fetchMock = vi.fn().mockImplementation((url) => {
77
+ if (url.includes("/token/refresh")) {
78
+ return Promise.resolve({ ok: false, status: 401, json: async () => ({}) });
79
+ }
80
+ if (url.includes("/agents/register")) {
81
+ registerCallCount++;
82
+ return registerPromise.then(() => ({
83
+ ok: true,
84
+ status: 200,
85
+ json: async () => ({ token: makeToken("new"), expires_at: "2099-01-01T00:00:00Z" }),
86
+ }));
87
+ }
88
+ return Promise.resolve({ ok: true, status: 200, json: async () => ({}) });
89
+ });
90
+ const manager = new TokenLifecycleManager({
91
+ hubUrl: "https://hub.test",
92
+ initialToken: makeToken(),
93
+ bootstrap: BOOTSTRAP,
94
+ onTokenUpdate: vi.fn(),
95
+ fetch: fetchMock,
96
+ checkIntervalMs: 60_000,
97
+ minReregisterIntervalMs: 0,
98
+ });
99
+ manager.start();
100
+ // Call twice rapidly
101
+ manager.notifyRejected();
102
+ manager.notifyRejected();
103
+ // Allow microtasks to settle
104
+ await new Promise((r) => setTimeout(r, 50));
105
+ // Only one register attempt should have started
106
+ expect(registerCallCount).toBe(1);
107
+ resolveRegister(undefined);
108
+ manager.stop();
109
+ });
110
+ it("is a no-op after stop()", async () => {
111
+ const fetchMock = vi.fn();
112
+ const manager = new TokenLifecycleManager({
113
+ hubUrl: "https://hub.test",
114
+ initialToken: makeToken(),
115
+ bootstrap: BOOTSTRAP,
116
+ onTokenUpdate: vi.fn(),
117
+ fetch: fetchMock,
118
+ checkIntervalMs: 60_000,
119
+ minReregisterIntervalMs: 0,
120
+ });
121
+ manager.start();
122
+ manager.stop();
123
+ manager.notifyRejected();
124
+ await new Promise((r) => setTimeout(r, 50));
125
+ expect(fetchMock).not.toHaveBeenCalled();
126
+ });
127
+ });
128
+ // ─── Heartbeat → notifyRejected() integration ────────────────────────────────
129
+ describe("Heartbeat 401 → notifyRejected()", () => {
130
+ let heartbeat;
131
+ afterEach(() => {
132
+ heartbeat?.stop();
133
+ });
134
+ it("calls lifecycle notifyRejected() when heartbeat returns 401", async () => {
135
+ const newToken = makeToken("agent-refreshed");
136
+ let heartbeatCallCount = 0;
137
+ const fetchMock = vi.fn().mockImplementation((url) => {
138
+ if (url.includes("/heartbeat")) {
139
+ heartbeatCallCount++;
140
+ // First call: 401 (hub restarted). Subsequent: 200 (after re-register).
141
+ const status = heartbeatCallCount === 1 ? 401 : 200;
142
+ return Promise.resolve({ ok: status === 200, status, json: async () => ({}) });
143
+ }
144
+ if (url.includes("/token/refresh")) {
145
+ return Promise.resolve({ ok: false, status: 401, json: async () => ({}) });
146
+ }
147
+ if (url.includes("/agents/register")) {
148
+ return Promise.resolve({
149
+ ok: true,
150
+ status: 200,
151
+ json: async () => ({ token: newToken, expires_at: "2099-01-01T00:00:00Z" }),
152
+ });
153
+ }
154
+ return Promise.resolve({ ok: true, status: 200, json: async () => ({}) });
155
+ });
156
+ const onTokenRefresh = vi.fn();
157
+ const lifecycleManager = new TokenLifecycleManager({
158
+ hubUrl: "https://hub.test",
159
+ initialToken: makeToken(),
160
+ bootstrap: BOOTSTRAP,
161
+ onTokenUpdate: (token, _expiresAt) => {
162
+ onTokenRefresh(token);
163
+ },
164
+ fetch: fetchMock,
165
+ checkIntervalMs: 60_000,
166
+ minReregisterIntervalMs: 0,
167
+ });
168
+ heartbeat = new Heartbeat({
169
+ url: "https://hub.test",
170
+ token: makeToken(),
171
+ intervalMs: 60_000, // long — we'll trigger manually
172
+ agentName: "test-agent",
173
+ agentId: "agent-1",
174
+ apiKey: "test-api-key",
175
+ workspace: "test-ws",
176
+ onTokenRefresh,
177
+ lifecycleManager,
178
+ fetch: fetchMock,
179
+ });
180
+ heartbeat.start();
181
+ // Wait for re-register to complete after the 401
182
+ await vi.waitFor(() => expect(onTokenRefresh).toHaveBeenCalledWith(newToken), {
183
+ timeout: 3000,
184
+ });
185
+ expect(heartbeat.getToken()).toBe(newToken);
186
+ });
187
+ it("re-registered token survives subsequent heartbeats", async () => {
188
+ const newToken = makeToken("agent-reregistered");
189
+ let heartbeatCount = 0;
190
+ const fetchMock = vi.fn().mockImplementation((url) => {
191
+ if (url.includes("/heartbeat")) {
192
+ heartbeatCount++;
193
+ const status = heartbeatCount === 1 ? 401 : 200;
194
+ return Promise.resolve({ ok: status === 200, status, json: async () => ({}) });
195
+ }
196
+ if (url.includes("/token/refresh")) {
197
+ return Promise.resolve({ ok: false, status: 401, json: async () => ({}) });
198
+ }
199
+ if (url.includes("/agents/register")) {
200
+ return Promise.resolve({
201
+ ok: true,
202
+ status: 200,
203
+ json: async () => ({ token: newToken, expires_at: "2099-01-01T00:00:00Z" }),
204
+ });
205
+ }
206
+ return Promise.resolve({ ok: true, status: 200, json: async () => ({}) });
207
+ });
208
+ const onTokenRefresh = vi.fn();
209
+ const lifecycleManager = new TokenLifecycleManager({
210
+ hubUrl: "https://hub.test",
211
+ initialToken: makeToken(),
212
+ bootstrap: BOOTSTRAP,
213
+ onTokenUpdate: (token) => onTokenRefresh(token),
214
+ fetch: fetchMock,
215
+ checkIntervalMs: 60_000,
216
+ minReregisterIntervalMs: 0,
217
+ });
218
+ heartbeat = new Heartbeat({
219
+ url: "https://hub.test",
220
+ token: makeToken(),
221
+ intervalMs: 60_000,
222
+ agentName: "test-agent",
223
+ agentId: "agent-1",
224
+ apiKey: "test-api-key",
225
+ workspace: "test-ws",
226
+ onTokenRefresh,
227
+ lifecycleManager,
228
+ fetch: fetchMock,
229
+ });
230
+ heartbeat.start();
231
+ await vi.waitFor(() => expect(onTokenRefresh).toHaveBeenCalledWith(newToken), {
232
+ timeout: 3000,
233
+ });
234
+ // After re-register, the token in use should be the new one
235
+ expect(heartbeat.getToken()).toBe(newToken);
236
+ // New token should be used in subsequent Authorization headers
237
+ const registerCall = fetchMock.mock.calls.find((args) => args[0].includes("/agents/register"));
238
+ expect(registerCall).toBeDefined();
239
+ });
240
+ });
241
+ //# sourceMappingURL=token-rejection-recovery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-rejection-recovery.test.js","sourceRoot":"","sources":["../../src/__tests__/token-rejection-recovery.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { reAuthWithApiKey } from "../core/watcher-reauth.js";
3
+ describe("reAuthWithApiKey", () => {
4
+ it("returns token from successful registration response", async () => {
5
+ const mockFetch = async (_url, _init) => ({
6
+ ok: true,
7
+ status: 200,
8
+ json: async () => ({ token: "new-jwt-token-abc" }),
9
+ });
10
+ const token = await reAuthWithApiKey({
11
+ hubUrl: "https://hub.example.com",
12
+ workspace: "ws-1",
13
+ agentId: "agent-abc",
14
+ agentName: "my-agent",
15
+ apiKey: "secret-key",
16
+ model: "claude-sonnet-4-6",
17
+ fetch: mockFetch,
18
+ });
19
+ expect(token).toBe("new-jwt-token-abc");
20
+ });
21
+ it("sends API key in x-agentmesh-secret header", async () => {
22
+ let capturedHeaders = {};
23
+ const mockFetch = async (_url, init) => {
24
+ capturedHeaders = (init.headers ?? {});
25
+ return {
26
+ ok: true,
27
+ status: 200,
28
+ json: async () => ({ token: "tok" }),
29
+ };
30
+ };
31
+ await reAuthWithApiKey({
32
+ hubUrl: "https://hub.example.com",
33
+ workspace: "ws-1",
34
+ agentId: "agent-abc",
35
+ agentName: "my-agent",
36
+ apiKey: "my-api-key",
37
+ model: "claude-sonnet-4-6",
38
+ fetch: mockFetch,
39
+ });
40
+ expect(capturedHeaders["x-agentmesh-secret"]).toBe("my-api-key");
41
+ });
42
+ it("sends correct request body", async () => {
43
+ let capturedBody = {};
44
+ const mockFetch = async (_url, init) => {
45
+ capturedBody = JSON.parse(init.body);
46
+ return {
47
+ ok: true,
48
+ status: 200,
49
+ json: async () => ({ token: "tok" }),
50
+ };
51
+ };
52
+ await reAuthWithApiKey({
53
+ hubUrl: "https://hub.example.com",
54
+ workspace: "ws-test",
55
+ agentId: "agent-xyz",
56
+ agentName: "watcher-agent",
57
+ apiKey: "key",
58
+ model: "claude-opus-4-6",
59
+ fetch: mockFetch,
60
+ });
61
+ expect(capturedBody.agent_id).toBe("agent-xyz");
62
+ expect(capturedBody.workspace).toBe("ws-test");
63
+ expect(capturedBody.display_name).toBe("watcher-agent");
64
+ expect(capturedBody.model).toBe("claude-opus-4-6");
65
+ });
66
+ it("falls back to agentId as display_name when agentName is undefined", async () => {
67
+ let capturedBody = {};
68
+ const mockFetch = async (_url, init) => {
69
+ capturedBody = JSON.parse(init.body);
70
+ return {
71
+ ok: true,
72
+ status: 200,
73
+ json: async () => ({ token: "tok" }),
74
+ };
75
+ };
76
+ await reAuthWithApiKey({
77
+ hubUrl: "https://hub.example.com",
78
+ workspace: "ws-1",
79
+ agentId: "agent-fallback",
80
+ agentName: undefined,
81
+ apiKey: "key",
82
+ model: "claude-sonnet-4-6",
83
+ fetch: mockFetch,
84
+ });
85
+ expect(capturedBody.display_name).toBe("agent-fallback");
86
+ });
87
+ it("falls back to 'watcher' as display_name when both agentName and agentId are undefined", async () => {
88
+ let capturedBody = {};
89
+ const mockFetch = async (_url, init) => {
90
+ capturedBody = JSON.parse(init.body);
91
+ return {
92
+ ok: true,
93
+ status: 200,
94
+ json: async () => ({ token: "tok" }),
95
+ };
96
+ };
97
+ await reAuthWithApiKey({
98
+ hubUrl: "https://hub.example.com",
99
+ workspace: "ws-1",
100
+ agentId: undefined,
101
+ agentName: undefined,
102
+ apiKey: "key",
103
+ model: "claude-sonnet-4-6",
104
+ fetch: mockFetch,
105
+ });
106
+ expect(capturedBody.display_name).toBe("watcher");
107
+ });
108
+ it("throws when registration response is not ok", async () => {
109
+ const mockFetch = async () => ({
110
+ ok: false,
111
+ status: 401,
112
+ json: async () => ({}),
113
+ });
114
+ await expect(reAuthWithApiKey({
115
+ hubUrl: "https://hub.example.com",
116
+ workspace: "ws-1",
117
+ agentId: "agent-abc",
118
+ agentName: "my-agent",
119
+ apiKey: "bad-key",
120
+ model: "claude-sonnet-4-6",
121
+ fetch: mockFetch,
122
+ })).rejects.toThrow("Re-register failed (401)");
123
+ });
124
+ it("posts to the correct registration endpoint", async () => {
125
+ let capturedUrl = "";
126
+ const mockFetch = async (url, _init) => {
127
+ capturedUrl = url;
128
+ return {
129
+ ok: true,
130
+ status: 200,
131
+ json: async () => ({ token: "tok" }),
132
+ };
133
+ };
134
+ await reAuthWithApiKey({
135
+ hubUrl: "https://hub.example.com",
136
+ workspace: "ws-1",
137
+ agentId: "agent-abc",
138
+ agentName: "my-agent",
139
+ apiKey: "key",
140
+ model: "claude-sonnet-4-6",
141
+ fetch: mockFetch,
142
+ });
143
+ expect(capturedUrl).toBe("https://hub.example.com/api/v1/agents/register");
144
+ });
145
+ });
146
+ //# sourceMappingURL=watcher-401-recovery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher-401-recovery.test.js","sourceRoot":"","sources":["../../src/__tests__/watcher-401-recovery.test.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,65 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
1
4
  import pc from "picocolors";
2
5
  import { attachSession, getSessionName, sessionExists } from "../core/tmux.js";
6
+ const WATCHER_STATE_PATH = path.join(process.env.HOME || ".", ".agentmesh", "watcher-state.json");
7
+ function loadWatcherState() {
8
+ try {
9
+ if (!fs.existsSync(WATCHER_STATE_PATH))
10
+ return { watchers: [] };
11
+ return JSON.parse(fs.readFileSync(WATCHER_STATE_PATH, "utf-8"));
12
+ }
13
+ catch {
14
+ return { watchers: [] };
15
+ }
16
+ }
17
+ function tmuxSessionExists(sessionName) {
18
+ try {
19
+ execSync(`tmux has-session -t "${sessionName}" 2>/dev/null`);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
3
26
  export function attach(name) {
4
27
  if (!name) {
5
28
  console.log(pc.red("Agent name is required."));
6
29
  process.exit(1);
7
30
  }
31
+ // Handle watcher sessions: `watcher-<code>` names bypass the agentmesh- prefix
32
+ if (name.startsWith("watcher-")) {
33
+ const watcherSession = name; // session is named exactly as given (e.g. "watcher-mesh")
34
+ if (tmuxSessionExists(watcherSession)) {
35
+ console.log(`Attaching to ${watcherSession}...`);
36
+ console.log(pc.dim("Detach with: Ctrl+B, D\n"));
37
+ try {
38
+ execSync(`tmux attach-session -t "${watcherSession}"`, { stdio: "inherit" });
39
+ }
40
+ catch {
41
+ // tmux attach exits non-zero when user detaches — that's expected
42
+ }
43
+ return;
44
+ }
45
+ // Not found — check watcher-state.json for a matching entry
46
+ const state = loadWatcherState();
47
+ const entry = state.watchers.find((w) => w.sessionName === name);
48
+ if (entry) {
49
+ const stateMsg = entry.status === "stopped" ? " (stopped)" : " (not running)";
50
+ console.log(pc.red(`Watcher session "${name}" not found in tmux${stateMsg}.`));
51
+ if (entry.projectId) {
52
+ console.log(` Start with: ${pc.cyan(`agentmesh watcher start --project-id ${entry.projectId}`)}`);
53
+ console.log(` Status: ${pc.cyan(`agentmesh watcher status --project-id ${entry.projectId}`)}`);
54
+ }
55
+ }
56
+ else {
57
+ console.log(pc.red(`Watcher session "${name}" not found. Is the watcher running?`));
58
+ console.log(` Start with: ${pc.cyan("agentmesh watcher start --project-id <id>")}`);
59
+ }
60
+ process.exit(1);
61
+ }
62
+ // Standard agent attach
8
63
  const sessionName = getSessionName(name);
9
64
  if (!sessionExists(sessionName)) {
10
65
  console.log(pc.red(`Agent "${name}" is not running.`));
@@ -1 +1 @@
1
- {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/cli/attach.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE/E,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAEzC,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,0BAA0B,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,WAAW,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAEhD,aAAa,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/cli/attach.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -27,7 +27,7 @@ import { stop } from "./stop.js";
27
27
  import { sync } from "./sync.js";
28
28
  import { test } from "./test.js";
29
29
  import { token } from "./token.js";
30
- import { pullWatcher, startProjectWatcher, startWatcher, stopWatcher, watcherStatus, } from "./watcher.js";
30
+ import { projectWatcherStatus, pullWatcher, startProjectWatcher, startWatcher, stopWatcher, watcherStatus, } from "./watcher.js";
31
31
  import { resolvePO, whoami } from "./whoami.js";
32
32
  import { pauseWorkerAutomation, resumeWorkerAutomation } from "./worker.js";
33
33
  const require = createRequire(import.meta.url);
@@ -278,6 +278,7 @@ watcherCommand
278
278
  .option("--interval-seconds <seconds>", "Snapshot publish interval in seconds (default: 15)")
279
279
  .option("--heartbeat-seconds <seconds>", "Lease renewal interval in seconds (default: 10)")
280
280
  .option("--ttl-seconds <seconds>", "Lease TTL in seconds (default: 30)")
281
+ .option("--dev", "Dev mode: short tick interval (10s), relaxed SLA for local testing")
281
282
  .action(async (options) => {
282
283
  try {
283
284
  if (options.projectId) {
@@ -286,6 +287,7 @@ watcherCommand
286
287
  name: options.name,
287
288
  foreground: options.foreground,
288
289
  intervalSeconds: options.interval ? parseInt(options.interval, 10) : undefined,
290
+ dev: options.dev,
289
291
  });
290
292
  }
291
293
  else if (options.teamId) {
@@ -327,21 +329,31 @@ watcherCommand
327
329
  });
328
330
  watcherCommand
329
331
  .command("status")
330
- .description("Show watcher status for a team")
331
- .requiredOption("--team-id <teamId>", "Team ID")
332
+ .description("Show watcher status for a team or project")
333
+ .option("--team-id <teamId>", "Team ID (team-scoped watcher)")
334
+ .option("--project-id <projectId>", "Project ID (project-scoped watcher)")
332
335
  .option("-n, --name <name>", "Agent name")
333
336
  .option("--json", "Output JSON")
334
337
  .option("--stale-after-seconds <seconds>", "Override stale threshold seconds")
335
338
  .action(async (options) => {
336
339
  try {
337
- await watcherStatus({
338
- teamId: options.teamId,
339
- name: options.name,
340
- json: options.json,
341
- staleAfterSeconds: options.staleAfterSeconds
342
- ? parseInt(options.staleAfterSeconds, 10)
343
- : undefined,
344
- });
340
+ if (options.projectId) {
341
+ projectWatcherStatus({ projectId: options.projectId });
342
+ }
343
+ else if (options.teamId) {
344
+ await watcherStatus({
345
+ teamId: options.teamId,
346
+ name: options.name,
347
+ json: options.json,
348
+ staleAfterSeconds: options.staleAfterSeconds
349
+ ? parseInt(options.staleAfterSeconds, 10)
350
+ : undefined,
351
+ });
352
+ }
353
+ else {
354
+ console.error(pc.red("Either --team-id or --project-id is required."));
355
+ process.exit(1);
356
+ }
345
357
  }
346
358
  catch (error) {
347
359
  console.error(pc.red(error.message));