codex-chatgpt-control 0.3.0-alpha.1 → 0.5.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 +7 -0
- package/README.md +42 -2
- package/contracts/v1/fixtures/backend-capabilities.json +10 -0
- package/contracts/v1/fixtures/command-descriptors.json +257 -6
- package/contracts/v1/fixtures/describe-runner-run.json +5 -2
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +34 -2
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/output-json-parse-success.json +9 -0
- package/contracts/v1/fixtures/run-timeout-partial.json +15 -4
- package/contracts/v1/fixtures/stream-in-progress.ndjson +3 -0
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
- package/contracts/v1/fixtures/surface-chat-legacy.json +35 -0
- package/contracts/v1/fixtures/surface-chat-simplified.json +37 -0
- package/contracts/v1/fixtures/surface-sidebar-false-positive.json +29 -0
- package/contracts/v1/fixtures/surface-work-advanced.json +45 -0
- package/contracts/v1/fixtures/surface-work-basic.json +38 -0
- package/contracts/v1/fixtures/workflow-ask-success.json +8 -0
- package/contracts/v1/manifest.json +32 -1
- package/contracts/v1/parity-suite.json +113 -0
- package/contracts/v1/schemas/backend-request.schema.json +15 -0
- package/contracts/v1/schemas/manifest.schema.json +6 -3
- package/contracts/v1/schemas/surface-profile.schema.json +166 -0
- package/dist/codex-chatgpt-control-backend.mjs +2045 -227
- package/dist/codex-chatgpt-control.bundle.mjs +3817 -1960
- package/dist/src/backend/client.d.ts +26 -1
- package/dist/src/backend/client.js +23 -1
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +10 -0
- package/dist/src/backend/session.js +20 -0
- package/dist/src/browser/attach.d.ts +1 -0
- package/dist/src/browser/attach.js +3 -3
- package/dist/src/browser/page-state.js +5 -2
- package/dist/src/client.d.ts +31 -1
- package/dist/src/client.js +69 -14
- package/dist/src/commands/artifacts.js +1 -7
- package/dist/src/commands/configuration.d.ts +15 -0
- package/dist/src/commands/configuration.js +674 -0
- package/dist/src/commands/doctor.js +38 -1
- package/dist/src/commands/experience.d.ts +12 -0
- package/dist/src/commands/experience.js +288 -0
- package/dist/src/commands/files.js +1 -7
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +138 -16
- package/dist/src/commands/modes.js +28 -14
- package/dist/src/commands/registry.js +69 -7
- package/dist/src/commands/response-actions.js +1 -7
- package/dist/src/commands/sequence.js +24 -1
- package/dist/src/commands/session.d.ts +2 -0
- package/dist/src/commands/session.js +50 -1
- package/dist/src/commands/threads.js +1 -7
- package/dist/src/commands/work.d.ts +6 -0
- package/dist/src/commands/work.js +338 -0
- package/dist/src/dom/locale/am.d.ts +2 -1
- package/dist/src/dom/locale/am.js +2 -1
- package/dist/src/dom/locale/ar.d.ts +2 -1
- package/dist/src/dom/locale/ar.js +2 -1
- package/dist/src/dom/locale/bg.d.ts +2 -1
- package/dist/src/dom/locale/bg.js +2 -1
- package/dist/src/dom/locale/bn.d.ts +2 -1
- package/dist/src/dom/locale/bn.js +2 -1
- package/dist/src/dom/locale/bs.d.ts +2 -1
- package/dist/src/dom/locale/bs.js +2 -1
- package/dist/src/dom/locale/ca.d.ts +2 -1
- package/dist/src/dom/locale/ca.js +2 -1
- package/dist/src/dom/locale/cs.d.ts +2 -1
- package/dist/src/dom/locale/cs.js +2 -1
- package/dist/src/dom/locale/da.d.ts +2 -1
- package/dist/src/dom/locale/da.js +2 -1
- package/dist/src/dom/locale/de.d.ts +4 -3
- package/dist/src/dom/locale/de.js +4 -3
- package/dist/src/dom/locale/el.d.ts +2 -1
- package/dist/src/dom/locale/el.js +2 -1
- package/dist/src/dom/locale/en.d.ts +27 -2
- package/dist/src/dom/locale/en.js +28 -2
- package/dist/src/dom/locale/es-419.d.ts +2 -1
- package/dist/src/dom/locale/es-419.js +2 -1
- package/dist/src/dom/locale/es-ES.d.ts +2 -1
- package/dist/src/dom/locale/es-ES.js +2 -1
- package/dist/src/dom/locale/et.d.ts +2 -1
- package/dist/src/dom/locale/et.js +2 -1
- package/dist/src/dom/locale/fa.d.ts +2 -1
- package/dist/src/dom/locale/fa.js +2 -1
- package/dist/src/dom/locale/fi.d.ts +2 -1
- package/dist/src/dom/locale/fi.js +2 -1
- package/dist/src/dom/locale/fr-CA.d.ts +2 -1
- package/dist/src/dom/locale/fr-CA.js +2 -1
- package/dist/src/dom/locale/fr-FR.d.ts +3 -3
- package/dist/src/dom/locale/fr-FR.js +3 -3
- package/dist/src/dom/locale/gu.d.ts +2 -1
- package/dist/src/dom/locale/gu.js +2 -1
- package/dist/src/dom/locale/hi.d.ts +2 -1
- package/dist/src/dom/locale/hi.js +2 -1
- package/dist/src/dom/locale/hr.d.ts +2 -1
- package/dist/src/dom/locale/hr.js +2 -1
- package/dist/src/dom/locale/hu.d.ts +2 -1
- package/dist/src/dom/locale/hu.js +2 -1
- package/dist/src/dom/locale/hy.d.ts +2 -1
- package/dist/src/dom/locale/hy.js +2 -1
- package/dist/src/dom/locale/id.d.ts +2 -1
- package/dist/src/dom/locale/id.js +2 -1
- package/dist/src/dom/locale/index.d.ts +17 -2
- package/dist/src/dom/locale/index.js +65 -0
- package/dist/src/dom/locale/is.d.ts +2 -1
- package/dist/src/dom/locale/is.js +2 -1
- package/dist/src/dom/locale/it.d.ts +2 -1
- package/dist/src/dom/locale/it.js +2 -1
- package/dist/src/dom/locale/ja.d.ts +2 -1
- package/dist/src/dom/locale/ja.js +2 -1
- package/dist/src/dom/locale/ka.d.ts +2 -1
- package/dist/src/dom/locale/ka.js +2 -1
- package/dist/src/dom/locale/kk.d.ts +2 -1
- package/dist/src/dom/locale/kk.js +2 -1
- package/dist/src/dom/locale/kn.d.ts +2 -1
- package/dist/src/dom/locale/kn.js +2 -1
- package/dist/src/dom/locale/ko.d.ts +2 -1
- package/dist/src/dom/locale/ko.js +2 -1
- package/dist/src/dom/locale/lt.d.ts +2 -1
- package/dist/src/dom/locale/lt.js +2 -1
- package/dist/src/dom/locale/lv.d.ts +2 -1
- package/dist/src/dom/locale/lv.js +2 -1
- package/dist/src/dom/locale/mk.d.ts +2 -1
- package/dist/src/dom/locale/mk.js +2 -1
- package/dist/src/dom/locale/ml.d.ts +2 -1
- package/dist/src/dom/locale/ml.js +2 -1
- package/dist/src/dom/locale/mn.d.ts +2 -1
- package/dist/src/dom/locale/mn.js +2 -1
- package/dist/src/dom/locale/mr.d.ts +2 -1
- package/dist/src/dom/locale/mr.js +2 -1
- package/dist/src/dom/locale/ms.d.ts +2 -1
- package/dist/src/dom/locale/ms.js +2 -1
- package/dist/src/dom/locale/my.d.ts +2 -1
- package/dist/src/dom/locale/my.js +2 -1
- package/dist/src/dom/locale/nb.d.ts +2 -1
- package/dist/src/dom/locale/nb.js +2 -1
- package/dist/src/dom/locale/nl.d.ts +2 -1
- package/dist/src/dom/locale/nl.js +2 -1
- package/dist/src/dom/locale/pa.d.ts +2 -1
- package/dist/src/dom/locale/pa.js +2 -1
- package/dist/src/dom/locale/pl.d.ts +2 -1
- package/dist/src/dom/locale/pl.js +2 -1
- package/dist/src/dom/locale/pt-BR.d.ts +2 -1
- package/dist/src/dom/locale/pt-BR.js +2 -1
- package/dist/src/dom/locale/pt-PT.d.ts +2 -1
- package/dist/src/dom/locale/pt-PT.js +2 -1
- package/dist/src/dom/locale/ro.d.ts +2 -1
- package/dist/src/dom/locale/ro.js +2 -1
- package/dist/src/dom/locale/ru.d.ts +2 -1
- package/dist/src/dom/locale/ru.js +2 -1
- package/dist/src/dom/locale/sk.d.ts +2 -1
- package/dist/src/dom/locale/sk.js +2 -1
- package/dist/src/dom/locale/sl.d.ts +2 -1
- package/dist/src/dom/locale/sl.js +2 -1
- package/dist/src/dom/locale/so.d.ts +2 -1
- package/dist/src/dom/locale/so.js +2 -1
- package/dist/src/dom/locale/sq.d.ts +2 -1
- package/dist/src/dom/locale/sq.js +2 -1
- package/dist/src/dom/locale/sr.d.ts +2 -1
- package/dist/src/dom/locale/sr.js +2 -1
- package/dist/src/dom/locale/sv.d.ts +2 -1
- package/dist/src/dom/locale/sv.js +2 -1
- package/dist/src/dom/locale/sw.d.ts +2 -1
- package/dist/src/dom/locale/sw.js +2 -1
- package/dist/src/dom/locale/ta.d.ts +2 -1
- package/dist/src/dom/locale/ta.js +2 -1
- package/dist/src/dom/locale/te.d.ts +2 -1
- package/dist/src/dom/locale/te.js +2 -1
- package/dist/src/dom/locale/th.d.ts +2 -1
- package/dist/src/dom/locale/th.js +2 -1
- package/dist/src/dom/locale/tl.d.ts +4 -5
- package/dist/src/dom/locale/tl.js +4 -5
- package/dist/src/dom/locale/tr.d.ts +2 -1
- package/dist/src/dom/locale/tr.js +2 -1
- package/dist/src/dom/locale/types.d.ts +18 -1
- package/dist/src/dom/locale/uk.d.ts +2 -1
- package/dist/src/dom/locale/uk.js +2 -1
- package/dist/src/dom/locale/ur.d.ts +2 -1
- package/dist/src/dom/locale/ur.js +2 -1
- package/dist/src/dom/locale/vi.d.ts +2 -1
- package/dist/src/dom/locale/vi.js +2 -1
- package/dist/src/dom/locale/zh-HK.d.ts +2 -1
- package/dist/src/dom/locale/zh-HK.js +4 -3
- package/dist/src/dom/locale/zh-Hans.d.ts +2 -1
- package/dist/src/dom/locale/zh-Hans.js +2 -1
- package/dist/src/dom/locale/zh-TW.d.ts +2 -1
- package/dist/src/dom/locale/zh-TW.js +2 -1
- package/dist/src/dom/selectors.js +6 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +16 -1
- package/dist/src/runner/result.js +129 -4
- package/dist/src/runner/stream.d.ts +1 -1
- package/dist/src/runner/stream.js +6 -0
- package/dist/src/runner/types.d.ts +35 -1
- package/dist/src/safety/risk.d.ts +10 -0
- package/dist/src/safety/risk.js +10 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +6 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.js +53 -7
- package/dist/src/scripts/capture-intelligence-locales.d.ts +3 -0
- package/dist/src/scripts/capture-intelligence-locales.js +372 -3
- package/dist/src/scripts/capture-surface-profile.d.ts +23 -0
- package/dist/src/scripts/capture-surface-profile.js +304 -0
- package/dist/src/scripts/live-smoke/scenarios.js +34 -1
- package/dist/src/scripts/live-smoke.js +1 -0
- package/dist/src/types.d.ts +245 -2
- package/package.json +3 -2
- package/references/2026-07-16-chat-work-surfaces.md +87 -0
- package/references/agents-runner.md +13 -2
- package/references/backend-protocol.md +15 -5
- package/references/language-coverage.md +40 -3
- package/references/localization.md +34 -6
- package/references/python-parity.md +11 -1
- package/references/responses-adapter.md +12 -1
- package/references/troubleshooting.md +22 -2
|
@@ -10,7 +10,7 @@ import { contextFromPage } from "./context.js";
|
|
|
10
10
|
import { createDeadline } from "./deadline.js";
|
|
11
11
|
import { withCommandOutputText } from "./output.js";
|
|
12
12
|
import { createSingleFlightProbe } from "./probes.js";
|
|
13
|
-
import {
|
|
13
|
+
import { ensurePage } from "./session.js";
|
|
14
14
|
export function isResponseComplete(snapshot) {
|
|
15
15
|
return snapshot.latestText.trim().length > 0
|
|
16
16
|
&& !isTransientAssistantText(snapshot.latestText)
|
|
@@ -98,8 +98,14 @@ export async function submitMessage(env, args = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
if (userTurn === undefined) {
|
|
100
100
|
const latestUser = await readLatestMessage(page, "user", "normalized_text");
|
|
101
|
+
const generation = await readAssistantGenerationState(page).catch(() => EMPTY_GENERATION_STATE);
|
|
102
|
+
const turnCount = await countPageMessages(page).catch(() => undefined);
|
|
101
103
|
if (submittedUserTurnMatches(latestUser?.text, args.text)) {
|
|
102
|
-
return resultOk(submitData(latestUser?.text,
|
|
104
|
+
return resultOk(submitData(latestUser?.text, turnCount, generation.active ? "submitted_generating" : "submitted", generation), await contextFromPage(page));
|
|
105
|
+
}
|
|
106
|
+
const turnAdvanced = previousTurnCount !== undefined && turnCount !== undefined && turnCount > previousTurnCount;
|
|
107
|
+
if (turnAdvanced || generation.active) {
|
|
108
|
+
return resultOk(submitData(undefined, turnCount, generation.active ? "submitted_generating" : "submitted_unconfirmed", generation), await contextFromPage(page), ["Submitted prompt could not be matched to a rendered user turn yet, but ChatGPT page state indicates progress."]);
|
|
103
109
|
}
|
|
104
110
|
return {
|
|
105
111
|
ok: false,
|
|
@@ -113,7 +119,7 @@ export async function submitMessage(env, args = {}) {
|
|
|
113
119
|
context: await contextFromPage(page)
|
|
114
120
|
};
|
|
115
121
|
}
|
|
116
|
-
return resultOk(submitData(userTurn, await countPageMessages(page).catch(() => undefined)), await contextFromPage(page));
|
|
122
|
+
return resultOk(submitData(userTurn, await countPageMessages(page).catch(() => undefined), "submitted", await readAssistantGenerationState(page).catch(() => EMPTY_GENERATION_STATE)), await contextFromPage(page));
|
|
117
123
|
}
|
|
118
124
|
catch (error) {
|
|
119
125
|
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
@@ -287,6 +293,7 @@ export async function waitForMessage(env, args = {}) {
|
|
|
287
293
|
let lastChangedAt = Date.now();
|
|
288
294
|
let lastObservedTextLength = 0;
|
|
289
295
|
let latestAssistantCount = await countPageMessages(page, "assistant").catch(() => 0);
|
|
296
|
+
let lastGeneration = EMPTY_GENERATION_STATE;
|
|
290
297
|
while (Date.now() - started < timeoutMs) {
|
|
291
298
|
const probeResult = await waitSnapshotProbe(page, deadline, { timeoutMs: probeTimeoutMs });
|
|
292
299
|
addWarnings(waitWarnings, probeResult.warnings);
|
|
@@ -317,6 +324,7 @@ export async function waitForMessage(env, args = {}) {
|
|
|
317
324
|
}
|
|
318
325
|
pollIndex += 1;
|
|
319
326
|
const hasResponseActions = await resolveResponseActions(page, snapshot);
|
|
327
|
+
lastGeneration = snapshot.generation;
|
|
320
328
|
if (targetReached && snapshot.generation.stopped && snapshot.text.length > 0) {
|
|
321
329
|
const stoppedText = normalizeWhitespace(await fetchLatestAssistantText(page) ?? "");
|
|
322
330
|
// A failed re-read must not fabricate an empty capture: real text was observed in
|
|
@@ -324,6 +332,9 @@ export async function waitForMessage(env, args = {}) {
|
|
|
324
332
|
const data = stoppedText.length > 0
|
|
325
333
|
? waitDataFromText(args, false, stoppedText, latestAssistantCount, Date.now() - started)
|
|
326
334
|
: waitDataWithoutText(latestAssistantCount, Date.now() - started);
|
|
335
|
+
data.completionState = "stopped";
|
|
336
|
+
data.generationActive = snapshot.generation.active;
|
|
337
|
+
data.generationSignals = snapshot.generation.signals;
|
|
327
338
|
if (stoppedText.length === 0) {
|
|
328
339
|
waitWarnings.add(`The interrupted assistant text (~${snapshot.text.length} chars observed) could not be re-read at wait exit; call messages.readLatest on the same thread to capture it.`);
|
|
329
340
|
}
|
|
@@ -361,6 +372,9 @@ export async function waitForMessage(env, args = {}) {
|
|
|
361
372
|
};
|
|
362
373
|
if (fetchedMetadata.hash === snapshot.text.hash && isResponseComplete(completionSnapshot)) {
|
|
363
374
|
const data = waitDataFromText(args, true, latestText, latestAssistantCount, Date.now() - started);
|
|
375
|
+
data.completionState = "complete";
|
|
376
|
+
data.generationActive = false;
|
|
377
|
+
data.generationSignals = snapshot.generation.signals;
|
|
364
378
|
return withCommandOutputText(resultOk(data, await contextFromPage(page), [...waitWarnings, ...responseContentWarnings(args, true)]));
|
|
365
379
|
}
|
|
366
380
|
if (latestText.length > 0) {
|
|
@@ -374,7 +388,11 @@ export async function waitForMessage(env, args = {}) {
|
|
|
374
388
|
if (lastObservedTextLength > 0) {
|
|
375
389
|
const partialText = await fetchLatestAssistantText(page);
|
|
376
390
|
if (partialText !== undefined && normalizeWhitespace(partialText).length > 0) {
|
|
377
|
-
const
|
|
391
|
+
const normalizedPartialText = normalizeWhitespace(partialText);
|
|
392
|
+
const data = waitDataFromText(args, false, normalizedPartialText, latestAssistantCount, Date.now() - started);
|
|
393
|
+
data.completionState = completionStateFromGeneration(lastGeneration, false, normalizedPartialText.length > 0);
|
|
394
|
+
data.generationActive = lastGeneration.active;
|
|
395
|
+
data.generationSignals = lastGeneration.signals;
|
|
378
396
|
return withCommandOutputText({
|
|
379
397
|
ok: false,
|
|
380
398
|
status: "partial",
|
|
@@ -552,9 +570,51 @@ export async function readLatest(env, args = {}) {
|
|
|
552
570
|
data.thoughtDurationText = latest.thoughtDurationText;
|
|
553
571
|
if (latest.sourcesAvailable !== undefined)
|
|
554
572
|
data.sourcesAvailable = latest.sourcesAvailable;
|
|
573
|
+
if (role === "assistant") {
|
|
574
|
+
const generation = await readAssistantGenerationState(page).catch(() => EMPTY_GENERATION_STATE);
|
|
575
|
+
data.completionState = completionStateFromGeneration(generation, undefined, latest.text.trim().length > 0);
|
|
576
|
+
data.generationActive = generation.active;
|
|
577
|
+
data.generationSignals = generation.signals;
|
|
578
|
+
}
|
|
555
579
|
return withCommandOutputText(resultOk(data, await contextFromPage(page), data.warnings ?? []));
|
|
556
580
|
}
|
|
581
|
+
export async function messageStatus(env, args = {}) {
|
|
582
|
+
const boot = await ensurePage(env);
|
|
583
|
+
if (!boot.ok) {
|
|
584
|
+
return boot;
|
|
585
|
+
}
|
|
586
|
+
const page = env.page;
|
|
587
|
+
// Reuse the combined wait-snapshot evaluate for counts and generation state in a single
|
|
588
|
+
// round trip; it never carries full assistant text, so a bounded preview is fetched
|
|
589
|
+
// separately below only when the snapshot proves an assistant turn has text.
|
|
590
|
+
const snapshot = await readWaitDomSnapshot(page).catch(() => undefined) ?? await fallbackWaitSnapshot(page, 0);
|
|
591
|
+
const maxPreviewChars = Math.max(0, args.maxPreviewChars ?? 240);
|
|
592
|
+
const latestText = snapshot.text.length > 0
|
|
593
|
+
? normalizeWhitespace(await fetchLatestAssistantText(page) ?? "")
|
|
594
|
+
: "";
|
|
595
|
+
const data = {
|
|
596
|
+
turnCount: snapshot.turnCount,
|
|
597
|
+
assistantTurnCount: snapshot.assistantTurnCount,
|
|
598
|
+
completionState: completionStateFromGeneration(snapshot.generation, undefined, latestText.length > 0),
|
|
599
|
+
generationActive: snapshot.generation.active,
|
|
600
|
+
generationSignals: snapshot.generation.signals
|
|
601
|
+
};
|
|
602
|
+
if (snapshot.latestAssistantTurnIndex !== undefined)
|
|
603
|
+
data.latestAssistantTurnIndex = snapshot.latestAssistantTurnIndex;
|
|
604
|
+
if (latestText.length > 0) {
|
|
605
|
+
data.latestAssistantTextLength = latestText.length;
|
|
606
|
+
data.latestAssistantPreview = latestText.length > maxPreviewChars
|
|
607
|
+
? `${latestText.slice(0, Math.max(0, maxPreviewChars - 1))}...`
|
|
608
|
+
: latestText;
|
|
609
|
+
}
|
|
610
|
+
return resultOk(data, await contextFromPage(page));
|
|
611
|
+
}
|
|
557
612
|
export async function askMessage(env, args) {
|
|
613
|
+
const normalizedPrompt = normalizeAskPrompt(args);
|
|
614
|
+
if (!normalizedPrompt.ok) {
|
|
615
|
+
return normalizedPrompt.result;
|
|
616
|
+
}
|
|
617
|
+
const prompt = normalizedPrompt.text;
|
|
558
618
|
const boot = await ensurePage(env);
|
|
559
619
|
if (!boot.ok) {
|
|
560
620
|
return boot;
|
|
@@ -562,7 +622,7 @@ export async function askMessage(env, args) {
|
|
|
562
622
|
const page = env.page;
|
|
563
623
|
const beforeTurnCount = await countPageMessages(page).catch(() => undefined);
|
|
564
624
|
const beforeAssistantTurnCount = await countPageMessages(page, "assistant").catch(() => undefined);
|
|
565
|
-
const composeArgs = { text:
|
|
625
|
+
const composeArgs = { text: prompt, mode: "replace" };
|
|
566
626
|
if (args.timeoutMs !== undefined) {
|
|
567
627
|
composeArgs.timeoutMs = args.timeoutMs;
|
|
568
628
|
}
|
|
@@ -570,7 +630,7 @@ export async function askMessage(env, args) {
|
|
|
570
630
|
if (!compose.ok) {
|
|
571
631
|
return forwardFailure(compose);
|
|
572
632
|
}
|
|
573
|
-
const submitArgs = { text:
|
|
633
|
+
const submitArgs = { text: prompt };
|
|
574
634
|
if (beforeTurnCount !== undefined) {
|
|
575
635
|
submitArgs.previousTurnCount = beforeTurnCount;
|
|
576
636
|
}
|
|
@@ -627,7 +687,7 @@ export async function askMessage(env, args) {
|
|
|
627
687
|
return forwardFailure(waitFailure);
|
|
628
688
|
}
|
|
629
689
|
const state = await readPageState(page).catch(() => undefined);
|
|
630
|
-
const data = { prompt
|
|
690
|
+
const data = { prompt };
|
|
631
691
|
const complete = waitResult?.data?.complete ?? (waitResult === undefined ? undefined : false);
|
|
632
692
|
if (complete !== undefined) {
|
|
633
693
|
data.complete = complete;
|
|
@@ -641,6 +701,7 @@ export async function askMessage(env, args) {
|
|
|
641
701
|
if (state?.title !== undefined) {
|
|
642
702
|
data.title = state.title;
|
|
643
703
|
}
|
|
704
|
+
applyMessageState(data, submit.data, waitResult?.data);
|
|
644
705
|
if (waitFailure !== undefined) {
|
|
645
706
|
data.complete = false;
|
|
646
707
|
return withCommandOutputText({
|
|
@@ -678,7 +739,7 @@ export async function waitAndRead(env, args = {}) {
|
|
|
678
739
|
}
|
|
679
740
|
return forwardFailure(read);
|
|
680
741
|
}
|
|
681
|
-
const data = askReadData("", read.data?.text, wait.data?.complete);
|
|
742
|
+
const data = askReadData("", read.data?.text, wait.data?.complete, wait.data, read.data);
|
|
682
743
|
const warnings = [...read.warnings, ...wait.warnings];
|
|
683
744
|
if (!wait.ok && wait.status === "partial") {
|
|
684
745
|
data.complete = false;
|
|
@@ -695,12 +756,6 @@ export async function waitAndRead(env, args = {}) {
|
|
|
695
756
|
}
|
|
696
757
|
return withCommandOutputText(resultOk(data, read.context, warnings));
|
|
697
758
|
}
|
|
698
|
-
async function ensurePage(env) {
|
|
699
|
-
if (env.page !== undefined) {
|
|
700
|
-
return resultOk({}, await contextFromPage(env.page));
|
|
701
|
-
}
|
|
702
|
-
return bootstrap(env, { preferExistingTab: true });
|
|
703
|
-
}
|
|
704
759
|
async function waitForSubmittedUserTurn(page, text, previousTurnCount, timeoutMs) {
|
|
705
760
|
const started = Date.now();
|
|
706
761
|
const wanted = text === undefined ? undefined : normalizeWhitespace(text);
|
|
@@ -820,7 +875,7 @@ async function sleep(page, ms) {
|
|
|
820
875
|
}
|
|
821
876
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
822
877
|
}
|
|
823
|
-
function submitData(userTurnText, turnCount) {
|
|
878
|
+
function submitData(userTurnText, turnCount, submissionState, generation) {
|
|
824
879
|
const data = { submitted: true };
|
|
825
880
|
if (userTurnText !== undefined) {
|
|
826
881
|
data.userTurnText = userTurnText;
|
|
@@ -828,9 +883,13 @@ function submitData(userTurnText, turnCount) {
|
|
|
828
883
|
if (turnCount !== undefined) {
|
|
829
884
|
data.turnCount = turnCount;
|
|
830
885
|
}
|
|
886
|
+
data.submissionState = submissionState;
|
|
887
|
+
data.completionState = completionStateFromGeneration(generation);
|
|
888
|
+
data.generationActive = generation.active;
|
|
889
|
+
data.generationSignals = generation.signals;
|
|
831
890
|
return data;
|
|
832
891
|
}
|
|
833
|
-
function askReadData(prompt, responseText, complete) {
|
|
892
|
+
function askReadData(prompt, responseText, complete, wait, read) {
|
|
834
893
|
const data = { prompt };
|
|
835
894
|
if (responseText !== undefined) {
|
|
836
895
|
data.responseText = responseText;
|
|
@@ -838,8 +897,71 @@ function askReadData(prompt, responseText, complete) {
|
|
|
838
897
|
if (complete !== undefined) {
|
|
839
898
|
data.complete = complete;
|
|
840
899
|
}
|
|
900
|
+
applyMessageState(data, undefined, wait, read);
|
|
841
901
|
return data;
|
|
842
902
|
}
|
|
903
|
+
function applyMessageState(data, submit, wait, read) {
|
|
904
|
+
if (submit?.submissionState !== undefined)
|
|
905
|
+
data.submissionState = submit.submissionState;
|
|
906
|
+
const completionState = wait?.completionState ?? read?.completionState ?? submit?.completionState;
|
|
907
|
+
if (completionState !== undefined)
|
|
908
|
+
data.completionState = completionState;
|
|
909
|
+
const generationActive = wait?.generationActive ?? read?.generationActive ?? submit?.generationActive;
|
|
910
|
+
if (generationActive !== undefined)
|
|
911
|
+
data.generationActive = generationActive;
|
|
912
|
+
const generationSignals = wait?.generationSignals ?? read?.generationSignals ?? submit?.generationSignals;
|
|
913
|
+
if (generationSignals !== undefined)
|
|
914
|
+
data.generationSignals = generationSignals;
|
|
915
|
+
}
|
|
916
|
+
function completionStateFromGeneration(generation, complete, hasPartialText = false) {
|
|
917
|
+
if (complete === true)
|
|
918
|
+
return "complete";
|
|
919
|
+
if (generation.active)
|
|
920
|
+
return "generating";
|
|
921
|
+
if (generation.stopped)
|
|
922
|
+
return "stopped";
|
|
923
|
+
if (complete === false || hasPartialText)
|
|
924
|
+
return "partial";
|
|
925
|
+
return "unknown";
|
|
926
|
+
}
|
|
927
|
+
function normalizeAskPrompt(args) {
|
|
928
|
+
const text = args.text;
|
|
929
|
+
const prompt = args.prompt;
|
|
930
|
+
if (text !== undefined && prompt !== undefined && text !== prompt) {
|
|
931
|
+
return {
|
|
932
|
+
ok: false,
|
|
933
|
+
result: {
|
|
934
|
+
ok: false,
|
|
935
|
+
status: "error",
|
|
936
|
+
warnings: [],
|
|
937
|
+
error: {
|
|
938
|
+
name: "InvalidAskArgs",
|
|
939
|
+
message: "messages.ask received both text and prompt with different values.",
|
|
940
|
+
recoverable: false
|
|
941
|
+
},
|
|
942
|
+
context: { timestamp: new Date().toISOString() }
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const normalized = text ?? prompt;
|
|
947
|
+
if (normalized === undefined || normalized.trim().length === 0) {
|
|
948
|
+
return {
|
|
949
|
+
ok: false,
|
|
950
|
+
result: {
|
|
951
|
+
ok: false,
|
|
952
|
+
status: "error",
|
|
953
|
+
warnings: [],
|
|
954
|
+
error: {
|
|
955
|
+
name: "InvalidAskArgs",
|
|
956
|
+
message: "messages.ask requires a non-empty text or prompt argument.",
|
|
957
|
+
recoverable: false
|
|
958
|
+
},
|
|
959
|
+
context: { timestamp: new Date().toISOString() }
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
return { ok: true, text: normalized };
|
|
964
|
+
}
|
|
843
965
|
function readRole(read) {
|
|
844
966
|
return typeof read === "object" ? read.role : undefined;
|
|
845
967
|
}
|
|
@@ -4,11 +4,12 @@ import { localeLabels } from "../dom/locale-labels.js";
|
|
|
4
4
|
import { isShortLatinToken, normalizeForLabelMatch, visibleLabelMatches } from "../dom/label-match.js";
|
|
5
5
|
import { normalizeLabel, normalizeWhitespace } from "../dom/visible-text.js";
|
|
6
6
|
import { contextFromPage } from "./context.js";
|
|
7
|
-
import {
|
|
7
|
+
import { ensurePage } from "./session.js";
|
|
8
8
|
const DEFAULT_MODE_EFFORT = "Thinking";
|
|
9
9
|
const CURRENT_MODE_LABELS = dedupeLabels([
|
|
10
10
|
...localeLabels.modeLabels,
|
|
11
11
|
...Object.values(localeLabels.modeOptions).flat(),
|
|
12
|
+
...Object.values(localeLabels.configurationOptions).flat(),
|
|
12
13
|
]);
|
|
13
14
|
const MODE_OPENER_LABELS = [...CURRENT_MODE_LABELS.filter(label => label !== "Pro"), ...localeLabels.modeOpenerExtra];
|
|
14
15
|
const MODEL_VERSION_FAMILY_PATTERN = /^gpt[\s-]/i;
|
|
@@ -224,12 +225,6 @@ export async function selectTool(env, args) {
|
|
|
224
225
|
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
|
-
async function ensurePage(env) {
|
|
228
|
-
if (env.page !== undefined) {
|
|
229
|
-
return resultOk({}, await contextFromPage(env.page));
|
|
230
|
-
}
|
|
231
|
-
return bootstrap(env, { preferExistingTab: true });
|
|
232
|
-
}
|
|
233
228
|
async function clickFirstUniqueButton(page, labels) {
|
|
234
229
|
for (const label of labels) {
|
|
235
230
|
const roleLocator = page.getByRole?.("button", { name: label, exact: true });
|
|
@@ -508,22 +503,35 @@ async function selectModelVersion(page, requestedVersion, currentCandidates, tim
|
|
|
508
503
|
candidates = await enumerateVisibleMenuItems(page);
|
|
509
504
|
}
|
|
510
505
|
}
|
|
511
|
-
let exact =
|
|
506
|
+
let exact = findExactSelectableMenuItem(candidates, requestedVersion);
|
|
512
507
|
if (exact !== undefined) {
|
|
513
|
-
return await
|
|
508
|
+
return await clickResolvedMenuItem(page, exact)
|
|
514
509
|
? { selected: exact.label, candidates: candidates.map(candidate => candidate.label) }
|
|
515
510
|
: { candidates: candidates.map(candidate => candidate.label) };
|
|
516
511
|
}
|
|
517
512
|
const opened = await openModelVersionSubmenu(page, currentCandidates);
|
|
518
513
|
candidates = await enumerateVisibleMenuItems(page);
|
|
519
|
-
exact =
|
|
514
|
+
exact = findExactSelectableMenuItem(candidates, requestedVersion);
|
|
520
515
|
if (!opened || exact === undefined) {
|
|
521
516
|
return { candidates: candidates.map(candidate => candidate.label) };
|
|
522
517
|
}
|
|
523
|
-
return await
|
|
518
|
+
return await clickResolvedMenuItem(page, exact)
|
|
524
519
|
? { selected: exact.label, candidates: candidates.map(candidate => candidate.label) }
|
|
525
520
|
: { candidates: candidates.map(candidate => candidate.label) };
|
|
526
521
|
}
|
|
522
|
+
function isModelVersionSubmenuOpener(item) {
|
|
523
|
+
return item.hasPopup === true
|
|
524
|
+
|| (item.role !== "menuitemradio" && MODEL_VERSION_FAMILY_PATTERN.test(item.label));
|
|
525
|
+
}
|
|
526
|
+
async function clickResolvedMenuItem(page, item) {
|
|
527
|
+
if (item.testId !== undefined && await clickIfUnique(page.locator?.(`[data-testid="${escapeAttributeValue(item.testId)}"]`))) {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
if (item.role !== undefined && await clickIfUnique(page.getByRole?.(item.role, { name: item.label, exact: true }))) {
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
return clickMenuItem(page, item.label);
|
|
534
|
+
}
|
|
527
535
|
async function openModelVersionSubmenu(page, candidates) {
|
|
528
536
|
const submenuOpeners = candidates.filter(item => item.hasPopup === true || MODEL_VERSION_FAMILY_PATTERN.test(item.label));
|
|
529
537
|
if (submenuOpeners.length === 0) {
|
|
@@ -604,9 +612,10 @@ async function modelVersionMenuItemsAreVisible(page) {
|
|
|
604
612
|
return (await enumerateVisibleMenuItems(page))
|
|
605
613
|
.some(candidate => candidate.role === "menuitemradio" && MODEL_VERSION_LABEL_PATTERN.test(candidate.label));
|
|
606
614
|
}
|
|
607
|
-
function
|
|
615
|
+
function findExactSelectableMenuItem(items, wanted) {
|
|
608
616
|
const normalized = normalizeLabel(wanted);
|
|
609
|
-
const matches = items.filter(item => item.normalized === normalized
|
|
617
|
+
const matches = items.filter(item => item.normalized === normalized
|
|
618
|
+
&& !isModelVersionSubmenuOpener(item));
|
|
610
619
|
return matches.length === 1 ? matches[0] : undefined;
|
|
611
620
|
}
|
|
612
621
|
function dedupeLabels(labels) {
|
|
@@ -666,9 +675,12 @@ async function visibleModeButtonLabelList(page) {
|
|
|
666
675
|
}
|
|
667
676
|
return text.includes(token);
|
|
668
677
|
};
|
|
678
|
+
const scopedRoots = Array.from(document.querySelectorAll("main form, main [data-testid*='composer' i], main [class*='composer' i]"));
|
|
669
679
|
return Array.from(document.querySelectorAll("button, [role='button']"))
|
|
670
680
|
.map(node => {
|
|
671
681
|
const element = node;
|
|
682
|
+
if (scopedRoots.length > 0 && !scopedRoots.some(root => root.contains(node)))
|
|
683
|
+
return "";
|
|
672
684
|
const visibleText = (element.innerText ?? element.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
673
685
|
const ariaLabel = (element.getAttribute("aria-label") ?? "").replace(/\s+/g, " ").trim();
|
|
674
686
|
const label = visibleText.length > 0 ? visibleText : ariaLabel;
|
|
@@ -680,7 +692,9 @@ async function visibleModeButtonLabelList(page) {
|
|
|
680
692
|
if (visibleText.length === 0 && /feedback|conversation options|dismiss/i.test(ariaLabel))
|
|
681
693
|
return "";
|
|
682
694
|
const normalized = label.toLowerCase();
|
|
683
|
-
|
|
695
|
+
const structuralModelControl = /model-switcher|model-selector|mode-selector/i.test(testId)
|
|
696
|
+
|| /\b(?:gpt|sol|luna|terra)\b/i.test(label);
|
|
697
|
+
if (!structuralModelControl && !normalizedModeLabels.some(modeLabel => tokenMatches(normalized, modeLabel)))
|
|
684
698
|
return "";
|
|
685
699
|
return label;
|
|
686
700
|
})
|
|
@@ -7,8 +7,9 @@ const descriptors = [
|
|
|
7
7
|
`await chatgpt.askInThread({ thread: { type: "search", query: "Naming macOS Utility" }, prompt: "Continue." });`,
|
|
8
8
|
`await chatgpt.askInThread({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, prompt: "Continue." });`
|
|
9
9
|
]),
|
|
10
|
-
workflow("askWithFiles", "Attach absolute local file paths, optionally
|
|
11
|
-
`await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true,
|
|
10
|
+
workflow("askWithFiles", "Attach absolute local file paths, optionally open/configure Chat or Work, ask, wait, and read.", [
|
|
11
|
+
`await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, experience: "chat", configuration: { intelligence: "Pro" }, files: ["/absolute/host/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`,
|
|
12
|
+
`await chatgpt.askWithFiles({ mode: { model: "Pro" }, files: ["/absolute/host/path/image.jpg"], prompt: "Describe this image.", wait: true });`
|
|
12
13
|
]),
|
|
13
14
|
workflow("askAndDownload", "Ask ChatGPT to produce a visible downloadable output and save the latest exposed file.", [
|
|
14
15
|
`await chatgpt.askAndDownload({ prompt: "Create a CSV.", download: { destDir: "/absolute/host/output" }, wait: true });`
|
|
@@ -16,13 +17,16 @@ const descriptors = [
|
|
|
16
17
|
workflow("runMessages", "Run sequential prompts where later prompts can use earlier step data.", [
|
|
17
18
|
`await chatgpt.runMessages({ messages: [{ id: "first", prompt: "alpha" }, { id: "second", prompt: "beta" }] });`
|
|
18
19
|
]),
|
|
19
|
-
workflow("runner.run", "Agents-style facade: run a visible ChatGPT
|
|
20
|
-
`const agent = chatgpt.agent({ name: "reviewer", instructions: "Review deeply." }); await chatgpt.runner.run(agent, { input: "Review this.", thread: { type: "new" } });`,
|
|
20
|
+
workflow("runner.run", "Agents-style facade: run a visible ChatGPT agent against input, files, thread, existing-tab, experience, configuration, legacy mode, and response options.", [
|
|
21
|
+
`const agent = chatgpt.agent({ name: "reviewer", instructions: "Review deeply." }); await chatgpt.runner.run(agent, { input: "Review this.", thread: { type: "new" }, experience: "chat" });`,
|
|
21
22
|
`await chatgpt.runner.run(agent, { input: "Continue.", thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true });`
|
|
22
23
|
]),
|
|
23
24
|
workflow("responses.create", "Narrow Responses-shaped adapter over the visible ChatGPT browser-control runner; rejects unsupported API-only fields before prompt submission.", [
|
|
24
25
|
`await chatgpt.responses.create({ input: "Summarize.", thread: { type: "current" }, text: { format: "markdown" }, stream: false });`
|
|
25
26
|
]),
|
|
27
|
+
workflow("work.start", "Open Work, optionally apply model/effort/speed configuration, submit one task, and optionally wait/read without blind resubmission.", [
|
|
28
|
+
`await chatgpt.work.start({ prompt: "Analyze the attached package.", files: ["/absolute/path/package.tgz"], configuration: { model: "GPT-5.6 Sol", effort: "High", speed: "Fast" }, wait: true, read: { format: "markdown" } });`
|
|
29
|
+
]),
|
|
26
30
|
workflow("copyLatest", "Copy or DOM-read the latest assistant response with Markdown-first fidelity.", [
|
|
27
31
|
`await chatgpt.copyLatest({ prefer: "clipboard" });`
|
|
28
32
|
]),
|
|
@@ -63,6 +67,14 @@ const descriptors = [
|
|
|
63
67
|
`await chatgpt.createReport(result, { destDir: "/absolute/host/reports" });`
|
|
64
68
|
]),
|
|
65
69
|
primitive("session.bootstrap", "Attach to ChatGPT in Chrome and detect login/blocker state.", 30000),
|
|
70
|
+
primitive("experience.detect", "Detect whether the scoped visible composer is Chat, Work, or unknown and return its selector profile.", 30000),
|
|
71
|
+
primitive("experience.open", "Open Chat or Work and verify the target surface from scoped composer evidence.", 30000),
|
|
72
|
+
primitive("configuration.inspect", "Inspect the active Chat or Work selector profile, axes, options, and selected values.", 30000),
|
|
73
|
+
primitive("configuration.apply", "Apply model/intelligence/effort/speed configuration one axis at a time and verify the final visible state.", 30000),
|
|
74
|
+
primitive("work.status", "Read current Work task progress and optionally enumerate visible artifacts.", 5000),
|
|
75
|
+
primitive("work.wait", "Wait for the current Work task response to stabilize.", 120000),
|
|
76
|
+
primitive("work.steer", "Submit a visible steering message to the current Work task without opening a new task.", 120000),
|
|
77
|
+
primitive("work.readLatest", "Read the latest response from the current Work task.", 30000),
|
|
66
78
|
primitive("threads.new", "Open a new ChatGPT thread.", 30000),
|
|
67
79
|
primitive("threads.search", "Search visible ChatGPT history by query.", 30000),
|
|
68
80
|
primitive("threads.open", "Open a thread by URL, conversation id, title, or search result.", 30000),
|
|
@@ -71,6 +83,7 @@ const descriptors = [
|
|
|
71
83
|
primitive("messages.ask", "Compose, submit, optionally wait, and optionally read.", 120000),
|
|
72
84
|
primitive("messages.wait", "Wait for the latest assistant response to stabilize.", 120000),
|
|
73
85
|
primitive("messages.readLatest", "Read the latest message as Markdown, normalized text, blocks, or HTML.", 30000),
|
|
86
|
+
primitive("messages.status", "Read a compact latest-assistant progress snapshot without treating partial text as complete.", 5000),
|
|
74
87
|
primitive("messages.waitAndRead", "Wait for completion and read the latest message.", 120000),
|
|
75
88
|
primitive("artifacts.listLatest", "Detect the latest visible generated ChatGPT artifact, such as an image-only result.", 30000),
|
|
76
89
|
primitive("artifacts.wait", "Wait for a visible generated ChatGPT artifact to appear and stabilize.", 120000),
|
|
@@ -82,8 +95,8 @@ const descriptors = [
|
|
|
82
95
|
primitive("projects.sources.planAdd", "Dry-run an append-only Project Sources file add from explicit local files without opening ChatGPT.", 30000),
|
|
83
96
|
primitive("projects.sources.add", "Append explicit local files to a visible ChatGPT Project Sources list after confirmMutation: true.", 180000),
|
|
84
97
|
primitive("response.copy", "Click Copy response and return clipboard Markdown, with DOM fallback.", 5000),
|
|
85
|
-
primitive("modes.set", "
|
|
86
|
-
primitive("modes.get", "
|
|
98
|
+
primitive("modes.set", "Legacy compatibility command: select a visible model/intelligence/effort/version candidate with historical warning-oriented verification.", 30000),
|
|
99
|
+
primitive("modes.get", "Legacy compatibility command: read mode labels shown on visible composer controls without changing them.", 30000),
|
|
87
100
|
primitive("tools.select", "Select a visible ChatGPT tool when unambiguous.", 30000)
|
|
88
101
|
];
|
|
89
102
|
export function commandDescriptors() {
|
|
@@ -191,12 +204,24 @@ function workflowArgs(name) {
|
|
|
191
204
|
return { first: "first message", second: "second message" };
|
|
192
205
|
if (name === "new-ask-read")
|
|
193
206
|
return { prompt: "message to send" };
|
|
207
|
+
if (name === "work.start") {
|
|
208
|
+
return {
|
|
209
|
+
prompt: "visible Work task prompt",
|
|
210
|
+
newTask: "start from a blank Work task; defaults to true",
|
|
211
|
+
files: "optional absolute local file paths",
|
|
212
|
+
configuration: "optional Work model, effort, and speed values",
|
|
213
|
+
wait: "optional wait behavior",
|
|
214
|
+
read: "optional response capture behavior"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
194
217
|
if (name === "askWithFiles") {
|
|
195
218
|
return {
|
|
196
219
|
files: "absolute local file paths to attach before submitting",
|
|
197
220
|
prompt: "message to send after files are attached",
|
|
198
221
|
thread: "optional thread selector",
|
|
199
222
|
existingTab: "true or explicit policy to claim a user-open Chrome tab instead of opening a replacement",
|
|
223
|
+
experience: "optional chat or work surface",
|
|
224
|
+
configuration: "optional strict visible configuration such as { intelligence: \"Pro\" } or Work model/effort/speed",
|
|
200
225
|
mode: "optional visible mode selection, e.g. { model: \"Pro\" } or { intelligence: \"Pro\", modelVersion: \"5.4\" }",
|
|
201
226
|
wait: "true or wait options; defaults to true",
|
|
202
227
|
read: "true or read options such as { format: \"markdown\" }; defaults to Markdown",
|
|
@@ -207,6 +232,9 @@ function workflowArgs(name) {
|
|
|
207
232
|
prompt: "message to send or workflow-specific input",
|
|
208
233
|
thread: "optional thread selector",
|
|
209
234
|
existingTab: "true or explicit policy to claim a user-open Chrome tab instead of opening a replacement",
|
|
235
|
+
experience: "optional chat or work surface",
|
|
236
|
+
configuration: "optional surface-aware visible configuration",
|
|
237
|
+
mode: "legacy visible mode preference",
|
|
210
238
|
report: "optional redacted report settings"
|
|
211
239
|
};
|
|
212
240
|
}
|
|
@@ -243,6 +271,24 @@ function primitiveArgs(name) {
|
|
|
243
271
|
};
|
|
244
272
|
if (name === "messages.readLatest")
|
|
245
273
|
return { role: "assistant or user", format: "markdown, normalized_text, visible_text, html, blocks, or all" };
|
|
274
|
+
if (name === "messages.status")
|
|
275
|
+
return { maxPreviewChars: "maximum latest-assistant preview characters to return; defaults to 240" };
|
|
276
|
+
if (name === "experience.detect")
|
|
277
|
+
return { timeoutMs: "optional timeout for attaching to the ChatGPT page" };
|
|
278
|
+
if (name === "experience.open")
|
|
279
|
+
return { experience: "chat or work", timeoutMs: "optional verified-switch timeout" };
|
|
280
|
+
if (name === "configuration.inspect")
|
|
281
|
+
return { experience: "optional expected chat or work surface", includeOptions: "open visible axis menus to enumerate options; defaults to true", timeoutMs: "optional inspection timeout" };
|
|
282
|
+
if (name === "configuration.apply")
|
|
283
|
+
return { experience: "optional target chat or work surface", desired: "model, intelligence, effort, speed, and/or modelVersion values", strict: "fail when visible postconditions are not verified; defaults to true", timeoutMs: "optional selection timeout" };
|
|
284
|
+
if (name === "work.status")
|
|
285
|
+
return { includeArtifacts: "include the latest visible artifact inventory; defaults to false for lightweight polling", maxPreviewChars: "maximum assistant preview characters" };
|
|
286
|
+
if (name === "work.wait")
|
|
287
|
+
return { timeoutMs: "optional Work wait timeout", stableMs: "required response stability window", pollMs: "polling interval" };
|
|
288
|
+
if (name === "work.steer")
|
|
289
|
+
return { prompt: "visible steering message for the current task", wait: "optional wait behavior", read: "optional response capture behavior" };
|
|
290
|
+
if (name === "work.readLatest")
|
|
291
|
+
return { format: "markdown, normalized_text, visible_text, html, blocks, or all", maxChars: "optional capture limit" };
|
|
246
292
|
if (name === "artifacts.listLatest")
|
|
247
293
|
return { kind: "artifact kind; currently image", max: "maximum artifacts to return" };
|
|
248
294
|
if (name === "artifacts.wait")
|
|
@@ -306,6 +352,22 @@ function primitiveExamples(name) {
|
|
|
306
352
|
`// Verify an expensive Pro consult before submitting: const current = await chatgpt.modes.get();`
|
|
307
353
|
];
|
|
308
354
|
}
|
|
355
|
+
if (name === "experience.detect")
|
|
356
|
+
return [`await chatgpt.experience.detect();`];
|
|
357
|
+
if (name === "experience.open")
|
|
358
|
+
return [`await chatgpt.experience.open({ experience: "work" });`];
|
|
359
|
+
if (name === "configuration.inspect")
|
|
360
|
+
return [`await chatgpt.configuration.inspect({ experience: "work" });`];
|
|
361
|
+
if (name === "configuration.apply")
|
|
362
|
+
return [`await chatgpt.configuration.apply({ experience: "work", desired: { model: "GPT-5.6 Sol", effort: "High", speed: "Fast" }, strict: true });`];
|
|
363
|
+
if (name === "work.status")
|
|
364
|
+
return [`await chatgpt.work.status();`];
|
|
365
|
+
if (name === "work.wait")
|
|
366
|
+
return [`await chatgpt.work.wait({ timeoutMs: 600000 });`];
|
|
367
|
+
if (name === "work.steer")
|
|
368
|
+
return [`await chatgpt.work.steer({ prompt: "Focus the comparison on deployment ergonomics." });`];
|
|
369
|
+
if (name === "work.readLatest")
|
|
370
|
+
return [`await chatgpt.work.readLatest({ format: "markdown" });`];
|
|
309
371
|
if (name === "files.preflight") {
|
|
310
372
|
return [
|
|
311
373
|
`await chatgpt.files.preflight({ paths: ["/absolute/host/path.md"] });`,
|
|
@@ -345,7 +407,7 @@ function primitiveBlockers(name) {
|
|
|
345
407
|
return ["browser_bridge_unavailable", "login_required", "selector_drift", "confirmation", "permission", "upload_failed"];
|
|
346
408
|
if (name.startsWith("artifacts."))
|
|
347
409
|
return ["browser_bridge_unavailable", "login_required", "artifact_unavailable", "artifact_selector_drift", "artifact_download_unavailable"];
|
|
348
|
-
if (name.startsWith("modes.") || name.startsWith("tools."))
|
|
410
|
+
if (name.startsWith("modes.") || name.startsWith("tools.") || name.startsWith("experience.") || name.startsWith("configuration.") || name.startsWith("work."))
|
|
349
411
|
return ["browser_bridge_unavailable", "login_required", "selector_drift"];
|
|
350
412
|
return commonBlockers();
|
|
351
413
|
}
|
|
@@ -6,7 +6,7 @@ import { readLatestMessage, readMessages } from "../dom/messages.js";
|
|
|
6
6
|
import { copyResponseButtons } from "../dom/selectors.js";
|
|
7
7
|
import { contextFromPage } from "./context.js";
|
|
8
8
|
import { withCommandOutputText } from "./output.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ensurePage } from "./session.js";
|
|
10
10
|
export async function copyResponse(env, args = {}) {
|
|
11
11
|
const boot = await ensurePage(env);
|
|
12
12
|
if (!boot.ok) {
|
|
@@ -166,9 +166,3 @@ function mergeResponseMetadata(data, latest) {
|
|
|
166
166
|
if (latest.sourcesAvailable !== undefined)
|
|
167
167
|
data.sourcesAvailable = latest.sourcesAvailable;
|
|
168
168
|
}
|
|
169
|
-
async function ensurePage(env) {
|
|
170
|
-
if (env.page !== undefined) {
|
|
171
|
-
return resultOk({}, await contextFromPage(env.page));
|
|
172
|
-
}
|
|
173
|
-
return bootstrap(env, { preferExistingTab: true });
|
|
174
|
-
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./artifacts.js";
|
|
2
|
+
import { applyConfiguration, inspectConfiguration } from "./configuration.js";
|
|
3
|
+
import { detectExperience, openExperience } from "./experience.js";
|
|
2
4
|
import { attachFiles, downloadLatestFile } from "./files.js";
|
|
3
5
|
import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./project-sources.js";
|
|
4
|
-
import { askMessage, composeMessage, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
|
|
6
|
+
import { askMessage, composeMessage, messageStatus, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
|
|
5
7
|
import { copyResponse } from "./response-actions.js";
|
|
6
8
|
import { bootstrap } from "./session.js";
|
|
7
9
|
import { newThread, openThread, searchThreads } from "./threads.js";
|
|
8
10
|
import { setMode, selectTool } from "./modes.js";
|
|
9
11
|
import { withCommandOutputText } from "./output.js";
|
|
12
|
+
import { readLatestWork, startWork, steerWork, waitForWork, workStatus } from "./work.js";
|
|
10
13
|
export const defaultSequencePolicy = {
|
|
11
14
|
stopOnError: true,
|
|
12
15
|
returnPartial: true,
|
|
@@ -43,6 +46,24 @@ export async function executeStep(step, env, previousResults) {
|
|
|
43
46
|
switch (step.command) {
|
|
44
47
|
case "session.bootstrap":
|
|
45
48
|
return bootstrap(env, step.args);
|
|
49
|
+
case "experience.detect":
|
|
50
|
+
return detectExperience(env, step.args);
|
|
51
|
+
case "experience.open":
|
|
52
|
+
return openExperience(env, step.args);
|
|
53
|
+
case "configuration.inspect":
|
|
54
|
+
return inspectConfiguration(env, step.args);
|
|
55
|
+
case "configuration.apply":
|
|
56
|
+
return applyConfiguration(env, step.args);
|
|
57
|
+
case "work.start":
|
|
58
|
+
return startWork(env, step.args);
|
|
59
|
+
case "work.status":
|
|
60
|
+
return workStatus(env, step.args);
|
|
61
|
+
case "work.wait":
|
|
62
|
+
return waitForWork(env, step.args);
|
|
63
|
+
case "work.steer":
|
|
64
|
+
return steerWork(env, step.args);
|
|
65
|
+
case "work.readLatest":
|
|
66
|
+
return readLatestWork(env, step.args);
|
|
46
67
|
case "threads.search":
|
|
47
68
|
return searchThreads(env, step.args);
|
|
48
69
|
case "threads.open":
|
|
@@ -59,6 +80,8 @@ export async function executeStep(step, env, previousResults) {
|
|
|
59
80
|
return waitForMessage(env, step.args);
|
|
60
81
|
case "messages.readLatest":
|
|
61
82
|
return readLatest(env, step.args);
|
|
83
|
+
case "messages.status":
|
|
84
|
+
return messageStatus(env, step.args);
|
|
62
85
|
case "messages.waitAndRead":
|
|
63
86
|
return waitAndRead(env, step.args);
|
|
64
87
|
case "artifacts.listLatest":
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { BootstrapArgs, BootstrapData, CommandResult, RuntimeEnv } from "../types.js";
|
|
2
2
|
export declare function bootstrap(env: RuntimeEnv, args?: BootstrapArgs): Promise<CommandResult<BootstrapData>>;
|
|
3
|
+
export declare function ensurePage(env: RuntimeEnv): Promise<CommandResult<unknown>>;
|
|
4
|
+
export declare function verifyTabAffinity(env: RuntimeEnv): Promise<CommandResult<unknown> | undefined>;
|