@markjaquith/agency 2.46.0 → 2.47.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/README.md CHANGED
@@ -54,7 +54,7 @@ workbase/
54
54
  opencode.jsonc # managed @agency subagent, instructions, and reference
55
55
  tui.jsonc # managed TUI plugin registration
56
56
  command/agency.md # managed /agency workflow command
57
- plugin/agency-repository-skills.ts # managed checkout skill discovery
57
+ plugin/agency-repository-skills.ts # managed workbase access and checkout skills
58
58
  tui/agency-debug.ts # managed /agency-debug TUI diagnostic
59
59
  agency.json # tracked config and portable repository declarations
60
60
  repos/ # ignored local materializations
@@ -99,7 +99,11 @@ The OpenCode config defines an `@agency` subagent for delegated workbase
99
99
  orchestration, loads Agency's hidden instructions in addition to any user-owned
100
100
  root `AGENTS.md`, advertises the complete workbase as one portable reference,
101
101
  and replaces the built-in Plan agent with `agency-plan`. That planning agent can
102
- update `TASK.md`, `PHASE.md`, and `EPIC.md` while other edits remain disabled.
102
+ update `TASK.md`, `PHASE.md`, and `EPIC.md`, inspect the workbase through
103
+ read-only Agency commands, and use explicit Agency CLI permissions to create or
104
+ update planning structure. Its normal research tools and the complete Agency CLI
105
+ remain available; managed Agency instructions and reported authority govern each
106
+ operation.
103
107
  When the subagent launches work in another agent, it verifies that the runner
104
108
  started and returns without waiting for the task to finish.
105
109
  The TUI-only `/agency-debug` command reports TUI companion initialization and
@@ -107,9 +111,10 @@ whether the server plugin registered writable-checkout skills. It uses a native
107
111
  toast and does not submit a prompt to an LLM. When no writable checkout skill
108
112
  directory is available, server initialization is reported as indeterminate
109
113
  rather than inferred from plugin discovery.
110
- OpenCode discovers the config from task and epic launch directories. Agents
111
- receive whole-workbase visibility from that reference. Bash and Agency operations
112
- must still follow the write authority reported by `agency context`.
114
+ OpenCode discovers the config and plugin from task and epic launch directories.
115
+ The plugin grants whole-workbase access dynamically, while the portable
116
+ reference advertises that context to agents. Bash and Agency operations must
117
+ still follow the write authority reported by `agency context`.
113
118
 
114
119
  OpenCode also discovers a managed `/agency` command. Use `/agency status` for a
115
120
  read-only current-work summary, `/agency start [target]` to begin or resume work
@@ -248,8 +253,9 @@ authoritative writable checkout path.
248
253
  `AGENCY_PROMPT` is empty unless `--auto` is set.
249
254
  The `opencode` runner remains rooted in its task or epic working directory so
250
255
  the workbase `AGENTS.md` and managed OpenCode config are discovered normally.
251
- Agency's managed OpenCode plugin adds existing checkout-local `.claude/skills`,
252
- `.agents/skills`, and `.opencode/{skill,skills}` directories to `skills.paths`.
256
+ Agency's managed OpenCode plugin grants the active workbase external-directory
257
+ access and adds existing checkout-local `.claude/skills`, `.agents/skills`, and
258
+ `.opencode/{skill,skills}` directories to `skills.paths`.
253
259
  `agency work` supplies the checkout directly; plain OpenCode launches resolve a
254
260
  materialized execution-unit checkout through `agency context`. A multi-phase
255
261
  task root has no single checkout, so launch from its phase directory when using
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.46.0",
3
+ "version": "2.47.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -92,9 +92,8 @@ checks readiness, materializes worktrees, marks this execution unit working
92
92
  without a claim, and launches the runner. Run it again to relaunch unclaimed
93
93
  working work. The runner opens without a prompt by default; add `--auto` only
94
94
  when the generated context prompt should start autonomous execution.
95
- OpenCode receives whole-workbase read visibility through runtime-only config and
96
- permission values; this does not expand the writable checkout reported by
97
- `agency context`.
95
+ OpenCode receives whole-workbase visibility through the managed project plugin;
96
+ this does not expand the writable checkout reported by `agency context`.
98
97
 
