@markjaquith/agency 3.4.0 → 3.4.2
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
CHANGED
|
@@ -318,11 +318,16 @@ set `agent` in `$XDG_CONFIG_HOME/agency/agency.json` (or
|
|
|
318
318
|
The selection precedence is `--agent` (including the legacy `--opencode` and
|
|
319
319
|
`--claude` aliases), then `AGENCY_AGENT`, then `agent`, then automatic
|
|
320
320
|
detection. Supported global values are `opencode2`, `opencode`, `pi`, and
|
|
321
|
-
`claude`; an unavailable configured agent fails rather than falling back. A
|
|
322
|
-
is fresh unless `AGENCY_SESSION_ID` is already set; resumed launches use
|
|
323
|
-
`resumeCommand` when configured. The built-in presets use
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
`claude`; an unavailable configured agent fails rather than falling back. A
|
|
322
|
+
launch is fresh unless `AGENCY_SESSION_ID` is already set; resumed launches use
|
|
323
|
+
the agent's `resumeCommand` when configured. The built-in presets use
|
|
324
|
+
`--continue` only for resumed launches, except autonomous OpenCode V2 launches,
|
|
325
|
+
which open a fresh TUI session so the generated continuation prompt cannot be
|
|
326
|
+
routed to an unrelated prior session. By default Agency opens the agent without
|
|
327
|
+
a prompt. `--auto` uses its autonomous command and sends the generated task,
|
|
328
|
+
phase, or epic prompt. OpenCode V2 receives a launch-only environment marker;
|
|
329
|
+
Agency's managed TUI companion waits for the populated composer and dispatches
|
|
330
|
+
its native submit command once.
|
|
326
331
|
|
|
327
332
|
Custom agents are direct argv commands, never shell snippets:
|
|
328
333
|
|
|
@@ -352,6 +357,9 @@ Every agent receives the same `AGENCY_AGENT`, `AGENCY_SESSION_ID`,
|
|
|
352
357
|
`AGENCY_WORKBASE`, `AGENCY_TARGET`,
|
|
353
358
|
`AGENCY_TASK_ID`, `AGENCY_PHASE_ID`, and `AGENCY_PROMPT` environment. Configured
|
|
354
359
|
environment is added without overriding these normalized values.
|
|
360
|
+
Autonomous OpenCode V2 launches additionally receive
|
|
361
|
+
`AGENCY_TUI_AUTOSUBMIT=1`; the client-side managed TUI companion consumes it,
|
|
362
|
+
and manual and non-V2 launches do not receive it.
|
|
355
363
|
Execution-unit agents also receive `AGENCY_WRITABLE_CHECKOUT` with the
|
|
356
364
|
authoritative writable checkout path.
|
|
357
365
|
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ interface IntegrationResult {
|
|
|
18
18
|
| "opencode-plugin"
|
|
19
19
|
| "opencode-tui"
|
|
20
20
|
| "opencode-tui-plugin"
|
|
21
|
+
| "opencode-v2-tui-plugin"
|
|
21
22
|
readonly path: string
|
|
22
23
|
readonly state: string
|
|
23
24
|
readonly diagnostic: string
|
|
@@ -32,6 +33,7 @@ const integrationNames = {
|
|
|
32
33
|
"opencode-plugin": "OpenCode workbase plugin",
|
|
33
34
|
"opencode-tui": "OpenCode TUI config",
|
|
34
35
|
"opencode-tui-plugin": "OpenCode /agency-debug",
|
|
36
|
+
"opencode-v2-tui-plugin": "OpenCode V2 TUI companion",
|
|
35
37
|
} as const
|
|
36
38
|
|
|
37
39
|
const logHumanResult = (
|
|
@@ -23,6 +23,12 @@ import {
|
|
|
23
23
|
canUpdateManagedWorkbaseOpencodeTuiPlugin,
|
|
24
24
|
managedWorkbaseOpencodeTuiPlugin,
|
|
25
25
|
} from "../workbase/opencode-tui-plugin-file"
|
|
26
|
+
import {
|
|
27
|
+
canUpdateManagedWorkbaseOpencodeV2TuiFile,
|
|
28
|
+
managedWorkbaseOpencodeV2TuiIndex,
|
|
29
|
+
managedWorkbaseOpencodeV2TuiPackage,
|
|
30
|
+
managedWorkbaseOpencodeV2TuiPlugin,
|
|
31
|
+
} from "../workbase/opencode-v2-tui-plugin-file"
|
|
26
32
|
|
|
27
33
|
const managedHeaderPattern =
|
|
28
34
|
/^\/\/ agency-managed: sha256=([a-f0-9]{64})\r?\n\r?\n/
|
|
@@ -46,6 +52,7 @@ interface IntegrationFileStatus {
|
|
|
46
52
|
| "opencode-plugin"
|
|
47
53
|
| "opencode-tui"
|
|
48
54
|
| "opencode-tui-plugin"
|
|
55
|
+
| "opencode-v2-tui-plugin"
|
|
49
56
|
readonly path: string
|
|
50
57
|
readonly state: IntegrationFileState
|
|
51
58
|
readonly diagnostic: string
|
|
@@ -144,6 +151,25 @@ const describe = (
|
|
|
144
151
|
"Run 'agency integration sync' to install /agency-debug.",
|
|
145
152
|
}
|
|
146
153
|
}
|
|
154
|
+
if (name === "opencode-v2-tui-plugin") {
|
|
155
|
+
return state === "managed"
|
|
156
|
+
? {
|
|
157
|
+
diagnostic: "Agency's managed OpenCode V2 TUI companion is current.",
|
|
158
|
+
remediation: null,
|
|
159
|
+
}
|
|
160
|
+
: state === "customized"
|
|
161
|
+
? {
|
|
162
|
+
diagnostic:
|
|
163
|
+
"A user-owned OpenCode V2 TUI companion is present and was preserved.",
|
|
164
|
+
remediation: null,
|
|
165
|
+
}
|
|
166
|
+
: {
|
|
167
|
+
diagnostic:
|
|
168
|
+
"The managed OpenCode V2 TUI companion needs synchronization.",
|
|
169
|
+
remediation:
|
|
170
|
+
"Run 'agency integration sync' to install V2 autonomous prompt submission.",
|
|
171
|
+
}
|
|
172
|
+
}
|
|
147
173
|
return state === "missing" || state === "drifted"
|
|
148
174
|
? {
|
|
149
175
|
diagnostic: "Managed workbase instructions need synchronization.",
|
|
@@ -202,6 +228,10 @@ const inspect = (root: string) =>
|
|
|
202
228
|
"agency-repository-skills.ts",
|
|
203
229
|
)
|
|
204
230
|
const tuiPluginPath = join(opencodeDirectory, "tui", "agency-debug.ts")
|
|
231
|
+
const v2TuiDirectory = join(opencodeDirectory, "plugins", "agency-tui")
|
|
232
|
+
const v2TuiPackagePath = join(v2TuiDirectory, "package.json")
|
|
233
|
+
const v2TuiIndexPath = join(v2TuiDirectory, "index.ts")
|
|
234
|
+
const v2TuiPluginPath = join(v2TuiDirectory, "tui.ts")
|
|
205
235
|
const [
|
|
206
236
|
agents,
|
|
207
237
|
opencode,
|
|
@@ -211,6 +241,9 @@ const inspect = (root: string) =>
|
|
|
211
241
|
tui,
|
|
212
242
|
tuiJson,
|
|
213
243
|
tuiPlugin,
|
|
244
|
+
v2TuiPackage,
|
|
245
|
+
v2TuiIndex,
|
|
246
|
+
v2TuiPlugin,
|
|
214
247
|
] = yield* Effect.all(
|
|
215
248
|
[
|
|
216
249
|
fs.inspectFile(agentsPath),
|
|
@@ -221,6 +254,9 @@ const inspect = (root: string) =>
|
|
|
221
254
|
fs.inspectFile(tuiPath),
|
|
222
255
|
fs.inspectFile(tuiJsonPath),
|
|
223
256
|
fs.inspectFile(tuiPluginPath),
|
|
257
|
+
fs.inspectFile(v2TuiPackagePath),
|
|
258
|
+
fs.inspectFile(v2TuiIndexPath),
|
|
259
|
+
fs.inspectFile(v2TuiPluginPath),
|
|
224
260
|
] as const,
|
|
225
261
|
{ concurrency: 8 },
|
|
226
262
|
)
|
|
@@ -317,6 +353,30 @@ const inspect = (root: string) =>
|
|
|
317
353
|
files.push(fileStatus("opencode-tui-plugin", tuiPluginPath, "missing"))
|
|
318
354
|
}
|
|
319
355
|
|
|
356
|
+
const v2TuiFiles = [
|
|
357
|
+
[v2TuiPackage, managedWorkbaseOpencodeV2TuiPackage],
|
|
358
|
+
[v2TuiIndex, managedWorkbaseOpencodeV2TuiIndex],
|
|
359
|
+
[v2TuiPlugin, managedWorkbaseOpencodeV2TuiPlugin],
|
|
360
|
+
] as const
|
|
361
|
+
const v2TuiStates = v2TuiFiles.map(([file, managed]) => {
|
|
362
|
+
if (file.kind === "missing") return "missing" as const
|
|
363
|
+
if (file.kind !== "file") return "customized" as const
|
|
364
|
+
if (file.content === managed) return "managed" as const
|
|
365
|
+
return canUpdateManagedWorkbaseOpencodeV2TuiFile(file.content)
|
|
366
|
+
? ("drifted" as const)
|
|
367
|
+
: ("customized" as const)
|
|
368
|
+
})
|
|
369
|
+
const v2TuiState = v2TuiStates.every((state) => state === "missing")
|
|
370
|
+
? "missing"
|
|
371
|
+
: v2TuiStates.every((state) => state === "managed")
|
|
372
|
+
? "managed"
|
|
373
|
+
: v2TuiStates.some((state) => state === "customized")
|
|
374
|
+
? "customized"
|
|
375
|
+
: "drifted"
|
|
376
|
+
files.push(
|
|
377
|
+
fileStatus("opencode-v2-tui-plugin", v2TuiPluginPath, v2TuiState),
|
|
378
|
+
)
|
|
379
|
+
|
|
320
380
|
return { files, legacyPlugin }
|
|
321
381
|
})
|
|
322
382
|
|
|
@@ -429,6 +489,21 @@ export class IntegrationService extends Effect.Service<IntegrationService>()(
|
|
|
429
489
|
status.path,
|
|
430
490
|
managedWorkbaseOpencodeTuiPlugin,
|
|
431
491
|
)
|
|
492
|
+
} else if (status.name === "opencode-v2-tui-plugin") {
|
|
493
|
+
const directory = dirname(status.path)
|
|
494
|
+
yield* fs.createDirectory(directory)
|
|
495
|
+
yield* fs.writeFile(
|
|
496
|
+
join(directory, "package.json"),
|
|
497
|
+
managedWorkbaseOpencodeV2TuiPackage,
|
|
498
|
+
)
|
|
499
|
+
yield* fs.writeFile(
|
|
500
|
+
join(directory, "index.ts"),
|
|
501
|
+
managedWorkbaseOpencodeV2TuiIndex,
|
|
502
|
+
)
|
|
503
|
+
yield* fs.writeFile(
|
|
504
|
+
status.path,
|
|
505
|
+
managedWorkbaseOpencodeV2TuiPlugin,
|
|
506
|
+
)
|
|
432
507
|
}
|
|
433
508
|
}
|
|
434
509
|
files.push({
|
|
@@ -31,7 +31,7 @@ const BUILTIN_AGENTS: Readonly<Record<string, AgentDefinition>> = {
|
|
|
31
31
|
command: ["opencode2"],
|
|
32
32
|
autoCommand: ["opencode2", "--prompt", "{prompt}"],
|
|
33
33
|
resumeCommand: ["opencode2", "--continue"],
|
|
34
|
-
autoResumeCommand: ["opencode2", "--
|
|
34
|
+
autoResumeCommand: ["opencode2", "--prompt", "{prompt}"],
|
|
35
35
|
},
|
|
36
36
|
opencode: {
|
|
37
37
|
command: ["opencode"],
|
|
@@ -125,6 +125,9 @@ export const agentEnvironment = (
|
|
|
125
125
|
AGENCY_TASK_ID: variables.task,
|
|
126
126
|
AGENCY_PHASE_ID: variables.phase,
|
|
127
127
|
AGENCY_PROMPT: variables.prompt,
|
|
128
|
+
...(agent === "opencode2" && variables.prompt
|
|
129
|
+
? { AGENCY_TUI_AUTOSUBMIT: "1" }
|
|
130
|
+
: {}),
|
|
128
131
|
})
|
|
129
132
|
|
|
130
133
|
const SECRET_NAME =
|
|
@@ -129,10 +129,11 @@ const skillInfo = (location: string) => {
|
|
|
129
129
|
const id = basename(location) === "SKILL.md"
|
|
130
130
|
? basename(dirname(location))
|
|
131
131
|
: basename(location, extname(location))
|
|
132
|
+
const description = scalar("description")
|
|
132
133
|
return {
|
|
133
134
|
id,
|
|
134
135
|
name: scalar("name") ?? id,
|
|
135
|
-
description:
|
|
136
|
+
...(description === undefined ? {} : { description }),
|
|
136
137
|
location,
|
|
137
138
|
content: match ? raw.slice(match[0].length) : raw,
|
|
138
139
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
|
|
3
|
+
const managedHeaderPattern =
|
|
4
|
+
/^\/\/ agency-managed: sha256=([a-f0-9]{64})\r?\n\r?\n/
|
|
5
|
+
|
|
6
|
+
const checksum = (content: string) =>
|
|
7
|
+
createHash("sha256").update(content).digest("hex")
|
|
8
|
+
|
|
9
|
+
const managed = (content: string) =>
|
|
10
|
+
`// agency-managed: sha256=${checksum(content)}\n\n${content}`
|
|
11
|
+
|
|
12
|
+
const packageBody = `${JSON.stringify(
|
|
13
|
+
{
|
|
14
|
+
name: "agency-tui",
|
|
15
|
+
private: true,
|
|
16
|
+
type: "module",
|
|
17
|
+
exports: {
|
|
18
|
+
".": "./index.ts",
|
|
19
|
+
"./tui": "./tui.ts",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
null,
|
|
23
|
+
2,
|
|
24
|
+
)}\n`
|
|
25
|
+
|
|
26
|
+
const indexBody = `const plugin = {
|
|
27
|
+
id: "agency.tui.loader",
|
|
28
|
+
setup() {},
|
|
29
|
+
async server() {
|
|
30
|
+
return {}
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default plugin
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
const tuiBody = `const autosubmitTimeoutMs = 10_000
|
|
38
|
+
const autosubmitRetryMs = 25
|
|
39
|
+
|
|
40
|
+
type AutosubmitContext = {
|
|
41
|
+
keymap: {
|
|
42
|
+
commands(): readonly { id?: string }[]
|
|
43
|
+
dispatch(id: string): unknown
|
|
44
|
+
}
|
|
45
|
+
renderer: { currentFocusedEditor?: unknown }
|
|
46
|
+
ui: {
|
|
47
|
+
router: { current(): { type: string } }
|
|
48
|
+
toast: {
|
|
49
|
+
show(input: {
|
|
50
|
+
variant: "error"
|
|
51
|
+
title: string
|
|
52
|
+
message: string
|
|
53
|
+
duration: number
|
|
54
|
+
}): void
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createAgencyAutosubmit = (
|
|
60
|
+
options: { timeoutMs?: number; retryMs?: number } = {},
|
|
61
|
+
) => {
|
|
62
|
+
let started = false
|
|
63
|
+
|
|
64
|
+
return (context: AutosubmitContext) => {
|
|
65
|
+
if (started) return () => {}
|
|
66
|
+
if (process.env.AGENCY_TUI_AUTOSUBMIT !== "1") return () => {}
|
|
67
|
+
if (!process.env.AGENCY_PROMPT) return () => {}
|
|
68
|
+
started = true
|
|
69
|
+
delete process.env.AGENCY_TUI_AUTOSUBMIT
|
|
70
|
+
|
|
71
|
+
const timeoutMs = options.timeoutMs ?? autosubmitTimeoutMs
|
|
72
|
+
const retryMs = options.retryMs ?? autosubmitRetryMs
|
|
73
|
+
const deadline = Date.now() + timeoutMs
|
|
74
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
75
|
+
let stopped = false
|
|
76
|
+
|
|
77
|
+
const stop = () => {
|
|
78
|
+
stopped = true
|
|
79
|
+
if (timer) clearTimeout(timer)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const attempt = () => {
|
|
83
|
+
if (stopped) return
|
|
84
|
+
if (context.ui.router.current().type !== "home") {
|
|
85
|
+
stop()
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const submitReady = context.keymap
|
|
90
|
+
.commands()
|
|
91
|
+
.some((command) => command.id === "prompt.submit")
|
|
92
|
+
if (context.renderer.currentFocusedEditor && submitReady) {
|
|
93
|
+
stop()
|
|
94
|
+
context.keymap.dispatch("prompt.submit")
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (Date.now() >= deadline) {
|
|
99
|
+
stop()
|
|
100
|
+
context.ui.toast.show({
|
|
101
|
+
variant: "error",
|
|
102
|
+
title: "Agency launch",
|
|
103
|
+
message: "The task prompt is ready but could not be submitted automatically.",
|
|
104
|
+
duration: 8_000,
|
|
105
|
+
})
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
timer = setTimeout(attempt, retryMs)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
attempt()
|
|
112
|
+
return stop
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const autosubmit = createAgencyAutosubmit()
|
|
117
|
+
|
|
118
|
+
export default {
|
|
119
|
+
id: "agency.tui",
|
|
120
|
+
setup(context: AutosubmitContext) {
|
|
121
|
+
return autosubmit(context)
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
export const managedWorkbaseOpencodeV2TuiPackage = packageBody
|
|
127
|
+
export const managedWorkbaseOpencodeV2TuiIndex = managed(indexBody)
|
|
128
|
+
export const managedWorkbaseOpencodeV2TuiPlugin = managed(tuiBody)
|
|
129
|
+
|
|
130
|
+
export const canUpdateManagedWorkbaseOpencodeV2TuiFile = (content: string) => {
|
|
131
|
+
const match = content.match(managedHeaderPattern)
|
|
132
|
+
if (!match?.[1]) return false
|
|
133
|
+
return checksum(content.slice(match[0].length)) === match[1]
|
|
134
|
+
}
|