@shipers-dev/multi 0.25.0 → 0.32.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/dist/index.js +335 -209
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -15001,7 +15001,7 @@ import { parseArgs } from "util";
|
|
|
15001
15001
|
// package.json
|
|
15002
15002
|
var package_default = {
|
|
15003
15003
|
name: "@shipers-dev/multi",
|
|
15004
|
-
version: "0.
|
|
15004
|
+
version: "0.32.0",
|
|
15005
15005
|
type: "module",
|
|
15006
15006
|
bin: {
|
|
15007
15007
|
"multi-agent": "./dist/index.js"
|
|
@@ -15705,8 +15705,12 @@ multi-managed: true
|
|
|
15705
15705
|
`;
|
|
15706
15706
|
writeFileSync4(out, fm + (agent.prompt || ""));
|
|
15707
15707
|
}
|
|
15708
|
-
async function materializeBundle(apiUrl, deviceId, log3) {
|
|
15709
|
-
|
|
15708
|
+
async function materializeBundle(apiUrl, wsId, deviceId, log3) {
|
|
15709
|
+
if (!wsId) {
|
|
15710
|
+
log3("materialize: skipped (no workspace id)");
|
|
15711
|
+
return null;
|
|
15712
|
+
}
|
|
15713
|
+
const res = await apiClient.get(`${apiUrl}/api/workspaces/${wsId}/devices/${deviceId}/agent_bundle`);
|
|
15710
15714
|
if (!res.success || !res.data) {
|
|
15711
15715
|
log3(`materialize: bundle fetch failed: ${res.error || "unknown"}`);
|
|
15712
15716
|
return null;
|
|
@@ -15775,11 +15779,11 @@ class Materializer extends exports_Effect.Service()("cli/Materializer", {
|
|
|
15775
15779
|
effect: exports_Effect.gen(function* () {
|
|
15776
15780
|
const logger = yield* Logger4;
|
|
15777
15781
|
const sync5 = (f = () => {}) => f;
|
|
15778
|
-
const materialize = (apiUrl, deviceId) => exports_Effect.tryPromise({
|
|
15779
|
-
try: () => materializeBundle(apiUrl, deviceId, (m) => {
|
|
15782
|
+
const materialize = (apiUrl, wsId, deviceId) => exports_Effect.tryPromise({
|
|
15783
|
+
try: () => materializeBundle(apiUrl, wsId, deviceId, (m) => {
|
|
15780
15784
|
exports_Effect.runPromise(logger.log(m));
|
|
15781
15785
|
}),
|
|
15782
|
-
catch: (cause3) => new ApiError({ url: `${apiUrl}/api/devices/${deviceId}/agent_bundle`, message: String(cause3) })
|
|
15786
|
+
catch: (cause3) => new ApiError({ url: `${apiUrl}/api/workspaces/${wsId}/devices/${deviceId}/agent_bundle`, message: String(cause3) })
|
|
15783
15787
|
});
|
|
15784
15788
|
const lastRevision = () => exports_Effect.sync(() => lastMaterializedRevision());
|
|
15785
15789
|
return { materialize, lastRevision };
|
|
@@ -31543,7 +31547,10 @@ ${entries2}` } });
|
|
|
31543
31547
|
if (allowOpt)
|
|
31544
31548
|
return { outcome: { outcome: "selected", optionId: allowOpt.optionId } };
|
|
31545
31549
|
}
|
|
31546
|
-
|
|
31550
|
+
if (!o.tenantWsId)
|
|
31551
|
+
return { outcome: { outcome: "cancelled" } };
|
|
31552
|
+
const permBase = `${o.apiUrl}/api/workspaces/${o.tenantWsId}/permissions`;
|
|
31553
|
+
const created = await apiClient.post(permBase, {
|
|
31547
31554
|
issue_id: o.issueId,
|
|
31548
31555
|
device_id: o.deviceId,
|
|
31549
31556
|
tool_call_id: tc.id || tc.toolCallId,
|
|
@@ -31557,7 +31564,7 @@ ${entries2}` } });
|
|
|
31557
31564
|
const deadline = Date.now() + 5 * 60 * 1000;
|
|
31558
31565
|
while (Date.now() < deadline) {
|
|
31559
31566
|
await new Promise((r) => setTimeout(r, 500));
|
|
31560
|
-
const got = await apiClient.get(`${
|
|
31567
|
+
const got = await apiClient.get(`${permBase}/${permId}`);
|
|
31561
31568
|
const row = got.data;
|
|
31562
31569
|
if (!row)
|
|
31563
31570
|
break;
|
|
@@ -31867,7 +31874,11 @@ var statusCmd = exports_Effect.fn("statusCmd")(function* () {
|
|
|
31867
31874
|
const apiUrl = cfg.apiUrl || "https://multi-api.adnb3r.workers.dev";
|
|
31868
31875
|
if (cfg.authToken)
|
|
31869
31876
|
yield* api2.setAuthToken(cfg.authToken);
|
|
31870
|
-
|
|
31877
|
+
if (!cfg.workspaceId) {
|
|
31878
|
+
console.log("❌ Device has no workspace id. Re-run setup.");
|
|
31879
|
+
process.exit(1);
|
|
31880
|
+
}
|
|
31881
|
+
const res = yield* api2.get(`${apiUrl}/api/workspaces/${cfg.workspaceId}/devices/${cfg.deviceId}`);
|
|
31871
31882
|
if (!res.success) {
|
|
31872
31883
|
console.log("❌", res.error);
|
|
31873
31884
|
process.exit(1);
|
|
@@ -31960,11 +31971,11 @@ class Materializer2 extends exports_Effect.Service()("cli/Materializer", {
|
|
|
31960
31971
|
effect: exports_Effect.gen(function* () {
|
|
31961
31972
|
const logger = yield* Logger4;
|
|
31962
31973
|
const sync5 = (f = () => {}) => f;
|
|
31963
|
-
const materialize = (apiUrl, deviceId) => exports_Effect.tryPromise({
|
|
31964
|
-
try: () => materializeBundle(apiUrl, deviceId, (m) => {
|
|
31974
|
+
const materialize = (apiUrl, wsId, deviceId) => exports_Effect.tryPromise({
|
|
31975
|
+
try: () => materializeBundle(apiUrl, wsId, deviceId, (m) => {
|
|
31965
31976
|
exports_Effect.runPromise(logger.log(m));
|
|
31966
31977
|
}),
|
|
31967
|
-
catch: (cause3) => new ApiError({ url: `${apiUrl}/api/devices/${deviceId}/agent_bundle`, message: String(cause3) })
|
|
31978
|
+
catch: (cause3) => new ApiError({ url: `${apiUrl}/api/workspaces/${wsId}/devices/${deviceId}/agent_bundle`, message: String(cause3) })
|
|
31968
31979
|
});
|
|
31969
31980
|
const lastRevision = () => exports_Effect.sync(() => lastMaterializedRevision());
|
|
31970
31981
|
return { materialize, lastRevision };
|
|
@@ -32037,11 +32048,10 @@ var setupCmd = exports_Effect.fn("setupCmd")(function* (name, apiUrl) {
|
|
|
32037
32048
|
✅ Device paired. ID: ${a.device_id}`);
|
|
32038
32049
|
yield* config2.save({ deviceId: a.device_id, authToken: a.token, dispatchSecret: a.dispatch_secret, apiUrl });
|
|
32039
32050
|
yield* api2.setAuthToken(a.token);
|
|
32040
|
-
const
|
|
32041
|
-
const workspaceId = dev.data?.workspace_id;
|
|
32051
|
+
const workspaceId = a.workspace_id || undefined;
|
|
32042
32052
|
if (workspaceId) {
|
|
32043
32053
|
yield* config2.update({ workspaceId });
|
|
32044
|
-
yield* materializer.materialize(apiUrl, a.device_id).pipe(exports_Effect.catchAll((e) => exports_Effect.sync(() => console.log(` materialize failed: ${e.message}`))));
|
|
32054
|
+
yield* materializer.materialize(apiUrl, workspaceId, a.device_id).pipe(exports_Effect.catchAll((e) => exports_Effect.sync(() => console.log(` materialize failed: ${e.message}`))));
|
|
32045
32055
|
}
|
|
32046
32056
|
console.log(`
|
|
32047
32057
|
Next: link to an agent with: multi-agent link --agent <agentId>`);
|
|
@@ -32059,7 +32069,10 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
|
|
|
32059
32069
|
}
|
|
32060
32070
|
if (cfg.authToken)
|
|
32061
32071
|
yield* api2.setAuthToken(cfg.authToken);
|
|
32062
|
-
|
|
32072
|
+
if (!cfg.workspaceId) {
|
|
32073
|
+
return yield* exports_Effect.fail(new UsageError({ message: "Device has no workspace id. Re-run setup." }));
|
|
32074
|
+
}
|
|
32075
|
+
const url2 = `${apiUrl}/api/workspaces/${cfg.workspaceId}/devices/${cfg.deviceId}/link`;
|
|
32063
32076
|
const res = yield* api2.post(url2, { agent_id: agentId });
|
|
32064
32077
|
if (!res.success) {
|
|
32065
32078
|
return yield* exports_Effect.fail(new ApiError({ url: url2, status: res.status, message: res.error || "link failed" }));
|
|
@@ -32264,7 +32277,9 @@ function bumpFailed(ids3, reason) {
|
|
|
32264
32277
|
WHERE id = ?`, attempts, Date.now() + backoff, reason, id);
|
|
32265
32278
|
}
|
|
32266
32279
|
}
|
|
32267
|
-
async function flushOutboxOnce(apiUrl) {
|
|
32280
|
+
async function flushOutboxOnce(apiUrl, wsId) {
|
|
32281
|
+
if (!wsId)
|
|
32282
|
+
return { attempted: 0, accepted: 0, rejected: 0 };
|
|
32268
32283
|
const rows = pickPending(BATCH_SIZE);
|
|
32269
32284
|
if (!rows.length)
|
|
32270
32285
|
return { attempted: 0, accepted: 0, rejected: 0 };
|
|
@@ -32276,7 +32291,7 @@ async function flushOutboxOnce(apiUrl) {
|
|
|
32276
32291
|
created_at: r.created_at
|
|
32277
32292
|
}));
|
|
32278
32293
|
try {
|
|
32279
|
-
const res = await apiClient.post(`${apiUrl}/api/streams/ingest`, { events });
|
|
32294
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${wsId}/streams/ingest`, { events });
|
|
32280
32295
|
if (!res.success || !res.data) {
|
|
32281
32296
|
bumpFailed(rows.map((r) => r.id), res.error ?? "ingest_post_failed");
|
|
32282
32297
|
return { attempted: rows.length, accepted: 0, rejected: rows.length };
|
|
@@ -32309,7 +32324,7 @@ function tryParse(s) {
|
|
|
32309
32324
|
return s;
|
|
32310
32325
|
}
|
|
32311
32326
|
}
|
|
32312
|
-
function startOutboxFlusher(apiUrl) {
|
|
32327
|
+
function startOutboxFlusher(apiUrl, wsId) {
|
|
32313
32328
|
let stopped = false;
|
|
32314
32329
|
let timer = null;
|
|
32315
32330
|
const tick = async () => {
|
|
@@ -32317,7 +32332,7 @@ function startOutboxFlusher(apiUrl) {
|
|
|
32317
32332
|
return;
|
|
32318
32333
|
try {
|
|
32319
32334
|
while (!stopped) {
|
|
32320
|
-
const r = await flushOutboxOnce(apiUrl);
|
|
32335
|
+
const r = await flushOutboxOnce(apiUrl, wsId);
|
|
32321
32336
|
if (r.attempted < BATCH_SIZE)
|
|
32322
32337
|
break;
|
|
32323
32338
|
}
|
|
@@ -32339,7 +32354,7 @@ import { join as join9, dirname as dirname8 } from "path";
|
|
|
32339
32354
|
// package.json
|
|
32340
32355
|
var package_default2 = {
|
|
32341
32356
|
name: "@shipers-dev/multi",
|
|
32342
|
-
version: "0.
|
|
32357
|
+
version: "0.32.0",
|
|
32343
32358
|
type: "module",
|
|
32344
32359
|
bin: {
|
|
32345
32360
|
"multi-agent": "./dist/index.js"
|
|
@@ -32388,20 +32403,31 @@ function log3(msg) {
|
|
|
32388
32403
|
appendFileSync4(LOG_PATH3, line);
|
|
32389
32404
|
process.stdout.write(line);
|
|
32390
32405
|
}
|
|
32391
|
-
|
|
32392
|
-
return
|
|
32406
|
+
function nestedIssueBase(apiUrl, wsId, projectId, issueId) {
|
|
32407
|
+
return `${apiUrl}/api/workspaces/${wsId}/projects/${projectId}/issues/${issueId}`;
|
|
32408
|
+
}
|
|
32409
|
+
async function patchIssueStatus(apiUrl, wsId, issueId, status) {
|
|
32410
|
+
if (!wsId)
|
|
32411
|
+
return { success: false, error: "no workspace id" };
|
|
32412
|
+
return apiClient.post(`${apiUrl}/api/workspaces/${wsId}/agent/issues/mutate`, { action: "update", id: issueId, status });
|
|
32393
32413
|
}
|
|
32394
|
-
async function markStopped(apiUrl,
|
|
32414
|
+
async function markStopped(apiUrl, task, reason) {
|
|
32415
|
+
const wsId = task?.tenant_workspace_id ?? null;
|
|
32416
|
+
const pid = task?.project_id ?? null;
|
|
32417
|
+
const issueId = task?.issue_id;
|
|
32395
32418
|
try {
|
|
32396
|
-
|
|
32419
|
+
if (wsId)
|
|
32420
|
+
await patchIssueStatus(apiUrl, wsId, issueId, "stopped");
|
|
32397
32421
|
} catch {}
|
|
32398
32422
|
try {
|
|
32399
|
-
|
|
32400
|
-
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
32404
|
-
|
|
32423
|
+
if (wsId && pid) {
|
|
32424
|
+
await apiClient.post(`${nestedIssueBase(apiUrl, wsId, pid, issueId)}/comments`, {
|
|
32425
|
+
author_type: "agent",
|
|
32426
|
+
author_id: "daemon",
|
|
32427
|
+
author_name: "daemon",
|
|
32428
|
+
body: `⏹ Stopped: ${reason}`
|
|
32429
|
+
});
|
|
32430
|
+
}
|
|
32405
32431
|
} catch {}
|
|
32406
32432
|
await postStream(apiUrl, issueId, "stopped", { reason });
|
|
32407
32433
|
}
|
|
@@ -32428,6 +32454,9 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32428
32454
|
const issueId = task.issue_id;
|
|
32429
32455
|
const isFollowup = !!task.followup;
|
|
32430
32456
|
const baseWorkingDir = task.working_dir && existsSync9(task.working_dir) ? task.working_dir : undefined;
|
|
32457
|
+
const tenantWsId = task.tenant_workspace_id ?? null;
|
|
32458
|
+
const projectId = task.project_id ?? null;
|
|
32459
|
+
const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
|
|
32431
32460
|
let workingDir = baseWorkingDir;
|
|
32432
32461
|
let worktreeBranch = "";
|
|
32433
32462
|
if (baseWorkingDir) {
|
|
@@ -32436,9 +32465,9 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32436
32465
|
workingDir = wt.path;
|
|
32437
32466
|
worktreeBranch = wt.branch;
|
|
32438
32467
|
await postStream(apiUrl, issueId, "worktree_created", { path: wt.path, branch: wt.branch, reused: !wt.created });
|
|
32439
|
-
if (task.workspace_id && task.project_id) {
|
|
32468
|
+
if (task.workspace_id && task.project_id && task.tenant_workspace_id) {
|
|
32440
32469
|
try {
|
|
32441
|
-
await apiClient.post(`${apiUrl}/api/agent-workspaces/${task.workspace_id}/ready?project_id=${encodeURIComponent(task.project_id)}`, { status: "ready", worktree_path: wt.path });
|
|
32470
|
+
await apiClient.post(`${apiUrl}/api/workspaces/${task.tenant_workspace_id}/agent-workspaces/${task.workspace_id}/ready?project_id=${encodeURIComponent(task.project_id)}`, { status: "ready", worktree_path: wt.path });
|
|
32442
32471
|
} catch (e) {
|
|
32443
32472
|
await postStream(apiUrl, issueId, "worktree_error", { message: `agent-workspace ack failed: ${fmtError(e)}` });
|
|
32444
32473
|
}
|
|
@@ -32448,13 +32477,14 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32448
32477
|
}
|
|
32449
32478
|
}
|
|
32450
32479
|
log3(`▶ run_task ${task.key}: ${isFollowup ? "(follow-up) " : ""}${task.title}${workingDir ? ` [cwd: ${workingDir}${worktreeBranch ? ` @${worktreeBranch}` : ""}]` : ""}`);
|
|
32451
|
-
|
|
32480
|
+
if (tenantWsId)
|
|
32481
|
+
await patchIssueStatus(apiUrl, tenantWsId, issueId, "in_progress");
|
|
32452
32482
|
await postStream(apiUrl, issueId, "progress", { message: `Device ${deviceId} picked up ${isFollowup ? "follow-up" : "task"}` });
|
|
32453
32483
|
let attachmentRefs = [];
|
|
32454
|
-
if (task.from_comment_id) {
|
|
32484
|
+
if (task.from_comment_id && task.tenant_workspace_id && task.project_id) {
|
|
32455
32485
|
const baseDir = workingDir || join9(MULTI_DIR4, "tmp", issueId);
|
|
32456
32486
|
const inDir = join9(baseDir, ".multi-in", task.from_comment_id);
|
|
32457
|
-
attachmentRefs = await downloadCommentAttachments(apiUrl, task.from_comment_id, inDir);
|
|
32487
|
+
attachmentRefs = await downloadCommentAttachments(apiUrl, task.tenant_workspace_id, task.project_id, issueId, task.from_comment_id, inDir);
|
|
32458
32488
|
if (attachmentRefs.length)
|
|
32459
32489
|
log3(` fetched ${attachmentRefs.length} attachment(s) → ${inDir}`);
|
|
32460
32490
|
}
|
|
@@ -32467,8 +32497,10 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32467
32497
|
const ensureLiveComment = () => {
|
|
32468
32498
|
if (liveCommentId)
|
|
32469
32499
|
return Promise.resolve();
|
|
32500
|
+
if (!ISSUE_BASE)
|
|
32501
|
+
return Promise.resolve();
|
|
32470
32502
|
if (!liveCommentPromise) {
|
|
32471
|
-
liveCommentPromise = apiClient.post(`${
|
|
32503
|
+
liveCommentPromise = apiClient.post(`${ISSUE_BASE}/comments`, {
|
|
32472
32504
|
author_type: "agent",
|
|
32473
32505
|
author_id: task.agent_id,
|
|
32474
32506
|
author_name: "agent",
|
|
@@ -32480,15 +32512,17 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32480
32512
|
return liveCommentPromise;
|
|
32481
32513
|
};
|
|
32482
32514
|
const patchLive = async (body) => {
|
|
32483
|
-
if (!liveCommentId)
|
|
32515
|
+
if (!liveCommentId || !ISSUE_BASE)
|
|
32484
32516
|
return;
|
|
32485
32517
|
try {
|
|
32486
|
-
await apiClient.patch(`${
|
|
32518
|
+
await apiClient.patch(`${ISSUE_BASE}/comments/${liveCommentId}`, { body: body || "…" });
|
|
32487
32519
|
} catch {}
|
|
32488
32520
|
};
|
|
32489
32521
|
const postComment = async (body) => {
|
|
32522
|
+
if (!ISSUE_BASE)
|
|
32523
|
+
return;
|
|
32490
32524
|
try {
|
|
32491
|
-
await apiClient.post(`${
|
|
32525
|
+
await apiClient.post(`${ISSUE_BASE}/comments`, { author_type: "agent", author_id: task.agent_id, author_name: "agent", body });
|
|
32492
32526
|
} catch {}
|
|
32493
32527
|
};
|
|
32494
32528
|
const turn = {
|
|
@@ -32680,9 +32714,11 @@ _${bits.join(" · ")}_`);
|
|
|
32680
32714
|
let preferType = task.agent_runtime || undefined;
|
|
32681
32715
|
if (!preferType) {
|
|
32682
32716
|
try {
|
|
32683
|
-
|
|
32684
|
-
|
|
32685
|
-
|
|
32717
|
+
if (tenantWsId) {
|
|
32718
|
+
const a = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsId}/agents/${task.agent_id}`);
|
|
32719
|
+
if (a.data?.type)
|
|
32720
|
+
preferType = a.data.type;
|
|
32721
|
+
}
|
|
32686
32722
|
} catch {}
|
|
32687
32723
|
}
|
|
32688
32724
|
const acpCapable = detected.filter((d) => d.type === "claude-code");
|
|
@@ -32724,35 +32760,36 @@ Respond in the chat. Only if you produce large artifact files (screenshots, data
|
|
|
32724
32760
|
}
|
|
32725
32761
|
let preamble = "";
|
|
32726
32762
|
try {
|
|
32727
|
-
|
|
32728
|
-
|
|
32729
|
-
|
|
32730
|
-
|
|
32763
|
+
if (tenantWsId) {
|
|
32764
|
+
const agentRes = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsId}/agents/${task.agent_id}`);
|
|
32765
|
+
const agent = agentRes.data;
|
|
32766
|
+
if (agent?.prompt)
|
|
32767
|
+
preamble += `# Agent instructions
|
|
32731
32768
|
|
|
32732
32769
|
${agent.prompt}
|
|
32733
32770
|
|
|
32734
32771
|
`;
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32772
|
+
const skillsRes = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsId}/agents/${task.agent_id}/skills`);
|
|
32773
|
+
const skillList = Array.isArray(skillsRes.data) ? skillsRes.data : [];
|
|
32774
|
+
if (skillList.length) {
|
|
32775
|
+
preamble += `# Attached skills (${skillList.length})
|
|
32739
32776
|
|
|
32740
32777
|
`;
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
32778
|
+
for (const s of skillList) {
|
|
32779
|
+
const body = String(s.body || s.description || "").trim();
|
|
32780
|
+
if (!body)
|
|
32781
|
+
continue;
|
|
32782
|
+
preamble += `## ${s.name}${s.version ? ` v${s.version}` : ""}
|
|
32746
32783
|
|
|
32747
32784
|
${body}
|
|
32748
32785
|
|
|
32749
32786
|
`;
|
|
32787
|
+
}
|
|
32750
32788
|
}
|
|
32751
32789
|
}
|
|
32752
32790
|
} catch (e) {
|
|
32753
32791
|
log3(`preamble fetch failed: ${String(e)}`);
|
|
32754
32792
|
}
|
|
32755
|
-
preamble += await fetchMemoryPreamble(apiUrl, task);
|
|
32756
32793
|
preamble += await buildPlanningPreamble(apiUrl, task);
|
|
32757
32794
|
const prompt = preamble ? `${preamble}
|
|
32758
32795
|
---
|
|
@@ -32770,6 +32807,7 @@ ${userPart}` : userPart;
|
|
|
32770
32807
|
issueId,
|
|
32771
32808
|
deviceId,
|
|
32772
32809
|
prompt,
|
|
32810
|
+
tenantWsId,
|
|
32773
32811
|
workspaceId: task.workspace_id || issueId,
|
|
32774
32812
|
workspaceSessionId: task.workspace_session_id || null,
|
|
32775
32813
|
sessionId: task.session_id || null,
|
|
@@ -32779,11 +32817,14 @@ ${userPart}` : userPart;
|
|
|
32779
32817
|
onEvent: eventHandler,
|
|
32780
32818
|
onSession: async (sid) => {
|
|
32781
32819
|
try {
|
|
32782
|
-
|
|
32820
|
+
if (ISSUE_BASE)
|
|
32821
|
+
await apiClient.post(`${ISSUE_BASE}/session`, { session_id: sid });
|
|
32783
32822
|
} catch {}
|
|
32784
32823
|
if (task.workspace_session_id) {
|
|
32785
32824
|
try {
|
|
32786
|
-
|
|
32825
|
+
if (tenantWsId) {
|
|
32826
|
+
await apiClient.patch(`${apiUrl}/api/workspaces/${tenantWsId}/sessions/${task.workspace_session_id}?session_workspace_id=${encodeURIComponent(task.workspace_id || issueId)}`, { provider_session_id: sid, status: "running" });
|
|
32827
|
+
}
|
|
32787
32828
|
} catch {}
|
|
32788
32829
|
}
|
|
32789
32830
|
},
|
|
@@ -32800,32 +32841,33 @@ ${userPart}` : userPart;
|
|
|
32800
32841
|
} else if (useAcpx) {
|
|
32801
32842
|
let preamble = "";
|
|
32802
32843
|
try {
|
|
32803
|
-
|
|
32804
|
-
|
|
32805
|
-
|
|
32806
|
-
|
|
32844
|
+
if (tenantWsId) {
|
|
32845
|
+
const agentRes = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsId}/agents/${task.agent_id}`);
|
|
32846
|
+
const agent = agentRes.data;
|
|
32847
|
+
if (agent?.prompt)
|
|
32848
|
+
preamble += `# Agent instructions
|
|
32807
32849
|
|
|
32808
32850
|
${agent.prompt}
|
|
32809
32851
|
|
|
32810
32852
|
`;
|
|
32811
|
-
|
|
32812
|
-
|
|
32813
|
-
|
|
32814
|
-
|
|
32853
|
+
const skillsRes = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsId}/agents/${task.agent_id}/skills`);
|
|
32854
|
+
const skillList = Array.isArray(skillsRes.data) ? skillsRes.data : [];
|
|
32855
|
+
if (skillList.length) {
|
|
32856
|
+
preamble += `# Attached skills (${skillList.length})
|
|
32815
32857
|
|
|
32816
32858
|
`;
|
|
32817
|
-
|
|
32818
|
-
|
|
32819
|
-
|
|
32820
|
-
|
|
32859
|
+
for (const s of skillList) {
|
|
32860
|
+
const body = String(s.body || s.description || "").trim();
|
|
32861
|
+
if (body)
|
|
32862
|
+
preamble += `## ${s.name}${s.version ? ` v${s.version}` : ""}
|
|
32821
32863
|
|
|
32822
32864
|
${body}
|
|
32823
32865
|
|
|
32824
32866
|
`;
|
|
32867
|
+
}
|
|
32825
32868
|
}
|
|
32826
32869
|
}
|
|
32827
32870
|
} catch {}
|
|
32828
|
-
preamble += await fetchMemoryPreamble(apiUrl, task);
|
|
32829
32871
|
preamble += await buildPlanningPreamble(apiUrl, task);
|
|
32830
32872
|
const issueContext = `## Issue ${task.key}: ${task.title}${task.description ? `
|
|
32831
32873
|
|
|
@@ -32885,8 +32927,8 @@ ${userPart}` : userPart;
|
|
|
32885
32927
|
for await (const event of runner(task))
|
|
32886
32928
|
await eventHandler(event);
|
|
32887
32929
|
}
|
|
32888
|
-
if (liveCommentId) {
|
|
32889
|
-
const n = await uploadOutputDir(apiUrl,
|
|
32930
|
+
if (liveCommentId && task.tenant_workspace_id && task.project_id) {
|
|
32931
|
+
const n = await uploadOutputDir(apiUrl, task.tenant_workspace_id, task.project_id, task.issue_id, liveCommentId, outDir);
|
|
32890
32932
|
if (n > 0)
|
|
32891
32933
|
log3(` \uD83D\uDCCE uploaded ${n} output file(s)`);
|
|
32892
32934
|
}
|
|
@@ -32898,7 +32940,8 @@ ${userPart}` : userPart;
|
|
|
32898
32940
|
const summary5 = await executePlanActions(apiUrl, task, actions, ctx);
|
|
32899
32941
|
if (summary5) {
|
|
32900
32942
|
try {
|
|
32901
|
-
|
|
32943
|
+
if (ISSUE_BASE)
|
|
32944
|
+
await apiClient.post(`${ISSUE_BASE}/comments`, { author_type: "agent", author_id: task.agent_id, author_name: "agent", body: summary5 });
|
|
32902
32945
|
} catch {}
|
|
32903
32946
|
}
|
|
32904
32947
|
}
|
|
@@ -32909,15 +32952,17 @@ ${userPart}` : userPart;
|
|
|
32909
32952
|
log3(` ⚠ ${task.key} produced no assistant output (stopReason=${stopReason})`);
|
|
32910
32953
|
}
|
|
32911
32954
|
if (ctx?.runEntry?.stopped) {
|
|
32912
|
-
await markStopped(apiUrl,
|
|
32955
|
+
await markStopped(apiUrl, task, ctx.runEntry.stopReason || "stopped");
|
|
32913
32956
|
log3(` ⏹ ${task.key} stopped`);
|
|
32914
32957
|
} else if (hadError) {
|
|
32915
|
-
|
|
32958
|
+
if (ISSUE_BASE)
|
|
32959
|
+
await apiClient.post(`${ISSUE_BASE}/fail`, {});
|
|
32916
32960
|
log3(` ✗ ${task.key} failed`);
|
|
32917
32961
|
if (baseWorkingDir)
|
|
32918
32962
|
await gcWorktreeAfterTerminal(baseWorkingDir, task.key || issueId);
|
|
32919
32963
|
} else {
|
|
32920
|
-
|
|
32964
|
+
if (ISSUE_BASE)
|
|
32965
|
+
await apiClient.post(`${ISSUE_BASE}/complete`, {});
|
|
32921
32966
|
log3(` ✓ ${task.key} complete`);
|
|
32922
32967
|
if (baseWorkingDir)
|
|
32923
32968
|
await gcWorktreeAfterTerminal(baseWorkingDir, task.key || issueId);
|
|
@@ -32925,64 +32970,18 @@ ${userPart}` : userPart;
|
|
|
32925
32970
|
} catch (e) {
|
|
32926
32971
|
const msg = fmtError(e);
|
|
32927
32972
|
if (ctx?.runEntry?.stopped) {
|
|
32928
|
-
await markStopped(apiUrl,
|
|
32973
|
+
await markStopped(apiUrl, task, ctx.runEntry.stopReason || "stopped");
|
|
32929
32974
|
log3(` ⏹ ${task.key} stopped (${msg})`);
|
|
32930
32975
|
} else {
|
|
32931
32976
|
await postStream(apiUrl, issueId, "error", { message: msg });
|
|
32932
32977
|
await postComment(`❌ spawn error: ${msg}`);
|
|
32933
|
-
|
|
32978
|
+
if (ISSUE_BASE)
|
|
32979
|
+
await apiClient.post(`${ISSUE_BASE}/fail`, {});
|
|
32934
32980
|
log3(` ✗ ${task.key} failed: ${msg}`);
|
|
32935
32981
|
}
|
|
32936
32982
|
}
|
|
32937
32983
|
}
|
|
32938
|
-
async function
|
|
32939
|
-
if (process.env.MULTI_MEMORY_ENABLED !== "1")
|
|
32940
|
-
return "";
|
|
32941
|
-
try {
|
|
32942
|
-
const issueRes = await apiClient.get(`${apiUrl}/api/issues/${task.issue_id}`);
|
|
32943
|
-
const projectId = issueRes.data?.project_id;
|
|
32944
|
-
if (!projectId)
|
|
32945
|
-
return "";
|
|
32946
|
-
const title = String(task.title || "").trim();
|
|
32947
|
-
const desc = String(task.description || "").trim();
|
|
32948
|
-
const followup = String(task.followup || "").trim();
|
|
32949
|
-
const query = [title, desc, followup].filter(Boolean).join(`
|
|
32950
|
-
`).slice(0, 4000);
|
|
32951
|
-
if (!query)
|
|
32952
|
-
return "";
|
|
32953
|
-
const res = await apiClient.post(`${apiUrl}/api/memory/recall`, {
|
|
32954
|
-
project_id: projectId,
|
|
32955
|
-
issue_id: task.issue_id,
|
|
32956
|
-
query,
|
|
32957
|
-
k: 10
|
|
32958
|
-
});
|
|
32959
|
-
if (!res.success)
|
|
32960
|
-
return "";
|
|
32961
|
-
const synthesis = String(res.data?.synthesis || "").trim();
|
|
32962
|
-
if (!synthesis)
|
|
32963
|
-
return "";
|
|
32964
|
-
const cites = Array.isArray(res.data?.citations) ? res.data.citations : [];
|
|
32965
|
-
let block = `## Project memory
|
|
32966
|
-
|
|
32967
|
-
${synthesis}
|
|
32968
|
-
`;
|
|
32969
|
-
if (cites.length) {
|
|
32970
|
-
block += `
|
|
32971
|
-
`;
|
|
32972
|
-
for (let i = 0;i < cites.length; i++) {
|
|
32973
|
-
const c = cites[i];
|
|
32974
|
-
block += `[${i + 1}] ${String(c.snippet || "").replace(/\s+/g, " ").trim()}
|
|
32975
|
-
`;
|
|
32976
|
-
}
|
|
32977
|
-
}
|
|
32978
|
-
return `${block}
|
|
32979
|
-
`;
|
|
32980
|
-
} catch (e) {
|
|
32981
|
-
log3(`memory recall failed: ${String(e?.message || e)}`);
|
|
32982
|
-
return "";
|
|
32983
|
-
}
|
|
32984
|
-
}
|
|
32985
|
-
async function buildPlanningPreamble(apiUrl, task) {
|
|
32984
|
+
async function buildPlanningPreamble(apiUrl, task, _wsId) {
|
|
32986
32985
|
const depth = typeof task.planning_depth === "number" ? task.planning_depth : 0;
|
|
32987
32986
|
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
32988
32987
|
return `# Planning (sub-task context)
|
|
@@ -32995,20 +32994,25 @@ You are acting on a sub-issue spawned by another agent. You MAY emit a \`multi-p
|
|
|
32995
32994
|
|
|
32996
32995
|
`;
|
|
32997
32996
|
}
|
|
32998
|
-
let projectId = "";
|
|
32997
|
+
let projectId = task.project_id || "";
|
|
32999
32998
|
let agentsBlock = "";
|
|
33000
32999
|
try {
|
|
33001
|
-
|
|
33002
|
-
|
|
33003
|
-
|
|
33004
|
-
|
|
33005
|
-
|
|
33006
|
-
|
|
33007
|
-
const
|
|
33008
|
-
const
|
|
33009
|
-
if (
|
|
33010
|
-
|
|
33000
|
+
if (!projectId && task.tenant_workspace_id) {
|
|
33001
|
+
const issueRes = await apiClient.get(`${apiUrl}/api/workspaces/${task.tenant_workspace_id}/issues/${task.issue_id}`);
|
|
33002
|
+
projectId = issueRes.data?.project_id || "";
|
|
33003
|
+
}
|
|
33004
|
+
const tenantWsIdLocal = task.tenant_workspace_id;
|
|
33005
|
+
if (tenantWsIdLocal) {
|
|
33006
|
+
const agentRes = await apiClient.get(`${apiUrl}/api/workspaces/${tenantWsIdLocal}/agents/${task.agent_id}`);
|
|
33007
|
+
const workspaceId = agentRes.data?.workspace_id;
|
|
33008
|
+
if (workspaceId) {
|
|
33009
|
+
const ag = await apiClient.get(`${apiUrl}/api/workspaces/${workspaceId}/agents`);
|
|
33010
|
+
const list = Array.isArray(ag.data) ? ag.data : [];
|
|
33011
|
+
const others = list.filter((a) => a.id !== task.agent_id);
|
|
33012
|
+
if (others.length) {
|
|
33013
|
+
agentsBlock = others.map((a) => `- \`${a.id}\` (${a.name}${a.type ? `, ${a.type}` : ""})`).join(`
|
|
33011
33014
|
`);
|
|
33015
|
+
}
|
|
33012
33016
|
}
|
|
33013
33017
|
}
|
|
33014
33018
|
} catch {}
|
|
@@ -33022,10 +33026,18 @@ Issue actions:
|
|
|
33022
33026
|
{"actions":[
|
|
33023
33027
|
{"type":"create","title":"...","description":"... (required, non-empty: relays full task context to the assignee)","assignee_type":"agent","assignee_id":"<agent id>"},
|
|
33024
33028
|
{"type":"update","id":"<issue id>","status":"done"},
|
|
33025
|
-
{"type":"delegate","id":"<issue id>","assignee_id":"<agent id>"}
|
|
33029
|
+
{"type":"delegate","id":"<issue id>","assignee_id":"<agent id>"},
|
|
33030
|
+
{"type":"issue.delete","id":"<issue id or key>"},
|
|
33031
|
+
{"type":"issue.delete_where","status":"todo"},
|
|
33032
|
+
{"type":"issue.list","status":"todo","assignee_id":"<agent id>","limit":20},
|
|
33033
|
+
{"type":"issue.search","query":"flaky tests","limit":10}
|
|
33026
33034
|
]}
|
|
33027
33035
|
\`\`\`
|
|
33028
33036
|
|
|
33037
|
+
Prefer the bulk \`issue.delete_where\` over \`issue.list\` + per-issue \`issue.delete\` when the user's intent matches a filter ("delete all todo issues", "remove anything assigned to agent X"). It runs in one turn instead of two.
|
|
33038
|
+
|
|
33039
|
+
Read actions (\`issue.list\`, \`issue.search\`) return the matched rows in the action summary comment. Use them to look up issue ids/keys before \`update\` / \`delegate\` / \`issue.delete\` ONLY when the per-row filter isn't enough (e.g. you need to inspect titles before acting).
|
|
33040
|
+
|
|
33029
33041
|
Agent + skill self-service (use sparingly — only when you genuinely need a new capability that isn't covered by an existing agent or skill):
|
|
33030
33042
|
|
|
33031
33043
|
\`\`\`multi-plan
|
|
@@ -33045,7 +33057,9 @@ Rules:
|
|
|
33045
33057
|
- \`agent.create\` is auto-approved on auto-autonomy issues. Caps + rate limits prevent abuse.
|
|
33046
33058
|
- \`skill.create\` ALWAYS waits for human review (skill bodies become future system prompts).
|
|
33047
33059
|
- \`allowed_tools\` on a new agent must be a subset of your own tools.
|
|
33048
|
-
- \`create\` / \`update\` / \`delegate\` target issues only in the current project (${projectId || "this project"}).
|
|
33060
|
+
- \`create\` / \`update\` / \`delegate\` / \`issue.delete\` target issues only in the current project (${projectId || "this project"}).
|
|
33061
|
+
- \`issue.list\` / \`issue.search\` / \`issue.delete_where\` default to the current project; pass \`project_id\` only when crossing into another project in the same workspace.
|
|
33062
|
+
- \`issue.delete_where\` requires \`status\` or \`assignee_id\` (no unfiltered project-wipe). Cap 50 per call.
|
|
33049
33063
|
|
|
33050
33064
|
${agentsBlock ? `Available agents you can delegate to:
|
|
33051
33065
|
${agentsBlock}
|
|
@@ -33082,7 +33096,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
33082
33096
|
const blocked3 = actions.filter((a) => a.type !== "update").length;
|
|
33083
33097
|
actions = actions.filter((a) => a.type === "update");
|
|
33084
33098
|
if (blocked3)
|
|
33085
|
-
lines.push(`-
|
|
33099
|
+
lines.push(`- [warn] ${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
|
|
33086
33100
|
}
|
|
33087
33101
|
const SUBCAPS = { "agent.create": 2, "skill.create": 3, "skill.attach": 5, "skill.detach": 5, "agent.update": 5, "session.create": 3 };
|
|
33088
33102
|
const counts = {};
|
|
@@ -33092,21 +33106,26 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
33092
33106
|
return true;
|
|
33093
33107
|
counts[a.type] = (counts[a.type] || 0) + 1;
|
|
33094
33108
|
if (counts[a.type] > cap) {
|
|
33095
|
-
lines.push(`-
|
|
33109
|
+
lines.push(`- [warn] ${a.type} sub-cap ${cap} hit, dropping extra`);
|
|
33096
33110
|
return false;
|
|
33097
33111
|
}
|
|
33098
33112
|
return true;
|
|
33099
33113
|
});
|
|
33100
33114
|
const parentId = parentTask.issue_id;
|
|
33101
|
-
const
|
|
33115
|
+
const parentWsId = parentTask.tenant_workspace_id ?? null;
|
|
33116
|
+
const parentProjectId = parentTask.project_id ?? await (async () => {
|
|
33117
|
+
if (!parentWsId)
|
|
33118
|
+
return null;
|
|
33102
33119
|
try {
|
|
33103
|
-
const r = await apiClient.get(`${apiUrl}/api/issues/${parentId}`);
|
|
33120
|
+
const r = await apiClient.get(`${apiUrl}/api/workspaces/${parentWsId}/issues/${parentId}`);
|
|
33104
33121
|
return r.data?.project_id;
|
|
33105
33122
|
} catch {
|
|
33106
33123
|
return null;
|
|
33107
33124
|
}
|
|
33108
33125
|
})();
|
|
33109
33126
|
const headers = { "x-agent-id": parentTask.agent_id, "x-origin-issue-id": parentTask.issue_id };
|
|
33127
|
+
const mutateUrl = parentWsId ? `${apiUrl}/api/workspaces/${parentWsId}/agent/issues/mutate` : "";
|
|
33128
|
+
const queryUrl = parentWsId ? `${apiUrl}/api/workspaces/${parentWsId}/agent/issues/query` : "";
|
|
33110
33129
|
if (typeof ctx.refreshLocalAgents === "function") {
|
|
33111
33130
|
try {
|
|
33112
33131
|
await ctx.refreshLocalAgents();
|
|
@@ -33125,83 +33144,172 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
33125
33144
|
assignee_id: a.assignee_id,
|
|
33126
33145
|
parent_id: a.parent_id || parentId
|
|
33127
33146
|
};
|
|
33128
|
-
const res = await apiClient.post(
|
|
33147
|
+
const res = await apiClient.post(mutateUrl, body, { headers });
|
|
33129
33148
|
if (!res.success) {
|
|
33130
|
-
lines.push(`-
|
|
33149
|
+
lines.push(`- [err] create "${a.title}": ${res.error || res.status}`);
|
|
33131
33150
|
continue;
|
|
33132
33151
|
}
|
|
33133
33152
|
const created = res.data;
|
|
33134
|
-
lines.push(`-
|
|
33153
|
+
lines.push(`- [ok] created **${created.key}** - ${created.title}${created.assignee_id ? ` -> @${created.assignee_id}` : ""} (autonomy=${created.autonomy_level || "auto"})`);
|
|
33135
33154
|
} else if (a.type === "update") {
|
|
33136
|
-
const res = await apiClient.post(
|
|
33155
|
+
const res = await apiClient.post(mutateUrl, { action: "update", ...a }, { headers });
|
|
33137
33156
|
if (!res.success) {
|
|
33138
|
-
lines.push(`-
|
|
33157
|
+
lines.push(`- [err] update ${a.id}: ${res.error || res.status}`);
|
|
33139
33158
|
continue;
|
|
33140
33159
|
}
|
|
33141
|
-
lines.push(`-
|
|
33160
|
+
lines.push(`- [ok] updated ${res.data.key}`);
|
|
33142
33161
|
if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync9(parentTask.working_dir)) {
|
|
33143
33162
|
const targetKey = res.data?.key;
|
|
33144
33163
|
if (targetKey)
|
|
33145
33164
|
await gcWorktreeAfterTerminal(parentTask.working_dir, targetKey);
|
|
33146
33165
|
}
|
|
33147
33166
|
} else if (a.type === "delegate") {
|
|
33148
|
-
const res = await apiClient.post(
|
|
33167
|
+
const res = await apiClient.post(mutateUrl, { action: "update", id: a.id, assignee_type: "agent", assignee_id: a.assignee_id, status: "todo" }, { headers });
|
|
33168
|
+
if (!res.success) {
|
|
33169
|
+
lines.push(`- [err] delegate ${a.id}: ${res.error || res.status}`);
|
|
33170
|
+
continue;
|
|
33171
|
+
}
|
|
33172
|
+
lines.push(`- [ok] delegated ${res.data.key} -> ${a.assignee_id}`);
|
|
33173
|
+
} else if (a.type === "issue.delete") {
|
|
33174
|
+
const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
|
|
33175
|
+
if (!res.success) {
|
|
33176
|
+
lines.push(`- [err] issue.delete ${a.id}: ${res.error || res.status}`);
|
|
33177
|
+
continue;
|
|
33178
|
+
}
|
|
33179
|
+
lines.push(`- [ok] deleted ${res.data?.key || a.id}`);
|
|
33180
|
+
} else if (a.type === "issue.delete_where") {
|
|
33181
|
+
const res = await apiClient.post(mutateUrl, {
|
|
33182
|
+
action: "delete_where",
|
|
33183
|
+
project_id: a.project_id || parentProjectId,
|
|
33184
|
+
status: a.status,
|
|
33185
|
+
assignee_id: a.assignee_id,
|
|
33186
|
+
limit: a.limit ?? 50
|
|
33187
|
+
}, { headers });
|
|
33149
33188
|
if (!res.success) {
|
|
33150
|
-
lines.push(`-
|
|
33189
|
+
lines.push(`- [err] issue.delete_where: ${res.error || res.status}`);
|
|
33190
|
+
continue;
|
|
33191
|
+
}
|
|
33192
|
+
const rows = Array.isArray(res.data?.deleted) ? res.data.deleted : [];
|
|
33193
|
+
const count = res.data?.count ?? rows.length;
|
|
33194
|
+
if (!count) {
|
|
33195
|
+
lines.push(`- [ok] issue.delete_where: 0 matches`);
|
|
33196
|
+
continue;
|
|
33197
|
+
}
|
|
33198
|
+
lines.push(`- [ok] issue.delete_where: deleted ${count} issue(s)`);
|
|
33199
|
+
for (const r of rows)
|
|
33200
|
+
lines.push(` - ${r.key}`);
|
|
33201
|
+
} else if (a.type === "issue.list") {
|
|
33202
|
+
const res = await apiClient.post(queryUrl, {
|
|
33203
|
+
kind: "list",
|
|
33204
|
+
project_id: a.project_id || parentProjectId,
|
|
33205
|
+
status: a.status,
|
|
33206
|
+
assignee_id: a.assignee_id,
|
|
33207
|
+
limit: a.limit ?? 20
|
|
33208
|
+
}, { headers });
|
|
33209
|
+
if (!res.success) {
|
|
33210
|
+
lines.push(`- [err] issue.list: ${res.error || res.status}`);
|
|
33211
|
+
continue;
|
|
33212
|
+
}
|
|
33213
|
+
const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
|
|
33214
|
+
if (!rows.length) {
|
|
33215
|
+
lines.push(`- [ok] issue.list: 0 issues`);
|
|
33216
|
+
continue;
|
|
33217
|
+
}
|
|
33218
|
+
lines.push(`- [ok] issue.list: ${rows.length} issue(s)`);
|
|
33219
|
+
for (const r of rows) {
|
|
33220
|
+
lines.push(` - **${r.key}** [${r.status}] ${r.title}${r.assignee_id ? ` (@${r.assignee_id})` : ""}`);
|
|
33221
|
+
}
|
|
33222
|
+
} else if (a.type === "issue.search") {
|
|
33223
|
+
const res = await apiClient.post(queryUrl, {
|
|
33224
|
+
kind: "search",
|
|
33225
|
+
project_id: a.project_id || parentProjectId,
|
|
33226
|
+
query: a.query,
|
|
33227
|
+
limit: a.limit ?? 10
|
|
33228
|
+
}, { headers });
|
|
33229
|
+
if (!res.success) {
|
|
33230
|
+
lines.push(`- [err] issue.search "${a.query}": ${res.error || res.status}`);
|
|
33231
|
+
continue;
|
|
33232
|
+
}
|
|
33233
|
+
const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
|
|
33234
|
+
if (!rows.length) {
|
|
33235
|
+
lines.push(`- [ok] issue.search "${a.query}": 0 hits`);
|
|
33151
33236
|
continue;
|
|
33152
33237
|
}
|
|
33153
|
-
lines.push(`-
|
|
33238
|
+
lines.push(`- [ok] issue.search "${a.query}": ${rows.length} hit(s)`);
|
|
33239
|
+
for (const r of rows) {
|
|
33240
|
+
lines.push(` - **${r.key}** [${r.status}] ${r.title}`);
|
|
33241
|
+
}
|
|
33154
33242
|
} else if (a.type === "agent.create") {
|
|
33155
|
-
|
|
33243
|
+
if (!parentWsId) {
|
|
33244
|
+
lines.push(`- [err] agent.create "${a.name}": no tenant workspace id`);
|
|
33245
|
+
continue;
|
|
33246
|
+
}
|
|
33247
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "create", name: a.name, type: a.agent_type, prompt: a.prompt, skill_ids: a.skill_ids, allowed_tools: a.allowed_tools }, { headers });
|
|
33156
33248
|
if (!res.success) {
|
|
33157
|
-
lines.push(`-
|
|
33249
|
+
lines.push(`- [err] agent.create "${a.name}": ${res.error || res.status}`);
|
|
33158
33250
|
continue;
|
|
33159
33251
|
}
|
|
33160
|
-
lines.push(`-
|
|
33252
|
+
lines.push(`- [ok] agent.create "${a.name}" -> ${res.data?.agent_id}`);
|
|
33161
33253
|
} else if (a.type === "agent.update") {
|
|
33162
|
-
|
|
33254
|
+
if (!parentWsId) {
|
|
33255
|
+
lines.push(`- [err] agent.update ${a.id}: no tenant workspace id`);
|
|
33256
|
+
continue;
|
|
33257
|
+
}
|
|
33258
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "update", ...a }, { headers });
|
|
33163
33259
|
if (!res.success) {
|
|
33164
|
-
lines.push(`-
|
|
33260
|
+
lines.push(`- [err] agent.update ${a.id}: ${res.error || res.status}`);
|
|
33165
33261
|
continue;
|
|
33166
33262
|
}
|
|
33167
33263
|
if (res.data?.queued)
|
|
33168
|
-
lines.push(`-
|
|
33264
|
+
lines.push(`- [pending] agent.update ${a.id} queued`);
|
|
33169
33265
|
else
|
|
33170
|
-
lines.push(`-
|
|
33266
|
+
lines.push(`- [ok] agent.update ${a.id}`);
|
|
33171
33267
|
} else if (a.type === "skill.create") {
|
|
33172
|
-
|
|
33268
|
+
if (!parentWsId) {
|
|
33269
|
+
lines.push(`- [err] skill.create "${a.name}": no tenant workspace id`);
|
|
33270
|
+
continue;
|
|
33271
|
+
}
|
|
33272
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/skills/mutate`, { action: "create", name: a.name, version: a.version, description: a.description, body: a.body, files: a.files }, { headers });
|
|
33173
33273
|
if (!res.success) {
|
|
33174
|
-
lines.push(`-
|
|
33274
|
+
lines.push(`- [err] skill.create "${a.name}": ${res.error || res.status}`);
|
|
33175
33275
|
continue;
|
|
33176
33276
|
}
|
|
33177
|
-
lines.push(`-
|
|
33277
|
+
lines.push(`- [pending] skill.create "${a.name}" queued for human review (op ${res.data?.pending_op_id})`);
|
|
33178
33278
|
} else if (a.type === "session.create") {
|
|
33179
|
-
|
|
33279
|
+
if (!parentWsId) {
|
|
33280
|
+
lines.push(`- [err] session.create role=${a.role}: no tenant workspace id`);
|
|
33281
|
+
continue;
|
|
33282
|
+
}
|
|
33283
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/sessions`, { workspace_id: a.workspace_id, agent_id: a.agent_id, role: a.role, device_id: a.device_id }, { headers });
|
|
33180
33284
|
if (!res.success) {
|
|
33181
|
-
lines.push(`-
|
|
33285
|
+
lines.push(`- [err] session.create role=${a.role}: ${res.error || res.status}`);
|
|
33182
33286
|
continue;
|
|
33183
33287
|
}
|
|
33184
33288
|
const sess = res.data?.session;
|
|
33185
33289
|
const reused = res.data?.reused;
|
|
33186
|
-
lines.push(`- ${reused ? "
|
|
33290
|
+
lines.push(`- [${reused ? "reuse" : "ok"}] session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
|
|
33187
33291
|
} else if (a.type === "skill.attach" || a.type === "skill.detach") {
|
|
33188
33292
|
const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
|
|
33189
|
-
|
|
33293
|
+
if (!parentWsId) {
|
|
33294
|
+
lines.push(`- [err] ${a.type}: no tenant workspace id`);
|
|
33295
|
+
continue;
|
|
33296
|
+
}
|
|
33297
|
+
const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action, agent_id: a.agent_id, skill_id: a.skill_id }, { headers });
|
|
33190
33298
|
if (!res.success) {
|
|
33191
|
-
lines.push(`-
|
|
33299
|
+
lines.push(`- [err] ${a.type} ${a.skill_id}->${a.agent_id}: ${res.error || res.status}`);
|
|
33192
33300
|
continue;
|
|
33193
33301
|
}
|
|
33194
33302
|
if (res.data?.queued)
|
|
33195
|
-
lines.push(`-
|
|
33303
|
+
lines.push(`- [pending] ${a.type} queued`);
|
|
33196
33304
|
else
|
|
33197
|
-
lines.push(`-
|
|
33305
|
+
lines.push(`- [ok] ${a.type} ${a.skill_id} <-> ${a.agent_id}`);
|
|
33198
33306
|
}
|
|
33199
33307
|
} catch (e) {
|
|
33200
|
-
lines.push(`-
|
|
33308
|
+
lines.push(`- [err] ${a.type} failed: ${String(e)}`);
|
|
33201
33309
|
}
|
|
33202
33310
|
}
|
|
33203
33311
|
if (truncated)
|
|
33204
|
-
lines.push(`-
|
|
33312
|
+
lines.push(`- [warn] action list truncated at ${PLAN_ACTION_LIMIT}`);
|
|
33205
33313
|
if (!lines.length)
|
|
33206
33314
|
return "";
|
|
33207
33315
|
return `**Planning actions**
|
|
@@ -33289,9 +33397,10 @@ async function postStream(_apiUrl, issueId, event_type, payload) {
|
|
|
33289
33397
|
} catch {}
|
|
33290
33398
|
}
|
|
33291
33399
|
}
|
|
33292
|
-
async function downloadCommentAttachments(apiUrl, commentId, destDir) {
|
|
33400
|
+
async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, commentId, destDir) {
|
|
33401
|
+
const issueBase = `${apiUrl}/api/workspaces/${wsId}/projects/${projectId}/issues/${issueId}`;
|
|
33293
33402
|
try {
|
|
33294
|
-
const list = await apiClient.get(`${
|
|
33403
|
+
const list = await apiClient.get(`${issueBase}/comments/${commentId}/attachments`);
|
|
33295
33404
|
const items = list.data?.results || list.data || [];
|
|
33296
33405
|
if (!Array.isArray(items) || items.length === 0)
|
|
33297
33406
|
return [];
|
|
@@ -33299,7 +33408,7 @@ async function downloadCommentAttachments(apiUrl, commentId, destDir) {
|
|
|
33299
33408
|
const token = authTokenHeader();
|
|
33300
33409
|
const out = [];
|
|
33301
33410
|
for (const it of items) {
|
|
33302
|
-
const res = await fetch(`${
|
|
33411
|
+
const res = await fetch(`${issueBase}/attachments/${it.id}`, { headers: token ? { Authorization: token } : {} });
|
|
33303
33412
|
if (!res.ok)
|
|
33304
33413
|
continue;
|
|
33305
33414
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
@@ -33317,7 +33426,7 @@ function authTokenHeader() {
|
|
|
33317
33426
|
const cfg = loadConfig();
|
|
33318
33427
|
return cfg.token ? `Bearer ${cfg.token}` : null;
|
|
33319
33428
|
}
|
|
33320
|
-
async function uploadOutputDir(apiUrl,
|
|
33429
|
+
async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
|
|
33321
33430
|
if (!existsSync9(dir))
|
|
33322
33431
|
return 0;
|
|
33323
33432
|
const files = [];
|
|
@@ -33339,6 +33448,7 @@ async function uploadOutputDir(apiUrl, commentId, dir, issueId) {
|
|
|
33339
33448
|
if (!files.length)
|
|
33340
33449
|
return 0;
|
|
33341
33450
|
const token = authTokenHeader();
|
|
33451
|
+
const issueBase = `${apiUrl}/api/workspaces/${wsId}/projects/${projectId}/issues/${issueId}`;
|
|
33342
33452
|
let uploaded = 0;
|
|
33343
33453
|
for (const f of files) {
|
|
33344
33454
|
try {
|
|
@@ -33349,9 +33459,7 @@ async function uploadOutputDir(apiUrl, commentId, dir, issueId) {
|
|
|
33349
33459
|
const headers = {};
|
|
33350
33460
|
if (token)
|
|
33351
33461
|
headers.Authorization = token;
|
|
33352
|
-
|
|
33353
|
-
headers["x-issue-id"] = issueId;
|
|
33354
|
-
const res = await fetch(`${apiUrl}/api/attachments/comments/${commentId}`, {
|
|
33462
|
+
const res = await fetch(`${issueBase}/comments/${commentId}/attachments`, {
|
|
33355
33463
|
method: "POST",
|
|
33356
33464
|
body: form,
|
|
33357
33465
|
headers
|
|
@@ -33606,6 +33714,14 @@ function sleep6(ms) {
|
|
|
33606
33714
|
function openTasksDb() {
|
|
33607
33715
|
ensureDirs2();
|
|
33608
33716
|
const db2 = new Database3(TASKS_DB_PATH4);
|
|
33717
|
+
const existing = db2.query("PRAGMA table_info(tasks)").all();
|
|
33718
|
+
if (existing.length) {
|
|
33719
|
+
const have = new Set(existing.map((c) => c.name));
|
|
33720
|
+
const wanted = ["id", "status", "payload", "attempts", "created_at", "started_at", "finished_at", "error", "agent_id", "issue_id"];
|
|
33721
|
+
const compatible = wanted.every((col) => have.has(col));
|
|
33722
|
+
if (!compatible)
|
|
33723
|
+
db2.exec("DROP TABLE tasks");
|
|
33724
|
+
}
|
|
33609
33725
|
db2.exec(`
|
|
33610
33726
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
33611
33727
|
id TEXT PRIMARY KEY,
|
|
@@ -33615,23 +33731,15 @@ function openTasksDb() {
|
|
|
33615
33731
|
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
33616
33732
|
started_at INTEGER,
|
|
33617
33733
|
finished_at INTEGER,
|
|
33618
|
-
error TEXT
|
|
33734
|
+
error TEXT,
|
|
33735
|
+
agent_id TEXT,
|
|
33736
|
+
issue_id TEXT
|
|
33619
33737
|
);
|
|
33620
33738
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
33621
|
-
`);
|
|
33622
|
-
const cols = db2.query("PRAGMA table_info(tasks)").all();
|
|
33623
|
-
const have = new Set(cols.map((c) => c.name));
|
|
33624
|
-
if (!have.has("agent_id"))
|
|
33625
|
-
db2.exec("ALTER TABLE tasks ADD COLUMN agent_id TEXT");
|
|
33626
|
-
if (!have.has("issue_id"))
|
|
33627
|
-
db2.exec("ALTER TABLE tasks ADD COLUMN issue_id TEXT");
|
|
33628
|
-
db2.exec(`
|
|
33629
33739
|
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(agent_id, status);
|
|
33630
33740
|
CREATE INDEX IF NOT EXISTS idx_tasks_issue ON tasks(issue_id);
|
|
33631
33741
|
`);
|
|
33632
33742
|
db2.run("UPDATE tasks SET status = 'queued' WHERE status = 'pending'");
|
|
33633
|
-
db2.run("UPDATE tasks SET agent_id = json_extract(payload, '$.agent_id') WHERE agent_id IS NULL AND payload IS NOT NULL AND json_valid(payload)");
|
|
33634
|
-
db2.run("UPDATE tasks SET issue_id = json_extract(payload, '$.issue_id') WHERE issue_id IS NULL AND payload IS NOT NULL AND json_valid(payload)");
|
|
33635
33743
|
return db2;
|
|
33636
33744
|
}
|
|
33637
33745
|
async function pickFreePort() {
|
|
@@ -33753,16 +33861,20 @@ async function probeTunnel(url2) {
|
|
|
33753
33861
|
return false;
|
|
33754
33862
|
}
|
|
33755
33863
|
}
|
|
33756
|
-
async function announceTunnel(apiUrl, deviceId, tunnelUrl) {
|
|
33864
|
+
async function announceTunnel(apiUrl, wsId, deviceId, tunnelUrl) {
|
|
33865
|
+
if (!wsId) {
|
|
33866
|
+
log3(`announce-tunnel skipped: no workspace id`);
|
|
33867
|
+
return;
|
|
33868
|
+
}
|
|
33757
33869
|
try {
|
|
33758
|
-
await apiClient.post(`${apiUrl}/api/devices/${deviceId}/announce-tunnel`, { tunnel_url: tunnelUrl });
|
|
33870
|
+
await apiClient.post(`${apiUrl}/api/workspaces/${wsId}/devices/${deviceId}/announce-tunnel`, { tunnel_url: tunnelUrl });
|
|
33759
33871
|
} catch (e) {
|
|
33760
33872
|
log3(`announce-tunnel failed: ${String(e?.message ?? e)}`);
|
|
33761
33873
|
}
|
|
33762
33874
|
}
|
|
33763
|
-
async function ackDispatch(apiUrl, dispatchId, secret) {
|
|
33875
|
+
async function ackDispatch(apiUrl, wsId, dispatchId, secret) {
|
|
33764
33876
|
try {
|
|
33765
|
-
await fetch(`${apiUrl}/api/
|
|
33877
|
+
await fetch(`${apiUrl}/api/workspaces/${wsId}/dispatches/${dispatchId}/ack`, {
|
|
33766
33878
|
method: "POST",
|
|
33767
33879
|
headers: { authorization: `Bearer ${secret}` }
|
|
33768
33880
|
});
|
|
@@ -33770,12 +33882,12 @@ async function ackDispatch(apiUrl, dispatchId, secret) {
|
|
|
33770
33882
|
log3(`ack dispatch ${dispatchId} failed: ${String(e)}`);
|
|
33771
33883
|
}
|
|
33772
33884
|
}
|
|
33773
|
-
async function drainOfflineDispatches(apiUrl, deviceId, secret, db2, onEnqueued, isAlive) {
|
|
33885
|
+
async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive) {
|
|
33774
33886
|
if (!isAlive())
|
|
33775
33887
|
return;
|
|
33776
33888
|
let list;
|
|
33777
33889
|
try {
|
|
33778
|
-
list = await apiClient.get(`${apiUrl}/api/devices/${deviceId}/dispatches/pending`);
|
|
33890
|
+
list = await apiClient.get(`${apiUrl}/api/workspaces/${wsId}/devices/${deviceId}/dispatches/pending`);
|
|
33779
33891
|
} catch (e) {
|
|
33780
33892
|
log3(`drain list failed: ${String(e)}`);
|
|
33781
33893
|
return;
|
|
@@ -33790,7 +33902,7 @@ async function drainOfflineDispatches(apiUrl, deviceId, secret, db2, onEnqueued,
|
|
|
33790
33902
|
return;
|
|
33791
33903
|
}
|
|
33792
33904
|
try {
|
|
33793
|
-
const res = await fetch(`${apiUrl}/api/
|
|
33905
|
+
const res = await fetch(`${apiUrl}/api/workspaces/${wsId}/dispatches/${r.id}/claim`, {
|
|
33794
33906
|
method: "POST",
|
|
33795
33907
|
headers: { authorization: `Bearer ${secret}` }
|
|
33796
33908
|
});
|
|
@@ -33803,7 +33915,7 @@ async function drainOfflineDispatches(apiUrl, deviceId, secret, db2, onEnqueued,
|
|
|
33803
33915
|
db2.run("INSERT INTO tasks (id, status, payload, agent_id, issue_id) VALUES (?, 'queued', ?, ?, ?)", [taskId, JSON.stringify(task), task.agent_id ?? null, task.issue_id ?? null]);
|
|
33804
33916
|
if (task.issue_id)
|
|
33805
33917
|
postStream(apiUrl, task.issue_id, "queued", { drained: true });
|
|
33806
|
-
ackDispatch(apiUrl, r.id, secret);
|
|
33918
|
+
ackDispatch(apiUrl, wsId, r.id, secret);
|
|
33807
33919
|
} catch (e) {
|
|
33808
33920
|
log3(`drain ${r.id} failed: ${String(e)}`);
|
|
33809
33921
|
}
|
|
@@ -33901,8 +34013,8 @@ async function daemonMain({ cfg, apiUrl }) {
|
|
|
33901
34013
|
const pos = db2.query("SELECT COUNT(*) AS c FROM tasks WHERE status = 'queued' AND created_at <= (SELECT created_at FROM tasks WHERE id = ?)").get(taskId)?.c ?? 1;
|
|
33902
34014
|
if (t.issue_id)
|
|
33903
34015
|
postStream(apiUrl, t.issue_id, "queued", { queue_position: pos });
|
|
33904
|
-
if (t.dispatch_id)
|
|
33905
|
-
ackDispatch(apiUrl, t.dispatch_id, cfg.dispatchSecret);
|
|
34016
|
+
if (t.dispatch_id && cfg.workspaceId)
|
|
34017
|
+
ackDispatch(apiUrl, cfg.workspaceId, t.dispatch_id, cfg.dispatchSecret);
|
|
33906
34018
|
queueMicrotask(() => schedule2());
|
|
33907
34019
|
return Response.json({ accepted: true, task_id: taskId }, { status: 202 });
|
|
33908
34020
|
} catch (e) {
|
|
@@ -33967,11 +34079,13 @@ async function daemonMain({ cfg, apiUrl }) {
|
|
|
33967
34079
|
log3(`☁️ Tunnel up: ${tunnel.url}`);
|
|
33968
34080
|
}
|
|
33969
34081
|
let alive = true;
|
|
33970
|
-
await announceTunnel(apiUrl, cfg.deviceId, tunnel?.url ?? null);
|
|
34082
|
+
await announceTunnel(apiUrl, cfg.workspaceId, cfg.deviceId, tunnel?.url ?? null);
|
|
33971
34083
|
try {
|
|
33972
|
-
await materializeBundle(apiUrl, cfg.deviceId, log3);
|
|
34084
|
+
await materializeBundle(apiUrl, cfg.workspaceId, cfg.deviceId, log3);
|
|
33973
34085
|
} catch {}
|
|
33974
|
-
|
|
34086
|
+
if (cfg.workspaceId) {
|
|
34087
|
+
drainOfflineDispatches(apiUrl, cfg.deviceId, cfg.workspaceId, cfg.dispatchSecret, db2, () => schedule2(), () => alive);
|
|
34088
|
+
}
|
|
33975
34089
|
const shutdown = async (reason) => {
|
|
33976
34090
|
if (!alive)
|
|
33977
34091
|
return;
|
|
@@ -33984,7 +34098,7 @@ async function daemonMain({ cfg, apiUrl }) {
|
|
|
33984
34098
|
tunnel?.child?.kill();
|
|
33985
34099
|
} catch {}
|
|
33986
34100
|
try {
|
|
33987
|
-
await announceTunnel(apiUrl, cfg.deviceId, null);
|
|
34101
|
+
await announceTunnel(apiUrl, cfg.workspaceId, cfg.deviceId, null);
|
|
33988
34102
|
} catch {}
|
|
33989
34103
|
if (existsSync10(PID_PATH3))
|
|
33990
34104
|
unlinkSync5(PID_PATH3);
|
|
@@ -34016,8 +34130,10 @@ async function daemonMain({ cfg, apiUrl }) {
|
|
|
34016
34130
|
if (next) {
|
|
34017
34131
|
tunnel = next;
|
|
34018
34132
|
log3(`☁️ Tunnel up: ${tunnel.url}`);
|
|
34019
|
-
await announceTunnel(apiUrl, cfg.deviceId, tunnel.url);
|
|
34020
|
-
|
|
34133
|
+
await announceTunnel(apiUrl, cfg.workspaceId, cfg.deviceId, tunnel.url);
|
|
34134
|
+
if (cfg.workspaceId) {
|
|
34135
|
+
drainOfflineDispatches(apiUrl, cfg.deviceId, cfg.workspaceId, cfg.dispatchSecret, db2, () => schedule2(), () => alive);
|
|
34136
|
+
}
|
|
34021
34137
|
return;
|
|
34022
34138
|
}
|
|
34023
34139
|
const wait = Math.min(30000, 2000 * attempt);
|
|
@@ -34168,10 +34284,20 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
|
|
|
34168
34284
|
process.on("exit", () => releasePidLock(PID_PATH));
|
|
34169
34285
|
}
|
|
34170
34286
|
yield* logger.log(`connect: device=${cfg.deviceId}`);
|
|
34171
|
-
|
|
34287
|
+
if (!cfg.workspaceId) {
|
|
34288
|
+
return yield* exports_Effect.fail(new UsageError({ message: "config.workspaceId missing — re-pair via 'multi-agent setup'." }));
|
|
34289
|
+
}
|
|
34290
|
+
const stopFlusher = startOutboxFlusher(cfg.apiUrl, cfg.workspaceId);
|
|
34172
34291
|
yield* exports_Effect.tryPromise({
|
|
34173
34292
|
try: () => daemonMain({ cfg, apiUrl: cfg.apiUrl }),
|
|
34174
|
-
catch: (cause3) =>
|
|
34293
|
+
catch: (cause3) => {
|
|
34294
|
+
try {
|
|
34295
|
+
const err = cause3;
|
|
34296
|
+
console.error(`[daemon] crash: ${err?.message ?? String(err)}
|
|
34297
|
+
${err?.stack ?? ""}`);
|
|
34298
|
+
} catch {}
|
|
34299
|
+
return new DaemonError({ message: "daemon failed", cause: cause3 });
|
|
34300
|
+
}
|
|
34175
34301
|
}).pipe(exports_Effect.ensuring(exports_Effect.sync(() => stopFlusher())));
|
|
34176
34302
|
});
|
|
34177
34303
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipers-dev/multi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"multi-agent": "./dist/index.js"
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"undici": "^7.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@multi/lib": "
|
|
24
|
+
"@multi/lib": "0.1.0"
|
|
25
25
|
}
|
|
26
26
|
}
|