codex-chatgpt-control 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/contracts/v1/fixtures/backend-capabilities.json +54 -0
  5. package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
  6. package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
  7. package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
  8. package/contracts/v1/fixtures/backend-version.json +10 -0
  9. package/contracts/v1/fixtures/command-descriptors.json +803 -0
  10. package/contracts/v1/fixtures/describe-runner-run.json +31 -0
  11. package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
  12. package/contracts/v1/fixtures/help-root.json +6 -0
  13. package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
  14. package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
  15. package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
  16. package/contracts/v1/fixtures/report-redaction-default.json +16 -0
  17. package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
  18. package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
  19. package/contracts/v1/fixtures/responses-basic-success.json +27 -0
  20. package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
  21. package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
  22. package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
  23. package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
  24. package/contracts/v1/fixtures/run-basic-success.json +36 -0
  25. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
  26. package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
  27. package/contracts/v1/fixtures/run-report-redacted.json +52 -0
  28. package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
  29. package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
  30. package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
  31. package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
  32. package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
  33. package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
  34. package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
  35. package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
  36. package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
  37. package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
  38. package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
  39. package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
  40. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
  41. package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
  42. package/contracts/v1/manifest.json +210 -0
  43. package/contracts/v1/parity-suite.json +635 -0
  44. package/contracts/v1/schemas/agent.schema.json +52 -0
  45. package/contracts/v1/schemas/backend-event.schema.json +74 -0
  46. package/contracts/v1/schemas/backend-request.schema.json +59 -0
  47. package/contracts/v1/schemas/backend-response.schema.json +42 -0
  48. package/contracts/v1/schemas/capabilities.schema.json +33 -0
  49. package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
  50. package/contracts/v1/schemas/command-result.schema.json +99 -0
  51. package/contracts/v1/schemas/manifest.schema.json +69 -0
  52. package/contracts/v1/schemas/response.schema.json +51 -0
  53. package/contracts/v1/schemas/run-result.schema.json +138 -0
  54. package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
  55. package/contracts/v1/schemas/stream-event.schema.json +25 -0
  56. package/dist/codex-chatgpt-control-backend.mjs +5284 -0
  57. package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
  58. package/dist/src/backend/client.d.ts +129 -0
  59. package/dist/src/backend/client.js +415 -0
  60. package/dist/src/backend/protocol.d.ts +74 -0
  61. package/dist/src/backend/protocol.js +128 -0
  62. package/dist/src/backend/session.d.ts +11 -0
  63. package/dist/src/backend/session.js +223 -0
  64. package/dist/src/backend/stdio-server.d.ts +9 -0
  65. package/dist/src/backend/stdio-server.js +88 -0
  66. package/dist/src/browser/attach.d.ts +8 -0
  67. package/dist/src/browser/attach.js +456 -0
  68. package/dist/src/browser/clipboard.d.ts +2 -0
  69. package/dist/src/browser/clipboard.js +26 -0
  70. package/dist/src/browser/downloads.d.ts +7 -0
  71. package/dist/src/browser/downloads.js +29 -0
  72. package/dist/src/browser/page-state.d.ts +17 -0
  73. package/dist/src/browser/page-state.js +66 -0
  74. package/dist/src/client.d.ts +167 -0
  75. package/dist/src/client.js +666 -0
  76. package/dist/src/commands/confirmations.d.ts +9 -0
  77. package/dist/src/commands/confirmations.js +33 -0
  78. package/dist/src/commands/context.d.ts +2 -0
  79. package/dist/src/commands/context.js +32 -0
  80. package/dist/src/commands/doctor.d.ts +16 -0
  81. package/dist/src/commands/doctor.js +120 -0
  82. package/dist/src/commands/files.d.ts +4 -0
  83. package/dist/src/commands/files.js +314 -0
  84. package/dist/src/commands/helpers.d.ts +16 -0
  85. package/dist/src/commands/helpers.js +128 -0
  86. package/dist/src/commands/messages.d.ts +16 -0
  87. package/dist/src/commands/messages.js +538 -0
  88. package/dist/src/commands/modes.d.ts +9 -0
  89. package/dist/src/commands/modes.js +323 -0
  90. package/dist/src/commands/output.d.ts +5 -0
  91. package/dist/src/commands/output.js +31 -0
  92. package/dist/src/commands/registry.d.ts +17 -0
  93. package/dist/src/commands/registry.js +275 -0
  94. package/dist/src/commands/reports.d.ts +14 -0
  95. package/dist/src/commands/reports.js +47 -0
  96. package/dist/src/commands/response-actions.d.ts +2 -0
  97. package/dist/src/commands/response-actions.js +141 -0
  98. package/dist/src/commands/sequence.d.ts +9 -0
  99. package/dist/src/commands/sequence.js +228 -0
  100. package/dist/src/commands/session.d.ts +2 -0
  101. package/dist/src/commands/session.js +25 -0
  102. package/dist/src/commands/threads.d.ts +6 -0
  103. package/dist/src/commands/threads.js +300 -0
  104. package/dist/src/dom/fixtures.d.ts +1 -0
  105. package/dist/src/dom/fixtures.js +8 -0
  106. package/dist/src/dom/menus.d.ts +8 -0
  107. package/dist/src/dom/menus.js +40 -0
  108. package/dist/src/dom/message-format.d.ts +30 -0
  109. package/dist/src/dom/message-format.js +593 -0
  110. package/dist/src/dom/messages.d.ts +40 -0
  111. package/dist/src/dom/messages.js +127 -0
  112. package/dist/src/dom/selectors.d.ts +20 -0
  113. package/dist/src/dom/selectors.js +72 -0
  114. package/dist/src/dom/visible-text.d.ts +5 -0
  115. package/dist/src/dom/visible-text.js +27 -0
  116. package/dist/src/errors.d.ts +32 -0
  117. package/dist/src/errors.js +127 -0
  118. package/dist/src/index.d.ts +34 -0
  119. package/dist/src/index.js +34 -0
  120. package/dist/src/logger.d.ts +15 -0
  121. package/dist/src/logger.js +25 -0
  122. package/dist/src/runner/agent.d.ts +2 -0
  123. package/dist/src/runner/agent.js +17 -0
  124. package/dist/src/runner/index.d.ts +7 -0
  125. package/dist/src/runner/index.js +7 -0
  126. package/dist/src/runner/interruptions.d.ts +3 -0
  127. package/dist/src/runner/interruptions.js +65 -0
  128. package/dist/src/runner/responses.d.ts +30 -0
  129. package/dist/src/runner/responses.js +171 -0
  130. package/dist/src/runner/result.d.ts +3 -0
  131. package/dist/src/runner/result.js +128 -0
  132. package/dist/src/runner/resume.d.ts +11 -0
  133. package/dist/src/runner/resume.js +26 -0
  134. package/dist/src/runner/stream.d.ts +13 -0
  135. package/dist/src/runner/stream.js +66 -0
  136. package/dist/src/runner/types.d.ts +264 -0
  137. package/dist/src/runner/types.js +1 -0
  138. package/dist/src/safety/blockers.d.ts +7 -0
  139. package/dist/src/safety/blockers.js +60 -0
  140. package/dist/src/safety/redaction.d.ts +2 -0
  141. package/dist/src/safety/redaction.js +18 -0
  142. package/dist/src/safety/report-redaction.d.ts +8 -0
  143. package/dist/src/safety/report-redaction.js +74 -0
  144. package/dist/src/safety/risk.d.ts +26 -0
  145. package/dist/src/safety/risk.js +28 -0
  146. package/dist/src/scripts/backend-server.d.ts +2 -0
  147. package/dist/src/scripts/backend-server.js +7 -0
  148. package/dist/src/scripts/check-parity-suite.d.ts +1 -0
  149. package/dist/src/scripts/check-parity-suite.js +10 -0
  150. package/dist/src/scripts/continue-thread.d.ts +27 -0
  151. package/dist/src/scripts/continue-thread.js +388 -0
  152. package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
  153. package/dist/src/scripts/live-smoke/harness.js +151 -0
  154. package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
  155. package/dist/src/scripts/live-smoke/scenarios.js +648 -0
  156. package/dist/src/scripts/live-smoke/types.d.ts +53 -0
  157. package/dist/src/scripts/live-smoke/types.js +1 -0
  158. package/dist/src/scripts/live-smoke-module.d.ts +3 -0
  159. package/dist/src/scripts/live-smoke-module.js +2 -0
  160. package/dist/src/scripts/live-smoke.d.ts +1 -0
  161. package/dist/src/scripts/live-smoke.js +54 -0
  162. package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
  163. package/dist/src/scripts/smoke-attach-file.js +39 -0
  164. package/dist/src/scripts/smoke-hi.d.ts +1 -0
  165. package/dist/src/scripts/smoke-hi.js +25 -0
  166. package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
  167. package/dist/src/scripts/smoke-search-open-read.js +41 -0
  168. package/dist/src/testing/parity-suite.d.ts +12 -0
  169. package/dist/src/testing/parity-suite.js +213 -0
  170. package/dist/src/types.d.ts +495 -0
  171. package/dist/src/types.js +1 -0
  172. package/package.json +81 -0
  173. package/references/agents-runner.md +32 -0
  174. package/references/backend-protocol.md +238 -0
  175. package/references/python-parity.md +168 -0
  176. package/references/responses-adapter.md +28 -0
  177. package/references/safety.md +14 -0
  178. package/references/streaming.md +15 -0
  179. package/references/troubleshooting.md +91 -0
