@markjaquith/agency 2.37.0 → 2.37.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 +5 -11
- package/package.json +1 -1
- package/src/cli.test.ts +71 -37
- package/src/commands/work.test.ts +10 -49
- package/src/commands/work.ts +1 -35
- package/src/workbase/AGENTS.md +3 -4
package/README.md
CHANGED
|
@@ -92,13 +92,9 @@ preserved as user-owned content.
|
|
|
92
92
|
|
|
93
93
|
The OpenCode config loads Agency's hidden instructions in addition to any
|
|
94
94
|
user-owned root `AGENTS.md` and advertises the complete workbase as one portable
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
This makes the config effective from nested task and phase Git checkouts without
|
|
99
|
-
persisting a machine-specific path or granting access outside the workbase. Bash
|
|
100
|
-
and Agency operations must still follow the write authority reported by
|
|
101
|
-
`agency context`.
|
|
95
|
+
reference. OpenCode discovers that config from task and epic launch directories.
|
|
96
|
+
Agents receive whole-workbase visibility from that reference. Bash and Agency
|
|
97
|
+
operations must still follow the write authority reported by `agency context`.
|
|
102
98
|
|
|
103
99
|
Repository aliases and canonical fetch remotes are declared in tracked
|
|
104
100
|
`agency.json`; local bare clones and symlinks remain ignored under
|
|
@@ -226,10 +222,8 @@ Every runner receives the same `AGENCY_RUNNER`, `AGENCY_CLAIMANT`,
|
|
|
226
222
|
environment is added without overriding these normalized values.
|
|
227
223
|
`AGENCY_CLAIM_REVISION` is empty for local `agency work` launches.
|
|
228
224
|
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
|
229
|
-
The `opencode` runner
|
|
230
|
-
|
|
231
|
-
workbase-scoped access and edit rules. These values keep the Git-synced config
|
|
232
|
-
portable while providing whole-workbase read access at every launch location.
|
|
225
|
+
The `opencode` runner discovers the managed project config from its task or epic
|
|
226
|
+
working directory; Agency does not inject OpenCode-specific configuration.
|
|
233
227
|
`AGENCY_CLAIM_REVISION` is empty for local `agency work` launches.
|
|
234
228
|
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
|
235
229
|
`--print-command` prints the exact cwd and argv plus non-secret environment keys
|
package/package.json
CHANGED
package/src/cli.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterAll, afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { access, mkdir, realpath } from "node:fs/promises"
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "node:path"
|
|
4
4
|
import errorFixture from "../fixtures/protocol/error.json"
|
|
5
5
|
import successFixture from "../fixtures/protocol/success.json"
|
|
6
6
|
import { cleanupTempDir, createTempDir } from "./test-utils"
|
|
@@ -862,6 +862,9 @@ status: open
|
|
|
862
862
|
await runGit(["-C", source, "remote", "add", "origin", daemon.remote])
|
|
863
863
|
|
|
864
864
|
parseJson(await runCli(["init", root, "--json"], parent))
|
|
865
|
+
expect(
|
|
866
|
+
Bun.spawnSync(["git", "init", "--initial-branch=main", root]).exitCode,
|
|
867
|
+
).toBe(0)
|
|
865
868
|
const agencyConfigPath = join(root, "agency.json")
|
|
866
869
|
const agencyConfig = JSON.parse(await Bun.file(agencyConfigPath).text())
|
|
867
870
|
await Bun.write(
|
|
@@ -942,10 +945,8 @@ status: open
|
|
|
942
945
|
),
|
|
943
946
|
)
|
|
944
947
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
)
|
|
948
|
-
const phaseWorkspace = parseJson(
|
|
948
|
+
parseJson(await runCli(["work", "prepare", "example", "--json"], root))
|
|
949
|
+
parseJson(
|
|
949
950
|
await runCli(
|
|
950
951
|
[
|
|
951
952
|
"work",
|
|
@@ -984,22 +985,18 @@ status: open
|
|
|
984
985
|
{
|
|
985
986
|
args: ["--task", "example"],
|
|
986
987
|
cwd: join(workbaseRoot, "tasks/example"),
|
|
987
|
-
writable: taskWorkspace.writablePath,
|
|
988
988
|
},
|
|
989
989
|
{
|
|
990
990
|
args: ["--task", "pipeline", "--phase", "build"],
|
|
991
991
|
cwd: join(workbaseRoot, "tasks/pipeline"),
|
|
992
|
-
writable: phaseWorkspace.writablePath,
|
|
993
992
|
},
|
|
994
993
|
{
|
|
995
994
|
args: ["--epic", "delivery"],
|
|
996
995
|
cwd: join(workbaseRoot, "epics/delivery"),
|
|
997
|
-
writable: null,
|
|
998
996
|
},
|
|
999
997
|
{
|
|
1000
998
|
args: ["--task", "pipeline"],
|
|
1001
999
|
cwd: join(workbaseRoot, "tasks/pipeline"),
|
|
1002
|
-
writable: null,
|
|
1003
1000
|
},
|
|
1004
1001
|
]
|
|
1005
1002
|
for (const launch of launches) {
|
|
@@ -1011,9 +1008,8 @@ status: open
|
|
|
1011
1008
|
expect(printed.stderr).toBe("")
|
|
1012
1009
|
const contract = JSON.parse(printed.stdout)
|
|
1013
1010
|
expect(contract.cwd).toBe(launch.cwd)
|
|
1014
|
-
expect(contract.environment.OPENCODE_CONFIG).
|
|
1015
|
-
|
|
1016
|
-
)
|
|
1011
|
+
expect(contract.environment.OPENCODE_CONFIG).toBeUndefined()
|
|
1012
|
+
expect(contract.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
|
|
1017
1013
|
const environment = {
|
|
1018
1014
|
...process.env,
|
|
1019
1015
|
...contract.environment,
|
|
@@ -1030,35 +1026,24 @@ status: open
|
|
|
1030
1026
|
expect.arrayContaining([
|
|
1031
1027
|
expect.objectContaining({
|
|
1032
1028
|
permission: "external_directory",
|
|
1033
|
-
pattern: join(workbaseRoot, "
|
|
1029
|
+
pattern: join(workbaseRoot, "*"),
|
|
1034
1030
|
action: "allow",
|
|
1035
1031
|
}),
|
|
1036
1032
|
]),
|
|
1037
1033
|
)
|
|
1038
|
-
expect(agent.permission).toEqual(
|
|
1039
|
-
expect.arrayContaining([
|
|
1040
|
-
expect.objectContaining({
|
|
1041
|
-
permission: "edit",
|
|
1042
|
-
pattern: "*",
|
|
1043
|
-
action: "deny",
|
|
1044
|
-
}),
|
|
1045
|
-
]),
|
|
1046
|
-
)
|
|
1047
|
-
if (launch.writable) {
|
|
1048
|
-
expect(agent.permission).toEqual(
|
|
1049
|
-
expect.arrayContaining(
|
|
1050
|
-
[workbaseRoot, launch.cwd].map((base) =>
|
|
1051
|
-
expect.objectContaining({
|
|
1052
|
-
permission: "edit",
|
|
1053
|
-
pattern: join(relative(base, launch.writable!), "**"),
|
|
1054
|
-
action: "allow",
|
|
1055
|
-
}),
|
|
1056
|
-
),
|
|
1057
|
-
),
|
|
1058
|
-
)
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
1034
|
if (launch === launches[0]) {
|
|
1035
|
+
const configProbe = Bun.spawnSync(["opencode", "debug", "config"], {
|
|
1036
|
+
cwd: contract.cwd,
|
|
1037
|
+
env: environment,
|
|
1038
|
+
})
|
|
1039
|
+
expect(configProbe.exitCode).toBe(0)
|
|
1040
|
+
expect(JSON.parse(configProbe.stdout.toString()).references).toEqual({
|
|
1041
|
+
workbase: {
|
|
1042
|
+
path: "..",
|
|
1043
|
+
description:
|
|
1044
|
+
"Complete Agency workbase context; write authority still comes only from agency context",
|
|
1045
|
+
},
|
|
1046
|
+
})
|
|
1062
1047
|
for (const document of documents) {
|
|
1063
1048
|
const read = Bun.spawnSync(
|
|
1064
1049
|
[
|
|
@@ -1080,6 +1065,55 @@ status: open
|
|
|
1080
1065
|
}
|
|
1081
1066
|
}
|
|
1082
1067
|
|
|
1068
|
+
const directEnvironment: Record<string, string | undefined> = {
|
|
1069
|
+
...process.env,
|
|
1070
|
+
XDG_CONFIG_HOME: isolatedConfigHome,
|
|
1071
|
+
OPENCODE_DISABLE_EXTERNAL_SKILLS: "1",
|
|
1072
|
+
}
|
|
1073
|
+
delete directEnvironment.OPENCODE_CONFIG
|
|
1074
|
+
delete directEnvironment.OPENCODE_CONFIG_CONTENT
|
|
1075
|
+
delete directEnvironment.AGENCY_WORKBASE
|
|
1076
|
+
for (const cwd of [
|
|
1077
|
+
join(workbaseRoot, "tasks/example"),
|
|
1078
|
+
join(workbaseRoot, "tasks/pipeline"),
|
|
1079
|
+
join(workbaseRoot, "epics/delivery"),
|
|
1080
|
+
]) {
|
|
1081
|
+
const probe = Bun.spawnSync(["opencode", "debug", "agent", "build"], {
|
|
1082
|
+
cwd,
|
|
1083
|
+
env: directEnvironment,
|
|
1084
|
+
})
|
|
1085
|
+
expect(probe.exitCode).toBe(0)
|
|
1086
|
+
const agent = JSON.parse(probe.stdout.toString())
|
|
1087
|
+
expect(agent.permission).toEqual(
|
|
1088
|
+
expect.arrayContaining([
|
|
1089
|
+
expect.objectContaining({
|
|
1090
|
+
permission: "external_directory",
|
|
1091
|
+
pattern: join(workbaseRoot, "*"),
|
|
1092
|
+
action: "allow",
|
|
1093
|
+
}),
|
|
1094
|
+
]),
|
|
1095
|
+
)
|
|
1096
|
+
for (const document of documents) {
|
|
1097
|
+
const read = Bun.spawnSync(
|
|
1098
|
+
[
|
|
1099
|
+
"opencode",
|
|
1100
|
+
"debug",
|
|
1101
|
+
"agent",
|
|
1102
|
+
"build",
|
|
1103
|
+
"--tool",
|
|
1104
|
+
"read",
|
|
1105
|
+
"--params",
|
|
1106
|
+
JSON.stringify({ filePath: document }),
|
|
1107
|
+
],
|
|
1108
|
+
{ cwd, env: directEnvironment },
|
|
1109
|
+
)
|
|
1110
|
+
expect(read.exitCode).toBe(0)
|
|
1111
|
+
expect(JSON.parse(read.stdout.toString()).result.output).toContain(
|
|
1112
|
+
`<path>${document}</path>`,
|
|
1113
|
+
)
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1083
1117
|
parseJson(
|
|
1084
1118
|
await runCli(["task", "status", "example", "done", "--json"], root),
|
|
1085
1119
|
)
|
|
@@ -1135,7 +1169,7 @@ status: open
|
|
|
1135
1169
|
graph.nodes.find((node: any) => node.id === "task:pipeline").status,
|
|
1136
1170
|
).toBe("working")
|
|
1137
1171
|
},
|
|
1138
|
-
|
|
1172
|
+
90_000,
|
|
1139
1173
|
)
|
|
1140
1174
|
|
|
1141
1175
|
test("envelopes help and version output in machine mode", async () => {
|
|
@@ -486,23 +486,10 @@ describe("work command", () => {
|
|
|
486
486
|
],
|
|
487
487
|
cwd: "/workbase/epics/delivery",
|
|
488
488
|
})
|
|
489
|
-
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).
|
|
490
|
-
"/workbase/.opencode/opencode.jsonc",
|
|
491
|
-
)
|
|
489
|
+
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
|
|
492
490
|
expect(
|
|
493
|
-
|
|
494
|
-
).
|
|
495
|
-
permission: {
|
|
496
|
-
external_directory: { "/workbase/**": "allow" },
|
|
497
|
-
edit: { "*": "deny" },
|
|
498
|
-
},
|
|
499
|
-
agent: {
|
|
500
|
-
build: { permission: { edit: { "*": "deny" } } },
|
|
501
|
-
plan: { permission: { edit: { "*": "deny" } } },
|
|
502
|
-
general: { permission: { edit: { "*": "deny" } } },
|
|
503
|
-
explore: { permission: { edit: { "*": "deny" } } },
|
|
504
|
-
},
|
|
505
|
-
})
|
|
491
|
+
harness.launchEnvironments[0]?.OPENCODE_CONFIG_CONTENT,
|
|
492
|
+
).toBeUndefined()
|
|
506
493
|
})
|
|
507
494
|
|
|
508
495
|
test("resolves an existing positional path before treating it as a task ID", async () => {
|
|
@@ -553,9 +540,7 @@ describe("work command", () => {
|
|
|
553
540
|
],
|
|
554
541
|
cwd: "/workbase/tasks/delivery",
|
|
555
542
|
})
|
|
556
|
-
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).
|
|
557
|
-
"/workbase/.opencode/opencode.jsonc",
|
|
558
|
-
)
|
|
543
|
+
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
|
|
559
544
|
})
|
|
560
545
|
|
|
561
546
|
test("infers a phase from a nested checkout directory", async () => {
|
|
@@ -585,9 +570,7 @@ describe("work command", () => {
|
|
|
585
570
|
expect(harness.statusUpdates).toEqual([
|
|
586
571
|
"phase:example:implementation:working",
|
|
587
572
|
])
|
|
588
|
-
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).
|
|
589
|
-
"/workbase/.opencode/opencode.jsonc",
|
|
590
|
-
)
|
|
573
|
+
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
|
|
591
574
|
})
|
|
592
575
|
|
|
593
576
|
test("infers a single-phase task from a nested checkout directory", async () => {
|
|
@@ -601,9 +584,7 @@ describe("work command", () => {
|
|
|
601
584
|
|
|
602
585
|
expect(harness.events[0]).toBe("materialize")
|
|
603
586
|
expect(harness.launches[0]?.cwd).toBe(taskDirectory)
|
|
604
|
-
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).
|
|
605
|
-
"/workbase/.opencode/opencode.jsonc",
|
|
606
|
-
)
|
|
587
|
+
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
|
|
607
588
|
})
|
|
608
589
|
|
|
609
590
|
test("selects a target when no directory is provided", async () => {
|
|
@@ -781,9 +762,7 @@ describe("work command", () => {
|
|
|
781
762
|
])
|
|
782
763
|
expect(harness.launchEnvironments[0]?.AGENCY_PROMPT).toBe("")
|
|
783
764
|
expect(harness.statusUpdates).toEqual(["task:example:working"])
|
|
784
|
-
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).
|
|
785
|
-
"/workbase/.opencode/opencode.jsonc",
|
|
786
|
-
)
|
|
765
|
+
expect(harness.launchEnvironments[0]?.OPENCODE_CONFIG).toBeUndefined()
|
|
787
766
|
expect(harness.progressUpdates).toEqual([
|
|
788
767
|
"start:Preparing workspace...",
|
|
789
768
|
"succeed:Workspace ready",
|
|
@@ -936,33 +915,15 @@ describe("work command", () => {
|
|
|
936
915
|
expect(harness.taskStatuses.example).toBe("done")
|
|
937
916
|
})
|
|
938
917
|
|
|
939
|
-
test("
|
|
918
|
+
test("does not inject runtime OpenCode configuration", async () => {
|
|
940
919
|
const harness = createHarness()
|
|
941
920
|
const output = await captureLogs(() =>
|
|
942
921
|
harness.run({ taskId: "example", opencode: true, printCommand: true }),
|
|
943
922
|
)
|
|
944
923
|
const printed = JSON.parse(output.join("\n"))
|
|
945
924
|
|
|
946
|
-
expect(printed.environment.OPENCODE_CONFIG).
|
|
947
|
-
|
|
948
|
-
)
|
|
949
|
-
const edit = {
|
|
950
|
-
"*": "deny",
|
|
951
|
-
"tasks/example/code/agency/**": "allow",
|
|
952
|
-
"code/agency/**": "allow",
|
|
953
|
-
}
|
|
954
|
-
expect(JSON.parse(printed.environment.OPENCODE_CONFIG_CONTENT)).toEqual({
|
|
955
|
-
permission: {
|
|
956
|
-
external_directory: { "/workbase/**": "allow" },
|
|
957
|
-
edit,
|
|
958
|
-
},
|
|
959
|
-
agent: {
|
|
960
|
-
build: { permission: { edit } },
|
|
961
|
-
plan: { permission: { edit } },
|
|
962
|
-
general: { permission: { edit } },
|
|
963
|
-
explore: { permission: { edit } },
|
|
964
|
-
},
|
|
965
|
-
})
|
|
925
|
+
expect(printed.environment.OPENCODE_CONFIG).toBeUndefined()
|
|
926
|
+
expect(printed.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
|
|
966
927
|
})
|
|
967
928
|
|
|
968
929
|
test("does not override customized OpenCode access policy", async () => {
|
package/src/commands/work.ts
CHANGED
|
@@ -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
|
|
@@ -239,7 +236,6 @@ export const work = (
|
|
|
239
236
|
(target.status !== undefined && target.status !== "open"))
|
|
240
237
|
let prompt: string
|
|
241
238
|
let launchPath: string
|
|
242
|
-
let writablePath: string | undefined
|
|
243
239
|
if (target.kind === "epic") {
|
|
244
240
|
prompt = `Work on the epic. Read ${target.path}.`
|
|
245
241
|
launchPath = dirname(target.path)
|
|
@@ -265,7 +261,6 @@ export const work = (
|
|
|
265
261
|
? `${action} the task. Read ${workspace.taskPath} and ${workspace.phasePath}.`
|
|
266
262
|
: `${action} the task. Read ${workspace.taskPath}.`
|
|
267
263
|
launchPath = dirname(workspace.taskPath)
|
|
268
|
-
writablePath = workspace.writablePath
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
const explicitlyRequested = Boolean(
|
|
@@ -336,35 +331,6 @@ export const work = (
|
|
|
336
331
|
...resolved.environment,
|
|
337
332
|
...runnerEnvironment(runner, variables),
|
|
338
333
|
}
|
|
339
|
-
if (runner === "opencode" && managedOpencode) {
|
|
340
|
-
environment.OPENCODE_CONFIG = join(root, ".opencode", "opencode.jsonc")
|
|
341
|
-
const execution =
|
|
342
|
-
target.kind === "phase" ||
|
|
343
|
-
(target.kind === "task" && !target.multiPhase)
|
|
344
|
-
const edit = execution
|
|
345
|
-
? {
|
|
346
|
-
"*": "deny" as const,
|
|
347
|
-
...Object.fromEntries(
|
|
348
|
-
[root, launchPath].map((base) => [
|
|
349
|
-
join(relative(base, writablePath!), "**").split(sep).join("/"),
|
|
350
|
-
"allow" as const,
|
|
351
|
-
]),
|
|
352
|
-
),
|
|
353
|
-
}
|
|
354
|
-
: { "*": "deny" as const }
|
|
355
|
-
environment.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
356
|
-
permission: {
|
|
357
|
-
external_directory: { [join(root, "**")]: "allow" },
|
|
358
|
-
edit,
|
|
359
|
-
},
|
|
360
|
-
agent: {
|
|
361
|
-
build: { permission: { edit } },
|
|
362
|
-
plan: { permission: { edit } },
|
|
363
|
-
general: { permission: { edit } },
|
|
364
|
-
explore: { permission: { edit } },
|
|
365
|
-
},
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
334
|
if (options.printCommand) {
|
|
369
335
|
log(
|
|
370
336
|
JSON.stringify(
|
package/src/workbase/AGENTS.md
CHANGED
|
@@ -77,7 +77,6 @@ files, and `agency work` reconciles them before launching an agent.
|
|
|
77
77
|
|
|
78
78
|
OpenCode can access the complete workbase tree, but this filesystem permission
|
|
79
79
|
does not expand Agency write authority beyond the checkout reported by
|
|
80
|
-
`agency context`.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
stored in the Git-synced config.
|
|
80
|
+
`agency context`. OpenCode discovers the managed project config from task and
|
|
81
|
+
epic launch directories. No machine-specific path or runtime permission overlay
|
|
82
|
+
is required; agents must follow the authority reported by `agency context`.
|