@pome-sh/cli 0.45.1 → 0.46.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/assets/dashboard/app.css +3 -0
- package/assets/dashboard/app.js +9 -0
- package/assets/dashboard/index.html +15 -0
- package/dist/build-info.json +3 -3
- package/dist/{checks-RDU56OGI.js → checks-FMIUVJ73.js} +2 -2
- package/dist/{twinTape-5P3HQ27N.js → chunk-4EPQF6CR.js} +261 -31
- package/dist/{chunk-IZFM7W2V.js → chunk-GBMPR32X.js} +34 -10
- package/dist/{chunk-DX2KCFJM.js → chunk-GTLGXFLQ.js} +1 -1
- package/dist/{chunk-HEEFOMSZ.js → chunk-J5IF4T3C.js} +7 -4
- package/dist/{chunk-BXYPNEJY.js → chunk-LDDR6XRN.js} +45 -22
- package/dist/{chunk-EZYVICDB.js → chunk-QO6QJ6MN.js} +3 -1
- package/dist/chunk-TG6GFWA3.js +48 -0
- package/dist/{runTrialGroup-MMTTTDWY.js → runTrialGroup-FZWKPVI5.js} +6 -6
- package/dist/src/cli/main.js +121 -154
- package/dist/{src-STAXYPD3.js → src-F5FHPGJP.js} +1 -1
- package/dist/{src-46YGR2WY.js → src-L54OSFDK.js} +1 -1
- package/dist/{src-LPTWNBO5.js → src-LYV6LLKW.js} +1 -1
- package/dist/{src-D3PKLT54.js → src-MRJ73S46.js} +1 -1
- package/dist/{src-CV7QDV5E.js → src-PQ35TNOF.js} +1 -1
- package/dist/twinHarness-VEIXQJ4Y.js +6 -0
- package/dist/{twinSeed-ZFZO7BL7.js → twinSeed-LDKR3NPY.js} +1 -1
- package/dist/{twinStart-XN3BYOUF.js → twinStart-44W63TRH.js} +379 -93
- package/dist/twinTape-433A4IC4.js +10 -0
- package/package.json +2 -1
- package/dist/chunk-AGZOWSLG.js +0 -142
- package/dist/twinHarness-I2JGKQYD.js +0 -6
- package/dist/{run-WONFTZS3.js → run-CFA4LZWQ.js} +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<!-- No favicon request, no font fetch, no analytics: the page makes no
|
|
7
|
+
network call other than its own `/api/snapshot`. D7 · 11. -->
|
|
8
|
+
<title>Pome — local twin dashboard</title>
|
|
9
|
+
<script type="module" crossorigin src="/app.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/app.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "pome-sh",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"git_sha": "
|
|
5
|
-
"build_time": "2026-09-
|
|
3
|
+
"version": "0.46.0",
|
|
4
|
+
"git_sha": "c830c89c6055e3371f4693f49bffe96cf230e060",
|
|
5
|
+
"build_time": "2026-09-19T16:59:46.785Z"
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getAvailablePort } from './chunk-XDU6TD4O.js';
|
|
2
2
|
import { findManifestPath, readManifest } from './chunk-26UAPLHK.js';
|
|
3
|
-
import './chunk-M7ATJ423.js';
|
|
4
3
|
import { buildEgressAllowlist } from './chunk-CBFKZZBR.js';
|
|
4
|
+
import './chunk-M7ATJ423.js';
|
|
5
5
|
import './chunk-NBOQN5VX.js';
|
|
6
6
|
import './chunk-YBWG5JK2.js';
|
|
7
7
|
import './chunk-7VZBAHQ2.js';
|
|
@@ -183,7 +183,7 @@ async function checkTwinReachable(_configDir) {
|
|
|
183
183
|
});
|
|
184
184
|
let harness;
|
|
185
185
|
try {
|
|
186
|
-
const { bootTwin } = await import('./twinHarness-
|
|
186
|
+
const { bootTwin } = await import('./twinHarness-VEIXQJ4Y.js');
|
|
187
187
|
harness = await bootTwin({
|
|
188
188
|
twin: "github",
|
|
189
189
|
seedState: void 0,
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import './chunk-4LQ4IJOC.js';
|
|
3
|
-
import './chunk-M7ATJ423.js';
|
|
4
|
-
import { TWIN_NAMES } from './chunk-BXYPNEJY.js';
|
|
5
|
-
import './chunk-NBOQN5VX.js';
|
|
6
|
-
import './chunk-YBWG5JK2.js';
|
|
1
|
+
import { TWIN_NAMES } from './chunk-LDDR6XRN.js';
|
|
7
2
|
import { recorderEventSchema } from './chunk-7VZBAHQ2.js';
|
|
8
|
-
import '
|
|
9
|
-
import '
|
|
10
|
-
import './chunk-FBSA5L36.js';
|
|
3
|
+
import { mkdir, writeFile, rename, chmod, readFile, unlink, open, stat } from 'node:fs/promises';
|
|
4
|
+
import { dirname, basename, join } from 'node:path';
|
|
11
5
|
|
|
12
6
|
// src/twin/stateDiff.ts
|
|
13
7
|
var IDENTITY_KEYS = ["full_name", "number", "name", "login", "email", "path", "ts", "key", "id"];
|
|
@@ -65,33 +59,45 @@ function sameScalars(a, b) {
|
|
|
65
59
|
return stableStringify(scalarsOf(a)) === stableStringify(scalarsOf(b));
|
|
66
60
|
}
|
|
67
61
|
function diffState(before, after) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
return censusState(before, after, { changedOnly: true }).map(({ path, added, changed, removed }) => ({
|
|
63
|
+
path,
|
|
64
|
+
added,
|
|
65
|
+
changed,
|
|
66
|
+
removed
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
function censusState(before, after, options = {}) {
|
|
70
|
+
const sink = { out: [], changedOnly: options.changedOnly === true };
|
|
71
|
+
walk(before, after, "", sink);
|
|
72
|
+
return sink.out;
|
|
73
|
+
}
|
|
74
|
+
function emit(sink, entry) {
|
|
75
|
+
const moved = entry.added.length > 0 || entry.changed.length > 0 || entry.removed.length > 0;
|
|
76
|
+
if (moved || !sink.changedOnly) sink.out.push(entry);
|
|
71
77
|
}
|
|
72
|
-
function walk(before, after, path,
|
|
78
|
+
function walk(before, after, path, sink) {
|
|
73
79
|
if (Array.isArray(before) || Array.isArray(after)) {
|
|
74
80
|
const b = Array.isArray(before) ? before : [];
|
|
75
81
|
const a = Array.isArray(after) ? after : [];
|
|
76
82
|
if (isMembership(b) || isMembership(a)) {
|
|
77
83
|
if ((b.length === 0 || isMembership(b)) && (a.length === 0 || isMembership(a))) {
|
|
78
|
-
diffMembership(b, a, path,
|
|
84
|
+
diffMembership(b, a, path, sink);
|
|
79
85
|
}
|
|
80
86
|
return;
|
|
81
87
|
}
|
|
82
88
|
if ((b.length === 0 || isCollection(b)) && (a.length === 0 || isCollection(a))) {
|
|
83
|
-
diffCollection(b, a, path,
|
|
89
|
+
diffCollection(b, a, path, sink);
|
|
84
90
|
}
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
93
|
if (isRow(before) && isRow(after)) {
|
|
88
94
|
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
89
95
|
for (const key of [...keys].sort()) {
|
|
90
|
-
walk(before[key], after[key], path === "" ? key : `${path}.${key}`,
|
|
96
|
+
walk(before[key], after[key], path === "" ? key : `${path}.${key}`, sink);
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
function diffMembership(before, after, path,
|
|
100
|
+
function diffMembership(before, after, path, sink) {
|
|
95
101
|
const count = (values) => {
|
|
96
102
|
const map = /* @__PURE__ */ new Map();
|
|
97
103
|
for (const value of values) map.set(String(value), (map.get(String(value)) ?? 0) + 1);
|
|
@@ -99,14 +105,30 @@ function diffMembership(before, after, path, out) {
|
|
|
99
105
|
};
|
|
100
106
|
const b = count(before);
|
|
101
107
|
const a = count(after);
|
|
102
|
-
const entry = {
|
|
108
|
+
const entry = {
|
|
109
|
+
path: path || "(root)",
|
|
110
|
+
boot: before.length,
|
|
111
|
+
now: after.length,
|
|
112
|
+
kind: "membership",
|
|
113
|
+
added: [],
|
|
114
|
+
changed: [],
|
|
115
|
+
removed: []
|
|
116
|
+
};
|
|
103
117
|
for (const [value, n] of a) for (let i = b.get(value) ?? 0; i < n; i += 1) entry.added.push(value);
|
|
104
118
|
for (const [value, n] of b) for (let i = a.get(value) ?? 0; i < n; i += 1) entry.removed.push(value);
|
|
105
|
-
|
|
119
|
+
emit(sink, entry);
|
|
106
120
|
}
|
|
107
|
-
function diffCollection(before, after, path,
|
|
121
|
+
function diffCollection(before, after, path, sink) {
|
|
108
122
|
const key = identityKeyOf(before, after);
|
|
109
|
-
const entry = {
|
|
123
|
+
const entry = {
|
|
124
|
+
path: path || "(root)",
|
|
125
|
+
boot: before.length,
|
|
126
|
+
now: after.length,
|
|
127
|
+
kind: "collection",
|
|
128
|
+
added: [],
|
|
129
|
+
changed: [],
|
|
130
|
+
removed: []
|
|
131
|
+
};
|
|
110
132
|
const byId = (rows) => {
|
|
111
133
|
const seen = /* @__PURE__ */ new Map();
|
|
112
134
|
return new Map(
|
|
@@ -138,14 +160,14 @@ function diffCollection(before, after, path, out) {
|
|
|
138
160
|
for (const [id, { row, index }] of beforeById) {
|
|
139
161
|
if (!afterById.has(id)) entry.removed.push(name(row));
|
|
140
162
|
}
|
|
141
|
-
|
|
163
|
+
emit(sink, entry);
|
|
142
164
|
for (const pair of nested) {
|
|
143
165
|
const keys = /* @__PURE__ */ new Set([...Object.keys(pair.before), ...Object.keys(pair.after)]);
|
|
144
166
|
for (const field of [...keys].sort()) {
|
|
145
167
|
const b = pair.before[field];
|
|
146
168
|
const a = pair.after[field];
|
|
147
169
|
if (Array.isArray(b) || Array.isArray(a) || isRow(b) && isRow(a)) {
|
|
148
|
-
walk(b, a, `${path}[${pair.label}].${field}`,
|
|
170
|
+
walk(b, a, `${path}[${pair.label}].${field}`, sink);
|
|
149
171
|
}
|
|
150
172
|
}
|
|
151
173
|
}
|
|
@@ -163,6 +185,142 @@ function renderStateDiff(entries) {
|
|
|
163
185
|
}
|
|
164
186
|
return lines;
|
|
165
187
|
}
|
|
188
|
+
var STANDALONE_STATUS_PATH = ".pome/twin-status.json";
|
|
189
|
+
async function writeStandaloneStatusFile(status, path = STANDALONE_STATUS_PATH) {
|
|
190
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
191
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
192
|
+
await writeFile(tmp, JSON.stringify(status, null, 2), { mode: 384 });
|
|
193
|
+
await rename(tmp, path);
|
|
194
|
+
await chmod(path, 384);
|
|
195
|
+
}
|
|
196
|
+
async function ensureSelfIgnoring(dir) {
|
|
197
|
+
if (basename(dir) !== ".pome") return;
|
|
198
|
+
const path = join(dir, ".gitignore");
|
|
199
|
+
try {
|
|
200
|
+
await writeFile(path, "*\n", { flag: "wx", mode: 384 });
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if (err.code !== "EEXIST") throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var STALE_LOCK_MS = 1e4;
|
|
206
|
+
async function withStatusLock(path, fn) {
|
|
207
|
+
const lockPath = `${path}.lock`;
|
|
208
|
+
const deadline = Date.now() + 5e3;
|
|
209
|
+
for (; ; ) {
|
|
210
|
+
try {
|
|
211
|
+
const handle = await open(lockPath, "wx", 384);
|
|
212
|
+
try {
|
|
213
|
+
await handle.writeFile(String(process.pid));
|
|
214
|
+
} finally {
|
|
215
|
+
await handle.close();
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err.code !== "EEXIST") throw err;
|
|
220
|
+
const age = await stat(lockPath).then((s) => Date.now() - s.mtimeMs, () => 0);
|
|
221
|
+
if (age > STALE_LOCK_MS) {
|
|
222
|
+
await unlink(lockPath).catch(() => void 0);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (Date.now() > deadline) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`pome twin start: ${lockPath} is held by another pome process \u2014 if none is running, delete it.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
return await fn();
|
|
235
|
+
} finally {
|
|
236
|
+
await unlink(lockPath).catch(() => void 0);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function updateStandaloneStatusFile(entries, path = STANDALONE_STATUS_PATH) {
|
|
240
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
241
|
+
await ensureSelfIgnoring(dirname(path));
|
|
242
|
+
return await withStatusLock(path, async () => {
|
|
243
|
+
const merged = mergeStandaloneStatus(await readStandaloneStatusFile(path), entries);
|
|
244
|
+
await writeStandaloneStatusFile(merged, path);
|
|
245
|
+
return merged;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function isStatusEntry(value) {
|
|
249
|
+
if (value === null || typeof value !== "object") return false;
|
|
250
|
+
const entry = value;
|
|
251
|
+
return typeof entry.name === "string" && typeof entry.url === "string" && typeof entry.rest_url === "string" && typeof entry.mcp_url === "string" && typeof entry.auth_token === "string";
|
|
252
|
+
}
|
|
253
|
+
function standaloneStatusEntries(contents) {
|
|
254
|
+
if (contents === null || typeof contents !== "object") return [];
|
|
255
|
+
const file = contents;
|
|
256
|
+
if (file.twins !== null && typeof file.twins === "object") {
|
|
257
|
+
return Object.values(file.twins).filter(isStatusEntry);
|
|
258
|
+
}
|
|
259
|
+
return isStatusEntry(file) ? [pick(file)] : [];
|
|
260
|
+
}
|
|
261
|
+
function pick(entry) {
|
|
262
|
+
return {
|
|
263
|
+
name: entry.name,
|
|
264
|
+
url: entry.url,
|
|
265
|
+
rest_url: entry.rest_url,
|
|
266
|
+
mcp_url: entry.mcp_url,
|
|
267
|
+
auth_token: entry.auth_token
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function mergeStandaloneStatus(existing, entries) {
|
|
271
|
+
const twins = {};
|
|
272
|
+
for (const entry of standaloneStatusEntries(existing)) twins[entry.name] = pick(entry);
|
|
273
|
+
for (const entry of entries) twins[entry.name] = pick(entry);
|
|
274
|
+
const first = entries[0];
|
|
275
|
+
if (first === void 0) throw new Error("mergeStandaloneStatus: no entries to write");
|
|
276
|
+
return { ...pick(first), twins };
|
|
277
|
+
}
|
|
278
|
+
async function readStandaloneStatusFile(path = STANDALONE_STATUS_PATH) {
|
|
279
|
+
try {
|
|
280
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
281
|
+
} catch {
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
var STANDALONE_STATE_DIR = ".pome/twin-state";
|
|
286
|
+
function standaloneInitialStatePath(name, dir = STANDALONE_STATE_DIR) {
|
|
287
|
+
return join(dir, `${name}.initial.json`);
|
|
288
|
+
}
|
|
289
|
+
async function writeStandaloneInitialState(name, state, dir = STANDALONE_STATE_DIR) {
|
|
290
|
+
const path = standaloneInitialStatePath(name, dir);
|
|
291
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
292
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
293
|
+
await writeFile(tmp, JSON.stringify(state), { mode: 384 });
|
|
294
|
+
await rename(tmp, path);
|
|
295
|
+
await chmod(path, 384);
|
|
296
|
+
}
|
|
297
|
+
async function readStandaloneInitialState(name, dir = STANDALONE_STATE_DIR) {
|
|
298
|
+
const path = standaloneInitialStatePath(name, dir);
|
|
299
|
+
let raw;
|
|
300
|
+
try {
|
|
301
|
+
raw = await readFile(path, "utf8");
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (err.code === "ENOENT") return void 0;
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
return JSON.parse(raw);
|
|
308
|
+
} catch {
|
|
309
|
+
throw new Error(`pome twin tape: ${path} is not valid JSON \u2014 restart the twin to rewrite it.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function snapshotStandaloneInitialState(name, exportState, dir = STANDALONE_STATE_DIR) {
|
|
313
|
+
await unlink(standaloneInitialStatePath(name, dir)).catch((err) => {
|
|
314
|
+
if (err.code !== "ENOENT") throw err;
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
await writeStandaloneInitialState(name, await exportState(), dir);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.error(
|
|
320
|
+
`pome twin start: could not snapshot the ${name} twin's state for \`pome twin tape --diff\`: ${err.message}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
166
324
|
|
|
167
325
|
// src/twin/twinTape.ts
|
|
168
326
|
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
@@ -223,15 +381,83 @@ var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
|
223
381
|
"confirm",
|
|
224
382
|
"void",
|
|
225
383
|
"finalize",
|
|
226
|
-
"pay"
|
|
384
|
+
"pay",
|
|
385
|
+
// Slack's `conversations.open` (opens a DM) and `chat.scheduleMessage`. Checked
|
|
386
|
+
// against all 115 MCP tool names across the five twins: neither word turns a
|
|
387
|
+
// read into a write, and `schedule` fixes `slack_schedule_message`, a write
|
|
388
|
+
// this list used to read as a read.
|
|
389
|
+
"open",
|
|
390
|
+
"schedule",
|
|
391
|
+
// GitHub's consolidated `issue_write` and Linear's `save_issue`,
|
|
392
|
+
// `save_project`, `save_document` — found by a real Claude Code session, whose
|
|
393
|
+
// failed `issue_write` would otherwise never have been marked. Every tool that
|
|
394
|
+
// declares `readOnlyHint` is now checked against this list
|
|
395
|
+
// (`requestKind.test.ts`).
|
|
396
|
+
"write",
|
|
397
|
+
"save"
|
|
227
398
|
]);
|
|
399
|
+
var COMPOUND_NOUNS = [[/pull_request/gi, "pullrequest"]];
|
|
400
|
+
function nameWords(name) {
|
|
401
|
+
let joined = name;
|
|
402
|
+
for (const [compound, noun] of COMPOUND_NOUNS) joined = joined.replace(compound, noun);
|
|
403
|
+
return joined.split(/[_\-.]|(?=[A-Z])/).map((word) => word.toLowerCase()).filter((word) => word.length > 0);
|
|
404
|
+
}
|
|
405
|
+
function namesAWrite(name) {
|
|
406
|
+
return nameWords(name).some((word) => WRITE_VERBS.has(word));
|
|
407
|
+
}
|
|
228
408
|
function isMcpTransport(path) {
|
|
229
409
|
return path === "/mcp" || path.startsWith("/mcp/");
|
|
230
410
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
411
|
+
function isRpcMethod(path) {
|
|
412
|
+
return /^\/[a-z][a-zA-Z]*(\.[a-zA-Z]+)+$/.test(path);
|
|
413
|
+
}
|
|
414
|
+
function graphqlOperationKind(body) {
|
|
415
|
+
if (Array.isArray(body)) {
|
|
416
|
+
const kinds = body.map(graphqlOperationKind);
|
|
417
|
+
if (kinds.includes("write")) return "write";
|
|
418
|
+
return kinds.some((kind) => kind === "read") ? "read" : void 0;
|
|
419
|
+
}
|
|
420
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
421
|
+
const { query, operationName } = body;
|
|
422
|
+
if (typeof query !== "string") return void 0;
|
|
423
|
+
const source = query.replace(/"""[\s\S]*?"""/g, '""').replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/#[^\n]*/g, "");
|
|
424
|
+
const operations = [];
|
|
425
|
+
let depth = 0;
|
|
426
|
+
let parens = 0;
|
|
427
|
+
let pending = null;
|
|
428
|
+
const tokens = /[{}()]|\b(query|mutation|subscription|fragment)\b\s*([A-Za-z_]\w*)?/g;
|
|
429
|
+
for (const [token, keyword, name] of source.matchAll(tokens)) {
|
|
430
|
+
if (token === "(") parens += 1;
|
|
431
|
+
else if (token === ")") parens = Math.max(0, parens - 1);
|
|
432
|
+
else if (parens > 0) continue;
|
|
433
|
+
else if (token === "{") {
|
|
434
|
+
if (depth === 0) {
|
|
435
|
+
if (pending === null) operations.push({ type: "query" });
|
|
436
|
+
pending = null;
|
|
437
|
+
}
|
|
438
|
+
depth += 1;
|
|
439
|
+
} else if (token === "}") {
|
|
440
|
+
depth = Math.max(0, depth - 1);
|
|
441
|
+
} else if (depth === 0 && keyword !== void 0) {
|
|
442
|
+
if (keyword === "fragment") {
|
|
443
|
+
pending = "fragment";
|
|
444
|
+
} else {
|
|
445
|
+
operations.push({ type: keyword, ...name ? { name } : {} });
|
|
446
|
+
pending = "operation";
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const chosen = typeof operationName === "string" ? operations.find((operation) => operation.name === operationName) : operations[0];
|
|
451
|
+
if (chosen === void 0) return void 0;
|
|
452
|
+
return chosen.type === "mutation" ? "write" : "read";
|
|
453
|
+
}
|
|
454
|
+
function requestKind(method, path, tool, body) {
|
|
455
|
+
if (tool && isMcpTransport(path)) return namesAWrite(tool) ? "write" : "read";
|
|
456
|
+
if (path === "/graphql" || path.endsWith("/graphql")) {
|
|
457
|
+
if (READ_METHODS.has(method)) return "read";
|
|
458
|
+
return graphqlOperationKind(body) ?? "read";
|
|
234
459
|
}
|
|
460
|
+
if (isRpcMethod(path)) return namesAWrite(path.slice(1)) ? "write" : "read";
|
|
235
461
|
return READ_METHODS.has(method) ? "read" : "write";
|
|
236
462
|
}
|
|
237
463
|
function tapeRows(events, sessionPath) {
|
|
@@ -248,7 +474,7 @@ function tapeRows(events, sessionPath) {
|
|
|
248
474
|
const event = parsed.data;
|
|
249
475
|
const method = event.method.toUpperCase();
|
|
250
476
|
const path = event.path.startsWith(sessionPath) ? event.path.slice(sessionPath.length) || "/" : event.path;
|
|
251
|
-
const kind = requestKind(method, path, event.tool ?? null);
|
|
477
|
+
const kind = requestKind(method, path, event.tool ?? null, event.request_body);
|
|
252
478
|
let note = null;
|
|
253
479
|
if (event.fidelity === "unsupported" || event.status === 501) {
|
|
254
480
|
note = "not modelled by this twin";
|
|
@@ -278,7 +504,11 @@ function tapeSummary(rows) {
|
|
|
278
504
|
changed_state: rows.filter((row) => row.state_mutation).length,
|
|
279
505
|
writes_not_landed: rows.filter((row) => row.note?.startsWith("write ")).length,
|
|
280
506
|
unsupported: rows.filter((row) => row.note?.startsWith("not modelled")).length,
|
|
281
|
-
|
|
507
|
+
// A row that changed state is counted there and nowhere else, whatever its
|
|
508
|
+
// name suggests: before, a `changed` row whose tool the verb list misread
|
|
509
|
+
// was counted twice, and "4 requests: 2 changed · 1 did not land · 2 reads"
|
|
510
|
+
// summed to five.
|
|
511
|
+
reads: rows.filter((row) => row.kind === "read" && !row.note && !row.state_mutation).length
|
|
282
512
|
};
|
|
283
513
|
}
|
|
284
514
|
function requestLabel(row) {
|
|
@@ -304,7 +534,7 @@ function renderTape(rows, where) {
|
|
|
304
534
|
state: stateLabel(row),
|
|
305
535
|
note: row.note
|
|
306
536
|
}));
|
|
307
|
-
const width = (
|
|
537
|
+
const width = (pick2, min) => Math.max(min, ...cells.map((cell) => pick2(cell).length));
|
|
308
538
|
const w = {
|
|
309
539
|
time: width((c) => c.time, 4),
|
|
310
540
|
request: width((c) => c.request, 7),
|
|
@@ -415,4 +645,4 @@ async function runTwinTapeCommand(nameArg, options) {
|
|
|
415
645
|
}
|
|
416
646
|
}
|
|
417
647
|
|
|
418
|
-
export { pickRecordedTwin, renderTape, requestKind, requestLabel, runTwinTapeCommand, tapeRows, tapeSummary };
|
|
648
|
+
export { STANDALONE_STATUS_PATH, censusState, graphqlOperationKind, mergeStandaloneStatus, pickRecordedTwin, readStandaloneStatusFile, renderTape, requestKind, requestLabel, runTwinTapeCommand, snapshotStandaloneInitialState, standaloneStatusEntries, tapeRows, tapeSummary, updateStandaloneStatusFile, writeStandaloneStatusFile };
|
|
@@ -335,21 +335,45 @@ function expandBrackets(form) {
|
|
|
335
335
|
if (segments.some((segment) => typeof segment === "string" && POLLUTION_KEYS.has(segment))) {
|
|
336
336
|
continue;
|
|
337
337
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
338
|
+
const appended = segments.length > 1 && segments.at(-1) === "";
|
|
339
|
+
if (appended)
|
|
340
|
+
segments.pop();
|
|
341
|
+
const decoded = appended ? Array.isArray(value) ? value : [value] : Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
342
|
+
const walks = segments.indexOf("") >= 0 && !appended && Array.isArray(decoded);
|
|
343
|
+
if (walks) {
|
|
344
|
+
for (const [element, one] of decoded.entries()) {
|
|
345
|
+
assignAt(out, elementPath(segments, element), one);
|
|
344
346
|
}
|
|
345
|
-
|
|
346
|
-
if (cursor[key] === void 0)
|
|
347
|
-
cursor[key] = typeof nextKey === "number" ? [] : {};
|
|
348
|
-
cursor = cursor[key];
|
|
347
|
+
continue;
|
|
349
348
|
}
|
|
349
|
+
assignAt(out, elementPath(segments, 0), decoded);
|
|
350
350
|
}
|
|
351
351
|
return out;
|
|
352
352
|
}
|
|
353
|
+
function elementPath(segments, element) {
|
|
354
|
+
let first = true;
|
|
355
|
+
return segments.map((segment) => {
|
|
356
|
+
if (segment !== "")
|
|
357
|
+
return segment;
|
|
358
|
+
const index = first ? element : 0;
|
|
359
|
+
first = false;
|
|
360
|
+
return index;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function assignAt(out, path, value) {
|
|
364
|
+
let cursor = out;
|
|
365
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
366
|
+
const key = path[index];
|
|
367
|
+
if (index === path.length - 1) {
|
|
368
|
+
cursor[key] = value;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const nextKey = path[index + 1];
|
|
372
|
+
if (cursor[key] === void 0)
|
|
373
|
+
cursor[key] = typeof nextKey === "number" ? [] : {};
|
|
374
|
+
cursor = cursor[key];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
353
377
|
function isPlainObject(value) {
|
|
354
378
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
355
379
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { HostedDiscardRefusedError, HostedOrchError, HostedAuthError, readManifest, normalizeManifestTwins, HostedQuotaError, HostedTrialError } from './chunk-26UAPLHK.js';
|
|
2
|
-
import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, MOUNTED_TWINS, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, agentResponseSchema, isMultiTwinSeedEnvelope, sessionPublicSchema } from './chunk-M7ATJ423.js';
|
|
3
2
|
import { gmailSeedSchema, defaultSeedState } from './chunk-PASFBRK4.js';
|
|
4
3
|
import { linearSeedSchema, defaultSeedState as defaultSeedState$1 } from './chunk-3FZY376K.js';
|
|
5
|
-
import { readSeedFileText, parseSeedFileText, soleTwinOf, twinsNamedBy, seedsForTwins } from './chunk-
|
|
6
|
-
import {
|
|
4
|
+
import { readSeedFileText, parseSeedFileText, soleTwinOf, twinsNamedBy, seedsForTwins } from './chunk-GTLGXFLQ.js';
|
|
5
|
+
import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, MOUNTED_TWINS, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, agentResponseSchema, isMultiTwinSeedEnvelope, sessionPublicSchema } from './chunk-M7ATJ423.js';
|
|
6
|
+
import { isTwinName, TWIN_REGISTRY } from './chunk-LDDR6XRN.js';
|
|
7
7
|
import { seedSchema as seedSchema$1, parseSeed, defaultSeedState as defaultSeedState$2 } from './chunk-NBOQN5VX.js';
|
|
8
8
|
import { seedSchema } from './chunk-YBWG5JK2.js';
|
|
9
9
|
import { toTwinHttpEventRow } from './chunk-TWURH7YM.js';
|
|
@@ -1510,7 +1510,9 @@ async function resolveCredentials(input) {
|
|
|
1510
1510
|
throw new Error(`${path} is not valid JSON.`);
|
|
1511
1511
|
}
|
|
1512
1512
|
if (typeof parsed.api_key !== "string" || parsed.api_key.trim().length === 0) {
|
|
1513
|
-
throw new
|
|
1513
|
+
throw new HostedAuthError(
|
|
1514
|
+
`${path} is missing "api_key". Run \`pome login\` or set POME_API_KEY.`
|
|
1515
|
+
);
|
|
1514
1516
|
}
|
|
1515
1517
|
return {
|
|
1516
1518
|
apiKey: parsed.api_key.trim(),
|
|
@@ -1768,6 +1770,7 @@ async function resolveRunAgentIdentity(input) {
|
|
|
1768
1770
|
}
|
|
1769
1771
|
return { ...base, agentId: resolved.id };
|
|
1770
1772
|
} catch (err) {
|
|
1773
|
+
if (err instanceof HostedAuthError) throw err;
|
|
1771
1774
|
console.error(
|
|
1772
1775
|
`pome: could not resolve agent identity (${err instanceof Error ? err.message : String(err)}); running unattributed.`
|
|
1773
1776
|
);
|