@ai-development-environment/control-agent 0.0.90 → 0.0.92

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 (2) hide show
  1. package/dist/control-agent.js +188 -10
  2. package/package.json +1 -1
@@ -6172,6 +6172,7 @@ var WORKTREE_OPERATIONS = [
6172
6172
  "PULL",
6173
6173
  "PUSH",
6174
6174
  "RESET",
6175
+ "CLEAR_LOCK",
6175
6176
  "STASH_ALL",
6176
6177
  "STAGE_ALL",
6177
6178
  "UNSTAGE_ALL"
@@ -14355,6 +14356,20 @@ function rootPath(location) {
14355
14356
  }
14356
14357
  return path;
14357
14358
  }
14359
+ function isContainedPath(base, candidate) {
14360
+ return candidate === base || candidate.startsWith(`${base}${sep6}`);
14361
+ }
14362
+ async function resolvedSkillRoot(location) {
14363
+ const root = rootPath(location);
14364
+ const resolvedRoot = await realpath6(root);
14365
+ if (location.scope === "PROJECT") {
14366
+ const resolvedProject = await realpath6(location.folder);
14367
+ if (!isContainedPath(resolvedProject, resolvedRoot)) {
14368
+ throw new Error("Project skill root resolves outside the project folder");
14369
+ }
14370
+ }
14371
+ return { root, resolvedRoot };
14372
+ }
14358
14373
  function targetForFolder(targets, folder) {
14359
14374
  return folder ? targets.find((target2) => resolve6(target2.folder) === resolve6(folder)) : void 0;
14360
14375
  }
@@ -14395,14 +14410,15 @@ async function listPackageFiles(skillDirectory) {
14395
14410
  await visit(skillDirectory);
14396
14411
  return files;
14397
14412
  }
