@markjaquith/agency 2.39.0 → 2.39.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.39.0",
3
+ "version": "2.39.2",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -59,6 +59,7 @@
59
59
  },
60
60
  "scripts": {
61
61
  "test": "find src \\( -name '*.test.ts' -o -name '*.test.tsx' \\) -print0 | xargs -0 -n 1 -P 4 bun test",
62
+ "test:opencode": "AGENCY_TEST_OPENCODE=1 bun test src/cli.test.ts --test-name-pattern 'provides effective whole-workbase OpenCode access'",
62
63
  "format": "oxfmt",
63
64
  "format:check": "oxfmt --check",
64
65
  "knip": "knip --production",
package/src/cli.test.ts CHANGED
@@ -416,7 +416,7 @@ describe("CLI", () => {
416
416
  })
417
417
 
418
418
  test("routes command help and global options on either side of commands", async () => {
419
- for (const [command, usage] of [
419
+ const commands = [
420
420
  ["init", "Usage: agency init"],
421
421
  ["workbase", "Usage: agency workbase"],
422
422
  ["integration", "Usage: agency integration"],
@@ -433,11 +433,19 @@ describe("CLI", () => {
433
433
  ["context", "Usage: agency context"],
434
434
  ["graph", "Usage: agency graph"],
435
435
  ["next", "Usage: agency next"],
436
- ] as const) {
437
- const result = await runCli([command, "--help"])
438
- expect(result.exitCode).toBe(0)
439
- expect(result.stdout).toContain(usage)
440
- expect(result.stderr).toBe("")
436
+ ] as const
437
+ for (let offset = 0; offset < commands.length; offset += 4) {
438
+ const results = await Promise.all(
439
+ commands.slice(offset, offset + 4).map(async ([command, usage]) => ({
440
+ usage,
441
+ result: await runCli([command, "--help"]),
442
+ })),
443
+ )
444
+ for (const { result, usage } of results) {
445
+ expect(result.exitCode).toBe(0)
446
+ expect(result.stdout).toContain(usage)
447
+ expect(result.stderr).toBe("")
448
+ }
441
449
  }
442
450
 
443
451
  const helpBefore = await runCli(["--help", "task"])
@@ -836,7 +844,9 @@ status: open
836
844
  ).toBe("example\n")
837
845
  })
838
846
 
839
- test.skipIf(Bun.which("opencode") === null)(
847
+ test.skipIf(
848
+ Bun.which("opencode") === null || process.env.AGENCY_TEST_OPENCODE !== "1",
849
+ )(
840
850
  "provides effective whole-workbase OpenCode access from every launch topology",
841
851
  async () => {
842
852
  const parent = await createTempDir()
@@ -1009,7 +1019,13 @@ status: open
1009
1019
  const contract = JSON.parse(printed.stdout)
1010
1020
  expect(contract.cwd).toBe(launch.cwd)
1011
1021
  expect(contract.environment.OPENCODE_CONFIG).toBeUndefined()
1012
- expect(contract.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
1022
+ expect(
1023
+ JSON.parse(contract.environment.OPENCODE_CONFIG_CONTENT),
1024
+ ).toEqual({
1025
+ permission: {
1026
+ external_directory: { [join(workbaseRoot, "*")]: "allow" },
1027
+ },
1028
+ })
1013
1029
  const environment = {
1014
1030
  ...process.env,
1015
1031
  ...contract.environment,
@@ -45,6 +45,9 @@ const multiPhaseWorkspace: ExecutionWorkspace = {
45
45
  }
46
46
 
47
47
  const taskDirectory = "/workbase/tasks/example"
48
+ const workbasePermission = JSON.stringify({
49
+ permission: { external_directory: { "/workbase/*": "allow" } },
50
+ })
48
51
 
49
52
  interface HarnessOptions {
50
53
  readonly workspace?: ExecutionWorkspace
@@ -487,9 +490,9 @@ describe("work command", () => {
487
490
  cwd: "/workbase/epics/delivery",
488
491
  })
489
492
  expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
490
- expect(
491
- harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT,
492
- ).toBeUndefined()
493
+ expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT).toBe(
494
+ workbasePermission,
495
+ )
493
496
  })
494
497
 
495
498
  test("resolves an existing positional path before treating it as a task ID", async () => {
@@ -915,7 +918,7 @@ describe("work command", () => {
915
918
  expect(harness.taskStatuses.example).toBe("done")
916
919
  })
917
920
 
918
- test("does not inject runtime OpenCode configuration", async () => {
921
+ test("grants managed OpenCode launches absolute workbase access", async () => {
919
922
  const harness = createHarness()
920
923
  const output = await captureLogs(() =>
921
924
  harness.run({ taskId: "example", opencode: true, printCommand: true }),
@@ -923,7 +926,7 @@ describe("work command", () => {
923
926
  const printed = JSON.parse(output.join("\n"))
924
927
 
925
928
  expect(printed.environment.OPENCODE_CONFIG).toBeUndefined()
926
- expect(printed.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
929
+ expect(printed.environment.OPENCODE_CONFIG_CONTENT).toBe(workbasePermission)
927
930
  })
928
931
 
929
932
  test("does not override customized OpenCode access policy", async () => {
@@ -122,7 +122,10 @@ export const work = (
122
122
  const inputAllowed = options.inputAllowed ?? true
123
123
  const root = yield* resolveWorkbase(startPath, pickBase, inputAllowed)
124
124
  if (!root) return
125
- yield* integrations.sync(root)
125
+ const integration = yield* integrations.sync(root)
126
+ const managedOpencode = integration.files.some(
127
+ (file) => file.name === "opencode" && file.state === "managed",
128
+ )
126
129
  const { config } = yield* workbase.loadConfig(root)
127
130
 
128
131
  let target: WorkTarget | null = null
@@ -331,6 +334,13 @@ export const work = (
331
334
  ...resolved.environment,
332
335
  ...runnerEnvironment(runner, variables),
333
336
  }
337
+ if (runner === "opencode" && managedOpencode) {
338
+ environment.OPENCODE_CONFIG_CONTENT = JSON.stringify({
339
+ permission: {
340
+ external_directory: { [join(root, "*")]: "allow" },
341
+ },
342
+ })
343
+ }
334
344
  if (options.printCommand) {
335
345
  log(
336
346
  JSON.stringify(