@markjaquith/agency 3.5.1 → 3.6.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 +167 -18
- package/cli-main.ts +13 -2
- package/docs/opencode-auto-start.md +67 -0
- package/package.json +10 -5
- package/src/act-schema.ts +62 -0
- package/src/cli-parser.ts +13 -3
- package/src/commands/act-actions.ts +734 -0
- package/src/commands/act-prompts.ts +294 -0
- package/src/commands/act.ts +602 -436
- package/src/commands/work.ts +16 -2
- package/src/utils/chooser.ts +7 -0
- package/src/utils/exec.ts +21 -6
- package/src/utils/interactive.tsx +528 -87
- package/src/workbase/AGENTS.md +49 -0
- package/src/workbase/opencode-launch.ts +120 -0
- package/src/workbase/work-target.ts +6 -4
package/src/workbase/AGENTS.md
CHANGED
|
@@ -4,6 +4,55 @@ This directory is an Agency workbase. Epics, tasks, and phases are durable
|
|
|
4
4
|
Markdown documents; repository aliases and generated Git worktrees provide code
|
|
5
5
|
access.
|
|
6
6
|
|
|
7
|
+
## Discover Actions First
|
|
8
|
+
|
|
9
|
+
After the `agency context . --json` bootstrap, use `agency act --json` as the
|
|
10
|
+
primary way to discover what Agency can do. Prefer its live action catalog over
|
|
11
|
+
searching command help, reading implementation code, or guessing commands from
|
|
12
|
+
memory. It reports available actions, blocked reasons, required inputs, exact
|
|
13
|
+
argv/templates, document revisions, and explicit follow-up choices.
|
|
14
|
+
|
|
15
|
+
Keep discovery focused on the work at hand:
|
|
16
|
+
|
|
17
|
+
- Current task: `agency act --task <context-task-id> --json`.
|
|
18
|
+
- Current phase: `agency act --task <context-task-id> --phase <context-phase-id> --json`.
|
|
19
|
+
- One operation: add `--action <action-id>` to either selector.
|
|
20
|
+
- Workbase-level creation or repository actions: use
|
|
21
|
+
`agency act --action <action-id> --json` without an item selector.
|
|
22
|
+
- Unsure which action or item applies: `agency act --json`.
|
|
23
|
+
|
|
24
|
+
Use selectors returned by context, not IDs inferred from checkout or branch
|
|
25
|
+
names. Execute an available `command` as argv from the returned workbase root.
|
|
26
|
+
For a `commandTemplate`, substitute required input placeholders; append optional
|
|
27
|
+
inputs using their declared `option`. Preserve multiline input as one argv value.
|
|
28
|
+
Re-discover after mutations because availability and revisions may change.
|
|
29
|
+
|
|
30
|
+
Discovery is read-only and does not grant consent. `followUpCommands` describe
|
|
31
|
+
the action's bookkeeping; `nextActions` require a separate explicit choice.
|
|
32
|
+
Creation never implies starting Work. Respect blocked reasons and the consent
|
|
33
|
+
boundaries below. The command fast paths remain useful when the exact operation
|
|
34
|
+
and arguments are already established; do not rediscover between every known
|
|
35
|
+
step or use help unless discovery does not cover the operation.
|
|
36
|
+
|
|
37
|
+
### Current Work and Branch Context
|
|
38
|
+
|
|
39
|
+
When the request concerns this execution unit, target its context task/phase
|
|
40
|
+
rather than browsing the entire workbase. Use `authority.writable.checkoutPath`
|
|
41
|
+
for repository work and the declared delivery branch for publication.
|
|
42
|
+
|
|
43
|
+
For new work that builds on the current implementation (a follow-up, additional
|
|
44
|
+
phase, or explicitly requested continuation), prefer the current execution's
|
|
45
|
+
declared branch as `--base` when that matches the user's intent. Do not blindly
|
|
46
|
+
copy `main` from examples or accept a wizard default. Confirm the branch from
|
|
47
|
+
Agency context; when checking the live Git branch is necessary, do so in the
|
|
48
|
+
writable checkout, not the workbase root. An explicit user base takes precedence.
|
|
49
|
+
Unrelated new work should use its intended integration base instead.
|
|
50
|
+
|
|
51
|
+
Branch ancestry is not a completion dependency. Choosing the current branch as
|
|
52
|
+
the base does not add `--depends-on`, reuse the current item for explicitly new
|
|
53
|
+
work, switch branches, or expand write authority. Pass the chosen base as the
|
|
54
|
+
discovered `base` input to the native creation/handoff command.
|
|
55
|
+
|
|
7
56
|
## Command Fast Paths
|
|
8
57
|
|
|
9
58
|
When a request clearly matches one of these intents, use the exact recipe without
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Schema } from "@effect/schema"
|
|
2
|
+
import { Effect } from "effect"
|
|
3
|
+
import { FileSystemService } from "../services/FileSystemService"
|
|
4
|
+
|
|
5
|
+
const Session = Schema.Struct({
|
|
6
|
+
id: Schema.String.pipe(Schema.pattern(/^ses/)),
|
|
7
|
+
location: Schema.Struct({ directory: Schema.String }),
|
|
8
|
+
})
|
|
9
|
+
const Created = Schema.Struct({ data: Session })
|
|
10
|
+
const Listed = Schema.Struct({ data: Schema.Array(Session) })
|
|
11
|
+
const Admitted = Schema.Struct({
|
|
12
|
+
data: Schema.Struct({
|
|
13
|
+
sessionID: Schema.String,
|
|
14
|
+
type: Schema.Literal("user"),
|
|
15
|
+
}),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// V2's TUI --prompt only seeds the composer. Submit through the same CLI's
|
|
19
|
+
// authenticated service connection, then attach without a prompt. Reloading or
|
|
20
|
+
// reconnecting the TUI cannot replay the launch input. Only built-in Agency
|
|
21
|
+
// auto-command templates call this helper: [cli, [--continue], --prompt, text].
|
|
22
|
+
export const prepareOpenCodeLaunch = (
|
|
23
|
+
argv: readonly string[],
|
|
24
|
+
cwd: string,
|
|
25
|
+
env: Record<string, string>,
|
|
26
|
+
) =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const fs = yield* FileSystemService
|
|
29
|
+
cwd = yield* fs.realPath(cwd)
|
|
30
|
+
const cli = argv[0]!
|
|
31
|
+
const run = (args: readonly string[]) =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
const result = yield* fs
|
|
34
|
+
.runCommand([cli, ...args], {
|
|
35
|
+
cwd,
|
|
36
|
+
env,
|
|
37
|
+
captureOutput: true,
|
|
38
|
+
timeoutMs: 60_000,
|
|
39
|
+
})
|
|
40
|
+
.pipe(
|
|
41
|
+
Effect.mapError(
|
|
42
|
+
() =>
|
|
43
|
+
new Error(
|
|
44
|
+
`OpenCode auto-start command failed: ${args.slice(0, args.indexOf("--data") < 0 ? args.length : args.indexOf("--data")).join(" ")}`,
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
if (result.exitCode !== 0) {
|
|
49
|
+
return yield* Effect.fail(
|
|
50
|
+
new Error(
|
|
51
|
+
`OpenCode auto-start failed (${args.slice(0, 3).join(" ")}): ${result.stderr}`,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
return result.stdout
|
|
56
|
+
})
|
|
57
|
+
const version = (yield* run(["--version"])).trim()
|
|
58
|
+
if (/^(?:opencode\s+v?)?1\./.test(version)) return [...argv]
|
|
59
|
+
if (!/^(?:opencode\s+v?)?2\./.test(version)) {
|
|
60
|
+
return yield* Effect.fail(
|
|
61
|
+
new Error(`Unsupported OpenCode version for auto-start: ${version}`),
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
const api = (method: string, path: string, body?: unknown) =>
|
|
65
|
+
run([
|
|
66
|
+
"api",
|
|
67
|
+
method,
|
|
68
|
+
path,
|
|
69
|
+
...(body === undefined ? [] : ["--data", JSON.stringify(body)]),
|
|
70
|
+
])
|
|
71
|
+
let session: typeof Session.Type | undefined
|
|
72
|
+
if (argv.includes("--continue")) {
|
|
73
|
+
const query = new URLSearchParams({
|
|
74
|
+
directory: cwd,
|
|
75
|
+
parentID: "null",
|
|
76
|
+
order: "desc",
|
|
77
|
+
limit: "1",
|
|
78
|
+
})
|
|
79
|
+
const listed = yield* Schema.decodeUnknown(Schema.parseJson(Listed))(
|
|
80
|
+
yield* api("get", `/api/session?${query}`),
|
|
81
|
+
)
|
|
82
|
+
session = listed.data[0]
|
|
83
|
+
}
|
|
84
|
+
if (!session) {
|
|
85
|
+
const created = yield* Schema.decodeUnknown(Schema.parseJson(Created))(
|
|
86
|
+
yield* api("post", "/api/session", { location: { directory: cwd } }),
|
|
87
|
+
)
|
|
88
|
+
session = created.data
|
|
89
|
+
}
|
|
90
|
+
if ((yield* fs.realPath(session.location.directory)) !== cwd) {
|
|
91
|
+
return yield* Effect.fail(
|
|
92
|
+
new Error(
|
|
93
|
+
`OpenCode returned a session outside the launch directory: ${session.id}`,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
// API client environment is not session environment on the shared server.
|
|
98
|
+
// Replace it before admission so the first shell/tool sees the caller's
|
|
99
|
+
// Agency identity and (for a real Herdr launch) the correct pane identity.
|
|
100
|
+
const variables = Object.fromEntries(
|
|
101
|
+
Object.entries({ ...process.env, ...env }).filter(
|
|
102
|
+
(entry): entry is [string, string] => entry[1] !== undefined,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
yield* api("put", `/api/session/${session.id}/environment`, { variables })
|
|
106
|
+
const submitted = yield* Schema.decodeUnknown(Schema.parseJson(Admitted))(
|
|
107
|
+
yield* api("post", `/api/session/${session.id}/prompt`, {
|
|
108
|
+
text: argv.at(-1)!,
|
|
109
|
+
resume: true,
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
if (submitted.data.sessionID !== session.id) {
|
|
113
|
+
return yield* Effect.fail(
|
|
114
|
+
new Error(
|
|
115
|
+
`OpenCode did not confirm the launch prompt for ${session.id}`,
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
return [cli, "--session", session.id]
|
|
120
|
+
})
|
|
@@ -68,7 +68,7 @@ const statuses: Record<
|
|
|
68
68
|
> = {
|
|
69
69
|
open: { icon: "", color: macchiato.overlay1 },
|
|
70
70
|
working: { icon: "", color: macchiato.blue },
|
|
71
|
-
delegated: { icon: "", color: macchiato.
|
|
71
|
+
delegated: { icon: "", color: macchiato.blue },
|
|
72
72
|
done: { icon: "", color: macchiato.green },
|
|
73
73
|
dropped: { icon: "", color: macchiato.red },
|
|
74
74
|
}
|
|
@@ -77,11 +77,13 @@ const kinds: Record<
|
|
|
77
77
|
WorkTarget["kind"],
|
|
78
78
|
{ readonly icon: string; readonly color: string }
|
|
79
79
|
> = {
|
|
80
|
-
epic: { icon: "", color: macchiato.
|
|
81
|
-
task: { icon: "", color: macchiato.
|
|
82
|
-
phase: { icon: "
|
|
80
|
+
epic: { icon: "", color: macchiato.overlay1 },
|
|
81
|
+
task: { icon: "", color: macchiato.overlay1 },
|
|
82
|
+
phase: { icon: "", color: macchiato.overlay1 },
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export { statuses as workStatusStyle, kinds as workKindStyle }
|
|
86
|
+
|
|
85
87
|
const hexToAnsi = (color: string) => {
|
|
86
88
|
const value = color.slice(1)
|
|
87
89
|
return [0, 2, 4]
|