@apilift/cli 1.4.32 → 1.4.33
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.
|
@@ -19973,6 +19973,8 @@ var CaptureStore = class {
|
|
|
19973
19973
|
`);
|
|
19974
19974
|
if (!hadCandidates) this.db.exec(`INSERT INTO capture_candidates (scope_id,seq,capture_id,entry_json) SELECT e.scope_id,g.first_seq,g.capture_id,e.entry_json FROM capture_entries e JOIN (SELECT scope_id,COALESCE(json_extract(entry_json,'$.captureId'),CAST(seq AS TEXT)) AS capture_id,MIN(seq) AS first_seq,MAX(seq) AS last_seq FROM capture_entries GROUP BY scope_id,capture_id) g ON e.scope_id=g.scope_id AND e.seq=g.last_seq`);
|
|
19975
19975
|
const columns = this.db.prepare("PRAGMA table_info(scopes)").all();
|
|
19976
|
+
if (!columns.some((column) => column.name === "owner_session")) this.db.exec("ALTER TABLE scopes ADD COLUMN owner_session TEXT");
|
|
19977
|
+
if (!this.db.prepare("PRAGMA table_info(bridge_status)").all().some((column) => column.name === "capabilities_json")) this.db.exec("ALTER TABLE bridge_status ADD COLUMN capabilities_json TEXT NOT NULL DEFAULT '[]'");
|
|
19976
19978
|
if (!columns.some((column) => column.name === "start_url")) this.db.exec("ALTER TABLE scopes ADD COLUMN start_url TEXT");
|
|
19977
19979
|
if (!columns.some((column) => column.name === "binding_mode")) this.db.exec("ALTER TABLE scopes ADD COLUMN binding_mode TEXT");
|
|
19978
19980
|
if (!columns.some((column) => column.name === "retained_at")) this.db.exec("ALTER TABLE scopes ADD COLUMN retained_at REAL");
|
|
@@ -20016,8 +20018,10 @@ var CaptureStore = class {
|
|
|
20016
20018
|
}
|
|
20017
20019
|
releaseStaleScope(id2) {
|
|
20018
20020
|
const capture = this.db.prepare("SELECT kind,window_json FROM scopes WHERE id=?").get(id2);
|
|
20019
|
-
if (capture?.kind === "capture" &&
|
|
20020
|
-
|
|
20021
|
+
if (capture?.kind === "capture" && this.db.prepare("SELECT 1 FROM capture_candidates WHERE scope_id=? LIMIT 1").get(id2)) {
|
|
20022
|
+
this.db.prepare("UPDATE scopes SET window_json=NULL WHERE id=?").run(id2);
|
|
20023
|
+
this.retainScope(id2);
|
|
20024
|
+
} else this.db.prepare("DELETE FROM scopes WHERE id=?").run(id2);
|
|
20021
20025
|
this.emit();
|
|
20022
20026
|
}
|
|
20023
20027
|
row(id2, freshOwner = false) {
|
|
@@ -20059,11 +20063,12 @@ var CaptureStore = class {
|
|
|
20059
20063
|
this.prune();
|
|
20060
20064
|
return this.transaction(() => {
|
|
20061
20065
|
if (input2.kind !== "inspect") {
|
|
20062
|
-
const held = this.db.prepare("SELECT id FROM scopes WHERE page_origin=? AND retained_at IS NULL AND kind IN ('execute','capture') LIMIT 1").get(input2.pageOrigin);
|
|
20063
|
-
if (held) throw new CaptureError("scope-busy", "This
|
|
20066
|
+
const held = this.db.prepare("SELECT id FROM scopes WHERE page_origin=? AND retained_at IS NULL AND kind IN ('execute','capture') AND (? IS NULL OR tab_selector IS NULL OR tab_selector=?) LIMIT 1").get(input2.pageOrigin, input2.tabSelector ?? null, input2.tabSelector ?? null);
|
|
20067
|
+
if (held) throw new CaptureError("scope-busy", "This tab already has an active execute or capture scope");
|
|
20064
20068
|
}
|
|
20065
20069
|
const now = this.clock(), id2 = randomUUID(), leaseId = input2.kind === "inspect" ? null : randomUUID(), expiresAt = now + scopeTtl[input2.kind];
|
|
20066
20070
|
this.db.prepare("INSERT INTO scopes (id,kind,app_id,context_id,page_origin,tab_selector,start_url,binding_mode,lease_id,owner_pid,owner_started_at,expires_at,activated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)").run(id2, input2.kind, input2.appId, input2.contextId, input2.pageOrigin, input2.tabSelector ?? null, input2.startUrl ?? null, input2.bindingMode ?? null, leaseId, input2.ownerPid, input2.ownerStartedAt, expiresAt, now);
|
|
20071
|
+
if (input2.ownerSession) this.db.prepare("UPDATE scopes SET owner_session=? WHERE id=?").run(input2.ownerSession, id2);
|
|
20067
20072
|
this.emit();
|
|
20068
20073
|
return { kind: input2.kind, id: id2, pageOrigin: input2.pageOrigin, ...leaseId ? { leaseId } : {}, ...input2.tabSelector ? { tabSelector: input2.tabSelector } : {}, ...input2.startUrl ? { startUrl: input2.startUrl } : {}, ...input2.bindingMode ? { bindingMode: input2.bindingMode } : {}, expiresAt: new Date(expiresAt).toISOString(), activatedAt: now };
|
|
20069
20074
|
});
|
|
@@ -20082,8 +20087,8 @@ var CaptureStore = class {
|
|
|
20082
20087
|
}
|
|
20083
20088
|
scopes() {
|
|
20084
20089
|
this.prune();
|
|
20085
|
-
const rows = this.db.prepare("SELECT id,kind,page_origin,tab_selector,start_url,binding_mode,lease_id,expires_at,activated_at,ack_through,window_json FROM scopes WHERE retained_at IS NULL ORDER BY activated_at").all();
|
|
20086
|
-
return rows.map((row) => ({ kind: row.kind, id: String(row.id), pageOrigin: String(row.page_origin), ...row.lease_id ? { leaseId: String(row.lease_id), ackThrough: Number(row.ack_through) } : {}, ...row.tab_selector ? { tabSelector: String(row.tab_selector) } : {}, ...row.start_url ? { startUrl: String(row.start_url) } : {}, ...row.binding_mode ? { bindingMode: row.binding_mode } : {}, expiresAt: new Date(Number(row.expires_at)).toISOString(), activatedAt: Number(row.activated_at), ...row.window_json ? { window: readJson(row.window_json) } : {} }));
|
|
20090
|
+
const rows = this.db.prepare("SELECT id,kind,page_origin,tab_selector,start_url,binding_mode,lease_id,expires_at,activated_at,ack_through,window_json,owner_session FROM scopes WHERE retained_at IS NULL ORDER BY activated_at").all();
|
|
20091
|
+
return rows.map((row) => ({ kind: row.kind, id: String(row.id), pageOrigin: String(row.page_origin), ...row.lease_id ? { leaseId: String(row.lease_id), ackThrough: Number(row.ack_through) } : {}, ...row.tab_selector ? { tabSelector: String(row.tab_selector) } : {}, ...row.start_url ? { startUrl: String(row.start_url) } : {}, ...row.binding_mode ? { bindingMode: row.binding_mode } : {}, expiresAt: new Date(Number(row.expires_at)).toISOString(), activatedAt: Number(row.activated_at), ...row.window_json ? { window: readJson(row.window_json) } : {}, ...row.owner_session ? { ownerSession: String(row.owner_session) } : {} }));
|
|
20087
20092
|
}
|
|
20088
20093
|
candidates(scopeId) {
|
|
20089
20094
|
const row = this.row(scopeId);
|
|
@@ -20129,10 +20134,14 @@ var CaptureStore = class {
|
|
|
20129
20134
|
}
|
|
20130
20135
|
active() {
|
|
20131
20136
|
this.prune();
|
|
20132
|
-
return Boolean(this.db.prepare("SELECT 1 FROM scopes WHERE retained_at IS NULL LIMIT 1").get() || this.db.prepare("SELECT 1 FROM jobs LIMIT 1").get() || this.db.prepare("SELECT 1 FROM scopes s WHERE s.retained_at>? AND (EXISTS (SELECT 1 FROM capture_candidates c WHERE c.scope_id=s.id AND json_extract(c.entry_json,'$.pending')=1) OR EXISTS (SELECT 1 FROM capture_body_refs r LEFT JOIN blobs b ON b.scope_id=r.scope_id AND b.digest=r.digest WHERE r.scope_id=s.id AND COALESCE(b.complete,0)=0)) LIMIT 1").get(this.clock() - 2e4));
|
|
20137
|
+
return Boolean(this.db.prepare("SELECT 1 FROM scopes WHERE retained_at IS NULL LIMIT 1").get() || this.db.prepare("SELECT 1 FROM jobs j JOIN scopes s ON s.id=j.scope_id WHERE s.retained_at IS NULL LIMIT 1").get() || this.db.prepare("SELECT 1 FROM scopes s WHERE s.retained_at>? AND (EXISTS (SELECT 1 FROM capture_candidates c WHERE c.scope_id=s.id AND json_extract(c.entry_json,'$.pending')=1) OR EXISTS (SELECT 1 FROM capture_body_refs r LEFT JOIN blobs b ON b.scope_id=r.scope_id AND b.digest=r.digest WHERE r.scope_id=s.id AND COALESCE(b.complete,0)=0)) LIMIT 1").get(this.clock() - 2e4));
|
|
20133
20138
|
}
|
|
20134
|
-
bridgeReady(version2) {
|
|
20135
|
-
this.db.prepare("INSERT INTO bridge_status(id,version,updated_at) VALUES(1
|
|
20139
|
+
bridgeReady(version2, capabilities = []) {
|
|
20140
|
+
this.db.prepare("INSERT INTO bridge_status(id,version,updated_at,capabilities_json) VALUES(1,?,?,?) ON CONFLICT(id) DO UPDATE SET version=excluded.version,updated_at=excluded.updated_at,capabilities_json=excluded.capabilities_json").run(version2, this.clock(), json3(capabilities));
|
|
20141
|
+
}
|
|
20142
|
+
bridgeCapabilities() {
|
|
20143
|
+
const row = this.db.prepare("SELECT capabilities_json FROM bridge_status WHERE id=1 AND updated_at>?").get(this.clock() - 6e4);
|
|
20144
|
+
return row ? readJson(row.capabilities_json) : [];
|
|
20136
20145
|
}
|
|
20137
20146
|
async changes(after, waitMs) {
|
|
20138
20147
|
if (this.revision !== after || waitMs <= 0) return { revision: this.revision };
|
|
@@ -20172,6 +20181,8 @@ var CaptureStore = class {
|
|
|
20172
20181
|
if (request2.payload.origin !== row.page_origin || new URL(request2.payload.url).origin !== row.page_origin) throw new CaptureError("origin-mismatch", "Claimed tab does not match the scope origin");
|
|
20173
20182
|
const found = this.candidates(scopeId).find((item) => item.selector === request2.payload.tabSelector && item.tabId === request2.payload.tabId);
|
|
20174
20183
|
if (!found || row.tab_selector && row.tab_selector !== found.selector) throw new CaptureError("tab-gone", "The selected tab is no longer available");
|
|
20184
|
+
const held = this.db.prepare("SELECT id FROM scopes WHERE id<>? AND retained_at IS NULL AND claim_tab_id=? AND claim_client_id=? LIMIT 1").get(scopeId, found.tabId, request2.payload.clientId);
|
|
20185
|
+
if (held) throw new CaptureError("scope-busy", "Another live scope owns the selected physical tab");
|
|
20175
20186
|
this.db.prepare("UPDATE scopes SET claim_tab_id=?,claim_selector=?,claim_client_id=? WHERE id=?").run(found.tabId, found.selector, request2.payload.clientId, scopeId);
|
|
20176
20187
|
this.emit();
|
|
20177
20188
|
return { ackThrough: Number(row.ack_through), captureBatchMax: 32 };
|
|
@@ -20180,7 +20191,7 @@ var CaptureStore = class {
|
|
|
20180
20191
|
if (row.kind === "inspect") throw new CaptureError("inspect-only", "Inspect scopes have no claim heartbeat");
|
|
20181
20192
|
if (row.claim_client_id && request2.payload.clientId !== row.claim_client_id) throw new CaptureError("client-mismatch", "Another extension client owns this claim");
|
|
20182
20193
|
if (request2.payload.origin && request2.payload.origin !== row.page_origin) throw new CaptureError("origin-mismatch", "Heartbeat origin does not match the scope");
|
|
20183
|
-
this.db.prepare("UPDATE scopes SET expires_at=? WHERE id=?").run(row.kind === "capture"
|
|
20194
|
+
this.db.prepare("UPDATE scopes SET expires_at=? WHERE id=?").run(row.kind === "capture" ? Math.min(Number(row.activated_at) + scopeTtl.capture, row.window_json ? now + scopeTtl.capture : now + 12e4) : now + scopeTtl.execute, scopeId);
|
|
20184
20195
|
if (request2.payload.lost) this.db.prepare("UPDATE scopes SET claim_tab_id=NULL,claim_selector=NULL WHERE id=?").run(scopeId);
|
|
20185
20196
|
return { ackThrough: Number(row.ack_through), captureBatchMax: 32 };
|
|
20186
20197
|
}
|
|
@@ -20319,13 +20330,13 @@ async function startCaptureServer(options) {
|
|
|
20319
20330
|
return;
|
|
20320
20331
|
}
|
|
20321
20332
|
if (path === "/scopes" && request2.method === "GET") {
|
|
20322
|
-
send(response, 200, { scopes: store.scopes() });
|
|
20333
|
+
send(response, 200, { scopes: store.scopes(), extensionCapabilities: store.bridgeCapabilities() });
|
|
20323
20334
|
return;
|
|
20324
20335
|
}
|
|
20325
20336
|
if (path === "/bridge/ready" && request2.method === "POST") {
|
|
20326
20337
|
const body2 = object2(await readBody(request2));
|
|
20327
|
-
if (typeof body2.version !== "string") throw new CaptureError("invalid-bridge-version", "Native host version
|
|
20328
|
-
store.bridgeReady(body2.version);
|
|
20338
|
+
if (typeof body2.version !== "string" || body2.capabilities !== void 0 && (!Array.isArray(body2.capabilities) || body2.capabilities.some((value) => typeof value !== "string"))) throw new CaptureError("invalid-bridge-version", "Native host version and supported capabilities are required");
|
|
20339
|
+
store.bridgeReady(body2.version, body2.capabilities);
|
|
20329
20340
|
send(response, 200, { ok: true });
|
|
20330
20341
|
return;
|
|
20331
20342
|
}
|
|
@@ -7208,6 +7208,7 @@ var LocalState = class {
|
|
|
7208
7208
|
if (version2.user_version < 4) {
|
|
7209
7209
|
this.db.exec("BEGIN EXCLUSIVE; CREATE TABLE pending_execution_report (id TEXT PRIMARY KEY, report_json TEXT NOT NULL, created_at TEXT NOT NULL); PRAGMA user_version=4; COMMIT");
|
|
7210
7210
|
}
|
|
7211
|
+
this.db.exec("CREATE TABLE IF NOT EXISTS browser_binding (agent_session_id TEXT NOT NULL, app_id TEXT NOT NULL, tab_selector TEXT NOT NULL, PRIMARY KEY(agent_session_id,app_id))");
|
|
7211
7212
|
}
|
|
7212
7213
|
installation() {
|
|
7213
7214
|
const row = this.db.prepare("SELECT id, token, account_id, workspace_id FROM installation LIMIT 1").get();
|
|
@@ -7232,9 +7233,16 @@ var LocalState = class {
|
|
|
7232
7233
|
this.db.prepare("INSERT INTO use_session (agent_session_id, app_id, context_json, credentials_json) VALUES (?, ?, ?, ?) ON CONFLICT(agent_session_id, app_id) DO UPDATE SET context_json=excluded.context_json, credentials_json=excluded.credentials_json").run(value.agentSessionId, value.appId, JSON.stringify(value.context), JSON.stringify(value.credentials));
|
|
7233
7234
|
}
|
|
7234
7235
|
releaseUseSession(agentSessionId, appId) {
|
|
7236
|
+
this.db.prepare("DELETE FROM browser_binding WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
7235
7237
|
this.db.prepare("DELETE FROM use_session WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
7236
7238
|
this.db.prepare("DELETE FROM selected_app WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
7237
7239
|
}
|
|
7240
|
+
browserTab(agentSessionId, appId) {
|
|
7241
|
+
return this.db.prepare("SELECT tab_selector FROM browser_binding WHERE agent_session_id=? AND app_id=?").get(agentSessionId, appId)?.tab_selector;
|
|
7242
|
+
}
|
|
7243
|
+
selectBrowserTab(agentSessionId, appId, tab) {
|
|
7244
|
+
this.db.prepare("INSERT INTO browser_binding VALUES (?,?,?) ON CONFLICT(agent_session_id,app_id) DO UPDATE SET tab_selector=excluded.tab_selector").run(agentSessionId, appId, tab);
|
|
7245
|
+
}
|
|
7238
7246
|
selectedApp(agentSessionId) {
|
|
7239
7247
|
const row = this.db.prepare("SELECT app_id, slug, title FROM selected_app WHERE agent_session_id=?").get(agentSessionId);
|
|
7240
7248
|
return row ? { agentSessionId, appId: row.app_id, slug: row.slug, title: row.title } : null;
|
|
@@ -9211,6 +9211,7 @@ var LocalState = class {
|
|
|
9211
9211
|
if (version2.user_version < 4) {
|
|
9212
9212
|
this.db.exec("BEGIN EXCLUSIVE; CREATE TABLE pending_execution_report (id TEXT PRIMARY KEY, report_json TEXT NOT NULL, created_at TEXT NOT NULL); PRAGMA user_version=4; COMMIT");
|
|
9213
9213
|
}
|
|
9214
|
+
this.db.exec("CREATE TABLE IF NOT EXISTS browser_binding (agent_session_id TEXT NOT NULL, app_id TEXT NOT NULL, tab_selector TEXT NOT NULL, PRIMARY KEY(agent_session_id,app_id))");
|
|
9214
9215
|
}
|
|
9215
9216
|
installation() {
|
|
9216
9217
|
const row = this.db.prepare("SELECT id, token, account_id, workspace_id FROM installation LIMIT 1").get();
|
|
@@ -9235,9 +9236,16 @@ var LocalState = class {
|
|
|
9235
9236
|
this.db.prepare("INSERT INTO use_session (agent_session_id, app_id, context_json, credentials_json) VALUES (?, ?, ?, ?) ON CONFLICT(agent_session_id, app_id) DO UPDATE SET context_json=excluded.context_json, credentials_json=excluded.credentials_json").run(value.agentSessionId, value.appId, JSON.stringify(value.context), JSON.stringify(value.credentials));
|
|
9236
9237
|
}
|
|
9237
9238
|
releaseUseSession(agentSessionId, appId) {
|
|
9239
|
+
this.db.prepare("DELETE FROM browser_binding WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
9238
9240
|
this.db.prepare("DELETE FROM use_session WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
9239
9241
|
this.db.prepare("DELETE FROM selected_app WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
9240
9242
|
}
|
|
9243
|
+
browserTab(agentSessionId, appId) {
|
|
9244
|
+
return this.db.prepare("SELECT tab_selector FROM browser_binding WHERE agent_session_id=? AND app_id=?").get(agentSessionId, appId)?.tab_selector;
|
|
9245
|
+
}
|
|
9246
|
+
selectBrowserTab(agentSessionId, appId, tab) {
|
|
9247
|
+
this.db.prepare("INSERT INTO browser_binding VALUES (?,?,?) ON CONFLICT(agent_session_id,app_id) DO UPDATE SET tab_selector=excluded.tab_selector").run(agentSessionId, appId, tab);
|
|
9248
|
+
}
|
|
9241
9249
|
selectedApp(agentSessionId) {
|
|
9242
9250
|
const row = this.db.prepare("SELECT app_id, slug, title FROM selected_app WHERE agent_session_id=?").get(agentSessionId);
|
|
9243
9251
|
return row ? { agentSessionId, appId: row.app_id, slug: row.slug, title: row.title } : null;
|
|
@@ -9269,7 +9277,7 @@ var LocalState = class {
|
|
|
9269
9277
|
};
|
|
9270
9278
|
|
|
9271
9279
|
// src/native-host/host.ts
|
|
9272
|
-
import { createHash as
|
|
9280
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9273
9281
|
import { readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
9274
9282
|
import { join as join3 } from "node:path";
|
|
9275
9283
|
|
|
@@ -29838,8 +29846,16 @@ function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
|
|
|
29838
29846
|
return createApiClient(baseUrl, transport);
|
|
29839
29847
|
}
|
|
29840
29848
|
|
|
29841
|
-
// src/
|
|
29849
|
+
// src/session.ts
|
|
29842
29850
|
import { createHash } from "node:crypto";
|
|
29851
|
+
function apiSessionId(installationId, session) {
|
|
29852
|
+
if (!session) return void 0;
|
|
29853
|
+
const hex3 = createHash("sha256").update("apilift-api-session/v1\0").update(installationId).update("\0").update(session.id).digest("hex");
|
|
29854
|
+
return `${hex3.slice(0, 8)}-${hex3.slice(8, 12)}-5${hex3.slice(13, 16)}-a${hex3.slice(17, 20)}-${hex3.slice(20, 32)}`;
|
|
29855
|
+
}
|
|
29856
|
+
|
|
29857
|
+
// src/capture-server/client.ts
|
|
29858
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
29843
29859
|
import { chmodSync as chmodSync2, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "node:fs";
|
|
29844
29860
|
import { dirname, join as join2 } from "node:path";
|
|
29845
29861
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
@@ -29868,8 +29884,8 @@ var CaptureServerClient = class {
|
|
|
29868
29884
|
scopes() {
|
|
29869
29885
|
return this.call("GET", "/scopes");
|
|
29870
29886
|
}
|
|
29871
|
-
bridgeReady(version2) {
|
|
29872
|
-
return this.call("POST", "/bridge/ready", { version: version2 }).then(() => {
|
|
29887
|
+
bridgeReady(version2, capabilities = []) {
|
|
29888
|
+
return this.call("POST", "/bridge/ready", { version: version2, capabilities }).then(() => {
|
|
29873
29889
|
});
|
|
29874
29890
|
}
|
|
29875
29891
|
candidates(id2) {
|
|
@@ -29889,7 +29905,7 @@ var CaptureServerClient = class {
|
|
|
29889
29905
|
throw Object.assign(new Error(issue2.error?.message ?? `Capture server HTTP ${response.status}`), { code: issue2.error?.code ?? "capture-server-error" });
|
|
29890
29906
|
}
|
|
29891
29907
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
29892
|
-
if (
|
|
29908
|
+
if (createHash2("sha256").update(bytes).digest("hex") !== digest) throw Object.assign(new Error("Selected body digest does not match stored bytes"), { code: "blob-integrity" });
|
|
29893
29909
|
return bytes;
|
|
29894
29910
|
}
|
|
29895
29911
|
openScope(input2) {
|
|
@@ -29976,7 +29992,7 @@ function installedCaptureVersion(script) {
|
|
|
29976
29992
|
}
|
|
29977
29993
|
|
|
29978
29994
|
// src/discovery/credential-draft.ts
|
|
29979
|
-
import { createHash as
|
|
29995
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
29980
29996
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
29981
29997
|
|
|
29982
29998
|
// ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
@@ -30870,6 +30886,7 @@ function parseDraftEnvelope(source) {
|
|
|
30870
30886
|
if (value.dependencies !== void 0 && !object2(value.dependencies)) issues.push({ path: "/dependencies", message: "Dependencies must be an object" });
|
|
30871
30887
|
if (value.verification !== void 0 && !object2(value.verification)) issues.push({ path: "/verification", message: "Verification settings must be an object" });
|
|
30872
30888
|
if (value.capture !== void 0 && !object2(value.capture)) issues.push({ path: "/capture", message: "Capture settings must be an object" });
|
|
30889
|
+
if (object2(value.capture) && value.capture.tab !== void 0 && (typeof value.capture.tab !== "string" || value.capture.tab.length > 160)) issues.push({ path: "/capture/tab", message: "Use a tab selector returned by browser tabs, or leave it empty to use the session selection" });
|
|
30873
30890
|
if (issues.length) return { issues };
|
|
30874
30891
|
return { envelope: value, issues };
|
|
30875
30892
|
}
|
|
@@ -30914,7 +30931,7 @@ function readCredentialDraft(filePath) {
|
|
|
30914
30931
|
}
|
|
30915
30932
|
const transport = checked.definition;
|
|
30916
30933
|
if (!transport.description.trim()) throw new Error(`Research credential acquisition and edit /definition/description in ${filePath}. Run discover check before secure setup`);
|
|
30917
|
-
return { transport, fingerprint:
|
|
30934
|
+
return { transport, fingerprint: createHash3("sha256").update(canonicalJson(transport)).digest("hex") };
|
|
30918
30935
|
}
|
|
30919
30936
|
|
|
30920
30937
|
// src/native-host/credentials.ts
|
|
@@ -31401,12 +31418,15 @@ var NativeHost = class {
|
|
|
31401
31418
|
state;
|
|
31402
31419
|
analytics;
|
|
31403
31420
|
helloDone = false;
|
|
31421
|
+
extensionCapabilities = [];
|
|
31422
|
+
recordingHeartbeatAt = 0;
|
|
31423
|
+
recordingHeartbeatPending = false;
|
|
31404
31424
|
reportedReady = false;
|
|
31405
31425
|
stopped = false;
|
|
31406
31426
|
client;
|
|
31407
31427
|
registryCache;
|
|
31408
31428
|
installationKey() {
|
|
31409
|
-
return
|
|
31429
|
+
return createHash4("sha256").update(JSON.stringify(this.state.installation())).digest("hex");
|
|
31410
31430
|
}
|
|
31411
31431
|
lanes = { control: [], scope: [], binding: [], metadata: [], blob: [], poll: [] };
|
|
31412
31432
|
admittedAt = /* @__PURE__ */ new WeakMap();
|
|
@@ -31573,6 +31593,7 @@ var NativeHost = class {
|
|
|
31573
31593
|
if (this.options.registry || this.options.fetcher) void this.publishReadiness(registry2);
|
|
31574
31594
|
else this.refreshRegistry();
|
|
31575
31595
|
this.helloDone = true;
|
|
31596
|
+
this.extensionCapabilities = request2.payload.capabilities;
|
|
31576
31597
|
if (!this.restartTimer) {
|
|
31577
31598
|
this.restartTimer = setInterval(() => {
|
|
31578
31599
|
void this.checkRestart();
|
|
@@ -31592,7 +31613,10 @@ var NativeHost = class {
|
|
|
31592
31613
|
if (request2.operation === "scopes") {
|
|
31593
31614
|
const client2 = await this.capture();
|
|
31594
31615
|
const scopes = await client2.scopes();
|
|
31595
|
-
if (
|
|
31616
|
+
if (this.stopped) return;
|
|
31617
|
+
void this.heartbeatRecordingSessions(scopes.scopes).catch(() => {
|
|
31618
|
+
});
|
|
31619
|
+
if (!client2.legacyBridge) await client2.bridgeReady?.(this.options.version, this.extensionCapabilities);
|
|
31596
31620
|
this.analytics.recovered();
|
|
31597
31621
|
if (!this.reportedReady) {
|
|
31598
31622
|
this.reportedReady = true;
|
|
@@ -31606,7 +31630,11 @@ var NativeHost = class {
|
|
|
31606
31630
|
if (registry2 && !this.options.registry && !this.options.fetcher) this.refreshRegistry();
|
|
31607
31631
|
if (!this.state.installation()) this.registryCache = void 0;
|
|
31608
31632
|
if (!this.stopped) this.analytics.updateIdentity(registry2?.identity ?? null);
|
|
31609
|
-
return { scopes: scopes.scopes
|
|
31633
|
+
return { scopes: scopes.scopes.map((scope2) => {
|
|
31634
|
+
const wire = { ...scope2 };
|
|
31635
|
+
delete wire.ownerSession;
|
|
31636
|
+
return wire;
|
|
31637
|
+
}), ...registry2?.identity ? { identity: registry2.identity } : {} };
|
|
31610
31638
|
}
|
|
31611
31639
|
if (request2.operation.startsWith("credentials.")) {
|
|
31612
31640
|
const payload = request2.payload;
|
|
@@ -31674,6 +31702,27 @@ var NativeHost = class {
|
|
|
31674
31702
|
}
|
|
31675
31703
|
}
|
|
31676
31704
|
}
|
|
31705
|
+
async heartbeatRecordingSessions(scopes) {
|
|
31706
|
+
if (this.stopped) return;
|
|
31707
|
+
const installation = this.state.installation();
|
|
31708
|
+
const owners = [...new Set(scopes.filter((scope2) => scope2.kind === "capture" && scope2.window && scope2.ownerSession).map((scope2) => scope2.ownerSession))];
|
|
31709
|
+
if (!installation || !owners.length || this.recordingHeartbeatPending || Date.now() - this.recordingHeartbeatAt < 3e4) return;
|
|
31710
|
+
this.recordingHeartbeatAt = Date.now();
|
|
31711
|
+
this.recordingHeartbeatPending = true;
|
|
31712
|
+
try {
|
|
31713
|
+
const api = createCliApi(this.options.siteUrl, this.options.fetcher ?? fetch, installation.token);
|
|
31714
|
+
for (const owner of owners.slice(0, 100)) {
|
|
31715
|
+
if (this.stopped) break;
|
|
31716
|
+
const agent2 = owner.startsWith("claude:") ? "claude" : "codex";
|
|
31717
|
+
try {
|
|
31718
|
+
await api.call("acquireSession", { params: { id: apiSessionId(installation.id, { id: owner, agent: agent2 }) }, body: { purpose: "discovery" } });
|
|
31719
|
+
} catch {
|
|
31720
|
+
}
|
|
31721
|
+
}
|
|
31722
|
+
} finally {
|
|
31723
|
+
this.recordingHeartbeatPending = false;
|
|
31724
|
+
}
|
|
31725
|
+
}
|
|
31677
31726
|
fatal(error62) {
|
|
31678
31727
|
if (this.helloDone) this.analytics.failure(publicError(error62).code, "unknown", "framing");
|
|
31679
31728
|
process.stderr.write(`${publicError(error62).code}: ${error62.message}
|
|
@@ -31692,7 +31741,7 @@ var NativeHost = class {
|
|
|
31692
31741
|
}
|
|
31693
31742
|
};
|
|
31694
31743
|
function hostFingerprint(script) {
|
|
31695
|
-
return
|
|
31744
|
+
return createHash4("sha256").update(readFileSync4(script)).digest("hex");
|
|
31696
31745
|
}
|
|
31697
31746
|
|
|
31698
31747
|
// src/capture-server/demand.ts
|
package/dist/apilift.js
CHANGED
|
@@ -28571,7 +28571,7 @@ var init_registry = __esm({
|
|
|
28571
28571
|
credentialsAttach: { path: ["credentials", "attach"], min: 3, max: 3 },
|
|
28572
28572
|
transportsShow: { path: ["transports", "show"], min: 1, max: 2 },
|
|
28573
28573
|
browserCheck: { path: ["browser", "check"], min: 1, max: 1 },
|
|
28574
|
-
browserTabs: { path: ["browser", "tabs"], min: 1, max: 1 },
|
|
28574
|
+
browserTabs: { path: ["browser", "tabs"], min: 1, max: 1, values: ["select"] },
|
|
28575
28575
|
contextSet: { path: ["context", "set"], min: 2, max: 2, values: ["app"] },
|
|
28576
28576
|
contextShow: { path: ["context", "show"], min: 0, max: 0, values: ["app"] },
|
|
28577
28577
|
contextUnset: { path: ["context", "unset"], min: 1, max: 1, values: ["app"] },
|
|
@@ -30228,6 +30228,7 @@ function parseDraftEnvelope(source) {
|
|
|
30228
30228
|
if (value.dependencies !== void 0 && !object3(value.dependencies)) issues.push({ path: "/dependencies", message: "Dependencies must be an object" });
|
|
30229
30229
|
if (value.verification !== void 0 && !object3(value.verification)) issues.push({ path: "/verification", message: "Verification settings must be an object" });
|
|
30230
30230
|
if (value.capture !== void 0 && !object3(value.capture)) issues.push({ path: "/capture", message: "Capture settings must be an object" });
|
|
30231
|
+
if (object3(value.capture) && value.capture.tab !== void 0 && (typeof value.capture.tab !== "string" || value.capture.tab.length > 160)) issues.push({ path: "/capture/tab", message: "Use a tab selector returned by browser tabs, or leave it empty to use the session selection" });
|
|
30231
30232
|
if (issues.length) return { issues };
|
|
30232
30233
|
return { envelope: value, issues };
|
|
30233
30234
|
}
|
|
@@ -30267,7 +30268,7 @@ function draftTemplate(kind) {
|
|
|
30267
30268
|
app: { creation: { visibility: "private" }, definition: { title: "", description: "", origin: "", category: "other" } },
|
|
30268
30269
|
entity: { definition: { title: "", description: "" } },
|
|
30269
30270
|
action: { definition: { title: "", description: "", effect: "read", inputs: { type: "object", properties: {}, additionalProperties: false }, request: { method: "GET", url: "" }, response: { schema: { type: "object", properties: {} } } }, dependencies: { transport: "" }, verification: { inputs: {}, review: "pending", notes: "" }, capture: { tab: "", authField: "", authScope: "" } },
|
|
30270
|
-
transport: { definition: { title: "", description: "", mode: "direct", baseUrl: "", allowedOrigins: [], credentials: [], probes: {} }, dependencies: {}, verification: { inputs: {}, probeOrigin: "", review: "pending", notes: "" } },
|
|
30271
|
+
transport: { definition: { title: "", description: "", mode: "direct", baseUrl: "", allowedOrigins: [], credentials: [], probes: {} }, dependencies: {}, verification: { inputs: {}, probeOrigin: "", review: "pending", notes: "" }, capture: { tab: "" } },
|
|
30271
30272
|
"browser-page": { definition: { title: "", description: "", pageOrigin: "", startUrl: "" } }
|
|
30272
30273
|
};
|
|
30273
30274
|
const probeGuidance = kind === "transport" ? [
|
|
@@ -30284,8 +30285,9 @@ function draftTemplate(kind) {
|
|
|
30284
30285
|
"// A locator hypothesis is not proof. Never copy a token value; verify the browser connection with discover run before accepting it.",
|
|
30285
30286
|
""
|
|
30286
30287
|
].join("\n") : "";
|
|
30288
|
+
const tabGuidance = ["action", "transport"].includes(kind) ? "// capture.tab is a selector returned by browser tabs; it selects the same signed-in Chrome tab for capture and discover run, including connection probes. Leave other sessions' tabs untouched.\n" : "";
|
|
30287
30289
|
const captureGuidance = kind === "action" ? '// Request JSON/query uses typed {"$input":"name"} bindings; URL segments use {input.name}. Resolve every {unresolved.N} marker from retained evidence before running.\n// Mutation proof compares a readback path with exactly one input, primary result path or internal JSON value: equals: [{"path":"/ready","value":true}]. Public literals require semantic review; fixed proof expectations are not caller flags.\n// After selecting a captured request, discover capture auth runs a bounded default search without authField or authScope. Set either only to narrow an observed field or location; browser-managed cookies need no locator.\n' : "";
|
|
30288
|
-
return `${head}${probeGuidance}${captureGuidance}${JSON.stringify(templates[kind], null, 2)}
|
|
30290
|
+
return `${head}${probeGuidance}${tabGuidance}${captureGuidance}${JSON.stringify(templates[kind], null, 2)}
|
|
30289
30291
|
`;
|
|
30290
30292
|
}
|
|
30291
30293
|
var browserLocatorExample, names, object3, pointer;
|
|
@@ -31282,8 +31284,8 @@ var init_client2 = __esm({
|
|
|
31282
31284
|
scopes() {
|
|
31283
31285
|
return this.call("GET", "/scopes");
|
|
31284
31286
|
}
|
|
31285
|
-
bridgeReady(version2) {
|
|
31286
|
-
return this.call("POST", "/bridge/ready", { version: version2 }).then(() => {
|
|
31287
|
+
bridgeReady(version2, capabilities = []) {
|
|
31288
|
+
return this.call("POST", "/bridge/ready", { version: version2, capabilities }).then(() => {
|
|
31287
31289
|
});
|
|
31288
31290
|
}
|
|
31289
31291
|
candidates(id2) {
|
|
@@ -31383,6 +31385,17 @@ var init_demand = __esm({
|
|
|
31383
31385
|
// src/commands/browser.ts
|
|
31384
31386
|
import { realpathSync } from "node:fs";
|
|
31385
31387
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
31388
|
+
async function checkTabOwnership(client, origin, tab, app2, prefix) {
|
|
31389
|
+
const state = await client.scopes();
|
|
31390
|
+
const active2 = state.scopes.filter((scope2) => scope2.pageOrigin === origin && scope2.kind !== "inspect");
|
|
31391
|
+
const held = active2.find((scope2) => !scope2.tabSelector || scope2.tabSelector === tab);
|
|
31392
|
+
if (held) {
|
|
31393
|
+
const ownerLabel = held.ownerSession?.split(":label:")[1];
|
|
31394
|
+
const settlement = held.kind === "capture" ? ownerLabel ? `Ask the owner to run apilift --session ${ownerLabel} discover capture stop or apilift --session ${ownerLabel} discover capture cancel` : "Ask the owning session to stop or cancel its recording" : "Wait for the owning command to finish or its lease to expire";
|
|
31395
|
+
throw new BrowserControlError(`The selected tab has a live ${held.kind} lease${held.ownerSession ? ` owned by ${held.ownerSession}` : ""}, started ${new Date(held.activatedAt).toISOString()}, expires ${held.expiresAt}. ${settlement}; leave its tab open. For independent work, open another signed-in Chrome tab, start a distinct worker with apilift --session <new-worker-label> apps update ${app2}, list tabs with apilift --session <new-worker-label> browser tabs ${app2}, then bind a returned selector with apilift --session <new-worker-label> browser tabs ${app2} --select <returned-tab-id>. Capture and execution use that worker's selected tab`, "tab-owned");
|
|
31396
|
+
}
|
|
31397
|
+
if (active2.length && !state.extensionCapabilities?.includes("tab-scopes-v1")) throw new BrowserControlError(`The browser bridge has not advertised independent-tab support. Update the CLI with apilift update and Apilift Companion in Chrome. Leave the other session's tab open; after its command finishes, retry ${prefix} browser check ${app2} and ${prefix} discover run`, "extension-update-required");
|
|
31398
|
+
}
|
|
31386
31399
|
async function browserContext(api, appId) {
|
|
31387
31400
|
const app2 = await api.call("getApp", { params: { app: appId } });
|
|
31388
31401
|
const transports = app2.transports;
|
|
@@ -31427,7 +31440,7 @@ async function inspectTabs(client, appId, contextId, pageOrigin) {
|
|
|
31427
31440
|
await client.closeScope(scope2.id);
|
|
31428
31441
|
}
|
|
31429
31442
|
}
|
|
31430
|
-
async function handleBrowser(invocation, api,
|
|
31443
|
+
async function handleBrowser(invocation, api, state, session, root) {
|
|
31431
31444
|
const appRef = invocation.positionals[0];
|
|
31432
31445
|
if (!session) return fail(`A stable agent identity is required for browser work. Use --session <worker-label>. No browser scope was opened.
|
|
31433
31446
|
`, 2);
|
|
@@ -31436,8 +31449,10 @@ async function handleBrowser(invocation, api, _state, session, root) {
|
|
|
31436
31449
|
if (!context) return fail(`browser-page-unselected \u2014 ${app2.title} has no unambiguous published browser page. Publish one page or a browser connection that selects its page before browser work.
|
|
31437
31450
|
`);
|
|
31438
31451
|
let candidates;
|
|
31452
|
+
let client;
|
|
31439
31453
|
try {
|
|
31440
|
-
|
|
31454
|
+
client = await captureClient(root);
|
|
31455
|
+
candidates = await inspectTabs(client, app2.id, context.name, context.pageOrigin);
|
|
31441
31456
|
} catch (error62) {
|
|
31442
31457
|
return fail(`extension-unavailable \u2014 ${error62 instanceof Error ? error62.message : "Chrome did not respond"}. Check native host and extension setup with ${command(invocation, "doctor")}.
|
|
31443
31458
|
`);
|
|
@@ -31446,11 +31461,23 @@ async function handleBrowser(invocation, api, _state, session, root) {
|
|
|
31446
31461
|
If the tab is already open, recheck the browser connection: ${command(invocation, "setup continue")}
|
|
31447
31462
|
No app request was sent. Agent browser-tool control and sign-in remain unverified.
|
|
31448
31463
|
`);
|
|
31449
|
-
if (invocation.key === "browserTabs")
|
|
31464
|
+
if (invocation.key === "browserTabs") {
|
|
31465
|
+
const selected = invocation.options.select;
|
|
31466
|
+
if (selected) {
|
|
31467
|
+
if (!candidates.some((candidate2) => candidate2.selector === selected)) return fail(`The selected tab was not reported. List current tabs: ${command(invocation, `browser tabs ${app2.slug}`)}. No binding changed.
|
|
31468
|
+
`, 2);
|
|
31469
|
+
state.selectBrowserTab(session.id, app2.id, selected);
|
|
31470
|
+
return ok(`Selected ${selected} for ${app2.title} in this agent session. Capture, connection probes and published action execution use this tab. An explicit capture.tab in a draft overrides this choice. No recording started and no app request was sent.
|
|
31471
|
+
`);
|
|
31472
|
+
}
|
|
31473
|
+
const scopes = (await client.scopes()).scopes.filter((scope2) => scope2.pageOrigin === context.pageOrigin && scope2.kind !== "inspect");
|
|
31474
|
+
return ok(`Tabs reported by the extension:
|
|
31450
31475
|
${candidates.map((item) => ` ${item.selector} \u2014 ${item.title}${item.active ? " (active)" : ""}`).join("\n")}
|
|
31451
31476
|
|
|
31452
|
-
|
|
31453
|
-
`);
|
|
31477
|
+
Select the intended returned tab for capture and execution in this session: ${command(invocation, `browser tabs ${app2.slug} --select <returned-tab-id>`)}. A draft's capture.tab overrides the session choice. Do not infer the correct tab from an example ID.
|
|
31478
|
+
${scopes.map((scope2) => `Active ${scope2.kind} lease: ${scope2.tabSelector ?? "unselected tab"}; owner ${scope2.ownerSession ?? "not recorded"}; started ${new Date(scope2.activatedAt).toISOString()}; expires ${scope2.expiresAt}. Ask its owning session to stop/cancel before reusing that tab.
|
|
31479
|
+
`).join("")}`);
|
|
31480
|
+
}
|
|
31454
31481
|
if (candidates.length > 1) return ok(`Several ${app2.title} tabs match. No tab was selected and recording did not start.
|
|
31455
31482
|
List them: ${command(invocation, `browser tabs ${app2.slug}`)}
|
|
31456
31483
|
Choose the intended reported selector in the current draft.
|
|
@@ -31459,7 +31486,7 @@ Choose the intended reported selector in the current draft.
|
|
|
31459
31486
|
Agent browser-tool control and ${app2.title} sign-in are not established by this check. Use your browser tools to inspect the actual page before observing an operation.
|
|
31460
31487
|
`);
|
|
31461
31488
|
}
|
|
31462
|
-
var ok, fail, command;
|
|
31489
|
+
var ok, fail, command, BrowserControlError;
|
|
31463
31490
|
var init_browser = __esm({
|
|
31464
31491
|
"src/commands/browser.ts"() {
|
|
31465
31492
|
"use strict";
|
|
@@ -31469,6 +31496,13 @@ var init_browser = __esm({
|
|
|
31469
31496
|
ok = (stdout) => ({ stdout, stderr: "", exitCode: 0 });
|
|
31470
31497
|
fail = (stderr, exitCode = 5) => ({ stdout: "", stderr, exitCode });
|
|
31471
31498
|
command = (invocation, text2) => `apilift${invocation.sessionLabel ? ` --session ${invocation.sessionLabel}` : ""} ${text2}`;
|
|
31499
|
+
BrowserControlError = class extends Error {
|
|
31500
|
+
constructor(message2, code, dispatched = false) {
|
|
31501
|
+
super(message2);
|
|
31502
|
+
this.code = code;
|
|
31503
|
+
this.dispatched = dispatched;
|
|
31504
|
+
}
|
|
31505
|
+
};
|
|
31472
31506
|
}
|
|
31473
31507
|
});
|
|
31474
31508
|
|
|
@@ -31512,6 +31546,7 @@ var init_state = __esm({
|
|
|
31512
31546
|
if (version2.user_version < 4) {
|
|
31513
31547
|
this.db.exec("BEGIN EXCLUSIVE; CREATE TABLE pending_execution_report (id TEXT PRIMARY KEY, report_json TEXT NOT NULL, created_at TEXT NOT NULL); PRAGMA user_version=4; COMMIT");
|
|
31514
31548
|
}
|
|
31549
|
+
this.db.exec("CREATE TABLE IF NOT EXISTS browser_binding (agent_session_id TEXT NOT NULL, app_id TEXT NOT NULL, tab_selector TEXT NOT NULL, PRIMARY KEY(agent_session_id,app_id))");
|
|
31515
31550
|
}
|
|
31516
31551
|
installation() {
|
|
31517
31552
|
const row = this.db.prepare("SELECT id, token, account_id, workspace_id FROM installation LIMIT 1").get();
|
|
@@ -31536,9 +31571,16 @@ var init_state = __esm({
|
|
|
31536
31571
|
this.db.prepare("INSERT INTO use_session (agent_session_id, app_id, context_json, credentials_json) VALUES (?, ?, ?, ?) ON CONFLICT(agent_session_id, app_id) DO UPDATE SET context_json=excluded.context_json, credentials_json=excluded.credentials_json").run(value.agentSessionId, value.appId, JSON.stringify(value.context), JSON.stringify(value.credentials));
|
|
31537
31572
|
}
|
|
31538
31573
|
releaseUseSession(agentSessionId, appId) {
|
|
31574
|
+
this.db.prepare("DELETE FROM browser_binding WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
31539
31575
|
this.db.prepare("DELETE FROM use_session WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
31540
31576
|
this.db.prepare("DELETE FROM selected_app WHERE agent_session_id=? AND app_id=?").run(agentSessionId, appId);
|
|
31541
31577
|
}
|
|
31578
|
+
browserTab(agentSessionId, appId) {
|
|
31579
|
+
return this.db.prepare("SELECT tab_selector FROM browser_binding WHERE agent_session_id=? AND app_id=?").get(agentSessionId, appId)?.tab_selector;
|
|
31580
|
+
}
|
|
31581
|
+
selectBrowserTab(agentSessionId, appId, tab) {
|
|
31582
|
+
this.db.prepare("INSERT INTO browser_binding VALUES (?,?,?) ON CONFLICT(agent_session_id,app_id) DO UPDATE SET tab_selector=excluded.tab_selector").run(agentSessionId, appId, tab);
|
|
31583
|
+
}
|
|
31542
31584
|
selectedApp(agentSessionId) {
|
|
31543
31585
|
const row = this.db.prepare("SELECT app_id, slug, title FROM selected_app WHERE agent_session_id=?").get(agentSessionId);
|
|
31544
31586
|
return row ? { agentSessionId, appId: row.app_id, slug: row.slug, title: row.title } : null;
|
|
@@ -32058,7 +32100,7 @@ async function send(fetcher, prepared, allowNonJsonProbe = false) {
|
|
|
32058
32100
|
const body = await responseBody(response, allowNonJsonProbe);
|
|
32059
32101
|
return { response, body };
|
|
32060
32102
|
}
|
|
32061
|
-
async function sendBrowser(root, appId, contextName, pageOrigin, effect2, transportName, transport, prepared, dispatched, credentialId, credentialDraftId, allowNonJsonProbe = false, dispatchBefore) {
|
|
32103
|
+
async function sendBrowser(root, appId, contextName, pageOrigin, effect2, transportName, transport, prepared, dispatched, credentialId, credentialDraftId, allowNonJsonProbe = false, dispatchBefore, selection = {}) {
|
|
32062
32104
|
const client = await captureClient(root);
|
|
32063
32105
|
const inspect = await client.openScope({ kind: "inspect", appId, contextId: contextName, pageOrigin, ownerPid: process.pid, ownerStartedAt: Date.now() - process.uptime() * 1e3 });
|
|
32064
32106
|
let candidates = [];
|
|
@@ -32072,8 +32114,11 @@ async function sendBrowser(root, appId, contextName, pageOrigin, effect2, transp
|
|
|
32072
32114
|
} finally {
|
|
32073
32115
|
await client.closeScope(inspect.id);
|
|
32074
32116
|
}
|
|
32075
|
-
|
|
32076
|
-
const
|
|
32117
|
+
const app2 = selection.appSlug ?? appId, prefix = selection.prefix ?? "apilift";
|
|
32118
|
+
const tab = selection.tab ? candidates.find((candidate2) => candidate2.selector === selection.tab) : candidates.length === 1 ? candidates[0] : void 0;
|
|
32119
|
+
if (!tab) throw new BrowserControlError(selection.tab ? `Configured capture.tab is not present. Inspect ${prefix} browser tabs ${app2} and select a reported tab in the current draft` : candidates.length ? `Several matching Chrome tabs were reported. Use ${prefix} browser tabs ${app2} and set capture.tab in the current draft for both capture and execution; leave other sessions' tabs open` : "No matching Chrome tab was reported within 10 seconds", "tab-unselected");
|
|
32120
|
+
await checkTabOwnership(client, pageOrigin, tab.selector, app2, prefix);
|
|
32121
|
+
const scope2 = await client.openScope({ kind: "execute", appId, contextId: contextName, pageOrigin, tabSelector: tab.selector, ownerPid: process.pid, ownerStartedAt: Date.now() - process.uptime() * 1e3, ownerSession: selection.ownerSession });
|
|
32077
32122
|
const id2 = randomUUID6();
|
|
32078
32123
|
try {
|
|
32079
32124
|
const headers = Object.fromEntries(new Headers(prepared.init.headers).entries());
|
|
@@ -32088,7 +32133,17 @@ async function sendBrowser(root, appId, contextName, pageOrigin, effect2, transp
|
|
|
32088
32133
|
if (held.status === "delivered") {
|
|
32089
32134
|
await client.acknowledgeJob(id2);
|
|
32090
32135
|
const value = held.result;
|
|
32091
|
-
if (value.transportError)
|
|
32136
|
+
if (value.transportError) {
|
|
32137
|
+
if (value.executionState === "not-dispatched" && value.mutationDispatched !== true) {
|
|
32138
|
+
let message2 = value.transportError.replace(/[.\s]+$/u, "");
|
|
32139
|
+
if (message2.includes("still recording")) {
|
|
32140
|
+
const peers = (await client.scopes()).scopes.filter((peer) => peer.kind !== "inspect" && peer.id !== scope2.id);
|
|
32141
|
+
message2 = peers.length ? `Chrome reports a recording on this tab. Inspect ${prefix} browser tabs ${app2}; ask the owning session to stop/cancel, or bind another signed-in tab with capture.tab` : `Chrome retained a stale recording after its recorder lease ended. No live recorder owns it. Open another signed-in Chrome tab, list it with ${prefix} browser tabs ${app2}, bind its returned ID with ${prefix} browser tabs ${app2} --select <returned-tab-id>, then retry ${prefix} discover run. To recover the original tab, if no other agent is using Companion, turn Apilift Companion off and on in Chrome's Extensions page`;
|
|
32142
|
+
}
|
|
32143
|
+
throw new BrowserControlError(message2, value.code ?? "browser-not-dispatched");
|
|
32144
|
+
}
|
|
32145
|
+
throw new Error(value.transportError);
|
|
32146
|
+
}
|
|
32092
32147
|
if (!Number.isInteger(value.status) || value.bodyState && value.bodyState !== "complete") throw new Error("Chrome did not return complete response evidence");
|
|
32093
32148
|
const body = [204, 205, 304].includes(value.status) ? null : value.body ?? null;
|
|
32094
32149
|
const response = new Response(body, { status: value.status });
|
|
@@ -32188,7 +32243,18 @@ Inspect current action documentation: ${prefix(`actions show ${appName} ${action
|
|
|
32188
32243
|
dispatchedAt ??= (/* @__PURE__ */ new Date()).toISOString();
|
|
32189
32244
|
};
|
|
32190
32245
|
const sendRequest = async (request2, effect2) => {
|
|
32191
|
-
if (transport.mode === "browser")
|
|
32246
|
+
if (transport.mode === "browser") {
|
|
32247
|
+
const prior = dispatchedAt;
|
|
32248
|
+
try {
|
|
32249
|
+
return await sendBrowser(root, bundle2.app.id, transport.browserContext, browserPage.pageOrigin, effect2, action.transport, transport, request2, markDispatched, selectedCredential, void 0, false, bundle2.receipt?.dispatchBefore, { tab: state.browserTab(session.id, bundle2.app.id), appSlug: bundle2.app.slug, prefix: prefix("").trim(), ownerSession: session.id });
|
|
32250
|
+
} catch (error62) {
|
|
32251
|
+
if (error62 instanceof BrowserControlError && !error62.dispatched) {
|
|
32252
|
+
if (prior) throw new BrowserControlError("The mutation was already dispatched; its browser readback could not run. Inspect app state instead of rerunning the mutation", error62.code, true);
|
|
32253
|
+
dispatchedAt = void 0;
|
|
32254
|
+
}
|
|
32255
|
+
throw error62;
|
|
32256
|
+
}
|
|
32257
|
+
}
|
|
32192
32258
|
if (selectedCredential) {
|
|
32193
32259
|
const store = new CredentialStore(root, new KeyringVault());
|
|
32194
32260
|
try {
|
|
@@ -32340,10 +32406,19 @@ async function executeDraftVerification(input2) {
|
|
|
32340
32406
|
}
|
|
32341
32407
|
}
|
|
32342
32408
|
if (transport.mode === "browser") {
|
|
32343
|
-
|
|
32344
|
-
|
|
32345
|
-
|
|
32346
|
-
|
|
32409
|
+
const prior = dispatched;
|
|
32410
|
+
try {
|
|
32411
|
+
return await sendBrowser(input2.root, input2.appId, transport.browserContext, browserPage.pageOrigin, effect2, input2.kind === "action" && action && "transport" in action ? action.transport : input2.name, transport, request2, () => {
|
|
32412
|
+
if (!dispatched) input2.onDispatch?.();
|
|
32413
|
+
dispatched = true;
|
|
32414
|
+
}, credential2, credentialDraftId, allowNonJsonProbe, void 0, { tab: input2.browserTab || input2.state.browserTab(input2.session.id, input2.appId), appSlug: input2.appSlug, prefix: input2.commandPrefix, ownerSession: input2.session.id });
|
|
32415
|
+
} catch (error62) {
|
|
32416
|
+
if (error62 instanceof BrowserControlError && !error62.dispatched) {
|
|
32417
|
+
if (prior) throw new BrowserControlError("The mutation was already dispatched; its browser readback could not run. Inspect app state instead of rerunning the mutation", error62.code, true);
|
|
32418
|
+
dispatched = false;
|
|
32419
|
+
}
|
|
32420
|
+
throw error62;
|
|
32421
|
+
}
|
|
32347
32422
|
}
|
|
32348
32423
|
if (credential2) {
|
|
32349
32424
|
const store = new CredentialStore(input2.root, new KeyringVault());
|
|
@@ -32366,7 +32441,7 @@ async function executeDraftVerification(input2) {
|
|
|
32366
32441
|
};
|
|
32367
32442
|
try {
|
|
32368
32443
|
const primary = await sendConfigured(prepared, input2.kind === "action" ? action.effect : "read");
|
|
32369
|
-
if (!primary.response.ok) return { ok: false, message: `The app returned HTTP ${primary.response.status}. Inspect its state before retrying a mutation`, exitCode: 4, dispatched };
|
|
32444
|
+
if (!primary.response.ok) return { ok: false, message: `The app returned HTTP ${primary.response.status}${input2.kind === "transport" || action && "effect" in action && action.effect === "read" ? "" : ". Inspect its state before retrying a mutation"}`, exitCode: 4, dispatched };
|
|
32370
32445
|
if (input2.kind === "action" && transport.mode === "direct" && dispatchStarted) input2.onDirectBaseline?.(Math.max(0, performance.now() - dispatchStarted), JSON.stringify({ method: prepared.init.method, url: prepared.url.toString(), body: prepared.init.body ?? null }), JSON.stringify(primary.body), dispatchAt, (/* @__PURE__ */ new Date()).toISOString());
|
|
32371
32446
|
if (input2.kind === "transport") {
|
|
32372
32447
|
const selected = probe.response.path !== void 0 ? pointer2(primary.body, probe.response.path) : primary.body;
|
|
@@ -32383,7 +32458,7 @@ async function executeDraftVerification(input2) {
|
|
|
32383
32458
|
}
|
|
32384
32459
|
return { ok: true, output: value, proof: "not-applicable", dispatched: true };
|
|
32385
32460
|
} catch (error62) {
|
|
32386
|
-
return { ok: false, message: `${error62 instanceof Error ? error62.message : "The test did not return"}. ${dispatched ? "The request may have reached the app; inspect its state before another mutation" : "No app request was sent"}`, exitCode: dispatched || ["extension-unavailable", "capture-server-unavailable", "native-host-upgrade-required"].includes(error62?.code ?? "") ? 5 : 2, dispatched };
|
|
32461
|
+
return { ok: false, message: `${(error62 instanceof Error ? error62.message : "The test did not return").replace(/[.\s]+$/u, "")}. ${dispatched ? input2.kind === "transport" || action && "effect" in action && action.effect === "read" ? "The read did not return complete evidence" : "The request may have reached the app; inspect its state before another mutation" : "No app request was sent"}`, exitCode: dispatched || error62 instanceof BrowserControlError || ["extension-unavailable", "capture-server-unavailable", "native-host-upgrade-required"].includes(error62?.code ?? "") ? 5 : 2, dispatched };
|
|
32387
32462
|
}
|
|
32388
32463
|
}
|
|
32389
32464
|
var import__3, import_ajv_formats, ajv3, result, fail2, ResponseContractFailure, omit2;
|
|
@@ -32947,7 +33022,9 @@ Attach only after they identify it: ${command3(invocation, "credentials attach <
|
|
|
32947
33022
|
${item.credentials.length ? `Required saved credential field${item.credentials.length === 1 ? "" : "s"}: ${item.credentials.map((field) => field.name).join(", ")}.` : "No stored credential fields are required."}
|
|
32948
33023
|
${current2?.credentials[item.name] ? `A saved entry is selected for this agent's session.` : "No saved entry is selected for this agent's session."}
|
|
32949
33024
|
${item.instructions || "Acquisition instructions are missing from this connection."}
|
|
32950
|
-
${item.mode === "direct" && item.credentials.length ? "A successful connection probe does not verify how another user obtains a key. Compare these published instructions with official key-issuance and permission steps; do not invent missing steps. Secure setup saves an existing key; it does not issue one.\n" : ""}${item.
|
|
33025
|
+
${item.mode === "direct" && item.credentials.length ? "A successful connection probe does not verify how another user obtains a key. Compare these published instructions with official key-issuance and permission steps; do not invent missing steps. Secure setup saves an existing key; it does not issue one.\n" : ""}${item.mode === "browser" ? `Select a signed-in tab for this session: ${command3(invocation, `browser tabs ${selectedApp.slug}`)}, then repeat it with --select <returned-tab-id>.
|
|
33026
|
+
To verify this published connection without recording, open ${command3(invocation, `apps update ${selectedApp.slug}`)}, then ${command3(invocation, `discover draft transport ${item.name}`)}. In the returned file, set verification.probeOrigin to a declared definition.probes origin and verification.inputs to the read probe's required values. Run ${command3(invocation, "discover check")}, then ${command3(invocation, "discover run")}.
|
|
33027
|
+
` : ""}${item.credentials.length ? `Inspect saved names: ${command3(invocation, `credentials list ${selectedApp.slug} ${item.name}`)}
|
|
32951
33028
|
Start secure setup: ${command3(invocation, `credentials add ${selectedApp.slug} ${item.name}`)}` : ""}`).join("\n\n") + `
|
|
32952
33029
|
|
|
32953
33030
|
Search actions: ${command3(invocation, `actions search ${selectedApp.slug} "<keyword>"`)}
|
|
@@ -33987,7 +34064,7 @@ Abandon removal: ${command4(invocation, "discover discard")}
|
|
|
33987
34064
|
return ok4(`Editing ${current.title} ${label(draft.kind)} ${draft.name}.
|
|
33988
34065
|
File: ${draft.filePath}
|
|
33989
34066
|
${evidence ? `Latest saved execution: ${evidence.resultPath}
|
|
33990
|
-
` : "No execution evidence is saved for this draft.\n"}${capture?.scope_id ? `Recording phase: ${capture.phase}. ${capture.phase === "closed" ? `Temporary auth evidence is available until run, publish, defer or close releases it automatically.` : `Do not repeat the app operation. Confirm the boundary with ${command4(invocation, "discover capture stop")} or ${command4(invocation, "discover capture cancel")}.`}
|
|
34067
|
+
` : "No execution evidence is saved for this draft.\n"}${capture?.phase === "lost" ? "Recorder lease ended; admission is closed and the stop boundary is unconfirmed. Inspect app state before another mutation. Retained evidence remains available.\n" : ""}${capture?.scope_id ? `Recording phase: ${capture.phase}. ${capture.phase === "closed" ? `Temporary auth evidence is available until run, publish, defer or close releases it automatically.` : `Do not repeat the app operation. Confirm the boundary with ${command4(invocation, "discover capture stop")} or ${command4(invocation, "discover capture cancel")}.`}
|
|
33991
34068
|
` : ""}${store.publication(draft.id) ? `A publication request may have been sent. Reconcile it with ${command4(invocation, "discover publish")} before changing or discarding this draft.
|
|
33992
34069
|
` : ""}
|
|
33993
34070
|
Check: ${command4(invocation, "discover check")}
|
|
@@ -34031,6 +34108,8 @@ Start again: ${command4(invocation, `discover ${draft.intent === "remove" ? "rem
|
|
|
34031
34108
|
`);
|
|
34032
34109
|
if (captureRow(state, store.selected(session.id)?.id ?? "")?.scope_id) return fail5(`A recording is active. Stop it with ${command4(invocation, "discover capture stop")} or cancel it with ${command4(invocation, "discover capture cancel")} before closing.
|
|
34033
34110
|
`);
|
|
34111
|
+
const installation = state.installation();
|
|
34112
|
+
if (installation) await api.call("releaseSession", { params: { id: apiSessionId(installation.id, session) } });
|
|
34034
34113
|
store.close(session.id);
|
|
34035
34114
|
return ok4(`Closed discovery for ${current.title}. Published actions remain usable.
|
|
34036
34115
|
To continue later: ${command4(invocation, `apps update ${current.appSlug}`)}
|
|
@@ -34299,6 +34378,8 @@ async function verifyDraft(invocation, api, state, store, session, fetcher, root
|
|
|
34299
34378
|
transport = related.definition;
|
|
34300
34379
|
}
|
|
34301
34380
|
if (!transport) return fail5("The configured connection is missing. No app request was sent.\n");
|
|
34381
|
+
const installation = state.installation();
|
|
34382
|
+
if (installation) await api.call("acquireSession", { params: { id: apiSessionId(installation.id, session) }, body: { purpose: "discovery", appId: draft.appId } });
|
|
34302
34383
|
const write = Boolean(action && "effect" in action && action.effect !== "read");
|
|
34303
34384
|
const previous = store.attempt(draft.id);
|
|
34304
34385
|
if (write && previous?.fingerprint === identity2.fingerprint && previous.state === "success") {
|
|
@@ -34311,7 +34392,7 @@ Review it and publish; no app request was sent.
|
|
|
34311
34392
|
if (write && previous?.fingerprint === identity2.fingerprint && previous.state === "possibly-dispatched") return fail5(`This configured mutation may already have reached the app. Do not repeat it. Inspect the app and use a changed, authorized test request before running again.
|
|
34312
34393
|
`, 12);
|
|
34313
34394
|
if (write) store.saveAttempt(draft.id, identity2.fingerprint, "possibly-dispatched");
|
|
34314
|
-
const executed = await executeDraftVerification({ api, state, session, root, fetcher, appId: draft.appId, appSlug: draft.appSlug, kind: draft.kind, name: draft.name, action, transport, commandPrefix: command4(invocation, "").trim(), probeOrigin: envelope.verification?.probeOrigin, sampleInputs: envelope.verification?.inputs ?? {}, onDispatch: () => withBaselines(root, (baselines) => baselines.closeObservation(draft.id, "dispatch")), onDirectBaseline: (durationMs, request2, response, startedAt, finishedAt) => {
|
|
34395
|
+
const executed = await executeDraftVerification({ api, state, session, root, fetcher, appId: draft.appId, appSlug: draft.appSlug, kind: draft.kind, name: draft.name, action, transport, commandPrefix: command4(invocation, "").trim(), browserTab: envelope.capture?.tab || void 0, probeOrigin: envelope.verification?.probeOrigin, sampleInputs: envelope.verification?.inputs ?? {}, onDispatch: () => withBaselines(root, (baselines) => baselines.closeObservation(draft.id, "dispatch")), onDirectBaseline: (durationMs, request2, response, startedAt, finishedAt) => {
|
|
34315
34396
|
if (draft.kind === "action") withBaselines(root, (baselines) => baselines.recordDirect({ draftId: draft.id, appId: draft.appId, sessionId: session.id, durationMs, request: request2, response, invocationId, startedAt, finishedAt }));
|
|
34316
34397
|
} });
|
|
34317
34398
|
if (!executed.ok) {
|
|
@@ -34335,7 +34416,7 @@ Saved output: ${resultPath}
|
|
|
34335
34416
|
|
|
34336
34417
|
Review the actual objects and field meanings against the official docs or UI.
|
|
34337
34418
|
Edit ${draft.filePath}: set verification.review to accepted only if this result has the intended meaning; otherwise rejected with verification.notes.
|
|
34338
|
-
Then publish: ${command4(invocation, "discover publish")}
|
|
34419
|
+
${draft.kind === "transport" && draft.baseVersion ? `If this task requires publishing connection changes or renewed verification, publish: ${command4(invocation, "discover publish")}. If you only checked the existing connection, discard this local draft: ${command4(invocation, "discover discard")}.` : `Then publish: ${command4(invocation, "discover publish")}`}
|
|
34339
34420
|
Publication does not send another app request.
|
|
34340
34421
|
`, exitCode: 0, stdoutJson: true, dispatched: true };
|
|
34341
34422
|
}
|
|
@@ -34358,9 +34439,19 @@ function captureRow(state, draftId) {
|
|
|
34358
34439
|
function captureEntries(row) {
|
|
34359
34440
|
return row.entries_json ? JSON.parse(row.entries_json) : [];
|
|
34360
34441
|
}
|
|
34442
|
+
async function retainedCaptureEntries(client, scopeId) {
|
|
34443
|
+
const entries = [];
|
|
34444
|
+
let after = 0;
|
|
34445
|
+
for (; ; ) {
|
|
34446
|
+
const page2 = (await client.entries(scopeId, after, 100)).entries;
|
|
34447
|
+
entries.push(...page2);
|
|
34448
|
+
if (page2.length < 100) return entries;
|
|
34449
|
+
after = page2.at(-1).seq;
|
|
34450
|
+
}
|
|
34451
|
+
}
|
|
34361
34452
|
async function releaseCapture(state, draftId, row, root) {
|
|
34362
34453
|
if (!row.scope_id) return;
|
|
34363
|
-
await (await captureClient(root)).retainScope(row.scope_id);
|
|
34454
|
+
await (await captureClient(root, false)).retainScope(row.scope_id);
|
|
34364
34455
|
state.db.prepare("UPDATE discovery_capture SET evidence_scope_id=COALESCE(evidence_scope_id,scope_id),scope_id=NULL WHERE draft_id=?").run(draftId);
|
|
34365
34456
|
}
|
|
34366
34457
|
async function awaitCaptureJob(client, id2) {
|
|
@@ -34602,15 +34693,23 @@ ${detail}
|
|
|
34602
34693
|
if (!page2) return fail5(`Published browser page ${transport.browserContext} is missing. No recording started.
|
|
34603
34694
|
`);
|
|
34604
34695
|
const context = page2.definition;
|
|
34696
|
+
const installation = state.installation();
|
|
34697
|
+
if (installation) await api.call("acquireSession", { params: { id: apiSessionId(installation.id, session) }, body: { purpose: "discovery", appId: draft.appId } });
|
|
34605
34698
|
const client = await captureClient(root);
|
|
34606
34699
|
const tabs = await inspectTabs(client, draft.appId, transport.browserContext, context.pageOrigin);
|
|
34607
|
-
const intended = envelope.capture?.tab;
|
|
34700
|
+
const intended = envelope.capture?.tab || state.browserTab(session.id, draft.appId);
|
|
34608
34701
|
if (!intended && tabs.length !== 1) return fail5(`${tabs.length ? "Several matching tabs are open" : "No matching tab was reported"}. Set capture.tab in ${draft.filePath} using ${command4(invocation, `browser tabs ${draft.appSlug}`)}. No recording started.
|
|
34609
34702
|
`);
|
|
34610
34703
|
const tab = intended ? tabs.find((item2) => item2.selector === intended) : tabs[0];
|
|
34611
34704
|
if (!tab) return fail5(`Configured capture.tab ${intended} is not present. Check ${command4(invocation, `browser tabs ${draft.appSlug}`)}. No recording started.
|
|
34612
34705
|
`);
|
|
34613
|
-
|
|
34706
|
+
try {
|
|
34707
|
+
await checkTabOwnership(client, context.pageOrigin, tab.selector, draft.appSlug, command4(invocation, "").trim());
|
|
34708
|
+
} catch (error62) {
|
|
34709
|
+
return fail5(`${error62 instanceof Error ? error62.message : "Tab ownership is unavailable"}. No recording started.
|
|
34710
|
+
`, 5);
|
|
34711
|
+
}
|
|
34712
|
+
const scope2 = await client.openScope({ kind: "capture", appId: draft.appId, contextId: transport.browserContext, pageOrigin: context.pageOrigin, tabSelector: tab.selector, ownerPid: client.receipt.pid, ownerStartedAt: Date.parse(client.receipt.startedAt), ownerSession: session.id });
|
|
34614
34713
|
const previousEvidence = row?.evidence_scope_id ?? row?.scope_id;
|
|
34615
34714
|
if (previousEvidence) await client.closeScope(previousEvidence);
|
|
34616
34715
|
const id2 = randomUUID8();
|
|
@@ -34629,11 +34728,13 @@ Perform that one UI operation now. Then ${command4(invocation, "discover capture
|
|
|
34629
34728
|
`);
|
|
34630
34729
|
}
|
|
34631
34730
|
if (!row) return fail5(`No recording exists for this draft. Start with ${command4(invocation, "discover capture start")}.
|
|
34731
|
+
`);
|
|
34732
|
+
if ((key === "discoverCaptureCancel" || key === "discoverCaptureStop") && row.phase === "lost") return ok4(`Recording admission is closed because the recorder lease ended. Its stop boundary was not confirmed. Inspect app state before any mutation; retained evidence and drafts remain available. No app operation was repeated.
|
|
34632
34733
|
`);
|
|
34633
34734
|
if (key === "discoverCaptureCancel" && row.phase === "closed") {
|
|
34634
34735
|
if (row.scope_id) {
|
|
34635
34736
|
try {
|
|
34636
|
-
await (await captureClient(root, false)).
|
|
34737
|
+
await (await captureClient(root, false)).retainScope(row.scope_id);
|
|
34637
34738
|
} catch (error62) {
|
|
34638
34739
|
return fail5(`Recording admission was closed, but scope release is unconfirmed: ${error62 instanceof Error ? error62.message : "unknown error"}. Retry ${command4(invocation, "discover capture cancel")}.
|
|
34639
34740
|
`, 12);
|
|
@@ -34651,7 +34752,12 @@ Review: ${command4(invocation, "discover status")}
|
|
|
34651
34752
|
if (!row.scope_id) return key === "discoverCaptureCancel" ? ok4("Recording admission is already closed. App operations were not undone.\n") : fail5("This recording is already closed. Inspect its candidates with discover capture list.\n");
|
|
34652
34753
|
let client = await captureClient(root, false);
|
|
34653
34754
|
if (!(await client.scopes()).scopes.some((scope2) => scope2.id === row.scope_id)) {
|
|
34654
|
-
state.db.prepare("UPDATE discovery_capture SET evidence_scope_id=COALESCE(evidence_scope_id,scope_id),scope_id=NULL,job_id=NULL,phase='
|
|
34755
|
+
state.db.prepare("UPDATE discovery_capture SET evidence_scope_id=COALESCE(evidence_scope_id,scope_id),scope_id=NULL,job_id=NULL,phase='lost' WHERE draft_id=?").run(draft.id);
|
|
34756
|
+
try {
|
|
34757
|
+
const entries3 = await retainedCaptureEntries(client, row.scope_id);
|
|
34758
|
+
state.db.prepare("UPDATE discovery_capture SET entries_json=? WHERE draft_id=?").run(JSON.stringify(entries3), draft.id);
|
|
34759
|
+
} catch {
|
|
34760
|
+
}
|
|
34655
34761
|
withBaselines(root, (baselines) => baselines.closeObservation(draft.id, "capture-lost"));
|
|
34656
34762
|
return fail5(`capture-scope-lost \u2014 The recorder exited before it confirmed the recording boundary. Admission is closed, but the app operation may have happened. Do not repeat a mutation. Inspect app state and the retained draft: ${draft.filePath}
|
|
34657
34763
|
`, 12);
|
|
@@ -34674,14 +34780,7 @@ Review: ${command4(invocation, "discover status")}
|
|
|
34674
34780
|
const job = await awaitCaptureJob(client, stopId);
|
|
34675
34781
|
if (job.status !== "delivered" || job.result?.boundaryConfirmed !== true) return fail5(`Recording closure is unconfirmed. Do not repeat the app operation. Retry the same ${command4(invocation, key === "discoverCaptureCancel" ? "discover capture cancel" : "discover capture stop")}.
|
|
34676
34782
|
`, 12);
|
|
34677
|
-
const entries2 =
|
|
34678
|
-
let after = 0;
|
|
34679
|
-
for (; ; ) {
|
|
34680
|
-
const page2 = (await client.entries(row.scope_id, after, 100)).entries;
|
|
34681
|
-
entries2.push(...page2);
|
|
34682
|
-
if (page2.length < 100) break;
|
|
34683
|
-
after = page2.at(-1).seq;
|
|
34684
|
-
}
|
|
34783
|
+
const entries2 = await retainedCaptureEntries(client, row.scope_id);
|
|
34685
34784
|
await client.acknowledgeJob(stopId);
|
|
34686
34785
|
state.db.prepare("UPDATE discovery_capture SET job_id=NULL,phase='closed',entries_json=? WHERE draft_id=?").run(JSON.stringify(entries2), draft.id);
|
|
34687
34786
|
if (key === "discoverCaptureCancel") {
|
|
@@ -34700,7 +34799,7 @@ Select the causal request for the draft: ${command4(invocation, "discover captur
|
|
|
34700
34799
|
List all captured metadata pages: ${command4(invocation, "discover capture list")}
|
|
34701
34800
|
`);
|
|
34702
34801
|
}
|
|
34703
|
-
if (row.phase !== "closed") return fail5(`Recording is ${row.phase}. Stop or cancel it before reading candidates.
|
|
34802
|
+
if (row.phase !== "closed" && row.phase !== "lost") return fail5(`Recording is ${row.phase}. Stop or cancel it before reading candidates.
|
|
34704
34803
|
`);
|
|
34705
34804
|
const entries = captureEntries(row);
|
|
34706
34805
|
if (key === "discoverCaptureList") {
|
|
@@ -34805,6 +34904,7 @@ var init_discover = __esm({
|
|
|
34805
34904
|
init_envelope();
|
|
34806
34905
|
init_lock();
|
|
34807
34906
|
init_session();
|
|
34907
|
+
init_session();
|
|
34808
34908
|
init_run();
|
|
34809
34909
|
init_baselines();
|
|
34810
34910
|
init_browser();
|
|
@@ -38213,7 +38313,7 @@ function commandHelp() {
|
|
|
38213
38313
|
apilift credentials list <app> [<transport>] | use <app> <transport> <name> | add <app> <transport> | remove <app> <transport> <name>
|
|
38214
38314
|
apilift credentials unbound | attach <app> <transport> <name>
|
|
38215
38315
|
apilift transports show <app> [<name>]
|
|
38216
|
-
apilift browser check <app> | browser tabs <app>
|
|
38316
|
+
apilift browser check <app> | browser tabs <app> [--select <tab-id>]
|
|
38217
38317
|
apilift apps new <slug> | apps update <app> | apps history <app>
|
|
38218
38318
|
apilift discover draft <kind> [name] | check | run | publish | status | defer | discard | rebase | close
|
|
38219
38319
|
apilift discover remove <kind> <name>
|
|
@@ -38418,7 +38518,9 @@ Use apilift help commands for syntax.
|
|
|
38418
38518
|
const apiError2 = envelope.data.error;
|
|
38419
38519
|
output2 = result2("", cliExitForError(apiError2), `${apiError2.code} \u2014 ${apiError2.message}
|
|
38420
38520
|
${apiError2.hint ? `${apiError2.hint}
|
|
38421
|
-
` : ""}
|
|
38521
|
+
` : ""}${apiError2.code === "session-limit" ? `${typeof apiError2.details.activeExpiresAt === "string" ? `Active session lease expires ${apiError2.details.activeExpiresAt}.
|
|
38522
|
+
` : ""}No app request was sent.
|
|
38523
|
+
` : ""}`, apiError2.code !== "session-limit");
|
|
38422
38524
|
} else output2 = result2("", 11, `registry-response-invalid \u2014 The registry returned HTTP ${error62.status} without a valid JSON response. The request was sent; check its outcome before retrying a mutation.
|
|
38423
38525
|
`, true);
|
|
38424
38526
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apilift/cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.33",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=24.16.0 <27"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "c4d0802b2b89d48cadc699d922e39587c1f92c98",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
42
|
"url": "git+https://github.com/egor-sergeev/apilift.git",
|