@hdwebsoft/hdcode-agent-team 0.2.0 → 0.2.1
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 +89 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { relative, isAbsolute } from "node:path";
|
|
3
|
+
|
|
1
4
|
// src/teams.ts
|
|
2
5
|
import { readdir as readdir2 } from "node:fs/promises";
|
|
3
6
|
import { join as join3 } from "node:path";
|
|
@@ -360,6 +363,7 @@ import { tool as tool2 } from "@opencode-ai/plugin";
|
|
|
360
363
|
|
|
361
364
|
// src/reservations.ts
|
|
362
365
|
import { join as join5 } from "node:path";
|
|
366
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
363
367
|
import { randomUUID } from "node:crypto";
|
|
364
368
|
var DEFAULT_TTL_SECONDS = 1800;
|
|
365
369
|
var MAX_TTL_SECONDS = 14400;
|
|
@@ -590,6 +594,70 @@ async function checkFile(base, teamName, path, agentId) {
|
|
|
590
594
|
const canEdit = !reserved || !!agentId && holders.every((h) => h.agentId === agentId);
|
|
591
595
|
return { reserved, canEdit, holders };
|
|
592
596
|
}
|
|
597
|
+
async function checkFileAcrossTeams(base, filePath, sessionId) {
|
|
598
|
+
const root = join5(base, TEAM_ROOT);
|
|
599
|
+
if (!await exists(root))
|
|
600
|
+
return null;
|
|
601
|
+
let normalized;
|
|
602
|
+
try {
|
|
603
|
+
normalized = normalizeRepoPath(filePath);
|
|
604
|
+
} catch {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
let entries;
|
|
608
|
+
try {
|
|
609
|
+
entries = await readdir4(root, { withFileTypes: true });
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
for (const entry of entries) {
|
|
614
|
+
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
615
|
+
continue;
|
|
616
|
+
const storePath = reservationStorePath(base, entry.name);
|
|
617
|
+
if (!await exists(storePath))
|
|
618
|
+
continue;
|
|
619
|
+
let store;
|
|
620
|
+
try {
|
|
621
|
+
store = await readJson(storePath);
|
|
622
|
+
} catch {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
pruneExpired(store);
|
|
626
|
+
for (const r of store.reservations) {
|
|
627
|
+
if (r.files.includes(normalized)) {
|
|
628
|
+
return {
|
|
629
|
+
teamName: entry.name,
|
|
630
|
+
holder: {
|
|
631
|
+
reservationId: r.id,
|
|
632
|
+
agentId: r.agentId,
|
|
633
|
+
taskId: r.taskId,
|
|
634
|
+
reason: r.reason,
|
|
635
|
+
expiresAt: r.expiresAt,
|
|
636
|
+
matchedBy: "file",
|
|
637
|
+
value: normalized
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
for (const p of r.prefixes) {
|
|
642
|
+
if (isPathInsidePrefix(normalized, p)) {
|
|
643
|
+
return {
|
|
644
|
+
teamName: entry.name,
|
|
645
|
+
holder: {
|
|
646
|
+
reservationId: r.id,
|
|
647
|
+
agentId: r.agentId,
|
|
648
|
+
taskId: r.taskId,
|
|
649
|
+
reason: r.reason,
|
|
650
|
+
expiresAt: r.expiresAt,
|
|
651
|
+
matchedBy: "prefix",
|
|
652
|
+
value: p
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
593
661
|
|
|
594
662
|
// src/tools/task-tools.ts
|
|
595
663
|
function parseStringArray(raw, label) {
|
|
@@ -1143,6 +1211,7 @@ function createReservationTools(directory) {
|
|
|
1143
1211
|
}
|
|
1144
1212
|
|
|
1145
1213
|
// src/index.ts
|
|
1214
|
+
var WRITE_TOOLS = new Set(["edit", "write", "create"]);
|
|
1146
1215
|
var HDTeamPlugin = async ({ directory }) => {
|
|
1147
1216
|
return {
|
|
1148
1217
|
"experimental.session.compacting": async (_input, output) => {
|
|
@@ -1159,6 +1228,26 @@ Use team_status(teamName) to get full details. Continue any in-progress tasks.
|
|
|
1159
1228
|
`);
|
|
1160
1229
|
}
|
|
1161
1230
|
},
|
|
1231
|
+
"tool.execute.before": async (input, output) => {
|
|
1232
|
+
if (!WRITE_TOOLS.has(input.tool))
|
|
1233
|
+
return;
|
|
1234
|
+
const filePath = output.args.filePath;
|
|
1235
|
+
if (!filePath)
|
|
1236
|
+
return;
|
|
1237
|
+
let repoPath = filePath;
|
|
1238
|
+
if (isAbsolute(filePath)) {
|
|
1239
|
+
repoPath = relative(directory, filePath);
|
|
1240
|
+
if (repoPath.startsWith(".."))
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const match = await checkFileAcrossTeams(directory, repoPath);
|
|
1244
|
+
if (match) {
|
|
1245
|
+
const h = match.holder;
|
|
1246
|
+
const who = h.taskId ? `${h.agentId} (task ${h.taskId})` : h.agentId;
|
|
1247
|
+
const reason = h.reason ? ` — ${h.reason}` : "";
|
|
1248
|
+
throw new Error(`\uD83D\uDD12 File "${repoPath}" is reserved by ${who} in team "${match.teamName}"${reason}. ` + `Use file_check to see details or wait for the reservation to expire (${h.expiresAt}).`);
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1162
1251
|
tool: {
|
|
1163
1252
|
...createTeamTools(directory),
|
|
1164
1253
|
...createTaskTools(directory),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hdwebsoft/hdcode-agent-team",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OpenCode plugin for multi-agent team coordination — per-agent inboxes, task management with dependency tracking, file reservation/locking, and atomic mkdir-based concurrency control.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|