@markjaquith/agency 2.69.1 → 2.71.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 +29 -16
- package/cli-main.ts +74 -3
- package/package.json +5 -1
- package/pi-extensions/agency.test.ts +117 -0
- package/pi-extensions/agency.ts +141 -0
- package/scripts/install-pi-extension.ts +25 -0
- package/src/cli-parser.ts +11 -0
- package/src/cli.test.ts +40 -2
- package/src/commands/init.test.ts +3 -6
- package/src/commands/integration.test.ts +2 -20
- package/src/commands/integration.ts +3 -5
- package/src/services/IntegrationService.test.ts +24 -156
- package/src/services/IntegrationService.ts +31 -52
- package/src/usage-log.test.ts +94 -0
- package/src/usage-log.ts +143 -0
- package/src/workbase/pi-extension-file.ts +0 -158
|
@@ -1,158 +0,0 @@
|
|
|
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 body = `import { existsSync, readFileSync } from "node:fs"
|
|
10
|
-
import { dirname, join, resolve } from "node:path"
|
|
11
|
-
import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
12
|
-
|
|
13
|
-
type AgencyContext = {
|
|
14
|
-
root?: string
|
|
15
|
-
checkout?: string
|
|
16
|
-
target?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const contextTarget = (result: Record<string, any>): string | undefined => {
|
|
20
|
-
const target = result.target
|
|
21
|
-
if (target?.kind === "epic") return \`epic:\${target.epicId}\`
|
|
22
|
-
if (target?.kind === "phase") {
|
|
23
|
-
return \`execution-unit:phase/\${target.taskId}/\${target.phaseId}\`
|
|
24
|
-
}
|
|
25
|
-
if (target?.kind === "task") {
|
|
26
|
-
return result.authority?.mode === "execution"
|
|
27
|
-
? \`execution-unit:task/\${target.taskId}\`
|
|
28
|
-
: \`task:\${target.taskId}\`
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const discoverWorkbase = (directory: string) => {
|
|
33
|
-
let current = directory
|
|
34
|
-
while (true) {
|
|
35
|
-
if (existsSync(join(current, "agency.json"))) return current
|
|
36
|
-
const parent = dirname(current)
|
|
37
|
-
if (parent === current) return
|
|
38
|
-
current = parent
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const discoverCheckout = (directory: string, root: string | undefined) => {
|
|
43
|
-
if (!root) return
|
|
44
|
-
let current = directory
|
|
45
|
-
while (current.startsWith(root)) {
|
|
46
|
-
for (const name of ["PHASE.md", "TASK.md"]) {
|
|
47
|
-
const document = join(current, name)
|
|
48
|
-
if (!existsSync(document)) continue
|
|
49
|
-
const repo = readFileSync(document, "utf8").match(/^repo:\\s*([^\\s]+)\\s*$/m)?.[1]
|
|
50
|
-
if (!repo) return
|
|
51
|
-
const checkout = join(current, "code", repo.replace(/^['\"]|['\"]$/g, ""))
|
|
52
|
-
return existsSync(checkout) ? checkout : undefined
|
|
53
|
-
}
|
|
54
|
-
if (current === root) return
|
|
55
|
-
current = dirname(current)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const agencyContext = async (
|
|
60
|
-
pi: ExtensionAPI,
|
|
61
|
-
directory: string,
|
|
62
|
-
): Promise<AgencyContext | undefined> => {
|
|
63
|
-
const task = process.env.AGENCY_TASK_ID
|
|
64
|
-
const phase = process.env.AGENCY_PHASE_ID
|
|
65
|
-
const args = task
|
|
66
|
-
? ["context", "--task", task, ...(phase ? ["--phase", phase] : []), "--compact", "--json"]
|
|
67
|
-
: ["context", ".", "--compact", "--json"]
|
|
68
|
-
const command = await pi.exec("agency", args, { timeout: 5000 })
|
|
69
|
-
if (command.code !== 0) return
|
|
70
|
-
const envelope = JSON.parse(command.stdout)
|
|
71
|
-
if (envelope.ok !== true) return
|
|
72
|
-
const result = envelope.result ?? {}
|
|
73
|
-
const target = contextTarget(result)
|
|
74
|
-
const document = result.target?.path
|
|
75
|
-
const status = result.documents?.phase?.data?.status ?? result.documents?.task?.data?.status
|
|
76
|
-
if (
|
|
77
|
-
result.validation?.valid !== true ||
|
|
78
|
-
!target ||
|
|
79
|
-
!document ||
|
|
80
|
-
dirname(document) !== resolve(directory) ||
|
|
81
|
-
(target.startsWith("execution-unit:") &&
|
|
82
|
-
(!result.authority?.writable?.checkoutPath || status !== "working"))
|
|
83
|
-
) return
|
|
84
|
-
return {
|
|
85
|
-
root: result.workbase?.root,
|
|
86
|
-
checkout: result.authority?.writable?.checkoutPath,
|
|
87
|
-
target,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const extension = (pi: ExtensionAPI) => {
|
|
92
|
-
const contexts = new Map<string, Promise<AgencyContext | undefined>>()
|
|
93
|
-
const runtimeContext = (directory: string) => {
|
|
94
|
-
let context = contexts.get(directory)
|
|
95
|
-
if (!context) {
|
|
96
|
-
context = agencyContext(pi, directory).catch(() => undefined)
|
|
97
|
-
contexts.set(directory, context)
|
|
98
|
-
}
|
|
99
|
-
return context
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
pi.on("resources_discover", async (event) => {
|
|
103
|
-
const context = await runtimeContext(event.cwd)
|
|
104
|
-
const root = process.env.AGENCY_WORKBASE ?? context?.root ?? discoverWorkbase(event.cwd)
|
|
105
|
-
const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout ?? discoverCheckout(event.cwd, root)
|
|
106
|
-
if (!checkout) return
|
|
107
|
-
|
|
108
|
-
const skillPaths = [
|
|
109
|
-
join(checkout, ".claude", "skills"),
|
|
110
|
-
join(checkout, ".agents", "skills"),
|
|
111
|
-
join(checkout, ".opencode", "skill"),
|
|
112
|
-
join(checkout, ".opencode", "skills"),
|
|
113
|
-
join(checkout, CONFIG_DIR_NAME, "skills"),
|
|
114
|
-
].filter(existsSync)
|
|
115
|
-
if (skillPaths.length === 0) return
|
|
116
|
-
return { skillPaths: [...new Set(skillPaths)] }
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
pi.on("before_agent_start", async (event, ctx) => {
|
|
120
|
-
const context = await runtimeContext(ctx.cwd)
|
|
121
|
-
const root = process.env.AGENCY_WORKBASE ?? context?.root ?? discoverWorkbase(ctx.cwd)
|
|
122
|
-
if (!root) return
|
|
123
|
-
const checkout = process.env.AGENCY_WRITABLE_CHECKOUT ?? context?.checkout ?? discoverCheckout(ctx.cwd, root)
|
|
124
|
-
const instructionsPath = join(root, ".agency", "AGENTS.md")
|
|
125
|
-
const instructions = existsSync(instructionsPath)
|
|
126
|
-
? readFileSync(instructionsPath, "utf8").trim()
|
|
127
|
-
: undefined
|
|
128
|
-
const activeTarget = process.env.AGENCY_TARGET
|
|
129
|
-
const worker = activeTarget && context?.target === activeTarget
|
|
130
|
-
? \`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.\`
|
|
131
|
-
: undefined
|
|
132
|
-
const access = \`The complete Agency workbase is available at \${root}. Use absolute paths under that root when workbase context is needed. Agency context remains the authority for writes.\`
|
|
133
|
-
const implementation = checkout
|
|
134
|
-
? \`Pi remains rooted in the task or phase directory for Agency instructions and context. Treat \${checkout} as the default implementation directory for source reads, edits, repository status, builds, tests, formatting, and other repository-local commands. Run Agency lifecycle and context commands from the task or phase directory. Any reference checkouts reported by Agency context are read-only.\`
|
|
135
|
-
: undefined
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
systemPrompt: [event.systemPrompt, instructions, access, worker, implementation]
|
|
139
|
-
.filter(Boolean)
|
|
140
|
-
.join("\\n\\n"),
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export default extension
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
const renderManagedWorkbasePiExtension = (content: string) =>
|
|
149
|
-
`// agency-managed: sha256=${checksum(content)}\n\n${content}`
|
|
150
|
-
|
|
151
|
-
export const managedWorkbasePiExtension = renderManagedWorkbasePiExtension(body)
|
|
152
|
-
|
|
153
|
-
export const canUpdateManagedWorkbasePiExtension = (content: string) => {
|
|
154
|
-
const match = content.match(managedHeaderPattern)
|
|
155
|
-
if (!match?.[1]) return false
|
|
156
|
-
|
|
157
|
-
return checksum(content.slice(match[0].length)) === match[1]
|
|
158
|
-
}
|