@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
@@ -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
+ }