@peakflo/agent-harness 0.0.0-stage → 0.2.2-rc.1
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 +21 -0
- package/README.md +68 -2
- package/dist/adapters/acp-adapter.d.ts +177 -0
- package/dist/adapters/acp-adapter.js +1732 -0
- package/dist/adapters/claude-code-adapter.d.ts +171 -0
- package/dist/adapters/claude-code-adapter.js +1918 -0
- package/dist/adapters/codex-adapter.d.ts +67 -0
- package/dist/adapters/codex-adapter.js +681 -0
- package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
- package/dist/adapters/codex-app-server-adapter.js +1309 -0
- package/dist/adapters/coding-agent-adapter.d.ts +289 -0
- package/dist/adapters/coding-agent-adapter.js +31 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +35 -0
- package/dist/adapters/model-gateway.d.ts +84 -0
- package/dist/adapters/model-gateway.js +270 -0
- package/dist/adapters/opencode-adapter.d.ts +403 -0
- package/dist/adapters/opencode-adapter.js +2710 -0
- package/dist/adapters/opencode-config.d.ts +41 -0
- package/dist/adapters/opencode-config.js +147 -0
- package/dist/adapters/opencode-server-policy.d.ts +82 -0
- package/dist/adapters/opencode-server-policy.js +148 -0
- package/dist/adapters/pi-adapter.d.ts +168 -0
- package/dist/adapters/pi-adapter.js +1468 -0
- package/dist/capabilities.d.ts +58 -0
- package/dist/capabilities.js +161 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +231 -0
- package/dist/contract.d.ts +273 -0
- package/dist/contract.js +357 -0
- package/dist/control-plane.d.ts +178 -0
- package/dist/control-plane.js +431 -0
- package/dist/credential-store.d.ts +110 -0
- package/dist/credential-store.js +181 -0
- package/dist/enrol/browser-enrol.d.ts +85 -0
- package/dist/enrol/browser-enrol.js +387 -0
- package/dist/github-command-guards.d.ts +20 -0
- package/dist/github-command-guards.js +330 -0
- package/dist/github-proxy-certificates.d.ts +25 -0
- package/dist/github-proxy-certificates.js +145 -0
- package/dist/github-proxy.d.ts +37 -0
- package/dist/github-proxy.js +476 -0
- package/dist/github-workspace.d.ts +55 -0
- package/dist/github-workspace.js +537 -0
- package/dist/harness.d.ts +333 -0
- package/dist/harness.js +1414 -0
- package/dist/health.d.ts +45 -0
- package/dist/health.js +116 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +123 -0
- package/dist/model-probe.d.ts +96 -0
- package/dist/model-probe.js +195 -0
- package/dist/options.d.ts +15 -0
- package/dist/options.js +212 -0
- package/dist/ports-default.d.ts +212 -0
- package/dist/ports-default.js +333 -0
- package/dist/ports.d.ts +436 -0
- package/dist/ports.js +27 -0
- package/dist/runtime/attachments.d.ts +92 -0
- package/dist/runtime/attachments.js +377 -0
- package/dist/runtime/child-stream-guards.d.ts +45 -0
- package/dist/runtime/child-stream-guards.js +111 -0
- package/dist/runtime/conversation.d.ts +87 -0
- package/dist/runtime/conversation.js +156 -0
- package/dist/runtime/lease-renewal.d.ts +169 -0
- package/dist/runtime/lease-renewal.js +433 -0
- package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
- package/dist/runtime/mcp-auth-proxy.js +300 -0
- package/dist/runtime/permission-mode.d.ts +116 -0
- package/dist/runtime/permission-mode.js +146 -0
- package/dist/runtime/projection.d.ts +71 -0
- package/dist/runtime/projection.js +211 -0
- package/dist/runtime/session-runner.d.ts +520 -0
- package/dist/runtime/session-runner.js +2122 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.js +20 -0
- package/dist/store/conversation-store.d.ts +100 -0
- package/dist/store/conversation-store.js +305 -0
- package/dist/store/sqlite-store.d.ts +120 -0
- package/dist/store/sqlite-store.js +346 -0
- package/dist/types.d.ts +952 -0
- package/dist/types.js +55 -0
- package/dist/version.d.ts +32 -0
- package/dist/version.js +77 -0
- package/package.json +53 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Peakflo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @peakflo/agent-harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run a Peakflo coding agent on your machine.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
Use Node.js 22.19 or newer and install Git. Install the Pi coding agent once:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Start a runner for staging or production:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx @peakflo/agent-harness@rc start --control-plane https://stage-api.peakflo.ai
|
|
17
|
+
npx @peakflo/agent-harness@latest start --control-plane https://api.peakflo.ai
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The runner opens a browser sign-in page. Sign in and select your organisation.
|
|
21
|
+
The runner then receives an enrolment token and starts its health service.
|
|
22
|
+
Check it in another terminal:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
curl http://127.0.0.1:7777/health
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
To start without a browser, get an enrolment token from your Peakflo
|
|
29
|
+
administrator and pass it through the environment:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
PF_ENROLMENT_TOKEN='<token>' npx @peakflo/agent-harness@rc start --control-plane https://stage-api.peakflo.ai
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Optional flags include `--health-port <port>`, `--runtime <desktop|self-hosted>`,
|
|
36
|
+
`--workspace-root <path>`, and `--github-proxy-port <port>`. Run
|
|
37
|
+
`npx @peakflo/agent-harness@rc --help` for the full list.
|
|
38
|
+
|
|
39
|
+
## Releasing
|
|
40
|
+
|
|
41
|
+
`.github/scripts/agent-harness-release.mjs` publishes this package
|
|
42
|
+
automatically: an rc from `main` on every push, a final from `production`.
|
|
43
|
+
**`version` in this package's `package.json` is a FLOOR, not the release** —
|
|
44
|
+
the lowest version the pipeline may produce, not the version it will produce.
|
|
45
|
+
The pipeline reads the registry and computes the actual version on every run:
|
|
46
|
+
|
|
47
|
+
1. `latestFinal` = the highest already-published **final** (non-prerelease)
|
|
48
|
+
version that is `>= floor`.
|
|
49
|
+
2. If this tree's content is byte-identical to `latestFinal`'s (ignoring only
|
|
50
|
+
the `version` field), there is nothing to publish on **either** channel —
|
|
51
|
+
`latestFinal` is reused as the version to pin. This is the common case:
|
|
52
|
+
most deploys do not touch the harness at all.
|
|
53
|
+
3. Otherwise `next = max(floor, patch(latestFinal) + 1)` — or just `floor`
|
|
54
|
+
when there is no `latestFinal` at or above it (a floor a human just
|
|
55
|
+
raised). The rc channel publishes `${next}-rc.N` (reusing an identical
|
|
56
|
+
existing rc rather than republishing); the final channel publishes `next`,
|
|
57
|
+
after confirming an rc of `next` has this exact content.
|
|
58
|
+
|
|
59
|
+
**A routine patch release needs no PR at all** — pushing a harness change to
|
|
60
|
+
`main` and then to `production` is enough; the version above `floor` is
|
|
61
|
+
computed, not hand-written. **A minor or major bump is still a human
|
|
62
|
+
decision**: raise `version` in `packages/agent-harness/package.json` (e.g.
|
|
63
|
+
`0.2.1` -> `0.3.0`) and the next computed release starts from there.
|
|
64
|
+
|
|
65
|
+
See the comments above `contentDigest()`, `planRelease()` and `release()` in
|
|
66
|
+
`agent-harness-release.mjs` for the full reasoning, including how two
|
|
67
|
+
concurrent runs are kept from publishing the same version.
|
|
68
|
+
|
|
69
|
+
Licensed under the MIT License. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified ACP (Agent Client Protocol) adapter for all ACP-compatible coding agents.
|
|
3
|
+
* Supports: codex-acp and other ACP-compliant agent processes.
|
|
4
|
+
*
|
|
5
|
+
* Protocol: JSON-RPC 2.0 over stdio (newline-delimited JSON)
|
|
6
|
+
* Spec: https://github.com/agentclientprotocol/typescript-sdk
|
|
7
|
+
*/
|
|
8
|
+
import type { CodingAgentAdapter, SessionConfig, SessionMessage, SessionStatus, MessagePart } from './coding-agent-adapter';
|
|
9
|
+
export type AcpAgentType = 'codex' | 'cursor';
|
|
10
|
+
interface AcpPermissionRequest {
|
|
11
|
+
requestId: string | number;
|
|
12
|
+
toolCallId: string;
|
|
13
|
+
question: string;
|
|
14
|
+
options: Array<{
|
|
15
|
+
optionId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
kind: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Unified ACP adapter for all ACP-compatible agents
|
|
22
|
+
*/
|
|
23
|
+
export declare class AcpAdapter implements CodingAgentAdapter {
|
|
24
|
+
private agentType;
|
|
25
|
+
private agentConfig;
|
|
26
|
+
private sessions;
|
|
27
|
+
private debugRpcLogs;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum number of raw JSON-RPC messages to keep in permanent history.
|
|
30
|
+
* This is used for replaying transcripts upon session resume.
|
|
31
|
+
* 1,000 events is enough for ~50-100 turns. Lowered from 2,000 to reduce
|
|
32
|
+
* memory footprint — each event can be multi-KB (tool outputs).
|
|
33
|
+
*/
|
|
34
|
+
private static readonly MAX_PERMANENT_MESSAGES;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of entries in toolCallMetadata before pruning.
|
|
37
|
+
* This map caches tool_call metadata for in-progress tool calls and should
|
|
38
|
+
* be pruned if it grows beyond what's reasonable (e.g. due to un-cleaned
|
|
39
|
+
* entries from tool calls that never completed).
|
|
40
|
+
*/
|
|
41
|
+
private static readonly MAX_TOOL_CALL_METADATA;
|
|
42
|
+
/** Callback set by agent-manager to trigger an immediate poll cycle */
|
|
43
|
+
onDataAvailable?: (sessionId: string) => void;
|
|
44
|
+
constructor(agentType: AcpAgentType);
|
|
45
|
+
private static isDebugLogLevel;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the path to the codex-acp native binary for the current platform.
|
|
48
|
+
*
|
|
49
|
+
* codex-acp ships as a native binary per platform (e.g. codex-acp-darwin-arm64).
|
|
50
|
+
* The JS wrapper (codex-acp.js) uses import.meta.resolve() which returns an ASAR
|
|
51
|
+
* path — but the OS can't execute a native binary from inside an ASAR archive.
|
|
52
|
+
* We bypass the wrapper and resolve the native binary directly, replacing the
|
|
53
|
+
* ASAR path with the unpacked path (electron-builder auto-unpacks native binaries).
|
|
54
|
+
*
|
|
55
|
+
* pnpm note: the platform-specific package (e.g. codex-acp-darwin-arm64) is an
|
|
56
|
+
* optionalDependency of codex-acp, so under pnpm it is NOT hoisted to the root
|
|
57
|
+
* node_modules. We must resolve it from codex-acp's own directory using the
|
|
58
|
+
* `{ paths }` option of require.resolve. In a packaged app (ASAR), electron-builder
|
|
59
|
+
* flattens the structure so a plain require.resolve would also work.
|
|
60
|
+
*/
|
|
61
|
+
private resolveCodexBinary;
|
|
62
|
+
private getAgentConfig;
|
|
63
|
+
/**
|
|
64
|
+
* Decide how Codex authenticates for this session and mutate `env` accordingly.
|
|
65
|
+
* Returns true when API-key auth is used, false when the ChatGPT subscription /
|
|
66
|
+
* Codex CLI login is used.
|
|
67
|
+
*
|
|
68
|
+
* The user's explicit choice in the agent configuration (config.authMethod) is
|
|
69
|
+
* the source of truth:
|
|
70
|
+
* - 'subscription' -> use the Codex CLI login (~/.codex), exactly like running
|
|
71
|
+
* `codex` in a terminal. Any ambient OPENAI_API_KEY/CODEX_API_KEY inherited
|
|
72
|
+
* from the user's shell is STRIPPED so it cannot hijack auth into API-key
|
|
73
|
+
* mode (which bills a separate, often-exhausted quota and made 20x show
|
|
74
|
+
* "out of rate limits" even though the terminal subscription worked fine).
|
|
75
|
+
* - 'api_key' -> use an API key (the per-agent config key if set, otherwise the
|
|
76
|
+
* ambient one) in an isolated temp CODEX_HOME so keys can differ per session.
|
|
77
|
+
*
|
|
78
|
+
* When authMethod is unset (legacy agents), fall back to the historical rule but
|
|
79
|
+
* WITHOUT the ambient-key hijack: only an explicitly-configured per-agent API
|
|
80
|
+
* key implies API-key mode; otherwise default to the subscription/login path.
|
|
81
|
+
*/
|
|
82
|
+
private configureCodexAuthEnv;
|
|
83
|
+
private configureCursorAuthEnv;
|
|
84
|
+
initialize(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Authenticate the session if required
|
|
87
|
+
*/
|
|
88
|
+
private authenticateSession;
|
|
89
|
+
createSession(config: SessionConfig): Promise<string>;
|
|
90
|
+
resumeSession(sessionId: string, config: SessionConfig): Promise<SessionMessage[]>;
|
|
91
|
+
sendPrompt(sessionId: string, parts: MessagePart[], _config: SessionConfig): Promise<void>;
|
|
92
|
+
getStatus(sessionId: string, _config: SessionConfig): Promise<SessionStatus>;
|
|
93
|
+
pollMessages(sessionId: string, seenMessageIds: Set<string>, seenPartIds: Set<string>, partContentLengths: Map<string, string>, _config: SessionConfig): Promise<MessagePart[]>;
|
|
94
|
+
abortPrompt(sessionId: string, _config: SessionConfig): Promise<void>;
|
|
95
|
+
destroySession(sessionId: string, _config: SessionConfig): Promise<void>;
|
|
96
|
+
getAllMessages(sessionId: string, _config: SessionConfig): Promise<SessionMessage[]>;
|
|
97
|
+
registerMcpServer(_serverName: string, _mcpConfig: {
|
|
98
|
+
type: 'local' | 'remote';
|
|
99
|
+
command?: string[];
|
|
100
|
+
args?: string[];
|
|
101
|
+
url?: string;
|
|
102
|
+
headers?: Record<string, string>;
|
|
103
|
+
environment?: Record<string, string>;
|
|
104
|
+
}, _workspaceDir?: string): Promise<void>;
|
|
105
|
+
checkHealth(): Promise<{
|
|
106
|
+
available: boolean;
|
|
107
|
+
reason?: string;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* Get pending approval request for a session
|
|
111
|
+
*/
|
|
112
|
+
getPendingApproval(sessionId: string): AcpPermissionRequest | null;
|
|
113
|
+
/**
|
|
114
|
+
* Respond to a pending approval request
|
|
115
|
+
*/
|
|
116
|
+
respondToApproval(sessionId: string, approved: boolean, optionId?: string): Promise<void>;
|
|
117
|
+
private setupStdoutParser;
|
|
118
|
+
private handleRpcMessage;
|
|
119
|
+
/**
|
|
120
|
+
* Extract codex-specific error info from an RPC error response.
|
|
121
|
+
* Returns a user-friendly error message if this is a known codex error type,
|
|
122
|
+
* or null if it's a generic error that should be handled normally.
|
|
123
|
+
*/
|
|
124
|
+
private extractCodexErrorInfo;
|
|
125
|
+
/**
|
|
126
|
+
* Handle a quota/usage limit error by setting session state and surfacing
|
|
127
|
+
* a clear, actionable error message to the user.
|
|
128
|
+
*/
|
|
129
|
+
private handleQuotaError;
|
|
130
|
+
private updateSessionStatus;
|
|
131
|
+
/**
|
|
132
|
+
* Adds a notification or event to the permanent message history, with
|
|
133
|
+
* consolidation and size-based pruning to prevent OOM.
|
|
134
|
+
*
|
|
135
|
+
* IMPORTANT: events are stored as deep clones. The incoming `event` object is
|
|
136
|
+
* the SAME object that handleRpcMessage() pushed into session.messageBuffer
|
|
137
|
+
* for live polling. This method mutates stored events in two ways:
|
|
138
|
+
* 1. chunk consolidation rewrites content.text on the last stored event
|
|
139
|
+
* 2. tool outputs are truncated to 100KB for history
|
|
140
|
+
* Without cloning, those mutations corrupted the un-polled live buffer:
|
|
141
|
+
* when several agent_message_chunk deltas arrived between polls (always the
|
|
142
|
+
* case at the START of a response — the first token burst lands before the
|
|
143
|
+
* first poll cycle), consolidation silently rewrote the FIRST buffered chunk
|
|
144
|
+
* into the full accumulated text while the later buffered chunks stayed raw
|
|
145
|
+
* deltas. pollMessages() then appended those deltas again after the already
|
|
146
|
+
* complete text, scrambling the beginning of the message
|
|
147
|
+
* ("Hello world, how are you? world, how are you?").
|
|
148
|
+
*/
|
|
149
|
+
private addToPermanentMessages;
|
|
150
|
+
private sendRpcRequest;
|
|
151
|
+
private sendRpcRequestNoWait;
|
|
152
|
+
private sendRpcResponse;
|
|
153
|
+
private sendRpcNotification;
|
|
154
|
+
private handlePermissionRequest;
|
|
155
|
+
private extractAcpSessionId;
|
|
156
|
+
/**
|
|
157
|
+
* Convert internal McpServerConfig map to ACP-spec McpServer array.
|
|
158
|
+
* ACP spec: https://agentclientprotocol.com/protocol/schema
|
|
159
|
+
*
|
|
160
|
+
* - mcpServers is Vec<McpServer> (array, not map)
|
|
161
|
+
* - stdio variant: { name, command, args: string[], env: EnvVariable[] }
|
|
162
|
+
* - http variant: { type: "http", name, url, headers: HttpHeader[] }
|
|
163
|
+
* - EnvVariable / HttpHeader = { name: string, value: string }
|
|
164
|
+
*/
|
|
165
|
+
private convertMcpServers;
|
|
166
|
+
private extractTextFromUpdateContent;
|
|
167
|
+
private mergeStreamingText;
|
|
168
|
+
private isUserUpdateType;
|
|
169
|
+
private isAssistantChunkUpdateType;
|
|
170
|
+
private isToolingUpdateType;
|
|
171
|
+
private getAssistantTurnId;
|
|
172
|
+
private getUserTurnId;
|
|
173
|
+
private normalizeToolName;
|
|
174
|
+
private buildToolTitle;
|
|
175
|
+
private convertAcpEventToMessageParts;
|
|
176
|
+
}
|
|
177
|
+
export {};
|