@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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* THE PROJECTION — adapter message parts in, transcript parts out.
|
|
4
|
+
*
|
|
5
|
+
* Ported from the single chokepoint in 20x `agent-manager.ts`
|
|
6
|
+
* (`persistTranscriptEvent`, :5269) through which every piece of live streaming
|
|
7
|
+
* output passes on its way to the durable log. Keeping it a chokepoint is the
|
|
8
|
+
* whole reason the 20x transcript is reliable, and it is reproduced here for the
|
|
9
|
+
* same reason: one function decides what a transcript part IS, so a new backend
|
|
10
|
+
* cannot invent a second answer.
|
|
11
|
+
*
|
|
12
|
+
* THREE FILTERS, IN ORDER, each of which exists because of a real defect:
|
|
13
|
+
*
|
|
14
|
+
* 1. NO ID, NO PART. The upsert is keyed on (sessionId, partId); a part with no
|
|
15
|
+
* id has no identity, so a second delta for it would insert a duplicate
|
|
16
|
+
* instead of updating the first.
|
|
17
|
+
*
|
|
18
|
+
* 2. THE EPHEMERAL THREE ARE DROPPED. 'step-start', 'step-finish' and
|
|
19
|
+
* 'system-status' are UI-only frame markers. Persisting them would put
|
|
20
|
+
* thousands of meaningless rows in every transcript and stream each one to
|
|
21
|
+
* every reader. Their actual information — duration, token counts — belongs
|
|
22
|
+
* in `TurnMetadata`, which is what closes defect G7.
|
|
23
|
+
*
|
|
24
|
+
* The check goes through the SHARED predicate `isEphemeralPartType`, which
|
|
25
|
+
* trims and lowercases. A backend emitting ' Step-Start ' must not slip past
|
|
26
|
+
* a case-sensitive comparison and corrupt the projection permanently.
|
|
27
|
+
*
|
|
28
|
+
* 3. AN EMPTY PART IS DROPPED. The opening frame of a text part that never
|
|
29
|
+
* received a delta is a stream artefact, and storing it puts a blank bubble
|
|
30
|
+
* in the UI for ever. A part with a payload and no content is KEPT — a tool
|
|
31
|
+
* call with arguments and no textual body is perfectly ordinary.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.projectParts = projectParts;
|
|
35
|
+
exports.toIngestPart = toIngestPart;
|
|
36
|
+
const contract_1 = require("../contract");
|
|
37
|
+
const VALID_ROLES = new Set(['user', 'assistant', 'system']);
|
|
38
|
+
/**
|
|
39
|
+
* The structured carriers, merged into one payload object.
|
|
40
|
+
*
|
|
41
|
+
* 20x wrapped questions/todos/taskProgress in a single object and stored it as
|
|
42
|
+
* one JSONB column, and the mobile and desktop readers both unwrap it that way.
|
|
43
|
+
* Changing the shape here would silently break both, so it is preserved even
|
|
44
|
+
* though a flatter shape would be tidier.
|
|
45
|
+
*
|
|
46
|
+
* Returns null — not an empty object — when there is nothing to carry, because
|
|
47
|
+
* `{}` is truthy and would defeat the emptiness filter above.
|
|
48
|
+
*/
|
|
49
|
+
function buildPayload(part) {
|
|
50
|
+
const hasCarrier = part.questions !== undefined ||
|
|
51
|
+
part.todos !== undefined ||
|
|
52
|
+
part.taskProgress !== undefined ||
|
|
53
|
+
isRichTool(part.tool);
|
|
54
|
+
if (!hasCarrier) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const payload = {};
|
|
58
|
+
if (part.questions !== undefined) {
|
|
59
|
+
payload.questions = asJson(part.questions);
|
|
60
|
+
}
|
|
61
|
+
if (part.todos !== undefined) {
|
|
62
|
+
payload.todos = asJson(part.todos);
|
|
63
|
+
}
|
|
64
|
+
if (part.taskProgress !== undefined) {
|
|
65
|
+
payload.taskProgress = asJson(part.taskProgress);
|
|
66
|
+
}
|
|
67
|
+
if (isRichTool(part.tool)) {
|
|
68
|
+
payload.tool = asJson(normaliseToolForTranscript(part.tool));
|
|
69
|
+
}
|
|
70
|
+
return Object.keys(payload).length > 0 ? payload : null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The transcript wire contract renders tool detail as text. Backends are less
|
|
74
|
+
* strict: Pi emits structured JSON for MCP input, while OpenCode usually emits
|
|
75
|
+
* a pre-serialised string. Normalise at the runtime boundary so a new adapter
|
|
76
|
+
* cannot hand React an object where the shared renderer calls string methods.
|
|
77
|
+
*/
|
|
78
|
+
function normaliseToolForTranscript(tool) {
|
|
79
|
+
if (!tool || typeof tool !== 'object' || Array.isArray(tool))
|
|
80
|
+
return tool;
|
|
81
|
+
const copy = { ...tool };
|
|
82
|
+
for (const key of ['title', 'description', 'input', 'output', 'error']) {
|
|
83
|
+
const value = copy[key];
|
|
84
|
+
if (value !== undefined && value !== null && typeof value !== 'string') {
|
|
85
|
+
copy[key] = displayText(value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return copy;
|
|
89
|
+
}
|
|
90
|
+
function displayText(value) {
|
|
91
|
+
try {
|
|
92
|
+
const encoded = JSON.stringify(value);
|
|
93
|
+
return encoded === undefined ? String(value) : encoded;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return String(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A tool that carries more than a name — status, input, output, an error.
|
|
101
|
+
*
|
|
102
|
+
* The bare name goes in the `tool` COLUMN so it can be indexed and filtered;
|
|
103
|
+
* the rest goes in the payload. Splitting them is what lets a reader show
|
|
104
|
+
* "Read(src/index.ts)" without parsing JSON for every row in the transcript.
|
|
105
|
+
*/
|
|
106
|
+
function isRichTool(tool) {
|
|
107
|
+
if (!tool || typeof tool !== 'object') {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const keys = Object.keys(tool);
|
|
111
|
+
return keys.some((k) => k !== 'name');
|
|
112
|
+
}
|
|
113
|
+
function toolName(tool) {
|
|
114
|
+
if (typeof tool === 'string') {
|
|
115
|
+
return tool;
|
|
116
|
+
}
|
|
117
|
+
if (tool && typeof tool === 'object') {
|
|
118
|
+
const name = tool.name;
|
|
119
|
+
return typeof name === 'string' ? name : null;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A role the projection is willing to store.
|
|
125
|
+
*
|
|
126
|
+
* Defaults to 'system' rather than throwing, matching 20x. An unknown role from
|
|
127
|
+
* a new backend must not be able to abort a turn — the transcript is the record
|
|
128
|
+
* of what happened, and refusing to record it because a field was odd is the
|
|
129
|
+
* worst possible failure mode.
|
|
130
|
+
*/
|
|
131
|
+
function normaliseRole(role) {
|
|
132
|
+
return typeof role === 'string' && VALID_ROLES.has(role)
|
|
133
|
+
? role
|
|
134
|
+
: 'system';
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Coerce to something that survives a JSON round trip.
|
|
138
|
+
*
|
|
139
|
+
* A tool result can contain anything a backend chose to put there, including a
|
|
140
|
+
* value that `JSON.stringify` throws on (a BigInt) or silently mangles (a
|
|
141
|
+
* circular reference). Losing one tool's metadata is acceptable; losing the
|
|
142
|
+
* turn because a payload would not serialise is not.
|
|
143
|
+
*/
|
|
144
|
+
function asJson(value) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Project a batch of adapter parts into transcript parts.
|
|
154
|
+
*
|
|
155
|
+
* Returns only the parts worth storing. An empty result is an ordinary outcome
|
|
156
|
+
* — a poll that saw nothing but step markers — and is NOT an error.
|
|
157
|
+
*/
|
|
158
|
+
function projectParts(raw) {
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const part of raw) {
|
|
161
|
+
if (!part || !part.id) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const partType = part.partType ?? part.type ?? 'text';
|
|
165
|
+
if ((0, contract_1.isEphemeralPartType)(partType)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// `||`, NOT `??`. Nullish coalescing only falls back on null/undefined, so
|
|
169
|
+
// a backend emitting `{ content: '', text: 'hello' }` — which OpenCode does
|
|
170
|
+
// for a part whose text arrives on a later field — kept the empty string,
|
|
171
|
+
// and filter 3 below then dropped the part as "empty". The user's text
|
|
172
|
+
// vanished with no error anywhere. Both fields mean the same thing here, so
|
|
173
|
+
// an empty one must yield to a populated one.
|
|
174
|
+
const content = part.content || part.text || '';
|
|
175
|
+
const payload = buildPayload(part);
|
|
176
|
+
// The 20x rule, kept exactly: content OR any structured carrier earns the
|
|
177
|
+
// part its place. Only a part with neither is dropped.
|
|
178
|
+
if (content.length === 0 && payload === null) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
out.push({
|
|
182
|
+
partId: part.id,
|
|
183
|
+
role: normaliseRole(part.role),
|
|
184
|
+
content,
|
|
185
|
+
partType,
|
|
186
|
+
tool: toolName(part.tool),
|
|
187
|
+
payload,
|
|
188
|
+
createdAt: typeof part.receivedAt === 'number' ? part.receivedAt : undefined
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Convert a stored part into the wire shape for `POST /ingest`.
|
|
195
|
+
*
|
|
196
|
+
* `seq` and `rev` are dropped: they are SERVER-ALLOCATED, and a runner that
|
|
197
|
+
* could send them could fabricate an ordering or replay an old rev over a newer
|
|
198
|
+
* one. The contract's `TranscriptPartInput` has no field for either, so this is
|
|
199
|
+
* enforced by the type rather than by a convention someone must remember.
|
|
200
|
+
*/
|
|
201
|
+
function toIngestPart(part) {
|
|
202
|
+
return {
|
|
203
|
+
partId: part.partId,
|
|
204
|
+
role: part.role,
|
|
205
|
+
content: part.content,
|
|
206
|
+
partType: part.partType,
|
|
207
|
+
tool: part.tool ?? undefined,
|
|
208
|
+
payload: part.payload ?? undefined,
|
|
209
|
+
createdAt: new Date(part.createdAt ?? Date.now()).toISOString()
|
|
210
|
+
};
|
|
211
|
+
}
|