pagegraph 0.8.1 → 0.8.3
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/dist/cli.js +99 -12
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -35710,7 +35710,7 @@ const runWith = (command, config) => {
|
|
|
35710
35710
|
};
|
|
35711
35711
|
//#endregion
|
|
35712
35712
|
//#region package.json
|
|
35713
|
-
var version = "0.8.
|
|
35713
|
+
var version = "0.8.3";
|
|
35714
35714
|
//#endregion
|
|
35715
35715
|
//#region src/core/projections.ts
|
|
35716
35716
|
const escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -43433,7 +43433,7 @@ const TranscriptSchema = Struct({ messages: ArraySchema(Unknown) });
|
|
|
43433
43433
|
const decodeTextPart = decodeUnknownOption(TextPartSchema);
|
|
43434
43434
|
const decodeAssistantMessage = decodeUnknownOption(AssistantMessageSchema);
|
|
43435
43435
|
const decodeTranscript = decodeUnknownSync(TranscriptSchema);
|
|
43436
|
-
const
|
|
43436
|
+
const parseWorkflowState = (transcript) => {
|
|
43437
43437
|
const candidates = decodeTranscript(transcript).messages.flatMap((message) => {
|
|
43438
43438
|
const assistant = decodeAssistantMessage(message);
|
|
43439
43439
|
if (isNone(assistant)) return [];
|
|
@@ -43452,6 +43452,25 @@ const parseState = (transcript) => {
|
|
|
43452
43452
|
}
|
|
43453
43453
|
throw new Error("The SEO agent did not return a valid workflow JSON object.");
|
|
43454
43454
|
};
|
|
43455
|
+
const parseWorkflowStateWithRepair = async (transcript, repair) => {
|
|
43456
|
+
try {
|
|
43457
|
+
return {
|
|
43458
|
+
state: parseWorkflowState(transcript),
|
|
43459
|
+
transcript
|
|
43460
|
+
};
|
|
43461
|
+
} catch {
|
|
43462
|
+
const repaired = await repair();
|
|
43463
|
+
return {
|
|
43464
|
+
state: parseWorkflowState(repaired),
|
|
43465
|
+
transcript: repaired
|
|
43466
|
+
};
|
|
43467
|
+
}
|
|
43468
|
+
};
|
|
43469
|
+
const STATE_REPAIR_PROMPT = [
|
|
43470
|
+
"Your previous turn did not end with the workflow JSON object requested by the original prompt.",
|
|
43471
|
+
"Do not call tools, add commentary, or wrap the response in Markdown.",
|
|
43472
|
+
"Return only one valid JSON object matching the original JSON Schema, using the evidence already collected."
|
|
43473
|
+
].join(" ");
|
|
43455
43474
|
const decodeToolPart = decodeUnknownOption(Struct({
|
|
43456
43475
|
type: Literal("tool"),
|
|
43457
43476
|
id: String$6,
|
|
@@ -43479,6 +43498,31 @@ const toolText = (value) => {
|
|
|
43479
43498
|
return Object.values(record).map(toolText).join("\n");
|
|
43480
43499
|
};
|
|
43481
43500
|
const pathsIn = (value) => [...toolText(value).matchAll(/tools\.([A-Za-z0-9_.-]+)/g)].map((match) => match[1] ?? "").filter(Boolean);
|
|
43501
|
+
const toolPathPattern = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+$/;
|
|
43502
|
+
const pathsInSearchOutput = (value) => {
|
|
43503
|
+
const found = /* @__PURE__ */ new Set();
|
|
43504
|
+
const visit = (current, pathField = false) => {
|
|
43505
|
+
if (typeof current === "string") {
|
|
43506
|
+
if (pathField) {
|
|
43507
|
+
const candidate = current.startsWith("tools.") ? current.slice(6) : current;
|
|
43508
|
+
if (toolPathPattern.test(candidate)) found.add(candidate);
|
|
43509
|
+
}
|
|
43510
|
+
try {
|
|
43511
|
+
visit(JSON.parse(current));
|
|
43512
|
+
} catch {}
|
|
43513
|
+
return;
|
|
43514
|
+
}
|
|
43515
|
+
if (Array.isArray(current)) {
|
|
43516
|
+
for (const item of current) visit(item);
|
|
43517
|
+
return;
|
|
43518
|
+
}
|
|
43519
|
+
if (current === null || typeof current !== "object") return;
|
|
43520
|
+
const record = current;
|
|
43521
|
+
for (const [key, nested] of Object.entries(record)) visit(nested, key === "path");
|
|
43522
|
+
};
|
|
43523
|
+
visit(value);
|
|
43524
|
+
return [...found];
|
|
43525
|
+
};
|
|
43482
43526
|
const evidenceRecord = (part) => ({
|
|
43483
43527
|
tool: part.name,
|
|
43484
43528
|
input: part.state.input ?? null,
|
|
@@ -43492,7 +43536,7 @@ const collectExecutorEvidence = (transcript) => {
|
|
|
43492
43536
|
const inputPaths = pathsIn(part.state.input);
|
|
43493
43537
|
const inputText = toolText(part.state.input);
|
|
43494
43538
|
if (part.name === "executor_search" || part.name === "executor.search" || inputPaths.some((path) => path === "executor.search")) {
|
|
43495
|
-
for (const path of
|
|
43539
|
+
for (const path of pathsInSearchOutput(part.state.content)) if (path !== "executor.search") discovered.add(path);
|
|
43496
43540
|
searches.push(evidenceRecord(part));
|
|
43497
43541
|
continue;
|
|
43498
43542
|
}
|
|
@@ -43515,14 +43559,14 @@ const interactionEventError = (event, sessionId) => {
|
|
|
43515
43559
|
if (form !== null && typeof form === "object" && form["sessionID"] === sessionId) return /* @__PURE__ */ new Error("OpenCode requested a form; embedded PageGraph workflows are noninteractive.");
|
|
43516
43560
|
}
|
|
43517
43561
|
};
|
|
43518
|
-
const waitForIdle = async (host, sessionId,
|
|
43562
|
+
const waitForIdle = async (host, sessionId, options) => {
|
|
43519
43563
|
const controller = new AbortController();
|
|
43520
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
43564
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
43521
43565
|
try {
|
|
43522
43566
|
for await (const event of host.sessions.log({
|
|
43523
43567
|
sessionID: sessionId,
|
|
43524
43568
|
follow: true,
|
|
43525
|
-
after
|
|
43569
|
+
after: options.after
|
|
43526
43570
|
}, { signal: controller.signal })) {
|
|
43527
43571
|
const interactionError = interactionEventError(event, sessionId);
|
|
43528
43572
|
if (interactionError !== void 0) throw interactionError;
|
|
@@ -43530,6 +43574,9 @@ const waitForIdle = async (host, sessionId, timeoutMs, after) => {
|
|
|
43530
43574
|
if (event.type === "session.execution.failed" && event.data.sessionID === sessionId) throw new Error(`OpenCode session failed: ${JSON.stringify(event.data.error)}`);
|
|
43531
43575
|
if (event.type === "session.execution.interrupted" && event.data.sessionID === sessionId) throw new Error(`OpenCode session was interrupted: ${event.data.reason}.`);
|
|
43532
43576
|
}
|
|
43577
|
+
} catch (cause) {
|
|
43578
|
+
if (controller.signal.aborted) throw new Error(`OpenCode session did not complete within ${options.configuredTimeoutMs ?? options.timeoutMs}ms.`, { cause });
|
|
43579
|
+
throw cause;
|
|
43533
43580
|
} finally {
|
|
43534
43581
|
clearTimeout(timer);
|
|
43535
43582
|
}
|
|
@@ -43556,22 +43603,62 @@ const acquireWorkflowHost = async (options) => {
|
|
|
43556
43603
|
sessionID: sessionId,
|
|
43557
43604
|
permissions: [...workflowOptions.permissions]
|
|
43558
43605
|
});
|
|
43606
|
+
const configuredTimeoutMs = options.timeoutMs ?? 18e4;
|
|
43607
|
+
const deadline = Date.now() + configuredTimeoutMs;
|
|
43608
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
43559
43609
|
await host.sessions.prompt({
|
|
43560
43610
|
sessionID: sessionId,
|
|
43561
43611
|
text: prompt,
|
|
43562
43612
|
skills: workflowOptions.skills.map((id) => ({ id }))
|
|
43563
43613
|
});
|
|
43564
|
-
|
|
43614
|
+
let cursor = await waitForIdle(host, sessionId, {
|
|
43615
|
+
timeoutMs: remaining(),
|
|
43616
|
+
configuredTimeoutMs,
|
|
43617
|
+
after: cursors.get(sessionId)
|
|
43618
|
+
});
|
|
43565
43619
|
cursors.set(sessionId, cursor);
|
|
43566
|
-
|
|
43620
|
+
let transcript = await host.sessions.export({
|
|
43567
43621
|
sessionID: sessionId,
|
|
43568
43622
|
sanitize: false
|
|
43569
43623
|
});
|
|
43624
|
+
const parsed = await parseWorkflowStateWithRepair(transcript, async () => {
|
|
43625
|
+
const priorPermissions = [...workflowOptions.permissions ?? []];
|
|
43626
|
+
await host.sessions.update({
|
|
43627
|
+
sessionID: sessionId,
|
|
43628
|
+
permissions: [{
|
|
43629
|
+
action: "*",
|
|
43630
|
+
resource: "*",
|
|
43631
|
+
effect: "deny"
|
|
43632
|
+
}]
|
|
43633
|
+
});
|
|
43634
|
+
try {
|
|
43635
|
+
await host.sessions.prompt({
|
|
43636
|
+
sessionID: sessionId,
|
|
43637
|
+
text: STATE_REPAIR_PROMPT
|
|
43638
|
+
});
|
|
43639
|
+
cursor = await waitForIdle(host, sessionId, {
|
|
43640
|
+
timeoutMs: remaining(),
|
|
43641
|
+
configuredTimeoutMs,
|
|
43642
|
+
after: cursor
|
|
43643
|
+
});
|
|
43644
|
+
cursors.set(sessionId, cursor);
|
|
43645
|
+
transcript = await host.sessions.export({
|
|
43646
|
+
sessionID: sessionId,
|
|
43647
|
+
sanitize: false
|
|
43648
|
+
});
|
|
43649
|
+
return transcript;
|
|
43650
|
+
} finally {
|
|
43651
|
+
await host.sessions.update({
|
|
43652
|
+
sessionID: sessionId,
|
|
43653
|
+
permissions: priorPermissions
|
|
43654
|
+
});
|
|
43655
|
+
}
|
|
43656
|
+
});
|
|
43570
43657
|
return {
|
|
43571
|
-
state:
|
|
43658
|
+
state: parsed.state,
|
|
43572
43659
|
sessionId,
|
|
43573
|
-
transcript,
|
|
43574
|
-
executor: collectExecutorEvidence(transcript)
|
|
43660
|
+
transcript: parsed.transcript,
|
|
43661
|
+
executor: collectExecutorEvidence(parsed.transcript)
|
|
43575
43662
|
};
|
|
43576
43663
|
};
|
|
43577
43664
|
return {
|
|
@@ -43588,7 +43675,7 @@ const acquireWorkflowHost = async (options) => {
|
|
|
43588
43675
|
permissions: workflowOptions.permissions === void 0 ? void 0 : [...workflowOptions.permissions]
|
|
43589
43676
|
});
|
|
43590
43677
|
const result = await complete(session.id, prompt, workflowOptions);
|
|
43591
|
-
if (result.executor.searches.length === 0 || result.executor.calls.length === 0) throw new Error(
|
|
43678
|
+
if (result.executor.searches.length === 0 || result.executor.calls.length === 0) throw new Error(`The SEO agent returned without the required Executor evidence (searches: ${result.executor.searches.length}, calls: ${result.executor.calls.length}).`);
|
|
43592
43679
|
return result;
|
|
43593
43680
|
},
|
|
43594
43681
|
continue: (sessionId, prompt, workflowOptions) => complete(sessionId, prompt, workflowOptions),
|