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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/contracts/v1/fixtures/backend-capabilities.json +54 -0
- package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
- package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
- package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
- package/contracts/v1/fixtures/backend-version.json +10 -0
- package/contracts/v1/fixtures/command-descriptors.json +803 -0
- package/contracts/v1/fixtures/describe-runner-run.json +31 -0
- package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
- package/contracts/v1/fixtures/help-root.json +6 -0
- package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
- package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
- package/contracts/v1/fixtures/report-redaction-default.json +16 -0
- package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
- package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
- package/contracts/v1/fixtures/responses-basic-success.json +27 -0
- package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
- package/contracts/v1/fixtures/run-basic-success.json +36 -0
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
- package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
- package/contracts/v1/fixtures/run-report-redacted.json +52 -0
- package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
- package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
- package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
- package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
- package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
- package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
- package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
- package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
- package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
- package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
- package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
- package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
- package/contracts/v1/manifest.json +210 -0
- package/contracts/v1/parity-suite.json +635 -0
- package/contracts/v1/schemas/agent.schema.json +52 -0
- package/contracts/v1/schemas/backend-event.schema.json +74 -0
- package/contracts/v1/schemas/backend-request.schema.json +59 -0
- package/contracts/v1/schemas/backend-response.schema.json +42 -0
- package/contracts/v1/schemas/capabilities.schema.json +33 -0
- package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
- package/contracts/v1/schemas/command-result.schema.json +99 -0
- package/contracts/v1/schemas/manifest.schema.json +69 -0
- package/contracts/v1/schemas/response.schema.json +51 -0
- package/contracts/v1/schemas/run-result.schema.json +138 -0
- package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
- package/contracts/v1/schemas/stream-event.schema.json +25 -0
- package/dist/codex-chatgpt-control-backend.mjs +5284 -0
- package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
- package/dist/src/backend/client.d.ts +129 -0
- package/dist/src/backend/client.js +415 -0
- package/dist/src/backend/protocol.d.ts +74 -0
- package/dist/src/backend/protocol.js +128 -0
- package/dist/src/backend/session.d.ts +11 -0
- package/dist/src/backend/session.js +223 -0
- package/dist/src/backend/stdio-server.d.ts +9 -0
- package/dist/src/backend/stdio-server.js +88 -0
- package/dist/src/browser/attach.d.ts +8 -0
- package/dist/src/browser/attach.js +456 -0
- package/dist/src/browser/clipboard.d.ts +2 -0
- package/dist/src/browser/clipboard.js +26 -0
- package/dist/src/browser/downloads.d.ts +7 -0
- package/dist/src/browser/downloads.js +29 -0
- package/dist/src/browser/page-state.d.ts +17 -0
- package/dist/src/browser/page-state.js +66 -0
- package/dist/src/client.d.ts +167 -0
- package/dist/src/client.js +666 -0
- package/dist/src/commands/confirmations.d.ts +9 -0
- package/dist/src/commands/confirmations.js +33 -0
- package/dist/src/commands/context.d.ts +2 -0
- package/dist/src/commands/context.js +32 -0
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +120 -0
- package/dist/src/commands/files.d.ts +4 -0
- package/dist/src/commands/files.js +314 -0
- package/dist/src/commands/helpers.d.ts +16 -0
- package/dist/src/commands/helpers.js +128 -0
- package/dist/src/commands/messages.d.ts +16 -0
- package/dist/src/commands/messages.js +538 -0
- package/dist/src/commands/modes.d.ts +9 -0
- package/dist/src/commands/modes.js +323 -0
- package/dist/src/commands/output.d.ts +5 -0
- package/dist/src/commands/output.js +31 -0
- package/dist/src/commands/registry.d.ts +17 -0
- package/dist/src/commands/registry.js +275 -0
- package/dist/src/commands/reports.d.ts +14 -0
- package/dist/src/commands/reports.js +47 -0
- package/dist/src/commands/response-actions.d.ts +2 -0
- package/dist/src/commands/response-actions.js +141 -0
- package/dist/src/commands/sequence.d.ts +9 -0
- package/dist/src/commands/sequence.js +228 -0
- package/dist/src/commands/session.d.ts +2 -0
- package/dist/src/commands/session.js +25 -0
- package/dist/src/commands/threads.d.ts +6 -0
- package/dist/src/commands/threads.js +300 -0
- package/dist/src/dom/fixtures.d.ts +1 -0
- package/dist/src/dom/fixtures.js +8 -0
- package/dist/src/dom/menus.d.ts +8 -0
- package/dist/src/dom/menus.js +40 -0
- package/dist/src/dom/message-format.d.ts +30 -0
- package/dist/src/dom/message-format.js +593 -0
- package/dist/src/dom/messages.d.ts +40 -0
- package/dist/src/dom/messages.js +127 -0
- package/dist/src/dom/selectors.d.ts +20 -0
- package/dist/src/dom/selectors.js +72 -0
- package/dist/src/dom/visible-text.d.ts +5 -0
- package/dist/src/dom/visible-text.js +27 -0
- package/dist/src/errors.d.ts +32 -0
- package/dist/src/errors.js +127 -0
- package/dist/src/index.d.ts +34 -0
- package/dist/src/index.js +34 -0
- package/dist/src/logger.d.ts +15 -0
- package/dist/src/logger.js +25 -0
- package/dist/src/runner/agent.d.ts +2 -0
- package/dist/src/runner/agent.js +17 -0
- package/dist/src/runner/index.d.ts +7 -0
- package/dist/src/runner/index.js +7 -0
- package/dist/src/runner/interruptions.d.ts +3 -0
- package/dist/src/runner/interruptions.js +65 -0
- package/dist/src/runner/responses.d.ts +30 -0
- package/dist/src/runner/responses.js +171 -0
- package/dist/src/runner/result.d.ts +3 -0
- package/dist/src/runner/result.js +128 -0
- package/dist/src/runner/resume.d.ts +11 -0
- package/dist/src/runner/resume.js +26 -0
- package/dist/src/runner/stream.d.ts +13 -0
- package/dist/src/runner/stream.js +66 -0
- package/dist/src/runner/types.d.ts +264 -0
- package/dist/src/runner/types.js +1 -0
- package/dist/src/safety/blockers.d.ts +7 -0
- package/dist/src/safety/blockers.js +60 -0
- package/dist/src/safety/redaction.d.ts +2 -0
- package/dist/src/safety/redaction.js +18 -0
- package/dist/src/safety/report-redaction.d.ts +8 -0
- package/dist/src/safety/report-redaction.js +74 -0
- package/dist/src/safety/risk.d.ts +26 -0
- package/dist/src/safety/risk.js +28 -0
- package/dist/src/scripts/backend-server.d.ts +2 -0
- package/dist/src/scripts/backend-server.js +7 -0
- package/dist/src/scripts/check-parity-suite.d.ts +1 -0
- package/dist/src/scripts/check-parity-suite.js +10 -0
- package/dist/src/scripts/continue-thread.d.ts +27 -0
- package/dist/src/scripts/continue-thread.js +388 -0
- package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
- package/dist/src/scripts/live-smoke/harness.js +151 -0
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +648 -0
- package/dist/src/scripts/live-smoke/types.d.ts +53 -0
- package/dist/src/scripts/live-smoke/types.js +1 -0
- package/dist/src/scripts/live-smoke-module.d.ts +3 -0
- package/dist/src/scripts/live-smoke-module.js +2 -0
- package/dist/src/scripts/live-smoke.d.ts +1 -0
- package/dist/src/scripts/live-smoke.js +54 -0
- package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
- package/dist/src/scripts/smoke-attach-file.js +39 -0
- package/dist/src/scripts/smoke-hi.d.ts +1 -0
- package/dist/src/scripts/smoke-hi.js +25 -0
- package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
- package/dist/src/scripts/smoke-search-open-read.js +41 -0
- package/dist/src/testing/parity-suite.d.ts +12 -0
- package/dist/src/testing/parity-suite.js +213 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.js +1 -0
- package/package.json +81 -0
- package/references/agents-runner.md +32 -0
- package/references/backend-protocol.md +238 -0
- package/references/python-parity.md +168 -0
- package/references/responses-adapter.md +28 -0
- package/references/safety.md +14 -0
- package/references/streaming.md +15 -0
- package/references/troubleshooting.md +91 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AskArgs, AskReadData, CommandResult, ComposeArgs, ComposeData, ReadLatestArgs, ReadLatestData, RuntimeEnv, SubmitArgs, SubmitData, WaitAndReadArgs, WaitArgs, WaitData } from "../types.js";
|
|
2
|
+
export type CompletionSnapshot = {
|
|
3
|
+
textStableForMs: number;
|
|
4
|
+
stableMs: number;
|
|
5
|
+
hasStopButton: boolean;
|
|
6
|
+
hasResponseActions: boolean;
|
|
7
|
+
latestText: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function isResponseComplete(snapshot: CompletionSnapshot): boolean;
|
|
10
|
+
export declare function composeMessage(env: RuntimeEnv, args: ComposeArgs): Promise<CommandResult<ComposeData>>;
|
|
11
|
+
export declare function submitMessage(env: RuntimeEnv, args?: SubmitArgs): Promise<CommandResult<SubmitData>>;
|
|
12
|
+
export declare function waitForMessage(env: RuntimeEnv, args?: WaitArgs): Promise<CommandResult<WaitData>>;
|
|
13
|
+
export declare function readLatest(env: RuntimeEnv, args?: ReadLatestArgs): Promise<CommandResult<ReadLatestData>>;
|
|
14
|
+
export declare function askMessage(env: RuntimeEnv, args: AskArgs): Promise<CommandResult<AskReadData>>;
|
|
15
|
+
export declare function waitAndRead(env: RuntimeEnv, args?: WaitAndReadArgs): Promise<CommandResult<AskReadData>>;
|
|
16
|
+
export declare function submittedUserTurnMatches(actual: string | undefined, wanted: string | undefined): boolean;
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { readPageState } from "../browser/page-state.js";
|
|
2
|
+
import { resultError, resultOk } from "../errors.js";
|
|
3
|
+
import { countPageMessages, isTransientAssistantText, readLatestMessage, readLatestMessageText, readLatestMessageTextSnapshot, readMessages } from "../dom/messages.js";
|
|
4
|
+
import { composerTextbox, copyResponseButtons, sendButton } from "../dom/selectors.js";
|
|
5
|
+
import { normalizeLineBreaks, normalizeWhitespace } from "../dom/visible-text.js";
|
|
6
|
+
import { contextFromPage } from "./context.js";
|
|
7
|
+
import { withCommandOutputText } from "./output.js";
|
|
8
|
+
import { bootstrap } from "./session.js";
|
|
9
|
+
export function isResponseComplete(snapshot) {
|
|
10
|
+
return snapshot.latestText.trim().length > 0
|
|
11
|
+
&& !isTransientAssistantText(snapshot.latestText)
|
|
12
|
+
&& snapshot.textStableForMs >= snapshot.stableMs
|
|
13
|
+
&& !snapshot.hasStopButton
|
|
14
|
+
&& snapshot.hasResponseActions;
|
|
15
|
+
}
|
|
16
|
+
export async function composeMessage(env, args) {
|
|
17
|
+
const boot = await ensurePage(env);
|
|
18
|
+
if (!boot.ok) {
|
|
19
|
+
return boot;
|
|
20
|
+
}
|
|
21
|
+
const page = env.page;
|
|
22
|
+
try {
|
|
23
|
+
const textbox = composerTextbox(page);
|
|
24
|
+
const text = args.mode === "append"
|
|
25
|
+
? `${await readLocatorText(textbox)}${args.text}`
|
|
26
|
+
: args.text;
|
|
27
|
+
await textbox.click?.();
|
|
28
|
+
await textbox.fill?.(text);
|
|
29
|
+
const actual = normalizeWhitespace(await readLocatorText(textbox));
|
|
30
|
+
const wanted = normalizeWhitespace(text);
|
|
31
|
+
if (actual !== wanted && actual.length > 0) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
status: "error",
|
|
35
|
+
warnings: [],
|
|
36
|
+
error: {
|
|
37
|
+
name: "ComposerVerificationError",
|
|
38
|
+
message: "Composer text did not match the requested prompt after fill.",
|
|
39
|
+
recoverable: true
|
|
40
|
+
},
|
|
41
|
+
context: await contextFromPage(page)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return resultOk({ text }, await contextFromPage(page));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function submitMessage(env, args = {}) {
|
|
51
|
+
const boot = await ensurePage(env);
|
|
52
|
+
if (!boot.ok) {
|
|
53
|
+
return boot;
|
|
54
|
+
}
|
|
55
|
+
const page = env.page;
|
|
56
|
+
const previousTurnCount = args.previousTurnCount ?? await countPageMessages(page).catch(() => undefined);
|
|
57
|
+
try {
|
|
58
|
+
try {
|
|
59
|
+
await sendButton(page).click?.();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
await page.keyboard?.press?.("Enter");
|
|
63
|
+
}
|
|
64
|
+
const userTurn = await waitForSubmittedUserTurn(page, args.text, previousTurnCount, args.timeoutMs ?? 30000);
|
|
65
|
+
if (userTurn === undefined) {
|
|
66
|
+
const latestUser = await readLatestMessage(page, "user", "normalized_text");
|
|
67
|
+
if (submittedUserTurnMatches(latestUser?.text, args.text)) {
|
|
68
|
+
return resultOk(submitData(latestUser?.text, await countPageMessages(page).catch(() => undefined)), await contextFromPage(page));
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
status: "timeout",
|
|
73
|
+
warnings: [],
|
|
74
|
+
error: {
|
|
75
|
+
name: "SubmitTimeout",
|
|
76
|
+
message: "No matching submitted user turn appeared before the timeout.",
|
|
77
|
+
recoverable: true
|
|
78
|
+
},
|
|
79
|
+
context: await contextFromPage(page)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return resultOk(submitData(userTurn, await countPageMessages(page).catch(() => undefined)), await contextFromPage(page));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function waitForMessage(env, args = {}) {
|
|
89
|
+
const boot = await ensurePage(env);
|
|
90
|
+
if (!boot.ok) {
|
|
91
|
+
return boot;
|
|
92
|
+
}
|
|
93
|
+
const page = env.page;
|
|
94
|
+
const timeoutMs = args.timeoutMs ?? (args.mode === "deep_research" ? 1_800_000 : 120_000);
|
|
95
|
+
const stableMs = args.stableMs ?? (args.mode === "deep_research" ? 10_000 : 2_000);
|
|
96
|
+
const pollMs = args.pollMs ?? 750;
|
|
97
|
+
const started = Date.now();
|
|
98
|
+
let lastTargetText = "";
|
|
99
|
+
let lastChangedAt = Date.now();
|
|
100
|
+
let latestAssistantCount = await countPageMessages(page, "assistant").catch(() => 0);
|
|
101
|
+
while (Date.now() - started < timeoutMs) {
|
|
102
|
+
const state = await readPageState(page).catch(() => undefined);
|
|
103
|
+
if (state?.blocker !== undefined && state.blocker.kind !== "modal") {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
status: "blocked",
|
|
107
|
+
warnings: [],
|
|
108
|
+
blocker: state.blocker,
|
|
109
|
+
context: await contextFromPage(page)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const progress = await readAssistantProgressSnapshot(page)
|
|
113
|
+
.catch(() => fallbackAssistantProgressSnapshot(page, latestAssistantCount));
|
|
114
|
+
latestAssistantCount = progress.assistantTurnCount;
|
|
115
|
+
const targetReached = waitTargetReached(args, progress);
|
|
116
|
+
const latestText = targetReached ? normalizeWhitespace(progress.latestText ?? "") : "";
|
|
117
|
+
if (latestText !== lastTargetText) {
|
|
118
|
+
lastTargetText = latestText;
|
|
119
|
+
lastChangedAt = Date.now();
|
|
120
|
+
}
|
|
121
|
+
const snapshot = {
|
|
122
|
+
latestText,
|
|
123
|
+
stableMs,
|
|
124
|
+
textStableForMs: Date.now() - lastChangedAt,
|
|
125
|
+
hasStopButton: await hasStopControl(page),
|
|
126
|
+
hasResponseActions: await hasResponseActions(page)
|
|
127
|
+
};
|
|
128
|
+
if (targetReached && isResponseComplete(snapshot)) {
|
|
129
|
+
return withCommandOutputText(resultOk({ complete: true, responseText: latestText, assistantTurnCount: latestAssistantCount, elapsedMs: Date.now() - started }, await contextFromPage(page)));
|
|
130
|
+
}
|
|
131
|
+
await sleep(page, pollMs);
|
|
132
|
+
}
|
|
133
|
+
if (lastTargetText.length > 0) {
|
|
134
|
+
return withCommandOutputText({
|
|
135
|
+
ok: false,
|
|
136
|
+
status: "partial",
|
|
137
|
+
data: {
|
|
138
|
+
complete: false,
|
|
139
|
+
responseText: lastTargetText,
|
|
140
|
+
assistantTurnCount: latestAssistantCount,
|
|
141
|
+
elapsedMs: Date.now() - started
|
|
142
|
+
},
|
|
143
|
+
warnings: ["Timed out after receiving partial assistant text."],
|
|
144
|
+
context: await contextFromPage(page)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
status: "timeout",
|
|
150
|
+
warnings: [],
|
|
151
|
+
error: {
|
|
152
|
+
name: "WaitTimeout",
|
|
153
|
+
message: "No assistant response appeared before the timeout.",
|
|
154
|
+
recoverable: true
|
|
155
|
+
},
|
|
156
|
+
context: await contextFromPage(page)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export async function readLatest(env, args = {}) {
|
|
160
|
+
const boot = await ensurePage(env);
|
|
161
|
+
if (!boot.ok) {
|
|
162
|
+
return boot;
|
|
163
|
+
}
|
|
164
|
+
const page = env.page;
|
|
165
|
+
const role = args.role ?? "assistant";
|
|
166
|
+
const format = args.format ?? "markdown";
|
|
167
|
+
const latest = await readLatestMessage(page, role, format, args.maxChars);
|
|
168
|
+
if (latest === undefined) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
status: "not_found",
|
|
172
|
+
warnings: [],
|
|
173
|
+
blocker: {
|
|
174
|
+
kind: "not_found",
|
|
175
|
+
message: `No ${role} message is currently loaded.`
|
|
176
|
+
},
|
|
177
|
+
context: await contextFromPage(page)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const data = { role, text: latest.text, format: latest.format };
|
|
181
|
+
if (latest.source !== undefined)
|
|
182
|
+
data.source = latest.source;
|
|
183
|
+
if (latest.fidelity !== undefined)
|
|
184
|
+
data.fidelity = latest.fidelity;
|
|
185
|
+
if (latest.warnings !== undefined)
|
|
186
|
+
data.warnings = latest.warnings;
|
|
187
|
+
if (latest.markdown !== undefined)
|
|
188
|
+
data.markdown = latest.markdown;
|
|
189
|
+
if (latest.visibleText !== undefined)
|
|
190
|
+
data.visibleText = latest.visibleText;
|
|
191
|
+
if (latest.normalizedText !== undefined)
|
|
192
|
+
data.normalizedText = latest.normalizedText;
|
|
193
|
+
if (latest.html !== undefined)
|
|
194
|
+
data.html = latest.html;
|
|
195
|
+
if (latest.blocks !== undefined)
|
|
196
|
+
data.blocks = latest.blocks;
|
|
197
|
+
if (latest.citations !== undefined)
|
|
198
|
+
data.citations = latest.citations;
|
|
199
|
+
if (latest.codeBlocks !== undefined)
|
|
200
|
+
data.codeBlocks = latest.codeBlocks;
|
|
201
|
+
if (latest.tables !== undefined)
|
|
202
|
+
data.tables = latest.tables;
|
|
203
|
+
if (latest.branch !== undefined)
|
|
204
|
+
data.branch = latest.branch;
|
|
205
|
+
if (latest.actions !== undefined)
|
|
206
|
+
data.actions = latest.actions;
|
|
207
|
+
if (latest.thoughtDurationText !== undefined)
|
|
208
|
+
data.thoughtDurationText = latest.thoughtDurationText;
|
|
209
|
+
if (latest.sourcesAvailable !== undefined)
|
|
210
|
+
data.sourcesAvailable = latest.sourcesAvailable;
|
|
211
|
+
return withCommandOutputText(resultOk(data, await contextFromPage(page), data.warnings ?? []));
|
|
212
|
+
}
|
|
213
|
+
export async function askMessage(env, args) {
|
|
214
|
+
const boot = await ensurePage(env);
|
|
215
|
+
if (!boot.ok) {
|
|
216
|
+
return boot;
|
|
217
|
+
}
|
|
218
|
+
const page = env.page;
|
|
219
|
+
const beforeTurnCount = await countPageMessages(page).catch(() => undefined);
|
|
220
|
+
const beforeAssistantTurnCount = await countPageMessages(page, "assistant").catch(() => undefined);
|
|
221
|
+
const composeArgs = { text: args.text, mode: "replace" };
|
|
222
|
+
if (args.timeoutMs !== undefined) {
|
|
223
|
+
composeArgs.timeoutMs = args.timeoutMs;
|
|
224
|
+
}
|
|
225
|
+
const compose = await composeMessage(env, composeArgs);
|
|
226
|
+
if (!compose.ok) {
|
|
227
|
+
return forwardFailure(compose);
|
|
228
|
+
}
|
|
229
|
+
const submitArgs = { text: args.text };
|
|
230
|
+
if (beforeTurnCount !== undefined) {
|
|
231
|
+
submitArgs.previousTurnCount = beforeTurnCount;
|
|
232
|
+
}
|
|
233
|
+
if (args.timeoutMs !== undefined) {
|
|
234
|
+
submitArgs.timeoutMs = args.timeoutMs;
|
|
235
|
+
}
|
|
236
|
+
const submit = await submitMessage(env, submitArgs);
|
|
237
|
+
if (!submit.ok) {
|
|
238
|
+
return forwardFailure(submit);
|
|
239
|
+
}
|
|
240
|
+
const readRequested = args.read === true || typeof args.read === "object";
|
|
241
|
+
let waitResult;
|
|
242
|
+
let waitFailure;
|
|
243
|
+
if (args.wait === true || typeof args.wait === "object") {
|
|
244
|
+
const waitArgs = typeof args.wait === "object" ? { ...args.wait } : {};
|
|
245
|
+
if (beforeTurnCount !== undefined) {
|
|
246
|
+
waitArgs.afterTurnCount = beforeTurnCount;
|
|
247
|
+
}
|
|
248
|
+
if (beforeAssistantTurnCount !== undefined) {
|
|
249
|
+
waitArgs.afterAssistantTurnCount = beforeAssistantTurnCount;
|
|
250
|
+
}
|
|
251
|
+
waitResult = await waitForMessage(env, waitArgs);
|
|
252
|
+
if (!waitResult.ok && waitResult.status !== "partial") {
|
|
253
|
+
if (!readRequested || readRole(args.read) === "user") {
|
|
254
|
+
return forwardFailure(waitResult);
|
|
255
|
+
}
|
|
256
|
+
waitFailure = waitResult;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
let responseText = waitResult?.data?.responseText;
|
|
260
|
+
const warnings = [];
|
|
261
|
+
if (readRequested) {
|
|
262
|
+
const read = await readLatest(env, typeof args.read === "object" ? args.read : {});
|
|
263
|
+
if (read.ok) {
|
|
264
|
+
if (waitFailure !== undefined && !readCapturedNewAssistantTurn(read, beforeTurnCount, beforeAssistantTurnCount)) {
|
|
265
|
+
return forwardFailure(waitFailure);
|
|
266
|
+
}
|
|
267
|
+
responseText = read.data?.text;
|
|
268
|
+
if (waitFailure !== undefined) {
|
|
269
|
+
warnings.push(...waitFailure.warnings, `Assistant response was read after ${waitFailure.status}, but completion was not confirmed by the wait step.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (responseText === undefined) {
|
|
273
|
+
return forwardFailure(waitFailure ?? read);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (waitFailure !== undefined && responseText === undefined) {
|
|
277
|
+
return forwardFailure(waitFailure);
|
|
278
|
+
}
|
|
279
|
+
const state = await readPageState(page).catch(() => undefined);
|
|
280
|
+
const data = { prompt: args.text };
|
|
281
|
+
const complete = waitResult?.data?.complete ?? (waitResult === undefined ? undefined : false);
|
|
282
|
+
if (complete !== undefined) {
|
|
283
|
+
data.complete = complete;
|
|
284
|
+
}
|
|
285
|
+
if (responseText !== undefined) {
|
|
286
|
+
data.responseText = responseText;
|
|
287
|
+
}
|
|
288
|
+
if (state?.conversationId !== undefined) {
|
|
289
|
+
data.conversationId = state.conversationId;
|
|
290
|
+
}
|
|
291
|
+
if (state?.title !== undefined) {
|
|
292
|
+
data.title = state.title;
|
|
293
|
+
}
|
|
294
|
+
return withCommandOutputText(resultOk(data, await contextFromPage(page), warnings));
|
|
295
|
+
}
|
|
296
|
+
export async function waitAndRead(env, args = {}) {
|
|
297
|
+
const wait = await waitForMessage(env, args);
|
|
298
|
+
if (!wait.ok && wait.status !== "partial") {
|
|
299
|
+
return forwardFailure(wait);
|
|
300
|
+
}
|
|
301
|
+
const read = await readLatest(env, args);
|
|
302
|
+
if (!read.ok) {
|
|
303
|
+
if (wait.data?.responseText !== undefined) {
|
|
304
|
+
return withCommandOutputText({
|
|
305
|
+
ok: wait.ok,
|
|
306
|
+
status: wait.status,
|
|
307
|
+
data: {
|
|
308
|
+
prompt: "",
|
|
309
|
+
responseText: wait.data.responseText,
|
|
310
|
+
complete: wait.data.complete
|
|
311
|
+
},
|
|
312
|
+
warnings: wait.warnings,
|
|
313
|
+
context: wait.context
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return forwardFailure(read);
|
|
317
|
+
}
|
|
318
|
+
return withCommandOutputText(resultOk(askReadData("", read.data?.text, wait.data?.complete), read.context, wait.warnings));
|
|
319
|
+
}
|
|
320
|
+
async function ensurePage(env) {
|
|
321
|
+
if (env.page !== undefined) {
|
|
322
|
+
return resultOk({}, await contextFromPage(env.page));
|
|
323
|
+
}
|
|
324
|
+
return bootstrap(env, { preferExistingTab: true });
|
|
325
|
+
}
|
|
326
|
+
async function waitForSubmittedUserTurn(page, text, previousTurnCount, timeoutMs) {
|
|
327
|
+
const started = Date.now();
|
|
328
|
+
const wanted = text === undefined ? undefined : normalizeWhitespace(text);
|
|
329
|
+
while (Date.now() - started < timeoutMs) {
|
|
330
|
+
const snapshot = await readLatestMessageTextSnapshot(page, "user").catch(() => undefined);
|
|
331
|
+
const latestText = snapshot?.latestText;
|
|
332
|
+
const turnCount = snapshot?.turnCount;
|
|
333
|
+
const countIncreased = previousTurnCount === undefined || (turnCount !== undefined && turnCount > previousTurnCount);
|
|
334
|
+
const latestMatches = submittedUserTurnMatches(latestText, wanted);
|
|
335
|
+
if (latestText !== undefined && countIncreased && latestMatches) {
|
|
336
|
+
return latestText;
|
|
337
|
+
}
|
|
338
|
+
await sleep(page, 250);
|
|
339
|
+
}
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
export function submittedUserTurnMatches(actual, wanted) {
|
|
343
|
+
if (wanted === undefined) {
|
|
344
|
+
return actual !== undefined && normalizeWhitespace(actual).length > 0;
|
|
345
|
+
}
|
|
346
|
+
const normalizedActual = normalizeWhitespace(actual ?? "");
|
|
347
|
+
const normalizedWanted = normalizeWhitespace(wanted);
|
|
348
|
+
if (normalizedActual === normalizedWanted || normalizedActual.includes(normalizedWanted)) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
const renderedActual = normalizeSubmittedTurnRenderedText(actual ?? "");
|
|
352
|
+
const renderedWanted = normalizeSubmittedTurnRenderedText(wanted);
|
|
353
|
+
if (renderedActual === renderedWanted || renderedActual.includes(renderedWanted)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
const structuralActual = normalizeSubmittedTurnText(actual ?? "");
|
|
357
|
+
const structuralWanted = normalizeSubmittedTurnText(wanted);
|
|
358
|
+
if (structuralActual === structuralWanted || structuralActual.includes(structuralWanted)) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
const structuralActualWithoutLanguage = normalizeSubmittedTurnText(actual ?? "", false);
|
|
362
|
+
const structuralWantedWithoutLanguage = normalizeSubmittedTurnText(wanted, false);
|
|
363
|
+
return structuralActualWithoutLanguage === structuralWantedWithoutLanguage
|
|
364
|
+
|| structuralActualWithoutLanguage.includes(structuralWantedWithoutLanguage);
|
|
365
|
+
}
|
|
366
|
+
function normalizeSubmittedTurnRenderedText(text) {
|
|
367
|
+
return normalizeWhitespace(renderSubmittedTurnMarkdownSyntax(text));
|
|
368
|
+
}
|
|
369
|
+
function normalizeSubmittedTurnText(text, preserveFenceLanguage = true) {
|
|
370
|
+
return normalizeWhitespace(renderSubmittedTurnMarkdownSyntax(text, preserveFenceLanguage)
|
|
371
|
+
.replace(/^\s{0,3}#{1,6}\s+/gm, "")
|
|
372
|
+
.replace(/^\s*[-*+]\s+/gm, "")
|
|
373
|
+
.replace(/\|/g, " ")
|
|
374
|
+
.replace(/(?:^|\s)-{3,}(?:\s|$)/g, " "));
|
|
375
|
+
}
|
|
376
|
+
function renderSubmittedTurnMarkdownSyntax(text, preserveFenceLanguage = true) {
|
|
377
|
+
return normalizeLineBreaks(text)
|
|
378
|
+
.replace(/```[ \t]*([a-z0-9_+#.-]+)?/gi, (_match, language) => language && preserveFenceLanguage ? `\n${language}\n` : "\n")
|
|
379
|
+
.replace(/~~~[ \t]*([a-z0-9_+#.-]+)?/gi, (_match, language) => language && preserveFenceLanguage ? `\n${language}\n` : "\n")
|
|
380
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
381
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1")
|
|
382
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
383
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
384
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
385
|
+
.replace(/_([^_]+)_/g, "$1");
|
|
386
|
+
}
|
|
387
|
+
async function hasStopControl(page) {
|
|
388
|
+
if (typeof page.evaluate === "function") {
|
|
389
|
+
return page.evaluate(() => {
|
|
390
|
+
const text = document.body?.innerText ?? "";
|
|
391
|
+
return /\b(stop generating|stop streaming|cancel)\b/i.test(text);
|
|
392
|
+
}).catch(() => false);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
async function hasResponseActions(page) {
|
|
397
|
+
try {
|
|
398
|
+
const copyButtons = copyResponseButtons(page);
|
|
399
|
+
const count = await copyButtons.count?.();
|
|
400
|
+
if (count !== undefined) {
|
|
401
|
+
return count > 0;
|
|
402
|
+
}
|
|
403
|
+
return await copyButtons.isVisible?.() === true;
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
if (typeof page.evaluate === "function") {
|
|
407
|
+
return page.evaluate(() => /\b(Copy response|More actions)\b/i.test(document.body?.innerText ?? "")).catch(() => false);
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async function readAssistantProgressSnapshot(page) {
|
|
413
|
+
if (typeof page.evaluate === "function") {
|
|
414
|
+
return page.evaluate(() => {
|
|
415
|
+
const nodes = Array.from(document.querySelectorAll("[data-message-author-role]"));
|
|
416
|
+
const assistantNodes = nodes.filter(node => node.getAttribute("data-message-author-role") === "assistant");
|
|
417
|
+
const latestAssistant = assistantNodes.at(-1);
|
|
418
|
+
const latestAssistantTurnIndex = latestAssistant === undefined ? undefined : nodes.indexOf(latestAssistant) + 1;
|
|
419
|
+
const snapshot = {
|
|
420
|
+
turnCount: nodes.length,
|
|
421
|
+
assistantTurnCount: assistantNodes.length
|
|
422
|
+
};
|
|
423
|
+
const latestText = latestAssistant?.innerText ?? latestAssistant?.textContent ?? undefined;
|
|
424
|
+
if (latestText !== undefined)
|
|
425
|
+
snapshot.latestText = latestText;
|
|
426
|
+
if (latestAssistantTurnIndex !== undefined)
|
|
427
|
+
snapshot.latestAssistantTurnIndex = latestAssistantTurnIndex;
|
|
428
|
+
return snapshot;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return fallbackAssistantProgressSnapshot(page, 0);
|
|
432
|
+
}
|
|
433
|
+
async function fallbackAssistantProgressSnapshot(page, previousAssistantTurnCount) {
|
|
434
|
+
const messages = await readMessages(page, { format: "normalized_text" }).catch(() => undefined);
|
|
435
|
+
if (messages !== undefined) {
|
|
436
|
+
let latestAssistantTurnIndex = -1;
|
|
437
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
438
|
+
if (messages[index]?.role === "assistant") {
|
|
439
|
+
latestAssistantTurnIndex = index;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const assistantMessages = messages.filter(message => message.role === "assistant");
|
|
444
|
+
const snapshot = {
|
|
445
|
+
turnCount: messages.length,
|
|
446
|
+
assistantTurnCount: assistantMessages.length
|
|
447
|
+
};
|
|
448
|
+
const latestAssistant = latestAssistantTurnIndex === -1 ? undefined : messages[latestAssistantTurnIndex];
|
|
449
|
+
if (latestAssistant?.text !== undefined)
|
|
450
|
+
snapshot.latestText = latestAssistant.text;
|
|
451
|
+
if (latestAssistantTurnIndex !== -1)
|
|
452
|
+
snapshot.latestAssistantTurnIndex = latestAssistantTurnIndex + 1;
|
|
453
|
+
return snapshot;
|
|
454
|
+
}
|
|
455
|
+
const snapshot = {
|
|
456
|
+
assistantTurnCount: await countPageMessages(page, "assistant").catch(() => previousAssistantTurnCount)
|
|
457
|
+
};
|
|
458
|
+
const latestText = await readLatestMessageText(page, "assistant").catch(() => undefined);
|
|
459
|
+
const turnCount = await countPageMessages(page).catch(() => undefined);
|
|
460
|
+
if (latestText !== undefined)
|
|
461
|
+
snapshot.latestText = latestText;
|
|
462
|
+
if (turnCount !== undefined)
|
|
463
|
+
snapshot.turnCount = turnCount;
|
|
464
|
+
return snapshot;
|
|
465
|
+
}
|
|
466
|
+
function waitTargetReached(args, snapshot) {
|
|
467
|
+
const assistantTargetReached = args.afterAssistantTurnCount === undefined
|
|
468
|
+
|| snapshot.assistantTurnCount > args.afterAssistantTurnCount;
|
|
469
|
+
const turnTargetReached = args.afterTurnCount === undefined
|
|
470
|
+
|| (snapshot.latestAssistantTurnIndex !== undefined
|
|
471
|
+
? snapshot.latestAssistantTurnIndex > args.afterTurnCount
|
|
472
|
+
: snapshot.turnCount !== undefined && snapshot.turnCount > args.afterTurnCount);
|
|
473
|
+
return assistantTargetReached && turnTargetReached;
|
|
474
|
+
}
|
|
475
|
+
async function readLocatorText(locator) {
|
|
476
|
+
if (typeof locator.innerText === "function") {
|
|
477
|
+
return locator.innerText().catch(() => "");
|
|
478
|
+
}
|
|
479
|
+
if (typeof locator.textContent === "function") {
|
|
480
|
+
return locator.textContent().then(text => text ?? "").catch(() => "");
|
|
481
|
+
}
|
|
482
|
+
return "";
|
|
483
|
+
}
|
|
484
|
+
async function sleep(page, ms) {
|
|
485
|
+
if (typeof page.waitForTimeout === "function") {
|
|
486
|
+
await page.waitForTimeout(ms);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
490
|
+
}
|
|
491
|
+
function submitData(userTurnText, turnCount) {
|
|
492
|
+
const data = { submitted: true };
|
|
493
|
+
if (userTurnText !== undefined) {
|
|
494
|
+
data.userTurnText = userTurnText;
|
|
495
|
+
}
|
|
496
|
+
if (turnCount !== undefined) {
|
|
497
|
+
data.turnCount = turnCount;
|
|
498
|
+
}
|
|
499
|
+
return data;
|
|
500
|
+
}
|
|
501
|
+
function askReadData(prompt, responseText, complete) {
|
|
502
|
+
const data = { prompt };
|
|
503
|
+
if (responseText !== undefined) {
|
|
504
|
+
data.responseText = responseText;
|
|
505
|
+
}
|
|
506
|
+
if (complete !== undefined) {
|
|
507
|
+
data.complete = complete;
|
|
508
|
+
}
|
|
509
|
+
return data;
|
|
510
|
+
}
|
|
511
|
+
function readRole(read) {
|
|
512
|
+
return typeof read === "object" ? read.role : undefined;
|
|
513
|
+
}
|
|
514
|
+
function readCapturedNewAssistantTurn(read, beforeTurnCount, beforeAssistantTurnCount) {
|
|
515
|
+
const assistantAdvanced = beforeAssistantTurnCount === undefined
|
|
516
|
+
|| (read.context.assistantTurnCount !== undefined && read.context.assistantTurnCount > beforeAssistantTurnCount);
|
|
517
|
+
const turnAdvanced = beforeTurnCount === undefined
|
|
518
|
+
|| (read.context.turnCount !== undefined && read.context.turnCount > beforeTurnCount);
|
|
519
|
+
return assistantAdvanced && turnAdvanced;
|
|
520
|
+
}
|
|
521
|
+
function forwardFailure(result) {
|
|
522
|
+
const forwarded = {
|
|
523
|
+
ok: false,
|
|
524
|
+
status: result.status,
|
|
525
|
+
warnings: result.warnings,
|
|
526
|
+
context: result.context
|
|
527
|
+
};
|
|
528
|
+
if (result.error !== undefined) {
|
|
529
|
+
forwarded.error = result.error;
|
|
530
|
+
}
|
|
531
|
+
if (result.blocker !== undefined) {
|
|
532
|
+
forwarded.blocker = result.blocker;
|
|
533
|
+
}
|
|
534
|
+
if (result.steps !== undefined) {
|
|
535
|
+
forwarded.steps = result.steps;
|
|
536
|
+
}
|
|
537
|
+
return forwarded;
|
|
538
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CommandResult, RuntimeEnv, SelectToolArgs, SetModeArgs } from "../types.js";
|
|
2
|
+
export declare function setMode(env: RuntimeEnv, args: SetModeArgs): Promise<CommandResult<{
|
|
3
|
+
selected: string[];
|
|
4
|
+
candidates: string[];
|
|
5
|
+
}>>;
|
|
6
|
+
export declare function selectTool(env: RuntimeEnv, args: SelectToolArgs): Promise<CommandResult<{
|
|
7
|
+
selected?: string;
|
|
8
|
+
candidates: string[];
|
|
9
|
+
}>>;
|