@fulldivevr/workcord-cli 1.6.0 → 3.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.
- package/README.md +69 -39
- package/dist/auth.js +32 -8
- package/dist/cli.js +5 -3
- package/dist/commands/{hermes.js → hub.js} +91 -73
- package/dist/commands/machine.js +212 -0
- package/dist/config.js +53 -1
- package/dist/generated/commands/browser-assistance.js +65 -0
- package/dist/generated/commands/call-recordings.js +13 -0
- package/dist/generated/commands/client-config.js +18 -0
- package/dist/generated/commands/dashboard-actions.js +33 -0
- package/dist/generated/commands/internal.js +25 -0
- package/dist/generated/commands/local-folders.js +157 -0
- package/dist/generated/commands/machine-agents.js +134 -0
- package/dist/generated/commands/messages.js +35 -1
- package/dist/generated/commands/room-agents.js +6 -0
- package/dist/generated/commands/room-call.js +14 -0
- package/dist/generated/commands/rooms.js +6 -0
- package/dist/generated/commands/users.js +24 -0
- package/dist/generated/index.js +10 -0
- package/dist/machine/adapters/claude.js +183 -0
- package/dist/machine/adapters/codex.js +166 -0
- package/dist/machine/agents.js +22 -0
- package/dist/machine/chunks.js +57 -0
- package/dist/machine/handler.js +90 -0
- package/dist/machine/lock.js +36 -0
- package/dist/machine/pair.js +67 -0
- package/dist/machine/policy.js +24 -0
- package/dist/machine/relay.js +234 -0
- package/dist/machine/rotate.js +36 -0
- package/dist/machine/service.js +112 -0
- package/dist/machine/sessions.js +143 -0
- package/dist/machine/threads.js +68 -0
- package/package.json +4 -3
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { call } from "../../client.js";
|
|
2
|
+
import { actingUserId } from "../../auth.js";
|
|
3
|
+
import { getOrCreateGroup, hasSubcommand } from "../_helpers.js";
|
|
4
|
+
export function register(program) {
|
|
5
|
+
const group = getOrCreateGroup(program, "machine-agents", "MachineAgents operations");
|
|
6
|
+
if (!hasSubcommand(group, "enroll")) {
|
|
7
|
+
group
|
|
8
|
+
.command("enroll")
|
|
9
|
+
.description("POST /api/v1/machine-agents")
|
|
10
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const as = actingUserId(program);
|
|
13
|
+
const path = `/api/v1/machine-agents`;
|
|
14
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
15
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
16
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (!hasSubcommand(group, "list")) {
|
|
20
|
+
group
|
|
21
|
+
.command("list")
|
|
22
|
+
.description("GET /api/v1/machine-agents")
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
const as = actingUserId(program);
|
|
25
|
+
const path = `/api/v1/machine-agents`;
|
|
26
|
+
const data = await call({ method: "GET", path, asUserId: as });
|
|
27
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!hasSubcommand(group, "self")) {
|
|
31
|
+
group
|
|
32
|
+
.command("self")
|
|
33
|
+
.description("GET /api/v1/machine-agents/self")
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const as = actingUserId(program);
|
|
36
|
+
const path = `/api/v1/machine-agents/self`;
|
|
37
|
+
const data = await call({ method: "GET", path, asUserId: as });
|
|
38
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (!hasSubcommand(group, "create-pairing")) {
|
|
42
|
+
group
|
|
43
|
+
.command("create-pairing")
|
|
44
|
+
.description("POST /api/v1/machine-agents/pairings")
|
|
45
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
46
|
+
.action(async (opts) => {
|
|
47
|
+
const as = actingUserId(program);
|
|
48
|
+
const path = `/api/v1/machine-agents/pairings`;
|
|
49
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
50
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
51
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!hasSubcommand(group, "claim-pairing")) {
|
|
55
|
+
group
|
|
56
|
+
.command("claim-pairing")
|
|
57
|
+
.description("POST /api/v1/machine-agents/pairings/claim")
|
|
58
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
59
|
+
.action(async (opts) => {
|
|
60
|
+
const as = actingUserId(program);
|
|
61
|
+
const path = `/api/v1/machine-agents/pairings/claim`;
|
|
62
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
63
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
64
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (!hasSubcommand(group, "pairing-status")) {
|
|
68
|
+
group
|
|
69
|
+
.command("pairing-status")
|
|
70
|
+
.description("GET /api/v1/machine-agents/pairings/{pairingId}")
|
|
71
|
+
.requiredOption("--pairing-id <value>", "pairingId (path param)")
|
|
72
|
+
.action(async (opts) => {
|
|
73
|
+
const as = actingUserId(program);
|
|
74
|
+
const path = `/api/v1/machine-agents/pairings/${encodeURIComponent(opts.pairingId)}`;
|
|
75
|
+
const data = await call({ method: "GET", path, asUserId: as });
|
|
76
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (!hasSubcommand(group, "rotate")) {
|
|
80
|
+
group
|
|
81
|
+
.command("rotate")
|
|
82
|
+
.description("POST /api/v1/machine-agents/{machineId}/rotate")
|
|
83
|
+
.requiredOption("--machine-id <value>", "machineId (path param)")
|
|
84
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
85
|
+
.action(async (opts) => {
|
|
86
|
+
const as = actingUserId(program);
|
|
87
|
+
const path = `/api/v1/machine-agents/${encodeURIComponent(opts.machineId)}/rotate`;
|
|
88
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
89
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
90
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (!hasSubcommand(group, "revoke")) {
|
|
94
|
+
group
|
|
95
|
+
.command("revoke")
|
|
96
|
+
.description("DELETE /api/v1/machine-agents/{machineId}")
|
|
97
|
+
.requiredOption("--machine-id <value>", "machineId (path param)")
|
|
98
|
+
.action(async (opts) => {
|
|
99
|
+
const as = actingUserId(program);
|
|
100
|
+
const path = `/api/v1/machine-agents/${encodeURIComponent(opts.machineId)}`;
|
|
101
|
+
const data = await call({ method: "DELETE", path, asUserId: as });
|
|
102
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!hasSubcommand(group, "card")) {
|
|
106
|
+
group
|
|
107
|
+
.command("card")
|
|
108
|
+
.description("GET /api/v1/users/{userId}/machines/{machineId}/.well-known/agent.json")
|
|
109
|
+
.requiredOption("--user-id <value>", "userId (path param)")
|
|
110
|
+
.requiredOption("--machine-id <value>", "machineId (path param)")
|
|
111
|
+
.action(async (opts) => {
|
|
112
|
+
const as = actingUserId(program);
|
|
113
|
+
const path = `/api/v1/users/${encodeURIComponent(opts.userId)}/machines/${encodeURIComponent(opts.machineId)}/.well-known/agent.json`;
|
|
114
|
+
const data = await call({ method: "GET", path, asUserId: as });
|
|
115
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (!hasSubcommand(group, "a2a")) {
|
|
119
|
+
group
|
|
120
|
+
.command("a2a")
|
|
121
|
+
.description("POST /api/v1/users/{userId}/machines/{machineId}/a2a")
|
|
122
|
+
.requiredOption("--user-id <value>", "userId (path param)")
|
|
123
|
+
.requiredOption("--machine-id <value>", "machineId (path param)")
|
|
124
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
125
|
+
.action(async (opts) => {
|
|
126
|
+
const as = actingUserId(program);
|
|
127
|
+
const path = `/api/v1/users/${encodeURIComponent(opts.userId)}/machines/${encodeURIComponent(opts.machineId)}/a2a`;
|
|
128
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
129
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
130
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=machine-agents.js.map
|
|
@@ -178,6 +178,18 @@ export function register(program) {
|
|
|
178
178
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
|
+
if (!hasSubcommand(group, "unread-anchor")) {
|
|
182
|
+
group
|
|
183
|
+
.command("unread-anchor")
|
|
184
|
+
.description("GET /api/v1/rooms/{roomId}/messages/unread-anchor")
|
|
185
|
+
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
186
|
+
.action(async (opts) => {
|
|
187
|
+
const as = actingUserId(program);
|
|
188
|
+
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/messages/unread-anchor`;
|
|
189
|
+
const data = await call({ method: "GET", path, asUserId: as });
|
|
190
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
191
|
+
});
|
|
192
|
+
}
|
|
181
193
|
if (!hasSubcommand(group, "messages-around")) {
|
|
182
194
|
group
|
|
183
195
|
.command("messages-around")
|
|
@@ -204,10 +216,32 @@ export function register(program) {
|
|
|
204
216
|
.description("GET /api/v1/rooms/{roomId}/messages/{messageId}/a2ui")
|
|
205
217
|
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
206
218
|
.requiredOption("--message-id <value>", "messageId (path param)")
|
|
219
|
+
.option("--supported-components <value>", "string")
|
|
207
220
|
.action(async (opts) => {
|
|
208
221
|
const as = actingUserId(program);
|
|
209
222
|
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/messages/${encodeURIComponent(opts.messageId)}/a2ui`;
|
|
210
|
-
const
|
|
223
|
+
const query = {};
|
|
224
|
+
{
|
|
225
|
+
const v = opts.supportedComponents;
|
|
226
|
+
if (v !== undefined)
|
|
227
|
+
query["supportedComponents"] = v;
|
|
228
|
+
}
|
|
229
|
+
const data = await call({ method: "GET", path, asUserId: as, query });
|
|
230
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (!hasSubcommand(group, "post-a2-uiaction")) {
|
|
234
|
+
group
|
|
235
|
+
.command("post-a2-uiaction")
|
|
236
|
+
.description("POST /api/v1/rooms/{roomId}/messages/{messageId}/a2ui/actions")
|
|
237
|
+
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
238
|
+
.requiredOption("--message-id <value>", "messageId (path param)")
|
|
239
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
240
|
+
.action(async (opts) => {
|
|
241
|
+
const as = actingUserId(program);
|
|
242
|
+
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/messages/${encodeURIComponent(opts.messageId)}/a2ui/actions`;
|
|
243
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
244
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
211
245
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
212
246
|
});
|
|
213
247
|
}
|
|
@@ -366,6 +366,7 @@ export function register(program) {
|
|
|
366
366
|
.option("--card-description <value>", "string, ≤1000 chars")
|
|
367
367
|
.option("--enabled <true|false>", "boolean")
|
|
368
368
|
.option("--api-key <value>", "string")
|
|
369
|
+
.option("--dashboard-action-credential <json>", "object (JSON)")
|
|
369
370
|
.option("--reply-in-thread <true|false>", "boolean")
|
|
370
371
|
.addOption(new Option("--mode <value>", "string, enum").choices(["silent", "proactive", "suggest"]))
|
|
371
372
|
.option("--trigger-rule <value>", "string, ≤500 chars")
|
|
@@ -396,6 +397,11 @@ export function register(program) {
|
|
|
396
397
|
if (v !== undefined)
|
|
397
398
|
body["apiKey"] = v;
|
|
398
399
|
}
|
|
400
|
+
{
|
|
401
|
+
const v = opts.dashboardActionCredential === undefined ? undefined : JSON.parse(opts.dashboardActionCredential);
|
|
402
|
+
if (v !== undefined)
|
|
403
|
+
body["dashboardActionCredential"] = v;
|
|
404
|
+
}
|
|
399
405
|
{
|
|
400
406
|
const v = opts.replyInThread === undefined ? undefined : opts.replyInThread === "true";
|
|
401
407
|
if (v !== undefined)
|
|
@@ -17,6 +17,20 @@ export function register(program) {
|
|
|
17
17
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
if (!hasSubcommand(group, "decline")) {
|
|
21
|
+
group
|
|
22
|
+
.command("decline")
|
|
23
|
+
.description("POST /api/v1/rooms/{roomId}/call/decline")
|
|
24
|
+
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
25
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
const as = actingUserId(program);
|
|
28
|
+
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/call/decline`;
|
|
29
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
30
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
31
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
32
|
+
});
|
|
33
|
+
}
|
|
20
34
|
if (!hasSubcommand(group, "state")) {
|
|
21
35
|
group
|
|
22
36
|
.command("state")
|
|
@@ -396,6 +396,7 @@ export function register(program) {
|
|
|
396
396
|
.command("update-room")
|
|
397
397
|
.description("PATCH /api/v1/rooms/{roomId}")
|
|
398
398
|
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
399
|
+
.option("--call-ring-enabled <true|false>", "boolean")
|
|
399
400
|
.option("--name <value>", "string, ≥1 chars")
|
|
400
401
|
.option("--briefing-time <value>", "string")
|
|
401
402
|
.option("--briefing-languages <comma-separated>", "array")
|
|
@@ -428,6 +429,11 @@ export function register(program) {
|
|
|
428
429
|
const as = actingUserId(program);
|
|
429
430
|
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}`;
|
|
430
431
|
const body = {};
|
|
432
|
+
{
|
|
433
|
+
const v = opts.callRingEnabled === undefined ? undefined : opts.callRingEnabled === "true";
|
|
434
|
+
if (v !== undefined)
|
|
435
|
+
body["callRingEnabled"] = v;
|
|
436
|
+
}
|
|
431
437
|
{
|
|
432
438
|
const v = opts.name;
|
|
433
439
|
if (v !== undefined)
|
|
@@ -316,6 +316,30 @@ export function register(program) {
|
|
|
316
316
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
|
+
if (!hasSubcommand(group, "register-voip-token")) {
|
|
320
|
+
group
|
|
321
|
+
.command("register-voip-token")
|
|
322
|
+
.description("POST /api/v1/users/me/voip-token")
|
|
323
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
324
|
+
.action(async (opts) => {
|
|
325
|
+
const as = actingUserId(program);
|
|
326
|
+
const path = `/api/v1/users/me/voip-token`;
|
|
327
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
328
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
329
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (!hasSubcommand(group, "remove-voip-token")) {
|
|
333
|
+
group
|
|
334
|
+
.command("remove-voip-token")
|
|
335
|
+
.description("DELETE /api/v1/users/me/voip-token")
|
|
336
|
+
.action(async (opts) => {
|
|
337
|
+
const as = actingUserId(program);
|
|
338
|
+
const path = `/api/v1/users/me/voip-token`;
|
|
339
|
+
const data = await call({ method: "DELETE", path, asUserId: as });
|
|
340
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
341
|
+
});
|
|
342
|
+
}
|
|
319
343
|
if (!hasSubcommand(group, "get-public-user-public")) {
|
|
320
344
|
group
|
|
321
345
|
.command("get-public-user-public")
|
package/dist/generated/index.js
CHANGED
|
@@ -5,13 +5,16 @@ import { register as register_ambassador } from "./commands/ambassador.js";
|
|
|
5
5
|
import { register as register_apple_iap } from "./commands/apple-iap.js";
|
|
6
6
|
import { register as register_attribution } from "./commands/attribution.js";
|
|
7
7
|
import { register as register_auth } from "./commands/auth.js";
|
|
8
|
+
import { register as register_browser_assistance } from "./commands/browser-assistance.js";
|
|
8
9
|
import { register as register_call_recordings } from "./commands/call-recordings.js";
|
|
9
10
|
import { register as register_challenges } from "./commands/challenges.js";
|
|
11
|
+
import { register as register_client_config } from "./commands/client-config.js";
|
|
10
12
|
import { register as register_clips } from "./commands/clips.js";
|
|
11
13
|
import { register as register_contact_match } from "./commands/contact-match.js";
|
|
12
14
|
import { register as register_content_crawler } from "./commands/content-crawler.js";
|
|
13
15
|
import { register as register_credit_packs } from "./commands/credit-packs.js";
|
|
14
16
|
import { register as register_custom_agents } from "./commands/custom-agents.js";
|
|
17
|
+
import { register as register_dashboard_actions } from "./commands/dashboard-actions.js";
|
|
15
18
|
import { register as register_desktop_releases } from "./commands/desktop-releases.js";
|
|
16
19
|
import { register as register_discover } from "./commands/discover.js";
|
|
17
20
|
import { register as register_dm } from "./commands/dm.js";
|
|
@@ -31,6 +34,8 @@ import { register as register_jesuscord_admin } from "./commands/jesuscord-admin
|
|
|
31
34
|
import { register as register_join } from "./commands/join.js";
|
|
32
35
|
import { register as register_kits } from "./commands/kits.js";
|
|
33
36
|
import { register as register_live_kit_webhook } from "./commands/live-kit-webhook.js";
|
|
37
|
+
import { register as register_local_folders } from "./commands/local-folders.js";
|
|
38
|
+
import { register as register_machine_agents } from "./commands/machine-agents.js";
|
|
34
39
|
import { register as register_meetings } from "./commands/meetings.js";
|
|
35
40
|
import { register as register_message_folders } from "./commands/message-folders.js";
|
|
36
41
|
import { register as register_messages } from "./commands/messages.js";
|
|
@@ -82,13 +87,16 @@ export function registerAll(program) {
|
|
|
82
87
|
register_apple_iap(program);
|
|
83
88
|
register_attribution(program);
|
|
84
89
|
register_auth(program);
|
|
90
|
+
register_browser_assistance(program);
|
|
85
91
|
register_call_recordings(program);
|
|
86
92
|
register_challenges(program);
|
|
93
|
+
register_client_config(program);
|
|
87
94
|
register_clips(program);
|
|
88
95
|
register_contact_match(program);
|
|
89
96
|
register_content_crawler(program);
|
|
90
97
|
register_credit_packs(program);
|
|
91
98
|
register_custom_agents(program);
|
|
99
|
+
register_dashboard_actions(program);
|
|
92
100
|
register_desktop_releases(program);
|
|
93
101
|
register_discover(program);
|
|
94
102
|
register_dm(program);
|
|
@@ -108,6 +116,8 @@ export function registerAll(program) {
|
|
|
108
116
|
register_join(program);
|
|
109
117
|
register_kits(program);
|
|
110
118
|
register_live_kit_webhook(program);
|
|
119
|
+
register_local_folders(program);
|
|
120
|
+
register_machine_agents(program);
|
|
111
121
|
register_meetings(program);
|
|
112
122
|
register_message_folders(program);
|
|
113
123
|
register_messages(program);
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { childEnv } from "../agents.js";
|
|
4
|
+
const TASK_TIMEOUT_MS = 20 * 60_000;
|
|
5
|
+
const STOP_GRACE_MS = 3_000;
|
|
6
|
+
/** The owner's own agent with no permission gate, as Codex runs with approvals and sandbox bypassed. */
|
|
7
|
+
export function claudeArgs(prompt, roots, resume) {
|
|
8
|
+
return [
|
|
9
|
+
"-p", "--output-format", "stream-json", "--verbose",
|
|
10
|
+
"--dangerously-skip-permissions",
|
|
11
|
+
...(resume ? ["--resume", resume] : []),
|
|
12
|
+
...(roots.length ? ["--add-dir", ...roots] : []),
|
|
13
|
+
"--",
|
|
14
|
+
prompt,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
const EDIT_TOOLS = new Set(["Edit", "MultiEdit", "Write", "NotebookEdit"]);
|
|
18
|
+
/** A Claude Code tool call in the hub's run-event shape, so its step line reads as a Codex one does. */
|
|
19
|
+
export function claudeToolEvent(block) {
|
|
20
|
+
const input = block?.input && typeof block.input === "object" ? block.input : {};
|
|
21
|
+
const name = String(block?.name || "");
|
|
22
|
+
const base = { event: "tool.started", item_id: String(block?.id || "") };
|
|
23
|
+
const path = String(input.file_path || input.notebook_path || input.path || "");
|
|
24
|
+
if (name === "Bash")
|
|
25
|
+
return { ...base, kind: "commandExecution", command: String(input.command || "") };
|
|
26
|
+
if (name === "Read")
|
|
27
|
+
return { ...base, kind: "commandExecution", command_actions: [{ type: "read", path }] };
|
|
28
|
+
if (name === "Grep" || name === "Glob") {
|
|
29
|
+
return { ...base, kind: "commandExecution", command_actions: [{ type: "search", query: String(input.pattern || ""), path: String(input.path || "") }] };
|
|
30
|
+
}
|
|
31
|
+
if (name === "LS")
|
|
32
|
+
return { ...base, kind: "commandExecution", command_actions: [{ type: "listFiles", path }] };
|
|
33
|
+
if (EDIT_TOOLS.has(name))
|
|
34
|
+
return { ...base, kind: "fileChange", paths: path ? [path] : [] };
|
|
35
|
+
if (name === "WebSearch" || name === "WebFetch")
|
|
36
|
+
return { ...base, kind: "webSearch" };
|
|
37
|
+
if (name === "Task" || name === "Agent")
|
|
38
|
+
return { ...base, kind: "collabAgentToolCall" };
|
|
39
|
+
const mcp = name.match(/^mcp__(.+?)__(.+)$/);
|
|
40
|
+
if (mcp)
|
|
41
|
+
return { ...base, kind: "mcpToolCall", tool_namespace: mcp[1], tool_name: mcp[2] };
|
|
42
|
+
return { ...base, kind: "dynamicToolCall", tool_name: name };
|
|
43
|
+
}
|
|
44
|
+
export async function runClaude(input) {
|
|
45
|
+
const signal = AbortSignal.any([input.signal, AbortSignal.timeout(TASK_TIMEOUT_MS)]);
|
|
46
|
+
if (!input.resume)
|
|
47
|
+
return runOnce(input, signal, { started: false });
|
|
48
|
+
const attempt = { started: false };
|
|
49
|
+
try {
|
|
50
|
+
return await runOnce(input, signal, attempt);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A session Claude Code no longer has fails before the turn starts; the turn then runs in a new session.
|
|
54
|
+
if (attempt.started || signal.aborted)
|
|
55
|
+
throw error;
|
|
56
|
+
return runOnce({ ...input, resume: undefined }, signal, { started: false });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function runOnce(input, signal, attempt) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const child = spawn(input.binary ?? "claude", claudeArgs(input.prompt, input.roots, input.resume), {
|
|
62
|
+
cwd: input.cwd,
|
|
63
|
+
// One process per turn: a background command would outlive the turn and its answer.
|
|
64
|
+
// This also lifts Claude Code's block on a long foreground `sleep`, which offers only the background.
|
|
65
|
+
env: { ...childEnv(input.env), CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" },
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
});
|
|
68
|
+
const onAbort = () => stopTree(child);
|
|
69
|
+
if (signal.aborted)
|
|
70
|
+
onAbort();
|
|
71
|
+
else
|
|
72
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
73
|
+
child.on("exit", () => {
|
|
74
|
+
signal.removeEventListener("abort", onAbort);
|
|
75
|
+
if (signal.aborted)
|
|
76
|
+
reject(new Error("The task was stopped."));
|
|
77
|
+
});
|
|
78
|
+
let stderr = "";
|
|
79
|
+
let result;
|
|
80
|
+
let lastText = "";
|
|
81
|
+
let sessionId = "";
|
|
82
|
+
child.stdout.setEncoding("utf8");
|
|
83
|
+
child.stderr.setEncoding("utf8");
|
|
84
|
+
child.stderr.on("data", (chunk) => {
|
|
85
|
+
if (stderr.length < 8_192)
|
|
86
|
+
stderr += chunk;
|
|
87
|
+
});
|
|
88
|
+
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
89
|
+
let message;
|
|
90
|
+
try {
|
|
91
|
+
message = JSON.parse(line);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (typeof message?.session_id === "string" && message.session_id && message.session_id !== sessionId) {
|
|
97
|
+
sessionId = message.session_id;
|
|
98
|
+
input.onThread?.(sessionId);
|
|
99
|
+
}
|
|
100
|
+
if (message?.type === "result") {
|
|
101
|
+
result = message;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (message?.type === "assistant")
|
|
105
|
+
attempt.started = true;
|
|
106
|
+
const content = Array.isArray(message?.message?.content) ? message.message.content : [];
|
|
107
|
+
for (const block of content) {
|
|
108
|
+
if (message.type === "assistant" && block?.type === "tool_use")
|
|
109
|
+
input.onEvent?.(claudeToolEvent(block));
|
|
110
|
+
else if (message.type === "assistant" && block?.type === "text" && String(block.text || "").trim()) {
|
|
111
|
+
lastText = String(block.text);
|
|
112
|
+
input.onEvent?.({ event: "message.completed", output: lastText });
|
|
113
|
+
}
|
|
114
|
+
else if (message.type === "user" && block?.type === "tool_result") {
|
|
115
|
+
input.onEvent?.({ event: "tool.completed", item_id: String(block.tool_use_id || "") });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
child.on("error", (error) => {
|
|
120
|
+
reject(new Error(error.code === "ENOENT"
|
|
121
|
+
? "Claude Code is not installed on this machine (no `claude` on PATH)."
|
|
122
|
+
: error.message));
|
|
123
|
+
});
|
|
124
|
+
child.on("close", (code) => {
|
|
125
|
+
if (!result) {
|
|
126
|
+
reject(new Error(`Claude Code exited with ${code}: ${stderr.trim().slice(0, 500)}`));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (result.is_error || code !== 0) {
|
|
130
|
+
reject(new Error(String(result.result || stderr || `Claude Code exited with ${code}`).slice(0, 1_000)));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
resolve(String(result.result ?? lastText));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function descendants(root) {
|
|
138
|
+
if (process.platform === "win32")
|
|
139
|
+
return [];
|
|
140
|
+
let table = "";
|
|
141
|
+
try {
|
|
142
|
+
table = execFileSync("ps", ["-axo", "pid=,ppid="], { encoding: "utf8" });
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
const children = new Map();
|
|
148
|
+
for (const line of table.split("\n")) {
|
|
149
|
+
const [pid, ppid] = line.trim().split(/\s+/).map(Number);
|
|
150
|
+
if (pid && ppid)
|
|
151
|
+
children.set(ppid, [...(children.get(ppid) ?? []), pid]);
|
|
152
|
+
}
|
|
153
|
+
const found = [];
|
|
154
|
+
for (let queue = [root]; queue.length;) {
|
|
155
|
+
for (const pid of children.get(queue.shift()) ?? []) {
|
|
156
|
+
found.push(pid);
|
|
157
|
+
queue.push(pid);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Claude Code runs its Bash tool in a detached shell of its own process group, and on
|
|
164
|
+
* SIGTERM it ends those shells itself. A Claude that does not exit in time is killed with
|
|
165
|
+
* the tree it had, which no process-group signal would reach.
|
|
166
|
+
*/
|
|
167
|
+
function stopTree(child) {
|
|
168
|
+
if (!child.pid || child.exitCode !== null || child.signalCode !== null)
|
|
169
|
+
return;
|
|
170
|
+
const tree = descendants(child.pid);
|
|
171
|
+
child.kill("SIGTERM");
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
174
|
+
child.kill("SIGKILL");
|
|
175
|
+
for (const pid of tree) {
|
|
176
|
+
try {
|
|
177
|
+
process.kill(pid, "SIGKILL");
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
180
|
+
}
|
|
181
|
+
}, STOP_GRACE_MS).unref();
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=claude.js.map
|