@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
|
@@ -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 (
|
|
41
|
-
|
|
42
|
-
|
|
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, {
|
|
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:
|
|
53
|
-
checkout:
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
config.permission
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
[join(root, "*")]: "allow"
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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()
|