@markjaquith/agency 3.10.1 → 3.11.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 +12 -6
- package/package.json +1 -1
- package/pi-extensions/agency.ts +26 -5
- package/src/commands/work.ts +13 -1
- package/src/utils/interactive.js +48 -43
- package/src/workbase/AGENTS.md +9 -6
- package/src/workbase/opencode-launch.ts +15 -0
- package/src/workbase/opencode-plugin-file.ts +53 -27
package/README.md
CHANGED
|
@@ -140,7 +140,8 @@ whether the server plugin registered writable-checkout skills. It uses a native
|
|
|
140
140
|
toast and does not submit a prompt to an LLM. When no writable checkout skill
|
|
141
141
|
directory is available, server initialization is reported as indeterminate
|
|
142
142
|
rather than inferred from plugin discovery.
|
|
143
|
-
OpenCode discovers the config and plugin from
|
|
143
|
+
OpenCode discovers the config and plugin from Agency launch directories and
|
|
144
|
+
their ancestors.
|
|
144
145
|
The plugin uses OpenCode's plural discovery directory and exports both the V1
|
|
145
146
|
server function and the `opencode2` module wrapper. Integration sync migrates a
|
|
146
147
|
checksum-valid legacy singular-path plugin and preserves customized files.
|
|
@@ -394,9 +395,13 @@ identify the invoking session, so Agency deliberately avoids leaking one
|
|
|
394
395
|
session's worker identity into another; the validated marker and injected system
|
|
395
396
|
instruction remain the V2 fallback when the long-lived server did not inherit
|
|
396
397
|
the client's launch environment.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
For execution units, the built-in `pi` agent and the `opencode2` and `opencode`
|
|
399
|
+
agents running OpenCode V2 launch from the authoritative writable checkout so
|
|
400
|
+
their project and Git interfaces reflect the implementation repository.
|
|
401
|
+
OpenCode V1 stops configuration discovery at the checkout's Git root, so V1
|
|
402
|
+
launches remain rooted in the task or phase directory. Epic and multi-phase task orchestration remains
|
|
403
|
+
rooted in its Agency document directory. Ancestor discovery still supplies the
|
|
404
|
+
workbase `AGENTS.md` and managed OpenCode config.
|
|
400
405
|
Agency's managed OpenCode plugin grants the active workbase external-directory
|
|
401
406
|
access and exposes existing checkout-local `.claude/skills`, `.agents/skills`,
|
|
402
407
|
and `.opencode/{skill,skills}` definitions. V1 adds those source directories to
|
|
@@ -406,8 +411,9 @@ hook. The global Pi
|
|
|
406
411
|
extension provides equivalent whole-workbase context and additionally discovers
|
|
407
412
|
checkout-local `.pi/skills` through Pi's `resources_discover` lifecycle.
|
|
408
413
|
`agency work` supplies the checkout directly; plain OpenCode and Pi launches
|
|
409
|
-
resolve a materialized execution-unit
|
|
410
|
-
|
|
414
|
+
from a task or phase directory resolve a materialized execution-unit checkout
|
|
415
|
+
through `agency context` and instruct the agent to change to it before
|
|
416
|
+
implementation or Git operations. A multi-phase
|
|
411
417
|
task root has no single checkout, so launch from its phase directory when using
|
|
412
418
|
plain OpenCode or Pi. Other checkout-local configuration is not composed.
|
|
413
419
|
`--print-command` prints the cwd, command template, and non-secret environment
|
package/package.json
CHANGED
package/pi-extensions/agency.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
-
import { dirname, join, resolve } from "node:path"
|
|
2
|
+
import { dirname, join, resolve, sep } from "node:path"
|
|
3
3
|
|
|
4
4
|
type ExtensionAPI = {
|
|
5
5
|
exec: (
|
|
@@ -26,7 +26,9 @@ type ExtensionAPI = {
|
|
|
26
26
|
type AgencyContext = {
|
|
27
27
|
root?: string
|
|
28
28
|
checkout?: string
|
|
29
|
+
documentDirectory?: string
|
|
29
30
|
target?: string
|
|
31
|
+
working?: boolean
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const contextTarget = (result: Record<string, any>): string | undefined => {
|
|
@@ -78,14 +80,18 @@ const agencyContext = async (
|
|
|
78
80
|
result.workbase?.root !== root ||
|
|
79
81
|
!target ||
|
|
80
82
|
(target.startsWith("execution-unit:") &&
|
|
81
|
-
|
|
83
|
+
!result.authority?.writable?.checkoutPath)
|
|
82
84
|
)
|
|
83
85
|
return
|
|
84
86
|
|
|
85
87
|
return {
|
|
86
88
|
root,
|
|
87
89
|
checkout: result.authority?.writable?.checkoutPath,
|
|
90
|
+
documentDirectory: result.target?.path
|
|
91
|
+
? dirname(result.target.path)
|
|
92
|
+
: undefined,
|
|
88
93
|
target,
|
|
94
|
+
working: status === "working",
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
|
|
@@ -150,12 +156,27 @@ export default function agencyExtension(pi: ExtensionAPI) {
|
|
|
150
156
|
: undefined
|
|
151
157
|
const activeTarget = process.env.AGENCY_TARGET
|
|
152
158
|
const worker =
|
|
153
|
-
activeTarget && context.target === activeTarget
|
|
159
|
+
activeTarget && context.target === activeTarget && context.working
|
|
154
160
|
? `Agency verified this Pi session as the active worker for ${activeTarget}. Perform the assigned work directly. Do not invoke agency work for this target or launch a replacement worker.`
|
|
155
161
|
: undefined
|
|
156
162
|
const access = `The complete Agency workbase is available at ${context.root}. Use absolute paths under that root when workbase context is needed. Agency context remains the authority for writes.`
|
|
157
|
-
const
|
|
158
|
-
|
|
163
|
+
const checkout = context.checkout ? resolve(context.checkout) : undefined
|
|
164
|
+
const current = resolve(ctx.cwd)
|
|
165
|
+
const repository =
|
|
166
|
+
checkout &&
|
|
167
|
+
(current === checkout || current.startsWith(`${checkout}${sep}`))
|
|
168
|
+
const implementation = checkout
|
|
169
|
+
? [
|
|
170
|
+
repository
|
|
171
|
+
? `Pi is rooted in Agency's authoritative writable checkout at ${checkout}. Use this directory for source reads, edits, Git status and other Git operations, builds, tests, and formatting.`
|
|
172
|
+
: `Agency's authoritative writable checkout is ${checkout}. Before doing implementation work or Git operations, change the working directory to that checkout. Set each tool's working directory to it when supported; otherwise use absolute paths.`,
|
|
173
|
+
context.documentDirectory
|
|
174
|
+
? `Run Agency lifecycle commands from ${context.documentDirectory}; agency context may also be queried from the checkout.`
|
|
175
|
+
: undefined,
|
|
176
|
+
"Any reference checkouts reported by Agency context are read-only.",
|
|
177
|
+
]
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.join(" ")
|
|
159
180
|
: undefined
|
|
160
181
|
|
|
161
182
|
return {
|
package/src/commands/work.ts
CHANGED
|
@@ -37,7 +37,10 @@ import {
|
|
|
37
37
|
readValidationEvidence,
|
|
38
38
|
} from "../workbase/execution-contract"
|
|
39
39
|
import { ValidationFailedError } from "./validate"
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
openCodeDiscoversAncestorConfig,
|
|
42
|
+
prepareOpenCodeLaunch,
|
|
43
|
+
} from "../workbase/opencode-launch"
|
|
41
44
|
|
|
42
45
|
export interface WorkOptions extends BaseCommandOptions {
|
|
43
46
|
readonly directory?: string
|
|
@@ -378,6 +381,15 @@ export const work = (
|
|
|
378
381
|
options.auto,
|
|
379
382
|
)
|
|
380
383
|
cli = resolved.argv[0]!
|
|
384
|
+
if (writablePath && !config.agents?.[agent]) {
|
|
385
|
+
if (
|
|
386
|
+
agent === "pi" ||
|
|
387
|
+
((agent === "opencode2" || agent === "opencode") &&
|
|
388
|
+
(yield* openCodeDiscoversAncestorConfig(cli)))
|
|
389
|
+
) {
|
|
390
|
+
launchPath = writablePath
|
|
391
|
+
}
|
|
392
|
+
}
|
|
381
393
|
const environment = {
|
|
382
394
|
...resolved.environment,
|
|
383
395
|
...agentEnvironment(agent, variables),
|
package/src/utils/interactive.js
CHANGED
|
@@ -81,6 +81,8 @@ var createReadlineEditing = (getInput, onInput) => {
|
|
|
81
81
|
};
|
|
82
82
|
var InteractiveTextPrompt = (props) => {
|
|
83
83
|
let input;
|
|
84
|
+
const dimensions = useTerminalDimensions();
|
|
85
|
+
const inputWidth = () => dimensions().width + 1;
|
|
84
86
|
const editing = createReadlineEditing(() => input);
|
|
85
87
|
useKeyboard((key) => {
|
|
86
88
|
if (key.propagationStopped)
|
|
@@ -110,6 +112,7 @@ var InteractiveTextPrompt = (props) => {
|
|
|
110
112
|
_$setProp(_el$, "flexDirection", "column");
|
|
111
113
|
_$setProp(_el$, "width", "100%");
|
|
112
114
|
_$setProp(_el$, "height", "100%");
|
|
115
|
+
_$setProp(_el$, "overflow", "hidden");
|
|
113
116
|
_$insert(_el$, (() => {
|
|
114
117
|
var _c$ = _$memo(() => !!(props.fullScreen && !props.embedded));
|
|
115
118
|
return () => _c$() && [(() => {
|
|
@@ -152,17 +155,18 @@ var InteractiveTextPrompt = (props) => {
|
|
|
152
155
|
_$setProp(_el$4, "flexShrink", 0);
|
|
153
156
|
_$insert(_el$4, () => props.embedded ? "enter submit | shift-enter newline | esc clear/back" : "enter submit | shift-enter newline | esc cancel");
|
|
154
157
|
_$effect((_p$) => {
|
|
155
|
-
var _v$ = macchiato.base, _v$2 = macchiato.blue, _v$3 = props.fullScreen ? 0 : 2, _v$
|
|
158
|
+
var _v$ = macchiato.base, _v$2 = macchiato.blue, _v$3 = inputWidth(), _v$4 = props.fullScreen ? 0 : 2, _v$5 = props.fullScreen ? 1 : 0, _v$6 = macchiato.mantle, _v$7 = macchiato.surface0, _v$8 = macchiato.text, _v$9 = macchiato.text, _v$0 = macchiato.blue, _v$1 = macchiato.overlay1;
|
|
156
159
|
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "backgroundColor", _v$, _p$.e));
|
|
157
160
|
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "fg", _v$2, _p$.t));
|
|
158
|
-
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$3, "
|
|
159
|
-
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$3, "
|
|
160
|
-
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$3, "
|
|
161
|
-
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "
|
|
162
|
-
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$3, "
|
|
163
|
-
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$3, "
|
|
164
|
-
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$3, "
|
|
165
|
-
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$
|
|
161
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$3, "width", _v$3, _p$.a));
|
|
162
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$3, "height", _v$4, _p$.o));
|
|
163
|
+
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$3, "flexGrow", _v$5, _p$.i));
|
|
164
|
+
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "backgroundColor", _v$6, _p$.n));
|
|
165
|
+
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$3, "focusedBackgroundColor", _v$7, _p$.s));
|
|
166
|
+
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$3, "textColor", _v$8, _p$.h));
|
|
167
|
+
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$3, "focusedTextColor", _v$9, _p$.r));
|
|
168
|
+
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$3, "cursorColor", _v$0, _p$.d));
|
|
169
|
+
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$4, "fg", _v$1, _p$.l));
|
|
166
170
|
return _p$;
|
|
167
171
|
}, {
|
|
168
172
|
e: undefined,
|
|
@@ -174,7 +178,8 @@ var InteractiveTextPrompt = (props) => {
|
|
|
174
178
|
s: undefined,
|
|
175
179
|
h: undefined,
|
|
176
180
|
r: undefined,
|
|
177
|
-
d: undefined
|
|
181
|
+
d: undefined,
|
|
182
|
+
l: undefined
|
|
178
183
|
});
|
|
179
184
|
return _el$;
|
|
180
185
|
})();
|
|
@@ -497,15 +502,15 @@ var InteractiveSelectPrompt = (props) => {
|
|
|
497
502
|
})()
|
|
498
503
|
}), null);
|
|
499
504
|
_$effect((_p$) => {
|
|
500
|
-
var _v$
|
|
505
|
+
var _v$25 = index === selected() ? macchiato.text : macchiato.subtext0, _v$26 = index === selected() ? macchiato.surface1 : background(), _v$27 = {
|
|
501
506
|
fg: index === selected() ? macchiato.blue : undefined
|
|
502
|
-
}, _v$
|
|
507
|
+
}, _v$28 = {
|
|
503
508
|
fg: macchiato.overlay1
|
|
504
509
|
};
|
|
505
|
-
_v$
|
|
506
|
-
_v$
|
|
507
|
-
_v$
|
|
508
|
-
_v$
|
|
510
|
+
_v$25 !== _p$.e && (_p$.e = _$setProp(_el$26, "fg", _v$25, _p$.e));
|
|
511
|
+
_v$26 !== _p$.t && (_p$.t = _$setProp(_el$26, "bg", _v$26, _p$.t));
|
|
512
|
+
_v$27 !== _p$.a && (_p$.a = _$setProp(_el$27, "style", _v$27, _p$.a));
|
|
513
|
+
_v$28 !== _p$.o && (_p$.o = _$setProp(_el$28, "style", _v$28, _p$.o));
|
|
509
514
|
return _p$;
|
|
510
515
|
}, {
|
|
511
516
|
e: undefined,
|
|
@@ -517,9 +522,9 @@ var InteractiveSelectPrompt = (props) => {
|
|
|
517
522
|
})();
|
|
518
523
|
})());
|
|
519
524
|
_$effect((_p$) => {
|
|
520
|
-
var _v$
|
|
521
|
-
_v$
|
|
522
|
-
_v$
|
|
525
|
+
var _v$23 = rowHeight(), _v$24 = index === selected() ? macchiato.surface1 : background();
|
|
526
|
+
_v$23 !== _p$.e && (_p$.e = _$setProp(_el$19, "height", _v$23, _p$.e));
|
|
527
|
+
_v$24 !== _p$.t && (_p$.t = _$setProp(_el$19, "backgroundColor", _v$24, _p$.t));
|
|
523
528
|
return _p$;
|
|
524
529
|
}, {
|
|
525
530
|
e: undefined,
|
|
@@ -529,20 +534,20 @@ var InteractiveSelectPrompt = (props) => {
|
|
|
529
534
|
})()
|
|
530
535
|
}));
|
|
531
536
|
_$effect((_p$) => {
|
|
532
|
-
var _v$
|
|
533
|
-
_v$
|
|
534
|
-
_v$
|
|
535
|
-
_v$
|
|
536
|
-
_v$
|
|
537
|
-
_v$
|
|
538
|
-
_v$
|
|
539
|
-
_v$
|
|
540
|
-
_v$
|
|
541
|
-
_v$
|
|
542
|
-
_v$
|
|
543
|
-
_v$
|
|
544
|
-
_v$
|
|
545
|
-
_v$
|
|
537
|
+
var _v$10 = background(), _v$11 = brandHeight(), _v$12 = macchiato.blue, _v$13 = macchiato.blue, _v$14 = props.active === false ? macchiato.mantle : props.embedded ? macchiato.surface1 : macchiato.surface0, _v$15 = props.active !== false, _v$16 = macchiato.overlay0, _v$17 = macchiato.mantle, _v$18 = props.embedded ? macchiato.surface1 : macchiato.surface0, _v$19 = macchiato.text, _v$20 = macchiato.text, _v$21 = macchiato.blue, _v$22 = gapHeight();
|
|
538
|
+
_v$10 !== _p$.e && (_p$.e = _$setProp(_el$8, "backgroundColor", _v$10, _p$.e));
|
|
539
|
+
_v$11 !== _p$.t && (_p$.t = _$setProp(_el$9, "height", _v$11, _p$.t));
|
|
540
|
+
_v$12 !== _p$.a && (_p$.a = _$setProp(_el$0, "fg", _v$12, _p$.a));
|
|
541
|
+
_v$13 !== _p$.o && (_p$.o = _$setProp(_el$10, "fg", _v$13, _p$.o));
|
|
542
|
+
_v$14 !== _p$.i && (_p$.i = _$setProp(_el$12, "backgroundColor", _v$14, _p$.i));
|
|
543
|
+
_v$15 !== _p$.n && (_p$.n = _$setProp(_el$13, "focused", _v$15, _p$.n));
|
|
544
|
+
_v$16 !== _p$.s && (_p$.s = _$setProp(_el$13, "placeholderColor", _v$16, _p$.s));
|
|
545
|
+
_v$17 !== _p$.h && (_p$.h = _$setProp(_el$13, "backgroundColor", _v$17, _p$.h));
|
|
546
|
+
_v$18 !== _p$.r && (_p$.r = _$setProp(_el$13, "focusedBackgroundColor", _v$18, _p$.r));
|
|
547
|
+
_v$19 !== _p$.d && (_p$.d = _$setProp(_el$13, "textColor", _v$19, _p$.d));
|
|
548
|
+
_v$20 !== _p$.l && (_p$.l = _$setProp(_el$13, "focusedTextColor", _v$20, _p$.l));
|
|
549
|
+
_v$21 !== _p$.u && (_p$.u = _$setProp(_el$13, "cursorColor", _v$21, _p$.u));
|
|
550
|
+
_v$22 !== _p$.c && (_p$.c = _$setProp(_el$14, "height", _v$22, _p$.c));
|
|
546
551
|
return _p$;
|
|
547
552
|
}, {
|
|
548
553
|
e: undefined,
|
|
@@ -605,12 +610,12 @@ var InteractiveTabbedPrompt = (props) => {
|
|
|
605
610
|
_$insert(_el$38, () => index() === active() ? "\u258E" : " ");
|
|
606
611
|
_$insert(_el$37, () => ` ${tab.label} `, null);
|
|
607
612
|
_$effect((_p$) => {
|
|
608
|
-
var _v$
|
|
613
|
+
var _v$31 = index() === active() ? macchiato.text : macchiato.overlay1, _v$32 = index() === active() ? macchiato.base : macchiato.surface0, _v$33 = {
|
|
609
614
|
fg: macchiato.blue
|
|
610
615
|
};
|
|
611
|
-
_v$
|
|
612
|
-
_v$
|
|
613
|
-
_v$
|
|
616
|
+
_v$31 !== _p$.e && (_p$.e = _$setProp(_el$37, "fg", _v$31, _p$.e));
|
|
617
|
+
_v$32 !== _p$.t && (_p$.t = _$setProp(_el$37, "bg", _v$32, _p$.t));
|
|
618
|
+
_v$33 !== _p$.a && (_p$.a = _$setProp(_el$38, "style", _v$33, _p$.a));
|
|
614
619
|
return _p$;
|
|
615
620
|
}, {
|
|
616
621
|
e: undefined,
|
|
@@ -702,9 +707,9 @@ var InteractiveTabbedPrompt = (props) => {
|
|
|
702
707
|
});
|
|
703
708
|
})(), null);
|
|
704
709
|
_$effect((_p$) => {
|
|
705
|
-
var _v$
|
|
706
|
-
_v$
|
|
707
|
-
_v$
|
|
710
|
+
var _v$29 = macchiato.surface0, _v$30 = macchiato.base;
|
|
711
|
+
_v$29 !== _p$.e && (_p$.e = _$setProp(_el$30, "backgroundColor", _v$29, _p$.e));
|
|
712
|
+
_v$30 !== _p$.t && (_p$.t = _$setProp(_el$35, "backgroundColor", _v$30, _p$.t));
|
|
708
713
|
return _p$;
|
|
709
714
|
}, {
|
|
710
715
|
e: undefined,
|
|
@@ -769,9 +774,9 @@ var createInteractiveSession = async (onCancel = () => {}, initialTabs = []) =>
|
|
|
769
774
|
_$setProp(_el$40, "height", "100%");
|
|
770
775
|
_$insert(_el$41, () => props.prompt);
|
|
771
776
|
_$effect((_p$) => {
|
|
772
|
-
var _v$
|
|
773
|
-
_v$
|
|
774
|
-
_v$
|
|
777
|
+
var _v$34 = macchiato.base, _v$35 = macchiato.text;
|
|
778
|
+
_v$34 !== _p$.e && (_p$.e = _$setProp(_el$40, "backgroundColor", _v$34, _p$.e));
|
|
779
|
+
_v$35 !== _p$.t && (_p$.t = _$setProp(_el$41, "fg", _v$35, _p$.t));
|
|
775
780
|
return _p$;
|
|
776
781
|
}, {
|
|
777
782
|
e: undefined,
|
package/src/workbase/AGENTS.md
CHANGED
|
@@ -303,9 +303,12 @@ before launching an agent.
|
|
|
303
303
|
|
|
304
304
|
OpenCode can access the complete workbase tree, but this filesystem permission
|
|
305
305
|
does not expand Agency write authority beyond the checkout reported by
|
|
306
|
-
`agency context`.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
`agency context`. Pi and OpenCode V2 execution units launched by `agency work`
|
|
307
|
+
are rooted in the authoritative writable checkout so their project and Git
|
|
308
|
+
status reflect the implementation repository; OpenCode V1 remains rooted in the
|
|
309
|
+
task or phase directory. Orchestration remains rooted in its
|
|
310
|
+
Agency document directory. The managed integrations resolve the writable
|
|
311
|
+
checkout from launch context or `agency context`, guide task-directory sessions
|
|
312
|
+
to use it for implementation, and expose its supported skill directories through
|
|
313
|
+
the applicable plugin API; this does not make other checkout-local configuration
|
|
314
|
+
authoritative. Agents must follow the authority reported by `agency context`.
|
|
@@ -15,6 +15,21 @@ const Admitted = Schema.Struct({
|
|
|
15
15
|
}),
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
+
// V1 stops config discovery at the Git root, so a checkout-rooted V1 launch
|
|
19
|
+
// would miss the workbase's managed OpenCode config and plugin.
|
|
20
|
+
export const openCodeDiscoversAncestorConfig = (cli: string) =>
|
|
21
|
+
Effect.gen(function* () {
|
|
22
|
+
const fs = yield* FileSystemService
|
|
23
|
+
const result = yield* fs.runCommand([cli, "--version"], {
|
|
24
|
+
captureOutput: true,
|
|
25
|
+
timeoutMs: 60_000,
|
|
26
|
+
})
|
|
27
|
+
return (
|
|
28
|
+
result.exitCode === 0 &&
|
|
29
|
+
/^(?:opencode\s+v?)?2\./.test(result.stdout.trim())
|
|
30
|
+
)
|
|
31
|
+
}).pipe(Effect.orElseSucceed(() => false))
|
|
32
|
+
|
|
18
33
|
// V2's TUI --prompt only seeds the composer. Submit through the same CLI's
|
|
19
34
|
// authenticated service connection, then attach without a prompt. Reloading or
|
|
20
35
|
// reconnecting the TUI cannot replay the launch input. Only built-in Agency
|
|
@@ -27,6 +27,7 @@ const workerLaunchPattern = /^Agency worker launch target: ([^.\\s]+)\\./
|
|
|
27
27
|
type AgencyContext = {
|
|
28
28
|
root?: string
|
|
29
29
|
checkout?: string
|
|
30
|
+
documentDirectory?: string
|
|
30
31
|
target?: string
|
|
31
32
|
task?: string
|
|
32
33
|
phase?: string
|
|
@@ -139,9 +140,27 @@ const skillInfo = (path: string) => {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
const implementationInstructions = (
|
|
144
|
+
client: "OpenCode" | "Pi",
|
|
145
|
+
context: AgencyContext | undefined,
|
|
146
|
+
directory: string,
|
|
147
|
+
) => {
|
|
148
|
+
if (!context?.checkout) return
|
|
149
|
+
const checkout = resolve(context.checkout)
|
|
150
|
+
const current = resolve(directory)
|
|
151
|
+
const repository = current === checkout || current.startsWith(\`\${checkout}\${sep}\`)
|
|
152
|
+
? \`\${client} is rooted in Agency's authoritative writable checkout at \${checkout}. Use this directory for source reads, edits, Git status and other Git operations, builds, tests, and formatting.\`
|
|
153
|
+
: \`Agency's authoritative writable checkout is \${checkout}. Before doing implementation work or Git operations, change the working directory to that checkout. Set each tool's working directory to it when supported; otherwise use absolute paths.\`
|
|
154
|
+
const lifecycle = context.documentDirectory
|
|
155
|
+
? \`Run Agency lifecycle commands from \${context.documentDirectory}; agency context may also be queried from the checkout. Any reference checkouts reported by Agency context are read-only.\`
|
|
156
|
+
: "Any reference checkouts reported by Agency context are read-only."
|
|
157
|
+
return \`\${repository} \${lifecycle}\`
|
|
158
|
+
}
|
|
159
|
+
|
|
142
160
|
const agencyContext = async (
|
|
143
161
|
directory: string,
|
|
144
162
|
useEnvironmentTarget = true,
|
|
163
|
+
requireWorking = false,
|
|
145
164
|
): Promise<AgencyContext | undefined> => {
|
|
146
165
|
const task = useEnvironmentTarget ? process.env.AGENCY_TASK_ID : undefined
|
|
147
166
|
const phase = useEnvironmentTarget ? process.env.AGENCY_PHASE_ID : undefined
|
|
@@ -161,18 +180,26 @@ const agencyContext = async (
|
|
|
161
180
|
const result = envelope.result ?? {}
|
|
162
181
|
const target = contextTarget(result)
|
|
163
182
|
const document = result.target?.path
|
|
183
|
+
const checkout = result.authority?.writable?.checkoutPath
|
|
164
184
|
const status = result.documents?.phase?.data?.status ?? result.documents?.task?.data?.status
|
|
185
|
+
const current = resolve(directory)
|
|
186
|
+
const documentDirectory = document ? dirname(document) : undefined
|
|
187
|
+
const matchesLocation =
|
|
188
|
+
documentDirectory === current ||
|
|
189
|
+
(checkout &&
|
|
190
|
+
(current === resolve(checkout) || current.startsWith(\`\${resolve(checkout)}\${sep}\`)))
|
|
165
191
|
if (
|
|
166
192
|
result.validation?.valid !== true ||
|
|
167
193
|
!target ||
|
|
168
194
|
!document ||
|
|
169
|
-
|
|
170
|
-
(target.startsWith("execution-unit:") &&
|
|
195
|
+
!matchesLocation ||
|
|
196
|
+
(requireWorking && target.startsWith("execution-unit:") &&
|
|
171
197
|
(!result.authority?.writable?.checkoutPath || status !== "working"))
|
|
172
198
|
) return
|
|
173
199
|
return {
|
|
174
200
|
root: result.workbase?.root,
|
|
175
|
-
checkout
|
|
201
|
+
checkout,
|
|
202
|
+
documentDirectory,
|
|
176
203
|
target,
|
|
177
204
|
task: result.target?.taskId,
|
|
178
205
|
phase: result.target?.phaseId,
|
|
@@ -181,10 +208,11 @@ const agencyContext = async (
|
|
|
181
208
|
|
|
182
209
|
const plugin: V1Plugin = async ({ directory }) => {
|
|
183
210
|
const workerSessions = new Map<string, AgencyContext>()
|
|
211
|
+
const locationContext = agencyContext(directory, false).catch(() => undefined)
|
|
184
212
|
|
|
185
213
|
return {
|
|
186
214
|
config: async (config: any) => {
|
|
187
|
-
const context = await
|
|
215
|
+
const context = await locationContext
|
|
188
216
|
const root = process.env.AGENCY_WORKBASE ?? context?.root ?? discoverWorkbase(directory)
|
|
189
217
|
const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout ?? discoverCheckout(directory, root)
|
|
190
218
|
|
|
@@ -220,7 +248,7 @@ const plugin: V1Plugin = async ({ directory }) => {
|
|
|
220
248
|
"chat.message": async ({ sessionID }: { sessionID: string }, output: any) => {
|
|
221
249
|
const launchTarget = workerLaunchTarget(output.parts)
|
|
222
250
|
if (!launchTarget) return
|
|
223
|
-
const context = await agencyContext(directory, false).catch(() => undefined)
|
|
251
|
+
const context = await agencyContext(directory, false, true).catch(() => undefined)
|
|
224
252
|
if (context?.target !== launchTarget) return
|
|
225
253
|
workerSessions.set(sessionID, context)
|
|
226
254
|
},
|
|
@@ -229,16 +257,16 @@ const plugin: V1Plugin = async ({ directory }) => {
|
|
|
229
257
|
output: any,
|
|
230
258
|
) => {
|
|
231
259
|
if (!sessionID) return
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
260
|
+
const worker = workerSessions.get(sessionID)
|
|
261
|
+
const context = worker ?? await locationContext
|
|
262
|
+
const implementation = implementationInstructions("OpenCode", context, directory)
|
|
263
|
+
if (!worker?.target && !implementation) return
|
|
264
|
+
output.system.push([
|
|
265
|
+
worker?.target
|
|
266
|
+
? \`Agency verified this OpenCode session as the active worker for \${worker.target}. Perform the assigned work directly. Do not invoke agency work for this target or launch a replacement worker.\`
|
|
267
|
+
: undefined,
|
|
268
|
+
implementation,
|
|
269
|
+
].filter(Boolean).join(" "))
|
|
242
270
|
},
|
|
243
271
|
"shell.env": async ({ sessionID }: { sessionID?: string }, output: any) => {
|
|
244
272
|
if (!sessionID) return
|
|
@@ -259,7 +287,7 @@ export const AgencyPlugin = plugin
|
|
|
259
287
|
|
|
260
288
|
const setup = async (context: V2PluginContext) => {
|
|
261
289
|
const directory = context.location.directory
|
|
262
|
-
const agency = await agencyContext(directory).catch(() => undefined)
|
|
290
|
+
const agency = await agencyContext(directory, false).catch(() => undefined)
|
|
263
291
|
const root = agency?.root ?? resolve(dirname(fileURLToPath(import.meta.url)), "../..")
|
|
264
292
|
const instructionsPath = join(root, ".agency", "AGENTS.md")
|
|
265
293
|
const instructions = existsSync(instructionsPath)
|
|
@@ -302,23 +330,21 @@ const setup = async (context: V2PluginContext) => {
|
|
|
302
330
|
await context.session.hook("prompt", async (event) => {
|
|
303
331
|
const launchTarget = event.prompt.text.match(workerLaunchPattern)?.[1]
|
|
304
332
|
if (!launchTarget) return
|
|
305
|
-
const current = await agencyContext(directory, false).catch(() => undefined)
|
|
333
|
+
const current = await agencyContext(directory, false, true).catch(() => undefined)
|
|
306
334
|
if (!current || current.target !== launchTarget) return
|
|
307
335
|
workerSessions.set(event.sessionID, current)
|
|
308
336
|
})
|
|
337
|
+
const implementation = implementationInstructions("OpenCode", agency, directory)
|
|
309
338
|
await context.session.hook("context", (event) => {
|
|
310
339
|
if (instructions) event.system.push({ type: "text", text: instructions })
|
|
311
340
|
const current = workerSessions.get(event.sessionID)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
: undefined,
|
|
320
|
-
].filter(Boolean).join(" "),
|
|
321
|
-
})
|
|
341
|
+
const text = [
|
|
342
|
+
current?.target
|
|
343
|
+
? \`Agency verified this OpenCode session as the active worker for \${current.target}. Perform the assigned work directly. Do not invoke agency work for this target or launch a replacement worker.\`
|
|
344
|
+
: undefined,
|
|
345
|
+
implementationInstructions("OpenCode", current, directory) ?? implementation,
|
|
346
|
+
].filter(Boolean).join(" ")
|
|
347
|
+
if (text) event.system.push({ type: "text", text })
|
|
322
348
|
})
|
|
323
349
|
}
|
|
324
350
|
|