@@ -0,0 +1,47 @@
1
+ import { mkdir, stat, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { resultError, resultOk } from "../errors.js";
4
+ import { redactReportValue } from "../safety/report-redaction.js";
5
+ import { contextFromPage } from "./context.js";
6
+ export async function createRunReport(env, result, options = {}) {
7
+ try {
8
+ const destDir = options.destDir ?? "reports/runs";
9
+ await mkdir(destDir, { recursive: true });
10
+ const stamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
11
+ const safeBase = sanitizeBasename(options.basename ?? "chatgpt-run-report");
12
+ const path = join(destDir, `${stamp}-${safeBase}.json`);
13
+ const includeContent = options.includeContent === true;
14
+ const summary = redactReportValue({
15
+ ok: result.ok,
16
+ status: result.status,
17
+ warnings: result.warnings,
18
+ blocker: result.blocker,
19
+ error: result.error,
20
+ context: result.context,
21
+ reportPath: result.reportPath
22
+ }, options);
23
+ const report = {
24
+ schemaVersion: 1,
25
+ createdAt: new Date().toISOString(),
26
+ includeContent,
27
+ summary,
28
+ steps: result.steps?.map(step => ({
29
+ ...step,
30
+ dataPreview: redactReportValue(step.dataPreview, options)
31
+ })),
32
+ data: redactReportValue(result.data, options)
33
+ };
34
+ await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, "utf8");
35
+ const saved = await stat(path);
36
+ return resultOk({ path, bytes: saved.size, includeContent }, await contextFromPage(env.page));
37
+ }
38
+ catch (error) {
39
+ return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(env.page));
40
+ }
41
+ }
42
+ function sanitizeBasename(name) {
43
+ return name.toLowerCase()
44
+ .replace(/[^a-z0-9._-]+/g, "-")
45
+ .replace(/^-+|-+$/g, "")
46
+ || "chatgpt-run-report";
47
+ }
@@ -0,0 +1,2 @@
1
+ import type { CommandResult, CopiedResponse, CopyResponseArgs, RuntimeEnv } from "../types.js";
2
+ export declare function copyResponse(env: RuntimeEnv, args?: CopyResponseArgs): Promise<CommandResult<CopiedResponse>>;
@@ -0,0 +1,141 @@
1
+ import { readSystemClipboard, waitForClipboardChange } from "../browser/clipboard.js";
2
+ import { formatClipboardMarkdown, normalizeResponseFormat } from "../dom/message-format.js";
3
+ import { resultError, resultOk } from "../errors.js";
4
+ import { readLatestMessage, readMessages } from "../dom/messages.js";
5
+ import { copyResponseButtons } from "../dom/selectors.js";
6
+ import { contextFromPage } from "./context.js";
7
+ import { withCommandOutputText } from "./output.js";
8
+ import { bootstrap } from "./session.js";
9
+ export async function copyResponse(env, args = {}) {
10
+ const boot = await ensurePage(env);
11
+ if (!boot.ok) {
12
+ return boot;
13
+ }
14
+ const page = env.page;
15
+ try {
16
+ if (args.prefer !== "dom") {
17
+ const before = await readClipboard(env);
18
+ const buttons = copyResponseButtons(page);
19
+ const target = args.which === undefined || args.which === "latest"
20
+ ? buttons.last?.() ?? buttons
21
+ : buttons.nth?.(args.which.assistantIndex) ?? buttons;
22
+ await target.click?.();
23
+ const copied = await waitForClipboard(env, before, args.timeoutMs ?? 3000);
24
+ if (copied !== undefined) {
25
+ const requestedFormat = normalizeResponseFormat(args.format);
26
+ if (requestedFormat === "html" || requestedFormat === "blocks" || requestedFormat === "all") {
27
+ const latest = await readSelectedAssistantMessage(page, args.which, requestedFormat);
28
+ if (latest !== undefined) {
29
+ const fallbackReason = `Clipboard copy succeeded, but ${formatLabel(requestedFormat)} requires DOM extraction.`;
30
+ const data = copiedResponseFromExtracted(latest, "dom", fallbackReason);
31
+ data.markdown = formatClipboardMarkdown(copied).markdown ?? copied;
32
+ data.warnings = [...(data.warnings ?? []), fallbackReason];
33
+ return withCommandOutputText(resultOk(data, await contextFromPage(page), data.warnings));
34
+ }
35
+ const warning = `Clipboard copy succeeded, but ${formatLabel(requestedFormat)} requires DOM extraction and no assistant DOM message was available; returned clipboard Markdown instead.`;
36
+ const data = {
37
+ ...formatClipboardMarkdown(copied, undefined, "markdown"),
38
+ source: "clipboard",
39
+ fallbackReason: warning,
40
+ warnings: [warning]
41
+ };
42
+ return withCommandOutputText(resultOk(data, await contextFromPage(page), [warning]));
43
+ }
44
+ const metadata = await readSelectedAssistantMessage(page, args.which, "markdown").catch(() => undefined);
45
+ const data = {
46
+ ...formatClipboardMarkdown(copied, undefined, args.format),
47
+ source: "clipboard"
48
+ };
49
+ mergeResponseMetadata(data, metadata);
50
+ return withCommandOutputText(resultOk(data, await contextFromPage(page)));
51
+ }
52
+ }
53
+ const latest = await readSelectedAssistantMessage(page, args.which, args.format ?? "markdown");
54
+ if (latest !== undefined) {
55
+ const fallbackReason = args.prefer === "dom"
56
+ ? `Returned DOM-derived ${formatLabel(latest.format)} because clipboard copy was not requested.`
57
+ : "System clipboard did not change; returned DOM-derived response content.";
58
+ const data = copiedResponseFromExtracted(latest, "dom", fallbackReason);
59
+ return withCommandOutputText(resultOk(data, await contextFromPage(page), data.warnings ?? [fallbackReason]));
60
+ }
61
+ return {
62
+ ok: false,
63
+ status: "not_found",
64
+ warnings: [],
65
+ blocker: {
66
+ kind: "not_found",
67
+ message: "No assistant response was available to copy."
68
+ },
69
+ context: await contextFromPage(page)
70
+ };
71
+ }
72
+ catch (error) {
73
+ return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
74
+ }
75
+ }
76
+ function readClipboard(env) {
77
+ return env.clipboard?.read() ?? readSystemClipboard();
78
+ }
79
+ function waitForClipboard(env, before, timeoutMs) {
80
+ return env.clipboard?.waitForChange(before, timeoutMs) ?? waitForClipboardChange(before, timeoutMs);
81
+ }
82
+ async function readSelectedAssistantMessage(page, which, format = "markdown") {
83
+ if (which === undefined || which === "latest") {
84
+ return readLatestMessage(page, "assistant", format);
85
+ }
86
+ const messages = await readMessages(page, { role: "assistant", format });
87
+ return messages.at(which.assistantIndex);
88
+ }
89
+ function formatLabel(format) {
90
+ return format === "markdown" ? "Markdown" : format.replaceAll("_", " ");
91
+ }
92
+ function copiedResponseFromExtracted(latest, source, fallbackReason) {
93
+ const data = {
94
+ text: latest.text,
95
+ format: latest.format,
96
+ source
97
+ };
98
+ if (latest.fidelity !== undefined)
99
+ data.fidelity = latest.fidelity;
100
+ if (latest.warnings !== undefined || fallbackReason !== undefined) {
101
+ data.warnings = [...(latest.warnings ?? []), ...(fallbackReason === undefined ? [] : [fallbackReason])];
102
+ }
103
+ if (fallbackReason !== undefined)
104
+ data.fallbackReason = fallbackReason;
105
+ mergeResponseMetadata(data, latest);
106
+ return data;
107
+ }
108
+ function mergeResponseMetadata(data, latest) {
109
+ if (latest === undefined)
110
+ return;
111
+ if (latest.markdown !== undefined && data.markdown === undefined)
112
+ data.markdown = latest.markdown;
113
+ if (latest.visibleText !== undefined)
114
+ data.visibleText = latest.visibleText;
115
+ if (latest.normalizedText !== undefined)
116
+ data.normalizedText = latest.normalizedText;
117
+ if (latest.html !== undefined)
118
+ data.html = latest.html;
119
+ if (latest.blocks !== undefined)
120
+ data.blocks = latest.blocks;
121
+ if (latest.citations !== undefined)
122
+ data.citations = latest.citations;
123
+ if (latest.codeBlocks !== undefined)
124
+ data.codeBlocks = latest.codeBlocks;
125
+ if (latest.tables !== undefined)
126
+ data.tables = latest.tables;
127
+ if (latest.branch !== undefined)
128
+ data.branch = latest.branch;
129
+ if (latest.actions !== undefined)
130
+ data.actions = latest.actions;
131
+ if (latest.thoughtDurationText !== undefined)
132
+ data.thoughtDurationText = latest.thoughtDurationText;
133
+ if (latest.sourcesAvailable !== undefined)
134
+ data.sourcesAvailable = latest.sourcesAvailable;
135
+ }
136
+ async function ensurePage(env) {
137
+ if (env.page !== undefined) {
138
+ return resultOk({}, await contextFromPage(env.page));
139
+ }
140
+ return bootstrap(env, { preferExistingTab: true });
141
+ }
@@ -0,0 +1,9 @@
1
+ import type { CommandResult, RuntimeEnv, SequencePlan, SequencePolicy, SequenceStep } from "../types.js";
2
+ export type SequenceExecutor = (step: SequenceStep, env: RuntimeEnv, previousResults: Map<string, CommandResult<unknown>>, policy: SequencePolicy) => Promise<CommandResult<unknown>>;
3
+ export declare const defaultSequencePolicy: SequencePolicy;
4
+ export declare function runSequence(plan: SequencePlan, env?: RuntimeEnv): Promise<CommandResult<unknown>>;
5
+ export declare function runSequenceWithExecutor(plan: SequencePlan, executor: SequenceExecutor, env?: RuntimeEnv): Promise<CommandResult<unknown>>;
6
+ export declare function executeStep(step: SequenceStep, env: RuntimeEnv, previousResults: Map<string, CommandResult<unknown>>): Promise<CommandResult<unknown>>;
7
+ export declare function normalizePolicy(policy: Partial<SequencePolicy> | undefined): SequencePolicy;
8
+ export declare function resolveStepArgs(step: SequenceStep, previousResults: Map<string, CommandResult<unknown>>, input?: Record<string, unknown>): SequenceStep;
9
+ export declare function resolveVariableReference(reference: string, previousResults: Map<string, CommandResult<unknown>>, input?: Record<string, unknown>): unknown;
@@ -0,0 +1,228 @@
1
+ import { attachFiles, downloadLatestFile } from "./files.js";
2
+ import { askMessage, composeMessage, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
3
+ import { copyResponse } from "./response-actions.js";
4
+ import { bootstrap } from "./session.js";
5
+ import { newThread, openThread, searchThreads } from "./threads.js";
6
+ import { setMode, selectTool } from "./modes.js";
7
+ import { withCommandOutputText } from "./output.js";
8
+ export const defaultSequencePolicy = {
9
+ stopOnError: true,
10
+ returnPartial: true,
11
+ defaultTimeoutMs: 120000,
12
+ screenshotOnBlocker: true,
13
+ allowPromptResubmit: "only_if_no_matching_user_turn"
14
+ };
15
+ export async function runSequence(plan, env = {}) {
16
+ return runSequenceWithExecutor(plan, executeStep, env);
17
+ }
18
+ export async function runSequenceWithExecutor(plan, executor, env = {}) {
19
+ const policy = normalizePolicy(plan.policy);
20
+ const stepResults = [];
21
+ const values = new Map();
22
+ const input = plan.input ?? {};
23
+ for (const step of plan.steps) {
24
+ const startedAt = new Date().toISOString();
25
+ const resolvedStep = resolveStepArgs(step, values, input);
26
+ const result = await executor(resolvedStep, env, values, policy);
27
+ values.set(step.id, result);
28
+ stepResults.push(toStepResult(step, result, startedAt));
29
+ if (!result.ok && policy.stopOnError) {
30
+ return sequenceFailure(result, values, stepResults, policy);
31
+ }
32
+ }
33
+ const lastStep = plan.steps.at(-1);
34
+ const finalResult = lastStep === undefined ? okSequenceResult(values, stepResults) : values.get(lastStep.id);
35
+ if (finalResult === undefined) {
36
+ return okSequenceResult(values, stepResults);
37
+ }
38
+ return withCommandOutputText({ ...finalResult, steps: stepResults });
39
+ }
40
+ export async function executeStep(step, env, previousResults) {
41
+ switch (step.command) {
42
+ case "session.bootstrap":
43
+ return bootstrap(env, step.args);
44
+ case "threads.search":
45
+ return searchThreads(env, step.args);
46
+ case "threads.open":
47
+ return openThread(env, step.args, previousResults);
48
+ case "threads.new":
49
+ return newThread(env, step.args);
50
+ case "messages.compose":
51
+ return composeMessage(env, step.args);
52
+ case "messages.submit":
53
+ return submitMessage(env, step.args);
54
+ case "messages.ask":
55
+ return askMessage(env, step.args);
56
+ case "messages.wait":
57
+ return waitForMessage(env, step.args);
58
+ case "messages.readLatest":
59
+ return readLatest(env, step.args);
60
+ case "messages.waitAndRead":
61
+ return waitAndRead(env, step.args);
62
+ case "files.attach":
63
+ return attachFiles(env, step.args);
64
+ case "files.downloadLatest":
65
+ return downloadLatestFile(env, step.args);
66
+ case "response.copy":
67
+ return copyResponse(env, step.args);
68
+ case "modes.set":
69
+ return setMode(env, step.args);
70
+ case "tools.select":
71
+ return selectTool(env, step.args);
72
+ }
73
+ }
74
+ export function normalizePolicy(policy) {
75
+ return { ...defaultSequencePolicy, ...(policy ?? {}) };
76
+ }
77
+ export function resolveStepArgs(step, previousResults, input = {}) {
78
+ if (!("args" in step) || step.args === undefined) {
79
+ return step;
80
+ }
81
+ return {
82
+ ...step,
83
+ args: resolveValue(step.args, previousResults, input)
84
+ };
85
+ }
86
+ export function resolveVariableReference(reference, previousResults, input = {}) {
87
+ const match = /^\$\{([^}]+)\}$/.exec(reference);
88
+ if (match === null) {
89
+ return reference;
90
+ }
91
+ const path = match[1];
92
+ if (path === undefined || path.length === 0) {
93
+ throw new Error("Empty variable reference is not allowed.");
94
+ }
95
+ if (path.includes("__proto__") || path.includes("prototype") || path.includes("constructor")) {
96
+ throw new Error(`Unsafe variable reference rejected: ${path}`);
97
+ }
98
+ const [root, ...segments] = tokenizePath(path);
99
+ let current;
100
+ if (root === "input") {
101
+ current = input;
102
+ }
103
+ else if (root !== undefined && previousResults.has(root)) {
104
+ current = previousResults.get(root);
105
+ }
106
+ else {
107
+ throw new Error(`Unknown variable root: ${root ?? ""}`);
108
+ }
109
+ for (const segment of segments) {
110
+ current = readPathSegment(current, segment);
111
+ }
112
+ return current;
113
+ }
114
+ function resolveValue(value, previousResults, input) {
115
+ if (typeof value === "string") {
116
+ return resolveVariableReference(value, previousResults, input);
117
+ }
118
+ if (Array.isArray(value)) {
119
+ return value.map(item => resolveValue(item, previousResults, input));
120
+ }
121
+ if (value !== null && typeof value === "object") {
122
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, resolveValue(child, previousResults, input)]));
123
+ }
124
+ return value;
125
+ }
126
+ function tokenizePath(path) {
127
+ const segments = [];
128
+ for (const part of path.split(".")) {
129
+ const head = /^([A-Za-z_][A-Za-z0-9_]*)/.exec(part)?.[1];
130
+ if (head === undefined) {
131
+ throw new Error(`Invalid variable path segment: ${part}`);
132
+ }
133
+ segments.push(head);
134
+ for (const indexMatch of part.matchAll(/\[(\d+)\]/g)) {
135
+ segments.push(indexMatch[1]);
136
+ }
137
+ const consumed = `${head}${Array.from(part.matchAll(/\[(\d+)\]/g)).map(match => `[${match[1]}]`).join("")}`;
138
+ if (consumed !== part) {
139
+ throw new Error(`Invalid variable path segment: ${part}`);
140
+ }
141
+ }
142
+ return segments;
143
+ }
144
+ function readPathSegment(value, segment) {
145
+ if (value === null || value === undefined) {
146
+ return undefined;
147
+ }
148
+ if (Array.isArray(value)) {
149
+ const index = Number(segment);
150
+ if (!Number.isInteger(index)) {
151
+ throw new Error(`Array segment must be numeric: ${segment}`);
152
+ }
153
+ return value[index];
154
+ }
155
+ if (typeof value === "object") {
156
+ return value[segment];
157
+ }
158
+ return undefined;
159
+ }
160
+ function toStepResult(step, result, startedAt) {
161
+ const stepResult = {
162
+ id: step.id,
163
+ command: step.command,
164
+ status: result.status,
165
+ ok: result.ok,
166
+ startedAt,
167
+ endedAt: new Date().toISOString(),
168
+ warnings: result.warnings
169
+ };
170
+ const dataPreview = previewData(result.data);
171
+ if (dataPreview !== undefined) {
172
+ stepResult.dataPreview = dataPreview;
173
+ }
174
+ return stepResult;
175
+ }
176
+ function previewData(data) {
177
+ if (data === undefined) {
178
+ return undefined;
179
+ }
180
+ if (typeof data === "string") {
181
+ return data.length > 120 ? `${data.slice(0, 119)}...` : data;
182
+ }
183
+ if (Array.isArray(data)) {
184
+ return { type: "array", length: data.length };
185
+ }
186
+ if (typeof data === "object" && data !== null) {
187
+ return Object.fromEntries(Object.entries(data).map(([key, value]) => {
188
+ if (/text|prompt|response/i.test(key) && typeof value === "string") {
189
+ return [key, value.length > 120 ? `${value.slice(0, 119)}...` : value];
190
+ }
191
+ return [key, value];
192
+ }));
193
+ }
194
+ return data;
195
+ }
196
+ function sequenceFailure(result, values, stepResults, policy) {
197
+ const failure = {
198
+ ok: false,
199
+ status: policy.returnPartial ? "partial" : result.status,
200
+ data: collectSequenceData(values),
201
+ warnings: collectWarnings(stepResults, result.warnings),
202
+ context: result.context,
203
+ steps: stepResults
204
+ };
205
+ if (result.error !== undefined) {
206
+ failure.error = result.error;
207
+ }
208
+ if (result.blocker !== undefined) {
209
+ failure.blocker = result.blocker;
210
+ }
211
+ return withCommandOutputText(failure);
212
+ }
213
+ function okSequenceResult(values, stepResults) {
214
+ return withCommandOutputText({
215
+ ok: true,
216
+ status: "ok",
217
+ data: collectSequenceData(values),
218
+ warnings: collectWarnings(stepResults),
219
+ context: { timestamp: new Date().toISOString() },
220
+ steps: stepResults
221
+ });
222
+ }
223
+ function collectSequenceData(values) {
224
+ return Object.fromEntries(Array.from(values.entries()).map(([id, result]) => [id, result.data]));
225
+ }
226
+ function collectWarnings(stepResults, extra = []) {
227
+ return [...stepResults.flatMap(step => step.warnings), ...extra];
228
+ }
@@ -0,0 +1,2 @@
1
+ import type { BootstrapArgs, BootstrapData, CommandResult, RuntimeEnv } from "../types.js";
2
+ export declare function bootstrap(env: RuntimeEnv, args?: BootstrapArgs): Promise<CommandResult<BootstrapData>>;
@@ -0,0 +1,25 @@
1
+ import { attachChatGPTBrowser } from "../browser/attach.js";
2
+ import { readPageState } from "../browser/page-state.js";
3
+ import { resultError, resultOk } from "../errors.js";
4
+ import { contextFromPage } from "./context.js";
5
+ export async function bootstrap(env, args = {}) {
6
+ try {
7
+ const attached = await attachChatGPTBrowser(env, args);
8
+ env.browser = attached.browser;
9
+ env.page = attached.page;
10
+ const state = await readPageState(attached.page);
11
+ const data = {
12
+ browserName: attached.browserName,
13
+ tabId: attached.tabId ?? "unknown",
14
+ url: state.url,
15
+ loggedIn: state.signedIn
16
+ };
17
+ const context = attached.tabId === undefined
18
+ ? { browserName: attached.browserName }
19
+ : { browserName: attached.browserName, tabId: attached.tabId };
20
+ return resultOk(data, await contextFromPage(attached.page, context));
21
+ }
22
+ catch (error) {
23
+ return resultError(error instanceof Error ? error : new Error(String(error)));
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ import type { CommandResult, NewThreadArgs, OpenThreadArgs, OpenThreadData, RuntimeEnv, SearchThreadsArgs, SearchThreadsData, ThreadSearchResult } from "../types.js";
2
+ export declare function extractThreadSearchResultsFromHtml(html: string): ThreadSearchResult[];
3
+ export declare function searchThreads(env: RuntimeEnv, args: SearchThreadsArgs): Promise<CommandResult<SearchThreadsData>>;
4
+ export declare function newThread(env: RuntimeEnv, args?: NewThreadArgs): Promise<CommandResult<OpenThreadData>>;
5
+ export declare function openThread(env: RuntimeEnv, args: OpenThreadArgs, previousResults?: Map<string, CommandResult<unknown>>): Promise<CommandResult<OpenThreadData>>;
6
+ export declare function selectSearchResult(results: ThreadSearchResult[], select?: OpenThreadArgs["select"]): ThreadSearchResult | undefined;