@markjaquith/agency 2.54.3 → 2.55.0
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 +31 -1
- package/cli-main.ts +20 -0
- package/package.json +1 -1
- package/src/cli-parser.test.ts +9 -0
- package/src/cli-parser.ts +10 -0
- package/src/cli.test.ts +2 -1
- package/src/commands/push.ts +25 -0
- package/src/commands/work.test.ts +23 -9
- package/src/commands/work.ts +1 -0
- package/src/protocol.ts +1 -0
- package/src/services/IntegrationService.test.ts +152 -0
- package/src/services/PushService.test.ts +516 -0
- package/src/services/PushService.ts +576 -0
- package/src/test-utils.ts +2 -0
- package/src/workbase/AGENTS.md +27 -0
- package/src/workbase/opencode-plugin-file.ts +130 -45
- package/src/workbase/runner-command.test.ts +3 -0
package/README.md
CHANGED
|
@@ -276,6 +276,19 @@ Execution-unit runners also receive `AGENCY_WRITABLE_CHECKOUT` with the
|
|
|
276
276
|
authoritative writable checkout path.
|
|
277
277
|
`AGENCY_CLAIM_REVISION` is empty for local `agency work` launches.
|
|
278
278
|
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
|
279
|
+
Autonomous prompts begin `Agency worker launch target: <target>.`, carrying the
|
|
280
|
+
same canonical target as `AGENCY_TARGET`. This is the process-local fallback for
|
|
281
|
+
runner clients that attach to a long-lived process and lose launch environment
|
|
282
|
+
variables. A worker must verify either signal against `agency context . --json`
|
|
283
|
+
before acting; a matching worker performs the task directly and must not invoke
|
|
284
|
+
`agency work` for the same target. Managed guidance also fails safe for older
|
|
285
|
+
generated prompts when their document paths, current directory, and active valid
|
|
286
|
+
context all agree. Herdr is not part of this identity contract.
|
|
287
|
+
The managed OpenCode plugin validates the marker against Agency context, binds it
|
|
288
|
+
to the receiving OpenCode session, injects an explicit active-worker system
|
|
289
|
+
instruction, and restores Agency identity for that session's shell environment.
|
|
290
|
+
This session bridge is necessary because an OpenCode client can attach to a
|
|
291
|
+
long-lived server process that did not inherit the client's launch environment.
|
|
279
292
|
The `opencode` runner remains rooted in its task or epic working directory so
|
|
280
293
|
the workbase `AGENTS.md` and managed OpenCode config are discovered normally.
|
|
281
294
|
Agency's managed OpenCode plugin grants the active workbase external-directory
|
|
@@ -799,12 +812,13 @@ restore preflight all destinations and graph references before changing files.
|
|
|
799
812
|
Versioned lifecycle provenance preserves parent declarations and dependency edges
|
|
800
813
|
for restoration. Archived IDs are reserved until restored.
|
|
801
814
|
|
|
802
|
-
### Work and Pull Requests
|
|
815
|
+
### Work, Publication, and Pull Requests
|
|
803
816
|
|
|
804
817
|
```text
|
|
805
818
|
agency work [<directory> | --epic <epic-id>] [--runner <name>] [--auto] [--print-command]
|
|
806
819
|
agency work prepare [target] [--dry-run] [--json]
|
|
807
820
|
agency worktree <list|inspect|prepare|remove|rebuild|repair>
|
|
821
|
+
agency push [--json]
|
|
808
822
|
agency pr create <task-id> [phase-id] [--draft] [--force] [--json]
|
|
809
823
|
agency pr [args...]
|
|
810
824
|
```
|
|
@@ -852,6 +866,22 @@ Agency resolves the ref to a commit and creates a detached worktree. Existing
|
|
|
852
866
|
reference worktrees are reused only while their commit still matches the declared
|
|
853
867
|
ref; use a commit SHA as `ref` when reproducibility matters.
|
|
854
868
|
|
|
869
|
+
`agency push` publishes the execution unit identified by current Agency context
|
|
870
|
+
without creating a pull request. It requires a valid, registered writable
|
|
871
|
+
checkout in `working` state, fetches the configured delivery remote, verifies
|
|
872
|
+
that the declared base is in the publication history, validates every outgoing
|
|
873
|
+
commit's description, author, and conflict state, and refuses non-fast-forward
|
|
874
|
+
updates.
|
|
875
|
+
|
|
876
|
+
For Git, YAML `branch` must exactly match the checked-out local branch, the
|
|
877
|
+
worktree must be clean, and `HEAD` is pushed with upstream tracking. For jj, YAML
|
|
878
|
+
`branch` is the authoritative delivery bookmark and need not exist before
|
|
879
|
+
publication. Agency publishes `@` unless it is the canonical empty, undescribed
|
|
880
|
+
post-commit working copy, in which case it publishes `@-`; described empty
|
|
881
|
+
changes remain intentional publication tips. Missing descriptions or authors
|
|
882
|
+
stop with exact change IDs and remediation commands. Agency creates or safely
|
|
883
|
+
advances only the declared bookmark and never invents a `push-*` bookmark.
|
|
884
|
+
|
|
855
885
|
Task-aware `agency pr create <task-id> [phase-id]` uses Agency's delivery flow,
|
|
856
886
|
including readiness checks and durable PR recording. Other `agency pr`
|
|
857
887
|
invocations forward every argument to `gh pr`. From an execution task or phase
|
package/cli-main.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { parseCli } from "./src/cli-parser"
|
|
|
6
6
|
import { init, help as initHelp } from "./src/commands/init"
|
|
7
7
|
import { task, help as taskHelp } from "./src/commands/task"
|
|
8
8
|
import { pr, prCreate, help as prHelp } from "./src/commands/pr"
|
|
9
|
+
import { push, help as pushHelp } from "./src/commands/push"
|
|
9
10
|
import { work, workPrepare, help as workHelp } from "./src/commands/work"
|
|
10
11
|
import { worktree, help as worktreeHelp } from "./src/commands/worktree"
|
|
11
12
|
import { status, help as statusHelp } from "./src/commands/status"
|
|
@@ -34,6 +35,7 @@ import { TaskService } from "./src/services/TaskService"
|
|
|
34
35
|
import { PhaseService } from "./src/services/PhaseService"
|
|
35
36
|
import { WorktreeService } from "./src/services/WorktreeService"
|
|
36
37
|
import { PullRequestService } from "./src/services/PullRequestService"
|
|
38
|
+
import { PushService } from "./src/services/PushService"
|
|
37
39
|
import { ArchiveService } from "./src/services/ArchiveService"
|
|
38
40
|
import { IntegrationService } from "./src/services/IntegrationService"
|
|
39
41
|
import { ContextService } from "./src/services/ContextService"
|
|
@@ -79,6 +81,7 @@ const CliLayer = Layer.mergeAll(
|
|
|
79
81
|
PhaseService.Default,
|
|
80
82
|
WorktreeService.Default,
|
|
81
83
|
PullRequestService.Default,
|
|
84
|
+
PushService.Default,
|
|
82
85
|
ArchiveService.Default,
|
|
83
86
|
IntegrationService.Default,
|
|
84
87
|
ContextService.Default,
|
|
@@ -305,6 +308,22 @@ const commands: Record<string, Command> = {
|
|
|
305
308
|
)
|
|
306
309
|
},
|
|
307
310
|
},
|
|
311
|
+
push: {
|
|
312
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
313
|
+
if (options.help) {
|
|
314
|
+
console.log(pushHelp)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
await runCommand(
|
|
318
|
+
push({
|
|
319
|
+
json: options.json,
|
|
320
|
+
silent: options.silent,
|
|
321
|
+
verbose: options.verbose,
|
|
322
|
+
cwd: options.cwd,
|
|
323
|
+
}),
|
|
324
|
+
)
|
|
325
|
+
},
|
|
326
|
+
},
|
|
308
327
|
phase: {
|
|
309
328
|
run: async (args: string[], options: Record<string, any>) => {
|
|
310
329
|
if (options.help) return console.log(phaseHelp)
|
|
@@ -742,6 +761,7 @@ Commands:
|
|
|
742
761
|
vcs <subcommand> Inspect or migrate the version-control backend
|
|
743
762
|
next List or select ready execution units
|
|
744
763
|
pr create / pr [...] Create an Agency PR or run gh pr with repository focus
|
|
764
|
+
push Validate and publish the current execution unit
|
|
745
765
|
review refresh Explicitly refresh a pinned review task
|
|
746
766
|
repo <subcommand> Manage workbase repositories
|
|
747
767
|
status Show status for the current workbase
|
package/package.json
CHANGED
package/src/cli-parser.test.ts
CHANGED
|
@@ -616,6 +616,15 @@ describe("strict CLI parsing", () => {
|
|
|
616
616
|
).toThrow("cannot be combined")
|
|
617
617
|
})
|
|
618
618
|
|
|
619
|
+
test("parses push without accepting an alternate target", () => {
|
|
620
|
+
expect(parseCli(["push", "--json"])).toMatchObject({
|
|
621
|
+
commandName: "push",
|
|
622
|
+
args: [],
|
|
623
|
+
values: { json: true },
|
|
624
|
+
})
|
|
625
|
+
expectUsageError(["push", "example"], "agency push")
|
|
626
|
+
})
|
|
627
|
+
|
|
619
628
|
test("accepts runner selection and command inspection for work", () => {
|
|
620
629
|
expect(
|
|
621
630
|
parseCli([
|
package/src/cli-parser.ts
CHANGED
|
@@ -913,6 +913,16 @@ const commands = {
|
|
|
913
913
|
},
|
|
914
914
|
},
|
|
915
915
|
},
|
|
916
|
+
push: {
|
|
917
|
+
usage: "agency push [--json]",
|
|
918
|
+
options: outputOptions,
|
|
919
|
+
command: {
|
|
920
|
+
usage: "agency push [--json]",
|
|
921
|
+
minArgs: 0,
|
|
922
|
+
maxArgs: 0,
|
|
923
|
+
options: ["json"],
|
|
924
|
+
},
|
|
925
|
+
},
|
|
916
926
|
next: {
|
|
917
927
|
usage: "agency next [--select] [--json]",
|
|
918
928
|
options: {
|
package/src/cli.test.ts
CHANGED
|
@@ -502,6 +502,7 @@ describe("CLI", () => {
|
|
|
502
502
|
["archive", "Usage: agency archive"],
|
|
503
503
|
["restore", "Usage: agency restore"],
|
|
504
504
|
["work", "Usage: agency work"],
|
|
505
|
+
["push", "Usage: agency push"],
|
|
505
506
|
["pr", "Work with GitHub pull requests."],
|
|
506
507
|
["status", "Usage: agency status"],
|
|
507
508
|
["validate", "Usage: agency validate"],
|
|
@@ -1177,7 +1178,7 @@ status: open
|
|
|
1177
1178
|
cwd: contract.cwd,
|
|
1178
1179
|
env: environment,
|
|
1179
1180
|
})
|
|
1180
|
-
expect(probe.exitCode).toBe(0)
|
|
1181
|
+
expect(probe.exitCode, probe.stderr.toString()).toBe(0)
|
|
1181
1182
|
const agent = JSON.parse(probe.stdout.toString())
|
|
1182
1183
|
expect(agent.permission).toEqual(
|
|
1183
1184
|
expect.arrayContaining([
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { PushService } from "../services/PushService"
|
|
3
|
+
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
+
import { createLoggers } from "../utils/effect"
|
|
5
|
+
|
|
6
|
+
export const push = (options: BaseCommandOptions = {}) =>
|
|
7
|
+
Effect.gen(function* () {
|
|
8
|
+
const publications = yield* PushService
|
|
9
|
+
const { log } = createLoggers(options)
|
|
10
|
+
const result = yield* publications.publish(options.cwd ?? process.cwd())
|
|
11
|
+
log(
|
|
12
|
+
options.json
|
|
13
|
+
? JSON.stringify(result, null, 2)
|
|
14
|
+
: `Published ${result.vcs} ${result.branch} at ${result.tip} to ${result.remote}`,
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const help = `
|
|
19
|
+
Usage: agency push [--json]
|
|
20
|
+
|
|
21
|
+
Validate and publish the current Agency execution unit without creating a pull
|
|
22
|
+
request. The command uses the durable base and branch declarations, requires the
|
|
23
|
+
managed writable checkout and working status, refreshes remote state, validates
|
|
24
|
+
every outgoing commit, and rejects non-fast-forward publication.
|
|
25
|
+
`
|
|
@@ -98,6 +98,8 @@ const createHarness = (options: HarnessOptions = {}) => {
|
|
|
98
98
|
cwd: string
|
|
99
99
|
}> = []
|
|
100
100
|
const launchEnvironments: Array<Readonly<Record<string, string>>> = []
|
|
101
|
+
const launchProcessEnvironments: Array<Record<string, string | undefined>> =
|
|
102
|
+
[]
|
|
101
103
|
const materializeOptions: Array<
|
|
102
104
|
Parameters<WorktreeService["materialize"]>[3]
|
|
103
105
|
> = []
|
|
@@ -265,6 +267,11 @@ const createHarness = (options: HarnessOptions = {}) => {
|
|
|
265
267
|
events.push(`launch:${cli}`)
|
|
266
268
|
launches.push({ cli, args, cwd })
|
|
267
269
|
launchEnvironments.push(environment)
|
|
270
|
+
launchProcessEnvironments.push({
|
|
271
|
+
AGENCY_SESSION_ID: process.env.AGENCY_SESSION_ID,
|
|
272
|
+
AGENCY_TARGET: process.env.AGENCY_TARGET,
|
|
273
|
+
AGENCY_PROMPT: process.env.AGENCY_PROMPT,
|
|
274
|
+
})
|
|
268
275
|
if (options.launchError) throw options.launchError
|
|
269
276
|
}
|
|
270
277
|
const defaultPick: PickWorkTarget = () => Effect.succeed(null)
|
|
@@ -307,6 +314,7 @@ const createHarness = (options: HarnessOptions = {}) => {
|
|
|
307
314
|
probes,
|
|
308
315
|
launches,
|
|
309
316
|
launchEnvironments,
|
|
317
|
+
launchProcessEnvironments,
|
|
310
318
|
materializeOptions,
|
|
311
319
|
statusUpdates,
|
|
312
320
|
taskStatuses,
|
|
@@ -484,7 +492,7 @@ describe("work command", () => {
|
|
|
484
492
|
args: [
|
|
485
493
|
"opencode",
|
|
486
494
|
"--prompt",
|
|
487
|
-
"Work on the epic. Read /workbase/epics/delivery/EPIC.md.",
|
|
495
|
+
"Agency worker launch target: epic:delivery. Work on the epic. Read /workbase/epics/delivery/EPIC.md.",
|
|
488
496
|
],
|
|
489
497
|
cwd: "/workbase/epics/delivery",
|
|
490
498
|
})
|
|
@@ -538,7 +546,7 @@ describe("work command", () => {
|
|
|
538
546
|
args: [
|
|
539
547
|
"opencode",
|
|
540
548
|
"--prompt",
|
|
541
|
-
"Work on the task. Read /workbase/tasks/delivery/TASK.md.",
|
|
549
|
+
"Agency worker launch target: task:delivery. Work on the task. Read /workbase/tasks/delivery/TASK.md.",
|
|
542
550
|
],
|
|
543
551
|
cwd: "/workbase/tasks/delivery",
|
|
544
552
|
})
|
|
@@ -565,7 +573,7 @@ describe("work command", () => {
|
|
|
565
573
|
args: [
|
|
566
574
|
"opencode",
|
|
567
575
|
"--prompt",
|
|
568
|
-
"Start the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
|
|
576
|
+
"Agency worker launch target: execution-unit:phase/example/implementation. Start the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
|
|
569
577
|
],
|
|
570
578
|
cwd: phaseDirectory,
|
|
571
579
|
})
|
|
@@ -797,11 +805,17 @@ describe("work command", () => {
|
|
|
797
805
|
expect(harness.launches[0]?.args).toEqual([
|
|
798
806
|
"opencode",
|
|
799
807
|
"--prompt",
|
|
800
|
-
"Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
808
|
+
"Agency worker launch target: execution-unit:task/example. Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
801
809
|
])
|
|
802
810
|
expect(harness.launchEnvironments[0]?.AGENCY_PROMPT).toBe(
|
|
803
|
-
"Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
811
|
+
"Agency worker launch target: execution-unit:task/example. Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
804
812
|
)
|
|
813
|
+
expect(harness.launchProcessEnvironments[0]).toEqual({
|
|
814
|
+
AGENCY_SESSION_ID: harness.launchEnvironments[0]?.AGENCY_SESSION_ID,
|
|
815
|
+
AGENCY_TARGET: "execution-unit:task/example",
|
|
816
|
+
AGENCY_PROMPT:
|
|
817
|
+
"Agency worker launch target: execution-unit:task/example. Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
818
|
+
})
|
|
805
819
|
})
|
|
806
820
|
|
|
807
821
|
test("continues existing work with the resume command", async () => {
|
|
@@ -813,10 +827,10 @@ describe("work command", () => {
|
|
|
813
827
|
"opencode",
|
|
814
828
|
"--continue",
|
|
815
829
|
"--prompt",
|
|
816
|
-
"Continue the task. Read /workbase/tasks/example/TASK.md.",
|
|
830
|
+
"Agency worker launch target: execution-unit:task/example. Continue the task. Read /workbase/tasks/example/TASK.md.",
|
|
817
831
|
])
|
|
818
832
|
expect(harness.launchEnvironments[0]?.AGENCY_PROMPT).toBe(
|
|
819
|
-
"Continue the task. Read /workbase/tasks/example/TASK.md.",
|
|
833
|
+
"Agency worker launch target: execution-unit:task/example. Continue the task. Read /workbase/tasks/example/TASK.md.",
|
|
820
834
|
)
|
|
821
835
|
})
|
|
822
836
|
|
|
@@ -840,7 +854,7 @@ describe("work command", () => {
|
|
|
840
854
|
"opencode",
|
|
841
855
|
"--continue",
|
|
842
856
|
"--prompt",
|
|
843
|
-
"Continue the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
|
|
857
|
+
"Agency worker launch target: execution-unit:phase/example/implementation. Continue the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
|
|
844
858
|
],
|
|
845
859
|
cwd: phaseDirectory,
|
|
846
860
|
})
|
|
@@ -870,7 +884,7 @@ describe("work command", () => {
|
|
|
870
884
|
"codex",
|
|
871
885
|
"--task",
|
|
872
886
|
"example",
|
|
873
|
-
"Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
887
|
+
"Agency worker launch target: execution-unit:task/example. Start the task. Read /workbase/tasks/example/TASK.md.",
|
|
874
888
|
],
|
|
875
889
|
cwd: taskDirectory,
|
|
876
890
|
})
|
package/src/commands/work.ts
CHANGED
|
@@ -264,6 +264,7 @@ export const work = (
|
|
|
264
264
|
launchPath = dirname(workspace.phasePath ?? workspace.taskPath)
|
|
265
265
|
writablePath = workspace.writablePath ?? undefined
|
|
266
266
|
}
|
|
267
|
+
prompt = `Agency worker launch target: ${targetNodeId(target)}. ${prompt}`
|
|
267
268
|
|
|
268
269
|
const explicitlyRequested = Boolean(
|
|
269
270
|
options.runner ||
|
package/src/protocol.ts
CHANGED
|
@@ -99,6 +99,7 @@ const errorMetadata: Readonly<Record<string, ErrorMetadata>> = {
|
|
|
99
99
|
},
|
|
100
100
|
ArchiveError: { code: "ARCHIVE_ERROR", retryable: false },
|
|
101
101
|
WorktreeError: { code: "WORKTREE_ERROR", retryable: false },
|
|
102
|
+
PushError: { code: "PUSH_ERROR", retryable: false },
|
|
102
103
|
PullRequestError: { code: "PULL_REQUEST_ERROR", retryable: false },
|
|
103
104
|
ReviewError: { code: "REVIEW_ERROR", retryable: false },
|
|
104
105
|
ContextError: {
|
|
@@ -129,6 +129,36 @@ describe("IntegrationService", () => {
|
|
|
129
129
|
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
130
130
|
"config.skills.paths = [...new Set",
|
|
131
131
|
)
|
|
132
|
+
expect(managedWorkbaseOpencodePlugin).toContain('"chat.message"')
|
|
133
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
134
|
+
"if (!Array.isArray(parts)) return",
|
|
135
|
+
)
|
|
136
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
137
|
+
'"experimental.chat.system.transform"',
|
|
138
|
+
)
|
|
139
|
+
expect(managedWorkbaseOpencodePlugin).toContain('"shell.env"')
|
|
140
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
141
|
+
"if (context?.target !== launchTarget) return",
|
|
142
|
+
)
|
|
143
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
144
|
+
"agencyContext(directory, false)",
|
|
145
|
+
)
|
|
146
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
147
|
+
"result.validation?.valid !== true",
|
|
148
|
+
)
|
|
149
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
150
|
+
"dirname(document) !== resolve(directory)",
|
|
151
|
+
)
|
|
152
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
153
|
+
"!result.authority?.writable?.checkoutPath",
|
|
154
|
+
)
|
|
155
|
+
expect(managedWorkbaseOpencodePlugin).toContain('status !== "working"')
|
|
156
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
157
|
+
"output.env.AGENCY_SESSION_ID = sessionID",
|
|
158
|
+
)
|
|
159
|
+
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
160
|
+
"Do not invoke agency work for this target",
|
|
161
|
+
)
|
|
132
162
|
expect(managedWorkbaseOpencodePlugin).toContain(
|
|
133
163
|
".map((path) => `${path}${sep}.`)",
|
|
134
164
|
)
|
|
@@ -142,6 +172,121 @@ describe("IntegrationService", () => {
|
|
|
142
172
|
).toBe(false)
|
|
143
173
|
})
|
|
144
174
|
|
|
175
|
+
test("binds validated worker identity to an OpenCode session", async () => {
|
|
176
|
+
const path = join(root, "agency-repository-skills.ts")
|
|
177
|
+
const contextResponse = JSON.stringify({
|
|
178
|
+
version: 1,
|
|
179
|
+
ok: true,
|
|
180
|
+
result: {
|
|
181
|
+
workbase: { root },
|
|
182
|
+
target: {
|
|
183
|
+
kind: "task",
|
|
184
|
+
taskId: "example",
|
|
185
|
+
path: join(root, "TASK.md"),
|
|
186
|
+
},
|
|
187
|
+
authority: {
|
|
188
|
+
mode: "execution",
|
|
189
|
+
writable: { checkoutPath: join(root, "tasks/example/code/agency") },
|
|
190
|
+
},
|
|
191
|
+
documents: { task: { data: { status: "working" } } },
|
|
192
|
+
validation: { valid: true, warnings: [] },
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
await Bun.write(
|
|
196
|
+
path,
|
|
197
|
+
managedWorkbaseOpencodePlugin
|
|
198
|
+
.replace("const contextTarget =", "export const contextTarget =")
|
|
199
|
+
.replace("const agencyContext =", "export const agencyContext =")
|
|
200
|
+
.replace(
|
|
201
|
+
"const workerLaunchTarget =",
|
|
202
|
+
"export const workerLaunchTarget =",
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
const originalSpawn = Bun.spawn
|
|
206
|
+
Bun.spawn = (() =>
|
|
207
|
+
({
|
|
208
|
+
stdout: new Response(contextResponse).body!,
|
|
209
|
+
exited: Promise.resolve(0),
|
|
210
|
+
}) as ReturnType<typeof Bun.spawn>) as typeof Bun.spawn
|
|
211
|
+
try {
|
|
212
|
+
const generated = await import(
|
|
213
|
+
`${pathToFileURL(path).href}?worker-identity`
|
|
214
|
+
)
|
|
215
|
+
expect(
|
|
216
|
+
generated.workerLaunchTarget([
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: "Agency worker launch target: execution-unit:task/example. Start the task.",
|
|
220
|
+
},
|
|
221
|
+
]),
|
|
222
|
+
).toBe("execution-unit:task/example")
|
|
223
|
+
expect(generated.workerLaunchTarget(undefined)).toBeUndefined()
|
|
224
|
+
expect(
|
|
225
|
+
generated.contextTarget({
|
|
226
|
+
target: { kind: "task", taskId: "example" },
|
|
227
|
+
authority: { mode: "execution" },
|
|
228
|
+
}),
|
|
229
|
+
).toBe("execution-unit:task/example")
|
|
230
|
+
expect(await generated.agencyContext(root)).toMatchObject({
|
|
231
|
+
root,
|
|
232
|
+
target: "execution-unit:task/example",
|
|
233
|
+
task: "example",
|
|
234
|
+
})
|
|
235
|
+
const hooks = await generated.default({ directory: root } as never)
|
|
236
|
+
await hooks["chat.message"]!(
|
|
237
|
+
{ sessionID: "worker-session" } as never,
|
|
238
|
+
{
|
|
239
|
+
parts: [
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: "Agency worker launch target: execution-unit:task/example. Start the task.",
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
} as never,
|
|
246
|
+
)
|
|
247
|
+
await hooks["chat.message"]!(
|
|
248
|
+
{ sessionID: "mismatched-session" } as never,
|
|
249
|
+
{
|
|
250
|
+
parts: [
|
|
251
|
+
{
|
|
252
|
+
type: "text",
|
|
253
|
+
text: "Agency worker launch target: execution-unit:task/other. Start the task.",
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
} as never,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const system = { system: [] as string[] }
|
|
260
|
+
await hooks["experimental.chat.system.transform"]!(
|
|
261
|
+
{ sessionID: "worker-session" } as never,
|
|
262
|
+
system,
|
|
263
|
+
)
|
|
264
|
+
expect(system.system).toEqual([
|
|
265
|
+
expect.stringContaining(
|
|
266
|
+
"active worker for execution-unit:task/example",
|
|
267
|
+
),
|
|
268
|
+
])
|
|
269
|
+
const mismatchedSystem = { system: [] as string[] }
|
|
270
|
+
await hooks["experimental.chat.system.transform"]!(
|
|
271
|
+
{ sessionID: "mismatched-session" } as never,
|
|
272
|
+
mismatchedSystem,
|
|
273
|
+
)
|
|
274
|
+
expect(mismatchedSystem.system).toEqual([])
|
|
275
|
+
|
|
276
|
+
const shell = { env: {} as Record<string, string> }
|
|
277
|
+
await hooks["shell.env"]!({ sessionID: "worker-session" } as never, shell)
|
|
278
|
+
expect(shell.env).toMatchObject({
|
|
279
|
+
AGENCY_SESSION_ID: "worker-session",
|
|
280
|
+
AGENCY_TARGET: "execution-unit:task/example",
|
|
281
|
+
AGENCY_WORKBASE: root,
|
|
282
|
+
AGENCY_TASK_ID: "example",
|
|
283
|
+
AGENCY_WRITABLE_CHECKOUT: join(root, "tasks/example/code/agency"),
|
|
284
|
+
})
|
|
285
|
+
} finally {
|
|
286
|
+
Bun.spawn = originalSpawn
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
145
290
|
test("registers a TUI-only /agency-debug diagnostic", async () => {
|
|
146
291
|
const config = JSON.parse(managedBody(managedWorkbaseOpencodeTui))
|
|
147
292
|
expect(config).toEqual({
|
|
@@ -268,10 +413,17 @@ describe("IntegrationService", () => {
|
|
|
268
413
|
expect(body).toContain("Preserve parent backlinks")
|
|
269
414
|
expect(body).toContain("dirty-worktree, active-claim, revision")
|
|
270
415
|
expect(body).toContain("`agency work` is the human launch flow")
|
|
416
|
+
expect(body).toContain("Agency worker launch target: <target>.")
|
|
417
|
+
expect(body).toContain("environment variables and a generated")
|
|
418
|
+
expect(body).toContain("the initial instruction is a generated")
|
|
419
|
+
expect(body).toContain("Herdr state is never part of worker identity")
|
|
420
|
+
expect(body).toMatch(/If the prompt\s+and context disagree/)
|
|
271
421
|
expect(body).toContain("marks execution work")
|
|
272
422
|
expect(body).toContain("without creating a claim")
|
|
273
423
|
expect(body).toContain("formatting, type checks, build, dead-code checks")
|
|
274
424
|
expect(body).toContain("Review and commit the diff")
|
|
425
|
+
expect(body).toContain("Use `agency push`")
|
|
426
|
+
expect(body).toContain("never authors semantic commit descriptions")
|
|
275
427
|
expect(body).toContain("Run `agency validate`")
|
|
276
428
|
expect(body).toContain("only with explicit user intent")
|
|
277
429
|
expect(body).toContain("An execution unit remains `working`")
|