99
98
  ## Active Agent: Execute Assigned Work
100
99
 
package/src/cli.test.ts CHANGED
@@ -1059,13 +1059,7 @@ status: open
1059
1059
  expect(contract.environment.AGENCY_WRITABLE_CHECKOUT).toBe(
1060
1060
  launch.checkoutPath,
1061
1061
  )
1062
- expect(
1063
- JSON.parse(contract.environment.OPENCODE_CONFIG_CONTENT),
1064
- ).toEqual({
1065
- permission: {
1066
- external_directory: { [join(workbaseRoot, "*")]: "allow" },
1067
- },
1068
- })
1062
+ expect(contract.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
1069
1063
  const environment = {
1070
1064
  ...process.env,
1071
1065
  ...contract.environment,
@@ -1093,6 +1087,9 @@ status: open
1093
1087
  })
1094
1088
  expect(configProbe.exitCode).toBe(0)
1095
1089
  const effectiveConfig = JSON.parse(configProbe.stdout.toString())
1090
+ expect(effectiveConfig.permission.external_directory).toMatchObject({
1091
+ [join(workbaseRoot, "*")]: "allow",
1092
+ })
1096
1093
  expect(effectiveConfig.plugin_origins).toEqual(
1097
1094
  expect.arrayContaining([
1098
1095
  expect.objectContaining({
@@ -1218,9 +1215,11 @@ status: open
1218
1215
  env: directEnvironment,
1219
1216
  })
1220
1217
  expect(configProbe.exitCode).toBe(0)
1221
- expect(
1222
- JSON.parse(configProbe.stdout.toString()).plugin_origins,
1223
- ).toEqual(
1218
+ const effectiveConfig = JSON.parse(configProbe.stdout.toString())
1219
+ expect(effectiveConfig.permission.external_directory).toMatchObject({
1220
+ [join(workbaseRoot, "*")]: "allow",
1221
+ })
1222
+ expect(effectiveConfig.plugin_origins).toEqual(
1224
1223
  expect.arrayContaining([
1225
1224
  expect.objectContaining({
1226
1225
  spec: expect.stringContaining("agency-repository-skills.ts"),
@@ -1247,6 +1246,31 @@ status: open
1247
1246
  )
1248
1247
  }
1249
1248
  }
1249
+ const denied = Bun.spawnSync(["opencode", "debug", "agent", "build"], {
1250
+ cwd: directDirectories[0],
1251
+ env: {
1252
+ ...directEnvironment,
1253
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
1254
+ permission: { external_directory: { "*": "deny" } },
1255
+ }),
1256
+ },
1257
+ })
1258
+ expect(denied.exitCode).toBe(0)
1259
+ const deniedRules = JSON.parse(denied.stdout.toString()).permission
1260
+ const managedRule = deniedRules.findIndex(
1261
+ (rule: any) =>
1262
+ rule.permission === "external_directory" &&
1263
+ rule.pattern === join(workbaseRoot, "*") &&
1264
+ rule.action === "allow",
1265
+ )
1266
+ const userRule = deniedRules.findLastIndex(
1267
+ (rule: any) =>
1268
+ rule.permission === "external_directory" &&
1269
+ rule.pattern === "*" &&
1270
+ rule.action === "deny",
1271
+ )
1272
+ expect(managedRule).toBeGreaterThanOrEqual(0)
1273
+ expect(userRule).toBeGreaterThan(managedRule)
1250
1274
  for (const cwd of [
1251
1275
  join(workbaseRoot, "tasks/example"),
1252
1276
  join(workbaseRoot, "tasks/pipeline/phases/build"),
@@ -1325,7 +1349,7 @@ status: open
1325
1349
  graph.nodes.find((node: any) => node.id === "task:pipeline").status,
1326
1350
  ).toBe("working")
1327
1351
  },
1328
- 90_000,
1352
+ 180_000,
1329
1353
  )
1330
1354
 
1331
1355
  test("envelopes help and version output in machine mode", async () => {
@@ -49,6 +49,9 @@ describe("init command", () => {
49
49
  expect(config.agent["agency-plan"]).toMatchObject({
50
50
  mode: "primary",
51
51
  permission: {
52
+ bash: {
53
+ "agency *": "allow",
54
+ },
52
55
  edit: {
53
56
  "*": "deny",
54
57
  "tasks/*/TASK.md": "allow",
@@ -96,10 +96,10 @@ OpenCode /agency command: missing
96
96
  The managed OpenCode /agency command needs synchronization.
97
97
  Action: Run 'agency integration sync' to install the managed /agency command.
98
98
 
99
- OpenCode checkout skills: missing
99
+ OpenCode workbase plugin: missing
100
100
  Path: .opencode/plugin/agency-repository-skills.ts
101
- The managed OpenCode checkout-skill plugin needs synchronization.
102
- Action: Run 'agency integration sync' to expose writable-checkout skills in OpenCode.
101
+ The managed OpenCode workbase plugin needs synchronization.
102
+ Action: Run 'agency integration sync' to provide workbase access and expose writable-checkout skills in OpenCode.
103
103
 
104
104
  OpenCode TUI config: missing
105
105
  Path: .opencode/tui.jsonc
@@ -165,9 +165,9 @@ OpenCode /agency command: synced
165
165
  Path: .opencode/command/agency.md
166
166
  Agency's managed OpenCode /agency command is current.
167
167
 
168
- OpenCode checkout skills: synced
168
+ OpenCode workbase plugin: synced
169
169
  Path: .opencode/plugin/agency-repository-skills.ts
170
- Agency's managed OpenCode plugin exposes writable-checkout skills to work-item sessions.
170
+ Agency's managed OpenCode plugin provides whole-workbase access and exposes writable-checkout skills.
171
171
 
172
172
  OpenCode TUI config: synced
173
173
  Path: .opencode/tui.jsonc
@@ -31,7 +31,7 @@ const integrationNames = {
31
31
  agents: "Agent instructions",
32
32
  opencode: "OpenCode config",
33
33
  "opencode-command": "OpenCode /agency command",
34
- "opencode-plugin": "OpenCode checkout skills",
34
+ "opencode-plugin": "OpenCode workbase plugin",
35
35
  "opencode-tui": "OpenCode TUI config",
36
36
  "opencode-tui-plugin": "OpenCode /agency-debug",
37
37
  } as const
@@ -91,9 +91,10 @@ export const help = `
91
91
  Usage: agency integration <subcommand>
92
92
 
93
93
  Inspect or explicitly synchronize managed agent integration files. OpenCode
94
- launches load the managed instructions and project config at runtime, and expose
95
- the managed /agency command, writable-checkout skills, and /agency-debug TUI
96
- diagnostic, without changing Agency write authority.
94
+ launches load the managed instructions and project config at runtime. The
95
+ managed plugin provides whole-workbase access and writable-checkout skills, and
96
+ the /agency-debug TUI diagnostic reports integration state without changing
97
+ Agency write authority.
97
98
 
98
99
  Subcommands:
99
100
  status Report file state, access diagnostics, and safe remediation
@@ -45,10 +45,6 @@ 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
- })
51
-
52
48
  interface HarnessOptions {
53
49
  readonly workspace?: ExecutionWorkspace
54
50
  readonly materializeError?: Error
@@ -74,7 +70,6 @@ interface HarnessOptions {
74
70
  readonly guardError?: Error
75
71
  readonly launchError?: Error
76
72
  readonly workTargetIds?: readonly string[]
77
- readonly opencodeIntegrationState?: "managed" | "customized"
78
73
  readonly taskStatus?: "open" | "working" | "delegated" | "done" | "dropped"
79
74
  readonly phaseStatus?: "open" | "working" | "delegated" | "done" | "dropped"
80
75
  readonly taskStatuses?: Readonly<
@@ -237,7 +232,7 @@ const createHarness = (options: HarnessOptions = {}) => {
237
232
  files: [
238
233
  {
239
234
  name: "opencode",
240
- state: options.opencodeIntegrationState ?? "managed",
235
+ state: "managed",
241
236
  },
242
237
  ],
243
238
  })
@@ -490,9 +485,9 @@ describe("work command", () => {
490
485
  cwd: "/workbase/epics/delivery",
491
486
  })
492
487
  expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
493
- expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT).toBe(
494
- workbasePermission,
495
- )
488
+ expect(
489
+ harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT,
490
+ ).toBeUndefined()
496
491
  })
497
492
 
498
493
  test("resolves an existing positional path before treating it as a task ID", async () => {
@@ -918,7 +913,7 @@ describe("work command", () => {
918
913
  expect(harness.taskStatuses.example).toBe("done")
919
914
  })
920
915
 
921
- test("grants managed OpenCode launches absolute workbase access", async () => {
916
+ test("leaves managed OpenCode access to the project plugin", async () => {
922
917
  const harness = createHarness()
923
918
  const output = await captureLogs(() =>
924
919
  harness.run({ taskId: "example", opencode: true, printCommand: true }),
@@ -926,7 +921,7 @@ describe("work command", () => {
926
921
  const printed = JSON.parse(output.join("\n"))
927
922
 
928
923
  expect(printed.environment.OPENCODE_CONFIG).toBeUndefined()
929
- expect(printed.environment.OPENCODE_CONFIG_CONTENT).toBe(workbasePermission)
924
+ expect(printed.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
930
925
  })
931
926
 
932
927
  test("provides the writable checkout to plugins without changing the project", async () => {
@@ -941,16 +936,6 @@ describe("work command", () => {
941
936
  expect(harness.launchEnvironments[0]?.AGENCY_WRITABLE_CHECKOUT).toBe(
942
937
  "/workbase/tasks/example/code/agency",
943
938
  )
944
- expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT).toBe(
945
- workbasePermission,
946
- )
947
- })
948
-
949
- test("does not override customized OpenCode access policy", async () => {
950
- const harness = createHarness({ opencodeIntegrationState: "customized" })
951
- await harness.run({ taskId: "example", opencode: true })
952
-
953
- expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
954
939
  expect(
955
940
  harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT,
956
941
  ).toBeUndefined()
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect"
2
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path"
3
3
  import type { BaseCommandOptions } from "../utils/command"
4
4
  import { WorktreeService } from "../services/WorktreeService"
5
5
  import { FileSystemService } from "../services/FileSystemService"
@@ -122,10 +122,7 @@ 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
- const integration = yield* integrations.sync(root)
126
- const managedOpencode = integration.files.some(
127
- (file) => file.name === "opencode" && file.state === "managed",
128
- )
125
+ yield* integrations.sync(root)
129
126
  const { config } = yield* workbase.loadConfig(root)
130
127
 
131
128
  let target: WorkTarget | null = null
@@ -337,13 +334,6 @@ export const work = (
337
334
  ...runnerEnvironment(runner, variables),
338
335
  }
339
336
  if (writablePath) environment.AGENCY_WRITABLE_CHECKOUT = writablePath
340
- if (runner === "opencode" && managedOpencode) {
341
- environment.OPENCODE_CONFIG_CONTENT = JSON.stringify({
342
- permission: {
343
- external_directory: { [join(root, "*")]: "allow" },
344
- },
345
- })
346
- }
347
337
  if (options.printCommand) {
348
338
  log(
349
339
  JSON.stringify(
@@ -490,8 +480,8 @@ Usage: agency work [<directory-or-task-id> | --epic <epic-id>] [--runner <name>]
490
480
  Launch an agent for an epic, task, or phase. With no directory, select one
491
481
  interactively. A positional argument resolves as a directory first, then as a task
492
482
  ID. Use '.' for the current directory. Outside a workbase, select a registered
493
- workbase first. OpenCode launches receive whole-workbase read access through the
494
- runtime environment; Agency context remains authoritative for writes.
483
+ workbase first. Managed OpenCode launches receive whole-workbase access through
484
+ Agency's project plugin; Agency context remains authoritative for writes.
495
485
 
496
486
  The prepare subcommand resolves and materializes an execution workspace without
497
487
  launching an agent or changing lifecycle status. --dry-run reports planned Git
@@ -113,7 +113,7 @@ describe("IntegrationService", () => {
113
113
  ])
114
114
  })
115
115
 
116
- test("generates a dynamic repository skill plugin", () => {
116
+ test("generates a dynamic workbase plugin", () => {
117
117
  expect(managedWorkbaseOpencodePlugin).toContain(
118
118
  "process.env.AGENCY_WRITABLE_CHECKOUT",
119
119
  )
@@ -129,6 +129,9 @@ describe("IntegrationService", () => {
129
129
  expect(managedWorkbaseOpencodePlugin).toContain(
130
130
  'join(checkout, ".opencode", "skills")',
131
131
  )
132
+ expect(managedWorkbaseOpencodePlugin).toContain(
133
+ 'config.permission.external_directory = { [join(root, "*")]: "allow" }',
134
+ )
132
135
  expect(managedWorkbaseOpencodePlugin).toContain(
133
136
  "config.skills.paths = [...new Set",
134
137
  )
@@ -283,11 +286,14 @@ describe("IntegrationService", () => {
283
286
  },
284
287
  "agency-plan": {
285
288
  description:
286
- "Agency planning mode. May edit only Agency planning documents.",
289
+ "Agency planning mode. May update Agency plans and planning structure.",
287
290
  mode: "primary",
288
291
  prompt: expect.stringContaining("You are in Agency Plan mode"),
289
292
  permission: {
290
293
  question: "allow",
294
+ bash: {
295
+ "agency *": "allow",
296
+ },
291
297
  edit: {
292
298
  "*": "deny",
293
299
  "tasks/*/TASK.md": "allow",
@@ -307,6 +313,23 @@ describe("IntegrationService", () => {
307
313
  expect(config.agent.agency.prompt).toContain(
308
314
  "return without waiting for the task to finish",
309
315
  )
316
+ expect(config.agent["agency-plan"].prompt).toContain(
317
+ "Start with `agency context . --json`",
318
+ )
319
+ expect(config.agent["agency-plan"].prompt).toContain(
320
+ "decompose it into independently deliverable tasks",
321
+ )
322
+ expect(config.agent["agency-plan"].prompt).toContain("Use `--if-revision`")
323
+ expect(config.agent["agency-plan"].prompt).toContain(
324
+ "available ticket tools",
325
+ )
326
+ expect(config.agent["agency-plan"].prompt).toContain(
327
+ "changing lifecycle state",
328
+ )
329
+ expect(config.agent["agency-plan"].prompt).toContain(
330
+ "Follow the managed Agency instructions and reported authority",
331
+ )
332
+ expect(config.agent["agency-plan"].permission.bash["*"]).toBeUndefined()
310
333
  expect(config.references).toEqual({
311
334
  workbase: {
312
335
  path: "..",
@@ -97,7 +97,7 @@ const describe = (
97
97
  return state === "managed"
98
98
  ? {
99
99
  diagnostic:
100
- "Agency's managed OpenCode plugin exposes writable-checkout skills to work-item sessions.",
100
+ "Agency's managed OpenCode plugin provides whole-workbase access and exposes writable-checkout skills.",
101
101
  remediation: null,
102
102
  }
103
103
  : state === "customized"
@@ -108,9 +108,9 @@ const describe = (
108
108
  }
109
109
  : {
110
110
  diagnostic:
111
- "The managed OpenCode checkout-skill plugin needs synchronization.",
111
+ "The managed OpenCode workbase plugin needs synchronization.",
112
112
  remediation:
113
- "Run 'agency integration sync' to expose writable-checkout skills in OpenCode.",
113
+ "Run 'agency integration sync' to provide workbase access and expose writable-checkout skills in OpenCode.",
114
114
  }
115
115
  }
116
116
  if (name === "opencode-tui") {
@@ -8,7 +8,17 @@ const checksum = (content: string) =>
8
8
 
9
9
  const agencyPlanPrompt = `You are in Agency Plan mode. Think, read, search, and delegate exploration to construct a well-formed plan for the user's goal. Keep the plan comprehensive but concise, and ask clarifying questions when important tradeoffs or intent are unclear.
10
10
 
11
- You may edit TASK.md, PHASE.md, and EPIC.md to record the outcome, current approach, and important decisions. Do not edit any other file or use shell commands to modify the system.`
11
+ Start with \`agency context . --json\`. Use its document paths and revisions, then inspect the graph, related epics, tasks, phases, linked tickets, and repository declarations needed to understand the work. Use machine-readable Agency output when available instead of inferring structure from directory names.
12
+
13
+ When planning an epic, decompose it into independently deliverable tasks with explicit dependencies. Add phases only when one task genuinely requires multiple ordered delivery units. Reuse or update existing work instead of creating duplicate tasks or phases.
14
+
15
+ Use the Agency CLI for full workbase orchestration when the plan requires it, including creating or updating related epics, tasks, and phases; moving tasks; maintaining dependencies; and changing lifecycle state. Use \`--if-revision\` with the revision returned by context for mutations that support it, and run \`agency validate\` after changing workbase structure. Use available ticket tools to inspect or update a linked external ticket when the plan requires it.
16
+
17
+ You may edit TASK.md, PHASE.md, and EPIC.md to record the outcome, current approach, and important decisions. Do not edit any other file or use shell commands to modify the system. Follow the managed Agency instructions and reported authority for every Agency operation.`
18
+
19
+ const agencyPlanBashPermissions = {
20
+ "agency *": "allow",
21
+ }
12
22
 
13
23
  const body = () =>
14
24
  `${JSON.stringify(
@@ -28,11 +38,12 @@ const body = () =>
28
38
  },
29
39
  "agency-plan": {
30
40
  description:
31
- "Agency planning mode. May edit only Agency planning documents.",
41
+ "Agency planning mode. May update Agency plans and planning structure.",
32
42
  mode: "primary",
33
43
  prompt: agencyPlanPrompt,
34
44
  permission: {
35
45
  question: "allow",
46
+ bash: agencyPlanBashPermissions,
36
47
  edit: {
37
48
  "*": "deny",
38
49
  "tasks/*/TASK.md": "allow",
@@ -10,7 +10,7 @@ const body = `import { existsSync } from "node:fs"
10
10
  import { join, sep } from "node:path"
11
11
  import type { Plugin } from "@opencode-ai/plugin"
12
12
 
13
- const contextCheckout = async (directory: string) => {
13
+ const agencyContext = async (directory: string) => {
14
14
  const task = process.env.AGENCY_TASK_ID
15
15
  const phase = process.env.AGENCY_PHASE_ID
16
16
  const args = task
@@ -21,14 +21,39 @@ const contextCheckout = async (directory: string) => {
21
21
  if ((await child.exited) !== 0) return
22
22
  const envelope = JSON.parse(output)
23
23
  if (envelope.ok !== true) return
24
- return envelope.result?.authority?.writable?.checkoutPath as string | undefined
24
+ return {
25
+ root: envelope.result?.workbase?.root as string | undefined,
26
+ checkout: envelope.result?.authority?.writable?.checkoutPath as string | undefined,
27
+ }
25
28
  }
26
29
 
27
30
  const plugin: Plugin = async ({ directory }) => ({
28
31
  config: async (config) => {
29
- const checkout =
30
- process.env.AGENCY_WRITABLE_CHECKOUT ??
31
- (await contextCheckout(directory).catch(() => undefined))
32
+ const context = await agencyContext(directory).catch(() => undefined)
33
+ const root = process.env.AGENCY_WORKBASE ?? context?.root
34
+ const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout
35
+
36
+ const reference = config.references?.workbase
37
+ if (
38
+ root &&
39
+ typeof reference === "object" &&
40
+ reference.path === ".." &&
41
+ reference.description ===
42
+ "Complete Agency workbase context; write authority still comes only from agency context" &&
43
+ typeof config.permission !== "string"
44
+ ) {
45
+ config.permission ??= {}
46
+ const external = config.permission.external_directory
47
+ if (external === undefined) {
48
+ config.permission.external_directory = { [join(root, "*")]: "allow" }
49
+ } else if (typeof external === "object") {
50
+ config.permission.external_directory = {
51
+ [join(root, "*")]: "allow",
52
+ ...external,
53
+ }
54
+ }
55
+ }
56
+
32
57
  if (!checkout) return
33
58
 
34
59
  const paths = [