14398
- async function readPackage(directory) {
14413
+ async function readPackage(directory, resolvedRoot) {
14399
14414
  const metadata = await lstat3(directory);
14400
- let resolvedDirectory = directory;
14401
- if (metadata.isSymbolicLink()) {
14402
- resolvedDirectory = await realpath6(directory);
14403
- } else if (!metadata.isDirectory()) {
14415
+ if (!metadata.isSymbolicLink() && !metadata.isDirectory()) {
14404
14416
  throw new Error("Skill path is not a directory");
14405
14417
  }
14418
+ const resolvedDirectory = await realpath6(directory);
14419
+ if (!isContainedPath(resolvedRoot, resolvedDirectory)) {
14420
+ throw new Error("Skill package resolves outside its configured root");
14421
+ }
14406
14422
  const files = await listPackageFiles(resolvedDirectory);
14407
14423
  const definition = files.find((file) => file.path === "SKILL.md");
14408
14424
  if (!definition) throw new Error("Skill does not contain SKILL.md");
@@ -14442,6 +14458,15 @@ function consumersForRoot(configured, rootKind, scope) {
14442
14458
  async function scanRoot2(location, configured, targets, warnings) {
14443
14459
  const root = rootPath(location);
14444
14460
  if (!await directoryExists(root)) return [];
14461
+ let resolvedRoot;
14462
+ try {
14463
+ resolvedRoot = (await resolvedSkillRoot(location)).resolvedRoot;
14464
+ } catch (error) {
14465
+ warnings.push(
14466
+ `${root}: ${error instanceof Error ? error.message : String(error)}`
14467
+ );
14468
+ return [];
14469
+ }
14445
14470
  const consumers = consumersForRoot(
14446
14471
  configured,
14447
14472
  location.rootKind,
@@ -14454,7 +14479,7 @@ async function scanRoot2(location, configured, targets, warnings) {
14454
14479
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
14455
14480
  const directory = join9(root, entry.name);
14456
14481
  try {
14457
- const skillPackage = await readPackage(directory);
14482
+ const skillPackage = await readPackage(directory, resolvedRoot);
14458
14483
  const relativeDirectory = location.folder ? relative6(location.folder, directory).split(sep6).join("/") : "";
14459
14484
  installations.push({
14460
14485
  ...location,
@@ -14529,9 +14554,10 @@ var readSkills = async (payload) => {
14529
14554
  if (!allowed.has(`${request.scope}:${root}`)) {
14530
14555
  throw new Error("Requested skill root is not enabled or configured");
14531
14556
  }
14557
+ const { resolvedRoot } = await resolvedSkillRoot(request);
14532
14558
  packages.push({
14533
14559
  ...request,
14534
- package: await readPackage(join9(root, request.skillName))
14560
+ package: await readPackage(join9(root, request.skillName), resolvedRoot)
14535
14561
  });
14536
14562
  }
14537
14563
  return { ...successfulProcess6, packages };
@@ -18012,6 +18038,9 @@ var operateWorktree = async (payload, timeoutMs, signal) => {
18012
18038
  await runGit(["clean", "-fd"], "Could not remove untracked files");
18013
18039
  break;
18014
18040
  }
18041
+ case "CLEAR_LOCK":
18042
+ await rm10(join14(input.gitDirectory, "index.lock"), { force: true });
18043
+ break;
18015
18044
  case "STASH_ALL":
18016
18045
  await runGit(["stash", "push", "--include-untracked"], "Stash failed");
18017
18046
  break;
@@ -51216,6 +51245,89 @@ function opencodeQuestionResolution(value) {
51216
51245
  const requestId = data.requestID ?? properties.requestID;
51217
51246
  return typeof requestId === "string" ? requestId : void 0;
51218
51247
  }
51248
+ function stringArray3(value) {
51249
+ return Array.isArray(value) ? value.map(String) : [];
51250
+ }
51251
+ function opencodePermissionRequest(value, fallbackSurface) {
51252
+ const event = asRecord(value);
51253
+ const payload = asRecord(event.payload);
51254
+ const type = String(payload.type ?? event.type ?? "");
51255
+ if (type && type !== "permission.asked" && type !== "permission.v2.asked")
51256
+ return null;
51257
+ const properties = asRecord(payload.properties ?? event.properties);
51258
+ const request = type ? asRecord(
51259
+ properties.request ?? properties.data ?? payload.data ?? event.data ?? properties
51260
+ ) : event;
51261
+ const permission = request.permission ?? request.action;
51262
+ if (typeof permission !== "string") return null;
51263
+ return {
51264
+ id: String(request.id ?? properties.id ?? "permission"),
51265
+ sessionId: typeof request.sessionID === "string" ? request.sessionID : typeof properties.sessionID === "string" ? properties.sessionID : void 0,
51266
+ surface: type === "permission.v2.asked" ? "V2" : fallbackSurface ?? "LEGACY",
51267
+ permission,
51268
+ patterns: stringArray3(request.patterns ?? request.resources),
51269
+ always: stringArray3(request.always ?? request.save)
51270
+ };
51271
+ }
51272
+ function opencodePermissionResolution(value) {
51273
+ const event = asRecord(value);
51274
+ const payload = asRecord(event.payload);
51275
+ const type = String(payload.type ?? event.type ?? "");
51276
+ if (type !== "permission.replied" && type !== "permission.v2.replied")
51277
+ return void 0;
51278
+ const properties = asRecord(payload.properties ?? event.properties);
51279
+ const data = asRecord(payload.data ?? event.data ?? properties.data);
51280
+ const requestId = data.requestID ?? properties.requestID;
51281
+ return typeof requestId === "string" ? requestId : void 0;
51282
+ }
51283
+ function permissionQuestions(request) {
51284
+ const permission = request.permission.replaceAll("_", " ");
51285
+ const targets = request.patterns.length ? ` for ${request.patterns.join(", ")}` : "";
51286
+ return [
51287
+ {
51288
+ id: "permission",
51289
+ header: "Permission required",
51290
+ prompt: `OpenCode requests ${permission} permission${targets}.`,
51291
+ multiSelect: false,
51292
+ allowCustom: false,
51293
+ options: [
51294
+ {
51295
+ label: "Allow once",
51296
+ description: "Approve only this request."
51297
+ },
51298
+ ...request.always.length ? [
51299
+ {
51300
+ label: "Always allow",
51301
+ description: `Approve this request and remember ${request.always.join(", ")}.`
51302
+ }
51303
+ ] : [],
51304
+ {
51305
+ label: "Reject",
51306
+ description: "Deny this request and let OpenCode continue safely."
51307
+ }
51308
+ ]
51309
+ }
51310
+ ];
51311
+ }
51312
+ function permissionReply(value) {
51313
+ const selected = answerArrays(value).flat().map((answer) => answer.trim().toLowerCase()).filter(Boolean);
51314
+ if (selected.length !== 1)
51315
+ throw new Error("Select one response for the OpenCode permission request");
51316
+ switch (selected[0]) {
51317
+ case "allow once":
51318
+ case "once":
51319
+ return "once";
51320
+ case "always allow":
51321
+ case "allow always":
51322
+ case "always":
51323
+ return "always";
51324
+ case "reject":
51325
+ case "deny":
51326
+ return "reject";
51327
+ default:
51328
+ throw new Error(`Unknown OpenCode permission response: ${selected[0]}`);
51329
+ }
51330
+ }
51219
51331
  function responseItems(value) {
51220
51332
  const data = resultData(value);
51221
51333
  if (Array.isArray(data)) return data;
@@ -51344,6 +51456,7 @@ var OpenCodeAdapter = class {
51344
51456
  let stopReason = null;
51345
51457
  const streamController = new AbortController();
51346
51458
  const questionSurfaces = /* @__PURE__ */ new Map();
51459
+ const permissionSurfaces = /* @__PURE__ */ new Map();
51347
51460
  const pendingQuestions = /* @__PURE__ */ new Set();
51348
51461
  let sawQuestion = false;
51349
51462
  const reportQuestion = async (request) => {
@@ -51359,6 +51472,19 @@ var OpenCodeAdapter = class {
51359
51472
  throw error;
51360
51473
  }
51361
51474
  };
51475
+ const reportPermission = async (request) => {
51476
+ if (permissionSurfaces.has(request.id)) return;
51477
+ permissionSurfaces.set(request.id, request.surface);
51478
+ pendingQuestions.add(request.id);
51479
+ sawQuestion = true;
51480
+ try {
51481
+ await callbacks.onQuestion(request.id, permissionQuestions(request));
51482
+ } catch (error) {
51483
+ permissionSurfaces.delete(request.id);
51484
+ pendingQuestions.delete(request.id);
51485
+ throw error;
51486
+ }
51487
+ };
51362
51488
  const reconcileLegacyQuestions = async () => {
51363
51489
  const response = await client2.question.list(
51364
51490
  { directory: cwd },
@@ -51381,6 +51507,27 @@ var OpenCodeAdapter = class {
51381
51507
  await reportQuestion(request);
51382
51508
  }
51383
51509
  };
51510
+ const reconcileLegacyPermissions = async () => {
51511
+ const response = await client2.permission.list(
51512
+ { directory: cwd },
51513
+ { signal: AbortSignal.timeout(QUESTION_RECONCILIATION_TIMEOUT_MS) }
51514
+ );
51515
+ for (const value of responseItems(response)) {
51516
+ const request = opencodePermissionRequest(value, "LEGACY");
51517
+ if (request?.sessionId === nativeId) await reportPermission(request);
51518
+ }
51519
+ };
51520
+ const reconcileV2Permissions = async () => {
51521
+ const response = await client2.v2.session.permission.list(
51522
+ { sessionID: nativeId },
51523
+ { signal: AbortSignal.timeout(QUESTION_RECONCILIATION_TIMEOUT_MS) }
51524
+ );
51525
+ for (const value of responseItems(response)) {
51526
+ const request = opencodePermissionRequest(value, "V2");
51527
+ if (request && (!request.sessionId || request.sessionId === nativeId))
51528
+ await reportPermission(request);
51529
+ }
51530
+ };
51384
51531
  const eventTask = (async () => {
51385
51532
  try {
51386
51533
  const subscription = await client2.event.subscribe(
@@ -51391,8 +51538,13 @@ var OpenCodeAdapter = class {
51391
51538
  if (eventSessionId(event) !== nativeId) continue;
51392
51539
  const question = opencodeQuestionRequest(event);
51393
51540
  if (question) await reportQuestion(question);
51541
+ const permission = opencodePermissionRequest(event);
51542
+ if (permission) await reportPermission(permission);
51394
51543
  const resolvedRequestId = opencodeQuestionResolution(event);
51395
51544
  if (resolvedRequestId) pendingQuestions.delete(resolvedRequestId);
51545
+ const resolvedPermissionId = opencodePermissionResolution(event);
51546
+ if (resolvedPermissionId)
51547
+ pendingQuestions.delete(resolvedPermissionId);
51396
51548
  const record = asRecord(event);
51397
51549
  const payload = asRecord(record.payload);
51398
51550
  const type = String(payload.type ?? record.type ?? "event");
@@ -51446,6 +51598,14 @@ var OpenCodeAdapter = class {
51446
51598
  "V2",
51447
51599
  reconcileV2Questions
51448
51600
  );
51601
+ const legacyPermissionReconciliationTask = questionReconciliationTask(
51602
+ "LEGACY permission",
51603
+ reconcileLegacyPermissions
51604
+ );
51605
+ const v2PermissionReconciliationTask = questionReconciliationTask(
51606
+ "V2 permission",
51607
+ reconcileV2Permissions
51608
+ );
51449
51609
  const send = async (prompt, attachments) => {
51450
51610
  const response = await client2.session.prompt({
51451
51611
  sessionID: nativeId,
@@ -51471,7 +51631,9 @@ var OpenCodeAdapter = class {
51471
51631
  let response = await send(input.prompt, input.attachments);
51472
51632
  await Promise.allSettled([
51473
51633
  reconcileLegacyQuestions(),
51474
- reconcileV2Questions()
51634
+ reconcileV2Questions(),
51635
+ reconcileLegacyPermissions(),
51636
+ reconcileV2Permissions()
51475
51637
  ]);
51476
51638
  while (!stopReason && pendingQuestions.size) {
51477
51639
  await waitForPoll(streamController.signal);
@@ -51518,7 +51680,9 @@ var OpenCodeAdapter = class {
51518
51680
  await Promise.allSettled([
51519
51681
  eventTask,
51520
51682
  legacyQuestionReconciliationTask,
51521
- v2QuestionReconciliationTask
51683
+ v2QuestionReconciliationTask,
51684
+ legacyPermissionReconciliationTask,
51685
+ v2PermissionReconciliationTask
51522
51686
  ]);
51523
51687
  isolatedRuntime?.server.close();
51524
51688
  }
@@ -51534,7 +51698,20 @@ var OpenCodeAdapter = class {
51534
51698
  await send(prompt, attachments);
51535
51699
  },
51536
51700
  async answer(requestId, answers) {
51537
- if (questionSurfaces.get(requestId) === "V2") {
51701
+ const permissionSurface = permissionSurfaces.get(requestId);
51702
+ if (permissionSurface === "V2") {
51703
+ await client2.v2.session.permission.reply({
51704
+ sessionID: nativeId,
51705
+ requestID: requestId,
51706
+ reply: permissionReply(answers)
51707
+ });
51708
+ } else if (permissionSurface === "LEGACY") {
51709
+ await client2.permission.reply({
51710
+ requestID: requestId,
51711
+ directory: cwd,
51712
+ reply: permissionReply(answers)
51713
+ });
51714
+ } else if (questionSurfaces.get(requestId) === "V2") {
51538
51715
  await client2.v2.session.question.reply({
51539
51716
  sessionID: nativeId,
51540
51717
  requestID: requestId,
@@ -51548,6 +51725,7 @@ var OpenCodeAdapter = class {
51548
51725
  });
51549
51726
  }
51550
51727
  pendingQuestions.delete(requestId);
51728
+ permissionSurfaces.delete(requestId);
51551
51729
  }
51552
51730
  };
51553
51731
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {