@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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -2
  3. package/dist/adapters/acp-adapter.d.ts +177 -0
  4. package/dist/adapters/acp-adapter.js +1732 -0
  5. package/dist/adapters/claude-code-adapter.d.ts +171 -0
  6. package/dist/adapters/claude-code-adapter.js +1918 -0
  7. package/dist/adapters/codex-adapter.d.ts +67 -0
  8. package/dist/adapters/codex-adapter.js +681 -0
  9. package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
  10. package/dist/adapters/codex-app-server-adapter.js +1309 -0
  11. package/dist/adapters/coding-agent-adapter.d.ts +289 -0
  12. package/dist/adapters/coding-agent-adapter.js +31 -0
  13. package/dist/adapters/index.d.ts +17 -0
  14. package/dist/adapters/index.js +35 -0
  15. package/dist/adapters/model-gateway.d.ts +84 -0
  16. package/dist/adapters/model-gateway.js +270 -0
  17. package/dist/adapters/opencode-adapter.d.ts +403 -0
  18. package/dist/adapters/opencode-adapter.js +2710 -0
  19. package/dist/adapters/opencode-config.d.ts +41 -0
  20. package/dist/adapters/opencode-config.js +147 -0
  21. package/dist/adapters/opencode-server-policy.d.ts +82 -0
  22. package/dist/adapters/opencode-server-policy.js +148 -0
  23. package/dist/adapters/pi-adapter.d.ts +168 -0
  24. package/dist/adapters/pi-adapter.js +1468 -0
  25. package/dist/capabilities.d.ts +58 -0
  26. package/dist/capabilities.js +161 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +231 -0
  29. package/dist/contract.d.ts +273 -0
  30. package/dist/contract.js +357 -0
  31. package/dist/control-plane.d.ts +178 -0
  32. package/dist/control-plane.js +431 -0
  33. package/dist/credential-store.d.ts +110 -0
  34. package/dist/credential-store.js +181 -0
  35. package/dist/enrol/browser-enrol.d.ts +85 -0
  36. package/dist/enrol/browser-enrol.js +387 -0
  37. package/dist/github-command-guards.d.ts +20 -0
  38. package/dist/github-command-guards.js +330 -0
  39. package/dist/github-proxy-certificates.d.ts +25 -0
  40. package/dist/github-proxy-certificates.js +145 -0
  41. package/dist/github-proxy.d.ts +37 -0
  42. package/dist/github-proxy.js +476 -0
  43. package/dist/github-workspace.d.ts +55 -0
  44. package/dist/github-workspace.js +537 -0
  45. package/dist/harness.d.ts +333 -0
  46. package/dist/harness.js +1414 -0
  47. package/dist/health.d.ts +45 -0
  48. package/dist/health.js +116 -0
  49. package/dist/index.d.ts +53 -0
  50. package/dist/index.js +123 -0
  51. package/dist/model-probe.d.ts +96 -0
  52. package/dist/model-probe.js +195 -0
  53. package/dist/options.d.ts +15 -0
  54. package/dist/options.js +212 -0
  55. package/dist/ports-default.d.ts +212 -0
  56. package/dist/ports-default.js +333 -0
  57. package/dist/ports.d.ts +436 -0
  58. package/dist/ports.js +27 -0
  59. package/dist/runtime/attachments.d.ts +92 -0
  60. package/dist/runtime/attachments.js +377 -0
  61. package/dist/runtime/child-stream-guards.d.ts +45 -0
  62. package/dist/runtime/child-stream-guards.js +111 -0
  63. package/dist/runtime/conversation.d.ts +87 -0
  64. package/dist/runtime/conversation.js +156 -0
  65. package/dist/runtime/lease-renewal.d.ts +169 -0
  66. package/dist/runtime/lease-renewal.js +433 -0
  67. package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
  68. package/dist/runtime/mcp-auth-proxy.js +300 -0
  69. package/dist/runtime/permission-mode.d.ts +116 -0
  70. package/dist/runtime/permission-mode.js +146 -0
  71. package/dist/runtime/projection.d.ts +71 -0
  72. package/dist/runtime/projection.js +211 -0
  73. package/dist/runtime/session-runner.d.ts +520 -0
  74. package/dist/runtime/session-runner.js +2122 -0
  75. package/dist/shared/reasoning-effort.d.ts +11 -0
  76. package/dist/shared/reasoning-effort.js +20 -0
  77. package/dist/store/conversation-store.d.ts +100 -0
  78. package/dist/store/conversation-store.js +305 -0
  79. package/dist/store/sqlite-store.d.ts +120 -0
  80. package/dist/store/sqlite-store.js +346 -0
  81. package/dist/types.d.ts +952 -0
  82. package/dist/types.js +55 -0
  83. package/dist/version.d.ts +32 -0
  84. package/dist/version.js +77 -0
  85. 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
- # Temporary Holding Version
1
+ # @peakflo/agent-harness
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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 {};