@defrex/autobuild 0.8.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/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import type { PiCatalogEntry } from '../../cli/models'
|
|
3
|
+
import { classifyProviderError } from './provider-error'
|
|
4
|
+
import type { PiModelRef, PiSession, PiTurn } from './pi'
|
|
5
|
+
|
|
6
|
+
export const PI_BRIDGE_PATH = fileURLToPath(new URL('./pi-bridge.js', import.meta.url))
|
|
7
|
+
export const PI_RPC_MODE_ARG = '--mode'
|
|
8
|
+
export const PI_MODEL_ARG = '--model'
|
|
9
|
+
const CATALOG_PREFIX = 'autobuild-pi-catalog:'
|
|
10
|
+
|
|
11
|
+
export interface PiRpcInvocation {
|
|
12
|
+
args: string[]
|
|
13
|
+
cwd: string
|
|
14
|
+
env: Record<string, string>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PiRpcProcess {
|
|
18
|
+
write(line: string): void
|
|
19
|
+
closeInput(): void
|
|
20
|
+
output: AsyncIterable<Uint8Array | string>
|
|
21
|
+
stderr: Promise<string>
|
|
22
|
+
exited: Promise<number>
|
|
23
|
+
kill(signal?: number): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PiRpcSpawnFn = (invocation: PiRpcInvocation) => PiRpcProcess
|
|
27
|
+
|
|
28
|
+
export const spawnPiRpc: PiRpcSpawnFn = (invocation) => {
|
|
29
|
+
const proc = Bun.spawn(['pi', ...invocation.args], {
|
|
30
|
+
cwd: invocation.cwd,
|
|
31
|
+
env: invocation.env,
|
|
32
|
+
stdin: 'pipe',
|
|
33
|
+
stdout: 'pipe',
|
|
34
|
+
stderr: 'pipe',
|
|
35
|
+
})
|
|
36
|
+
return {
|
|
37
|
+
write(line) {
|
|
38
|
+
proc.stdin.write(line)
|
|
39
|
+
proc.stdin.flush()
|
|
40
|
+
},
|
|
41
|
+
closeInput() {
|
|
42
|
+
proc.stdin.end()
|
|
43
|
+
},
|
|
44
|
+
output: proc.stdout,
|
|
45
|
+
stderr: new Response(proc.stderr).text(),
|
|
46
|
+
exited: proc.exited,
|
|
47
|
+
kill(signal) {
|
|
48
|
+
proc.kill(signal)
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class JsonlDecoder {
|
|
54
|
+
private readonly decoder = new TextDecoder()
|
|
55
|
+
private buffer = ''
|
|
56
|
+
|
|
57
|
+
push(chunk: Uint8Array | string): string[] {
|
|
58
|
+
this.buffer += typeof chunk === 'string' ? chunk : this.decoder.decode(chunk, { stream: true })
|
|
59
|
+
return this.takeLines(false)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
end(): string[] {
|
|
63
|
+
this.buffer += this.decoder.decode()
|
|
64
|
+
return this.takeLines(true)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private takeLines(final: boolean): string[] {
|
|
68
|
+
const lines: string[] = []
|
|
69
|
+
while (true) {
|
|
70
|
+
const index = this.buffer.indexOf('\n')
|
|
71
|
+
if (index < 0) break
|
|
72
|
+
let line = this.buffer.slice(0, index)
|
|
73
|
+
this.buffer = this.buffer.slice(index + 1)
|
|
74
|
+
if (line.endsWith('\r')) line = line.slice(0, -1)
|
|
75
|
+
if (line.length > 0) lines.push(line)
|
|
76
|
+
}
|
|
77
|
+
if (final && this.buffer.length > 0) {
|
|
78
|
+
lines.push(this.buffer.endsWith('\r') ? this.buffer.slice(0, -1) : this.buffer)
|
|
79
|
+
this.buffer = ''
|
|
80
|
+
}
|
|
81
|
+
return lines
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type JsonRecord = Record<string, unknown>
|
|
86
|
+
|
|
87
|
+
interface PendingResponse {
|
|
88
|
+
command: string
|
|
89
|
+
resolve: (value: JsonRecord) => void
|
|
90
|
+
reject: (error: Error) => void
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface TurnCapture {
|
|
94
|
+
text: string[]
|
|
95
|
+
failure?: string
|
|
96
|
+
settled: Promise<void>
|
|
97
|
+
resolve: () => void
|
|
98
|
+
reject: (error: Error) => void
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class PiRpcClient implements PiSession {
|
|
102
|
+
readonly sessionId: string
|
|
103
|
+
private nextId = 0
|
|
104
|
+
private closed = false
|
|
105
|
+
private fatal: Error | undefined
|
|
106
|
+
private readonly pending = new Map<string, PendingResponse>()
|
|
107
|
+
private readonly notifications = new Set<(value: JsonRecord) => void>()
|
|
108
|
+
private activeTurn: TurnCapture | undefined
|
|
109
|
+
/** Optional observer invoked with every non-response session event
|
|
110
|
+
* (SPEC §9 translation hook). Fires regardless of turn state. */
|
|
111
|
+
onEvent: ((event: JsonRecord) => void) | undefined
|
|
112
|
+
|
|
113
|
+
private constructor(
|
|
114
|
+
private readonly process: PiRpcProcess,
|
|
115
|
+
sessionId: string,
|
|
116
|
+
private readonly configuredTools: readonly string[],
|
|
117
|
+
) {
|
|
118
|
+
this.sessionId = sessionId
|
|
119
|
+
void this.readOutput()
|
|
120
|
+
void process.exited.then((code) => {
|
|
121
|
+
if (!this.closed)
|
|
122
|
+
void this.fail(new Error(`pi runtime: RPC process exited with code ${code}`))
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static async launch(
|
|
127
|
+
invocation: PiRpcInvocation,
|
|
128
|
+
spawn: PiRpcSpawnFn = spawnPiRpc,
|
|
129
|
+
configuredTools: readonly string[] = [],
|
|
130
|
+
onEvent?: (event: JsonRecord) => void,
|
|
131
|
+
): Promise<PiRpcClient> {
|
|
132
|
+
const process = spawn(invocation)
|
|
133
|
+
const client = new PiRpcClient(process, crypto.randomUUID(), configuredTools)
|
|
134
|
+
client.onEvent = onEvent
|
|
135
|
+
try {
|
|
136
|
+
const state = await client.command('get_state')
|
|
137
|
+
const data = record(state.data)
|
|
138
|
+
const id = typeof data?.sessionId === 'string' ? data.sessionId : undefined
|
|
139
|
+
if (id === undefined || id.length === 0)
|
|
140
|
+
throw new Error('pi runtime: RPC get_state returned no sessionId')
|
|
141
|
+
Object.defineProperty(client, 'sessionId', { value: id })
|
|
142
|
+
return client
|
|
143
|
+
} catch (error) {
|
|
144
|
+
await client.dispose()
|
|
145
|
+
throw error
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async prompt(text: string, env: Record<string, string>, signal?: AbortSignal): Promise<PiTurn> {
|
|
150
|
+
if (this.activeTurn !== undefined)
|
|
151
|
+
throw new Error('pi runtime: concurrent RPC prompts are not supported')
|
|
152
|
+
if (signal?.aborted) throw abortReason(signal)
|
|
153
|
+
|
|
154
|
+
await this.configure(env)
|
|
155
|
+
const before = await this.stats()
|
|
156
|
+
let resolve!: () => void
|
|
157
|
+
let reject!: (error: Error) => void
|
|
158
|
+
const settled = new Promise<void>((res, rej) => {
|
|
159
|
+
resolve = res
|
|
160
|
+
reject = rej
|
|
161
|
+
})
|
|
162
|
+
const capture: TurnCapture = { text: [], settled, resolve, reject }
|
|
163
|
+
this.activeTurn = capture
|
|
164
|
+
|
|
165
|
+
let aborting: Promise<unknown> | undefined
|
|
166
|
+
const onAbort = (): void => {
|
|
167
|
+
aborting ??= this.command('abort').catch(() => undefined)
|
|
168
|
+
}
|
|
169
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
170
|
+
try {
|
|
171
|
+
await this.command('prompt', { message: text })
|
|
172
|
+
await capture.settled
|
|
173
|
+
if (aborting !== undefined) await aborting
|
|
174
|
+
if (signal?.aborted) throw abortReason(signal)
|
|
175
|
+
const after = await this.stats()
|
|
176
|
+
return {
|
|
177
|
+
text: capture.text.join(''),
|
|
178
|
+
usage: {
|
|
179
|
+
inputTokens: tokenDelta(after.input, before.input),
|
|
180
|
+
outputTokens: tokenDelta(after.output, before.output),
|
|
181
|
+
},
|
|
182
|
+
...(capture.failure !== undefined
|
|
183
|
+
? { failure: classifyProviderError(capture.failure) }
|
|
184
|
+
: {}),
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
signal?.removeEventListener('abort', onAbort)
|
|
188
|
+
if (this.activeTurn === capture) this.activeTurn = undefined
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async catalog(availableOnly: boolean): Promise<PiCatalogEntry[]> {
|
|
193
|
+
const nonce = crypto.randomUUID()
|
|
194
|
+
const payload = encode({ nonce, availableOnly })
|
|
195
|
+
const prefix = `${CATALOG_PREFIX}${nonce}:`
|
|
196
|
+
const matches: string[] = []
|
|
197
|
+
const listener = (event: JsonRecord): void => {
|
|
198
|
+
if (event.type !== 'extension_ui_request' || event.method !== 'notify') return
|
|
199
|
+
const message = typeof event.message === 'string' ? event.message : ''
|
|
200
|
+
if (message.startsWith(prefix)) matches.push(message.slice(prefix.length))
|
|
201
|
+
}
|
|
202
|
+
this.notifications.add(listener)
|
|
203
|
+
try {
|
|
204
|
+
await this.command('prompt', { message: `/autobuild-models ${payload}` })
|
|
205
|
+
await Promise.resolve()
|
|
206
|
+
if (matches.length === 0) throw new Error('pi runtime: missing model-catalog bridge response')
|
|
207
|
+
if (matches.length > 1) throw new Error('pi runtime: duplicate model-catalog bridge response')
|
|
208
|
+
try {
|
|
209
|
+
return parseCatalog(decode(matches[0]!))
|
|
210
|
+
} catch (error) {
|
|
211
|
+
throw asError(error, 'pi runtime: malformed model-catalog bridge response')
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
this.notifications.delete(listener)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async dispose(): Promise<void> {
|
|
219
|
+
if (this.closed) return
|
|
220
|
+
this.closed = true
|
|
221
|
+
this.process.closeInput()
|
|
222
|
+
this.process.kill()
|
|
223
|
+
await Promise.race([this.process.exited.catch(() => -1), delay(1_000)])
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async configure(environment: Record<string, string>): Promise<void> {
|
|
227
|
+
await this.command('prompt', {
|
|
228
|
+
message: `/autobuild-configure ${encode({
|
|
229
|
+
environment,
|
|
230
|
+
tools: this.configuredTools,
|
|
231
|
+
})}`,
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async stats(): Promise<{ input: number; output: number }> {
|
|
236
|
+
const response = await this.command('get_session_stats')
|
|
237
|
+
const data = record(response.data)
|
|
238
|
+
const tokens = record(data?.tokens)
|
|
239
|
+
return { input: token(tokens?.input), output: token(tokens?.output) }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private command(type: string, fields: JsonRecord = {}): Promise<JsonRecord> {
|
|
243
|
+
if (this.fatal !== undefined) return Promise.reject(this.fatal)
|
|
244
|
+
if (this.closed) return Promise.reject(new Error('pi runtime: RPC process is closed'))
|
|
245
|
+
const id = `ab-${++this.nextId}`
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
this.pending.set(id, { command: type, resolve, reject })
|
|
248
|
+
try {
|
|
249
|
+
this.process.write(`${JSON.stringify({ id, type, ...fields })}\n`)
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.pending.delete(id)
|
|
252
|
+
reject(asError(error, 'pi runtime: failed to write RPC command'))
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async readOutput(): Promise<void> {
|
|
258
|
+
const decoder = new JsonlDecoder()
|
|
259
|
+
try {
|
|
260
|
+
for await (const chunk of this.process.output) {
|
|
261
|
+
for (const line of decoder.push(chunk)) this.receive(line)
|
|
262
|
+
}
|
|
263
|
+
for (const line of decoder.end()) this.receive(line)
|
|
264
|
+
} catch (error) {
|
|
265
|
+
await this.fail(asError(error, 'pi runtime: failed to read RPC output'))
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private receive(line: string): void {
|
|
270
|
+
let value: unknown
|
|
271
|
+
try {
|
|
272
|
+
value = JSON.parse(line)
|
|
273
|
+
} catch {
|
|
274
|
+
void this.fail(new Error(`pi runtime: malformed JSONL from RPC process: ${line}`))
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
if (!isRecord(value)) {
|
|
278
|
+
void this.fail(new Error('pi runtime: RPC process emitted a non-object JSON record'))
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
if (value.type === 'response' && typeof value.id === 'string') {
|
|
282
|
+
const pending = this.pending.get(value.id)
|
|
283
|
+
if (pending === undefined) return
|
|
284
|
+
this.pending.delete(value.id)
|
|
285
|
+
if (value.command !== pending.command) {
|
|
286
|
+
pending.reject(new Error(`pi runtime: RPC response command mismatch for ${value.id}`))
|
|
287
|
+
} else if (value.success !== true) {
|
|
288
|
+
pending.reject(
|
|
289
|
+
new Error(
|
|
290
|
+
typeof value.error === 'string' ? value.error : `pi RPC ${pending.command} failed`,
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
} else {
|
|
294
|
+
pending.resolve(value)
|
|
295
|
+
}
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const listener of this.notifications) listener(value)
|
|
300
|
+
this.onEvent?.(value)
|
|
301
|
+
const capture = this.activeTurn
|
|
302
|
+
if (capture === undefined) return
|
|
303
|
+
if (value.type === 'message_update') {
|
|
304
|
+
const update = record(value.assistantMessageEvent)
|
|
305
|
+
if (update?.type === 'text_delta' && typeof update.delta === 'string')
|
|
306
|
+
capture.text.push(update.delta)
|
|
307
|
+
}
|
|
308
|
+
if (value.type === 'message_end') {
|
|
309
|
+
const message = record(value.message)
|
|
310
|
+
if (message?.role === 'assistant') {
|
|
311
|
+
const stopReason = typeof message.stopReason === 'string' ? message.stopReason : undefined
|
|
312
|
+
if (stopReason === 'error' || stopReason === 'aborted') {
|
|
313
|
+
capture.failure =
|
|
314
|
+
typeof message.errorMessage === 'string' && message.errorMessage.length > 0
|
|
315
|
+
? message.errorMessage
|
|
316
|
+
: `pi runtime: assistant turn ended with stopReason "${stopReason}"`
|
|
317
|
+
} else if (stopReason !== undefined) {
|
|
318
|
+
capture.failure = undefined
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (value.type === 'agent_settled') capture.resolve()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private async fail(error: Error): Promise<void> {
|
|
326
|
+
if (this.fatal !== undefined) return
|
|
327
|
+
this.fatal = error
|
|
328
|
+
for (const pending of this.pending.values()) pending.reject(error)
|
|
329
|
+
this.pending.clear()
|
|
330
|
+
this.activeTurn?.reject(error)
|
|
331
|
+
if (!this.closed) this.process.kill()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface PiRpcSessionOptions {
|
|
336
|
+
cwd: string
|
|
337
|
+
model?: PiModelRef
|
|
338
|
+
tools: readonly string[]
|
|
339
|
+
args: readonly string[]
|
|
340
|
+
skill?: string
|
|
341
|
+
env: Record<string, string>
|
|
342
|
+
spawn?: PiRpcSpawnFn
|
|
343
|
+
/** Session-event observer handed to the RPC client (SPEC §9). */
|
|
344
|
+
onEvent?: (event: JsonRecord) => void
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function createPiRpcSession(opts: PiRpcSessionOptions): Promise<PiSession> {
|
|
348
|
+
const args = [
|
|
349
|
+
PI_RPC_MODE_ARG,
|
|
350
|
+
'rpc',
|
|
351
|
+
'--no-session',
|
|
352
|
+
'--no-approve',
|
|
353
|
+
'--no-context-files',
|
|
354
|
+
'--no-prompt-templates',
|
|
355
|
+
'--no-themes',
|
|
356
|
+
]
|
|
357
|
+
if (opts.model !== undefined) args.push(PI_MODEL_ARG, `${opts.model.provider}/${opts.model.id}`)
|
|
358
|
+
// `--tools` filters both builtin and package tools out of Pi's registry.
|
|
359
|
+
// Keep the complete registry for build sessions and let the bridge activate
|
|
360
|
+
// exactly the configured builtin/package subset before every agent prompt.
|
|
361
|
+
if (opts.tools.length === 0) args.push('--no-tools')
|
|
362
|
+
args.push('--no-skills')
|
|
363
|
+
if (opts.skill !== undefined) args.push('--skill', `${opts.cwd}/.agents/skills/${opts.skill}`)
|
|
364
|
+
// Disable ambient discovery unconditionally. Explicit repeatable
|
|
365
|
+
// --extension paths in configured args still load alongside the bridge.
|
|
366
|
+
args.push('--no-extensions', '--extension', PI_BRIDGE_PATH)
|
|
367
|
+
args.push(...opts.args)
|
|
368
|
+
return PiRpcClient.launch(
|
|
369
|
+
{ args, cwd: opts.cwd, env: opts.env },
|
|
370
|
+
opts.spawn,
|
|
371
|
+
opts.tools,
|
|
372
|
+
opts.onEvent,
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function readLocalPiCatalog(opts: {
|
|
377
|
+
cwd: string
|
|
378
|
+
env: Record<string, string>
|
|
379
|
+
availableOnly: boolean
|
|
380
|
+
spawn?: PiRpcSpawnFn
|
|
381
|
+
}): Promise<PiCatalogEntry[]> {
|
|
382
|
+
const client = await PiRpcClient.launch(
|
|
383
|
+
{
|
|
384
|
+
args: [
|
|
385
|
+
PI_RPC_MODE_ARG,
|
|
386
|
+
'rpc',
|
|
387
|
+
'--no-session',
|
|
388
|
+
'--no-approve',
|
|
389
|
+
'--no-context-files',
|
|
390
|
+
'--no-tools',
|
|
391
|
+
'--no-skills',
|
|
392
|
+
'--no-extensions',
|
|
393
|
+
'--no-prompt-templates',
|
|
394
|
+
'--no-themes',
|
|
395
|
+
'--extension',
|
|
396
|
+
PI_BRIDGE_PATH,
|
|
397
|
+
],
|
|
398
|
+
cwd: opts.cwd,
|
|
399
|
+
env: opts.env,
|
|
400
|
+
},
|
|
401
|
+
opts.spawn,
|
|
402
|
+
)
|
|
403
|
+
try {
|
|
404
|
+
return await client.catalog(opts.availableOnly)
|
|
405
|
+
} finally {
|
|
406
|
+
await client.dispose()
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function parseCatalog(value: unknown): PiCatalogEntry[] {
|
|
411
|
+
if (!Array.isArray(value)) throw new Error('catalog is not an array')
|
|
412
|
+
return value.map((entry) => {
|
|
413
|
+
if (!isRecord(entry) || typeof entry.provider !== 'string' || typeof entry.id !== 'string') {
|
|
414
|
+
throw new Error('catalog entry is malformed')
|
|
415
|
+
}
|
|
416
|
+
return { provider: entry.provider, id: entry.id }
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function encode(value: unknown): string {
|
|
421
|
+
return Buffer.from(JSON.stringify(value), 'utf8').toString('base64url')
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function decode(value: string): unknown {
|
|
425
|
+
return JSON.parse(Buffer.from(value, 'base64url').toString('utf8'))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function record(value: unknown): JsonRecord | undefined {
|
|
429
|
+
return isRecord(value) ? value : undefined
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
433
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function token(value: unknown): number {
|
|
437
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function tokenDelta(after: number, before: number): number {
|
|
441
|
+
return Math.max(0, Math.round(after - before))
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function abortReason(signal: AbortSignal): Error {
|
|
445
|
+
return signal.reason instanceof Error ? signal.reason : new Error('pi runtime: prompt aborted')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function asError(error: unknown, prefix: string): Error {
|
|
449
|
+
return new Error(`${prefix}: ${error instanceof Error ? error.message : String(error)}`)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function delay(ms: number): Promise<void> {
|
|
453
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
454
|
+
}
|