@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.
@@ -7,9 +7,41 @@ const checksum = (content: string) =>
7
7
  createHash("sha256").update(content).digest("hex")
8
8
 
9
9
  const body = `import { existsSync, readFileSync } from "node:fs"
10
- import { dirname, join, sep } from "node:path"
10
+ import { dirname, join, resolve, sep } from "node:path"
11
11
  import type { Plugin } from "@opencode-ai/plugin"
12
12
 
13
+ const workerLaunchPattern = /^Agency worker launch target: ([^.\\s]+)\\./
14
+
15
+ type AgencyContext = {
16
+ root?: string
17
+ checkout?: string
18
+ target?: string
19
+ task?: string
20
+ phase?: string
21
+ }
22
+
23
+ const contextTarget = (result: Record<string, any>): string | undefined => {
24
+ const target = result.target
25
+ if (target?.kind === "epic") return \`epic:\${target.epicId}\`
26
+ if (target?.kind === "phase") {
27
+ return \`execution-unit:phase/\${target.taskId}/\${target.phaseId}\`
28
+ }
29
+ if (target?.kind === "task") {
30
+ return result.authority?.mode === "execution"
31
+ ? \`execution-unit:task/\${target.taskId}\`
32
+ : \`task:\${target.taskId}\`
33
+ }
34
+ }
35
+
36
+ const workerLaunchTarget = (parts: unknown) => {
37
+ if (!Array.isArray(parts)) return
38
+ const text = parts
39
+ .filter((part) => part.type === "text")
40
+ .map((part) => part.text ?? "")
41
+ .join("\\n")
42
+ return text.match(workerLaunchPattern)?.[1]
43
+ }
44
+
13
45
  const discoverWorkbase = (directory: string) => {
14
46
  let current = directory
15
47
  while (true) {
@@ -37,64 +69,117 @@ const discoverCheckout = (directory: string, root: string | undefined) => {
37
69
  }
38
70
  }
39
71
 
40
- const agencyContext = async (directory: string) => {
41
- const task = process.env.AGENCY_TASK_ID
42
- const phase = process.env.AGENCY_PHASE_ID
72
+ const agencyContext = async (
73
+ directory: string,
74
+ useEnvironmentTarget = true,
75
+ ): Promise<AgencyContext | undefined> => {
76
+ const task = useEnvironmentTarget ? process.env.AGENCY_TASK_ID : undefined
77
+ const phase = useEnvironmentTarget ? process.env.AGENCY_PHASE_ID : undefined
43
78
  const args = task
44
79
  ? ["agency", "context", "--task", task, ...(phase ? ["--phase", phase] : []), "--compact", "--json"]
45
80
  : ["agency", "context", ".", "--compact", "--json"]
46
- const child = Bun.spawn(args, { cwd: directory, stdout: "pipe", stderr: "ignore" })
81
+ const child = Bun.spawn(args, {
82
+ cwd: directory,
83
+ env: process.env,
84
+ stdout: "pipe",
85
+ stderr: "ignore",
86
+ })
47
87
  const output = await new Response(child.stdout).text()
48
88
  if ((await child.exited) !== 0) return
49
89
  const envelope = JSON.parse(output)
50
90
  if (envelope.ok !== true) return
91
+ const result = envelope.result ?? {}
92
+ const target = contextTarget(result)
93
+ const document = result.target?.path
94
+ const status = result.documents?.phase?.data?.status ?? result.documents?.task?.data?.status
95
+ if (
96
+ result.validation?.valid !== true ||
97
+ !target ||
98
+ !document ||
99
+ dirname(document) !== resolve(directory) ||
100
+ (target.startsWith("execution-unit:") &&
101
+ (!result.authority?.writable?.checkoutPath || status !== "working"))
102
+ ) return
51
103
  return {
52
- root: envelope.result?.workbase?.root as string | undefined,
53
- checkout: envelope.result?.authority?.writable?.checkoutPath as string | undefined,
104
+ root: result.workbase?.root,
105
+ checkout: result.authority?.writable?.checkoutPath,
106
+ target,
107
+ task: result.target?.taskId,
108
+ phase: result.target?.phaseId,
54
109
  }
55
110
  }
56
111
 
57
- const plugin: Plugin = async ({ directory }) => ({
58
- config: async (config) => {
59
- const context = await agencyContext(directory).catch(() => undefined)
60
- const root = process.env.AGENCY_WORKBASE ?? context?.root ?? discoverWorkbase(directory)
61
- const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout ?? discoverCheckout(directory, root)
62
-
63
- const reference = config.references?.workbase
64
- if (
65
- root &&
66
- typeof reference === "object" &&
67
- reference.path === ".." &&
68
- reference.description ===
69
- "Complete Agency workbase context; write authority still comes only from agency context" &&
70
- typeof config.permission !== "string"
71
- ) {
72
- config.permission ??= {}
73
- const external = config.permission.external_directory
74
- if (external === undefined) {
75
- config.permission.external_directory = { [join(root, "*")]: "allow" }
76
- } else if (typeof external === "object") {
77
- config.permission.external_directory = {
78
- [join(root, "*")]: "allow",
79
- ...external,
112
+ const plugin: Plugin = async ({ directory }) => {
113
+ const workerSessions = new Map<string, AgencyContext>()
114
+
115
+ return {
116
+ config: async (config) => {
117
+ const context = await agencyContext(directory).catch(() => undefined)
118
+ const root = process.env.AGENCY_WORKBASE ?? context?.root ?? discoverWorkbase(directory)
119
+ const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout ?? discoverCheckout(directory, root)
120
+
121
+ const reference = config.references?.workbase
122
+ if (
123
+ root &&
124
+ typeof reference === "object" &&
125
+ reference.path === ".." &&
126
+ reference.description ===
127
+ "Complete Agency workbase context; write authority still comes only from agency context" &&
128
+ typeof config.permission !== "string"
129
+ ) {
130
+ config.permission ??= {}
131
+ const external = config.permission.external_directory
132
+ if (external === undefined) {
133
+ config.permission.external_directory = { [join(root, "*")]: "allow" }
134
+ } else if (typeof external === "object") {
135
+ config.permission.external_directory = {
136
+ [join(root, "*")]: "allow",
137
+ ...external,
138
+ }
80
139
  }
81
140
  }
82
- }
83
141
 
84
- if (!checkout) return
85
-
86
- const paths = [
87
- join(checkout, ".claude", "skills"),
88
- join(checkout, ".agents", "skills"),
89
- join(checkout, ".opencode", "skill"),
90
- join(checkout, ".opencode", "skills"),
91
- ].filter(existsSync).map((path) => \`\${path}\${sep}.\`)
92
- if (paths.length === 0) return
93
-
94
- config.skills ??= {}
95
- config.skills.paths = [...new Set([...(config.skills.paths ?? []), ...paths])]
96
- },
97
- })
142
+ if (!checkout) return
143
+
144
+ const paths = [
145
+ join(checkout, ".claude", "skills"),
146
+ join(checkout, ".agents", "skills"),
147
+ join(checkout, ".opencode", "skill"),
148
+ join(checkout, ".opencode", "skills"),
149
+ ].filter(existsSync).map((path) => \`\${path}\${sep}.\`)
150
+ if (paths.length === 0) return
151
+
152
+ config.skills ??= {}
153
+ config.skills.paths = [...new Set([...(config.skills.paths ?? []), ...paths])]
154
+ },
155
+ "chat.message": async ({ sessionID }, output) => {
156
+ const launchTarget = workerLaunchTarget(output.parts)
157
+ if (!launchTarget) return
158
+ const context = await agencyContext(directory, false).catch(() => undefined)
159
+ if (context?.target !== launchTarget) return
160
+ workerSessions.set(sessionID, context)
161
+ },
162
+ "experimental.chat.system.transform": async ({ sessionID }, output) => {
163
+ if (!sessionID) return
164
+ const context = workerSessions.get(sessionID)
165
+ if (!context?.target) return
166
+ output.system.push(
167
+ \`Agency verified this OpenCode session as the active worker for \${context.target}. Perform the assigned work directly. Do not invoke agency work for this target or launch a replacement worker.\`,
168
+ )
169
+ },
170
+ "shell.env": async ({ sessionID }, output) => {
171
+ if (!sessionID) return
172
+ const context = workerSessions.get(sessionID)
173
+ if (!context?.target) return
174
+ output.env.AGENCY_SESSION_ID = sessionID
175
+ output.env.AGENCY_TARGET = context.target
176
+ if (context.root) output.env.AGENCY_WORKBASE = context.root
177
+ if (context.checkout) output.env.AGENCY_WRITABLE_CHECKOUT = context.checkout
178
+ if (context.task) output.env.AGENCY_TASK_ID = context.task
179
+ if (context.phase) output.env.AGENCY_PHASE_ID = context.phase
180
+ },
181
+ }
182
+ }
98
183
 
99
184
  export default plugin
100
185
  `
@@ -95,9 +95,12 @@ describe("runner commands", () => {
95
95
  expect(environment).toMatchObject({
96
96
  AGENCY_RUNNER: "custom",
97
97
  AGENCY_CLAIMANT: "orchestrator",
98
+ AGENCY_SESSION_ID: "session-1",
99
+ AGENCY_WORKBASE: "/workbase",
98
100
  AGENCY_TARGET: "execution-unit:phase/task/build",
99
101
  AGENCY_TASK_ID: "task",
100
102
  AGENCY_PHASE_ID: "build",
103
+ AGENCY_PROMPT: "Read the task.",
101
104
  })
102
105
  expect(printableEnvironment(environment).VISIBLE).toBe("yes")
103
106
  expect(printableEnvironment(environment).ACCESS_TOKEN).toBeUndefined()