@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 +13 -7
- package/package.json +1 -1
- package/skills/agency/references/recipes.md +2 -3
- package/src/cli.test.ts +35 -11
- package/src/commands/init.test.ts +3 -0
- package/src/commands/integration.test.ts +5 -5
- package/src/commands/integration.ts +5 -4
- package/src/commands/work.test.ts +6 -21
- package/src/commands/work.ts +4 -14
- package/src/services/IntegrationService.test.ts +25 -2
- package/src/services/IntegrationService.ts +3 -3
- package/src/workbase/opencode-file.ts +13 -2
- package/src/workbase/opencode-plugin-file.ts +30 -5
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
|
|
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
|
|
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.
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
252
|
-
`.
|
|
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
|
@@ -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
|
|
96
|
-
|
|
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
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
-
|
|
1352
|
+
180_000,
|
|
1329
1353
|
)
|
|
1330
1354
|
|
|
1331
1355
|
test("envelopes help and version output in machine mode", async () => {
|
|
@@ -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
|
|
99
|
+
OpenCode workbase plugin: missing
|
|
100
100
|
Path: .opencode/plugin/agency-repository-skills.ts
|
|
101
|
-
The managed OpenCode
|
|
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
|
|
168
|
+
OpenCode workbase plugin: synced
|
|
169
169
|
Path: .opencode/plugin/agency-repository-skills.ts
|
|
170
|
-
Agency's managed OpenCode plugin exposes writable-checkout skills
|
|
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
|
|
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
|
|
95
|
-
|
|
96
|
-
diagnostic
|
|
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:
|
|
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(
|
|
494
|
-
|
|
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("
|
|
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).
|
|
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()
|
package/src/commands/work.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
|
-
import { dirname, isAbsolute,
|
|
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
|
-
|
|
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
|
|
494
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
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 = [
|