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.
Files changed (214) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +42 -2
  3. package/contracts/v1/fixtures/backend-capabilities.json +10 -0
  4. package/contracts/v1/fixtures/command-descriptors.json +257 -6
  5. package/contracts/v1/fixtures/describe-runner-run.json +5 -2
  6. package/contracts/v1/fixtures/doctor-scenario-preflight.json +34 -2
  7. package/contracts/v1/fixtures/help-root.json +1 -1
  8. package/contracts/v1/fixtures/output-json-parse-success.json +9 -0
  9. package/contracts/v1/fixtures/run-timeout-partial.json +15 -4
  10. package/contracts/v1/fixtures/stream-in-progress.ndjson +3 -0
  11. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
  12. package/contracts/v1/fixtures/surface-chat-legacy.json +35 -0
  13. package/contracts/v1/fixtures/surface-chat-simplified.json +37 -0
  14. package/contracts/v1/fixtures/surface-sidebar-false-positive.json +29 -0
  15. package/contracts/v1/fixtures/surface-work-advanced.json +45 -0
  16. package/contracts/v1/fixtures/surface-work-basic.json +38 -0
  17. package/contracts/v1/fixtures/workflow-ask-success.json +8 -0
  18. package/contracts/v1/manifest.json +32 -1
  19. package/contracts/v1/parity-suite.json +113 -0
  20. package/contracts/v1/schemas/backend-request.schema.json +15 -0
  21. package/contracts/v1/schemas/manifest.schema.json +6 -3
  22. package/contracts/v1/schemas/surface-profile.schema.json +166 -0
  23. package/dist/codex-chatgpt-control-backend.mjs +2045 -227
  24. package/dist/codex-chatgpt-control.bundle.mjs +3817 -1960
  25. package/dist/src/backend/client.d.ts +26 -1
  26. package/dist/src/backend/client.js +23 -1
  27. package/dist/src/backend/protocol.d.ts +1 -1
  28. package/dist/src/backend/protocol.js +10 -0
  29. package/dist/src/backend/session.js +20 -0
  30. package/dist/src/browser/attach.d.ts +1 -0
  31. package/dist/src/browser/attach.js +3 -3
  32. package/dist/src/browser/page-state.js +5 -2
  33. package/dist/src/client.d.ts +31 -1
  34. package/dist/src/client.js +69 -14
  35. package/dist/src/commands/artifacts.js +1 -7
  36. package/dist/src/commands/configuration.d.ts +15 -0
  37. package/dist/src/commands/configuration.js +674 -0
  38. package/dist/src/commands/doctor.js +38 -1
  39. package/dist/src/commands/experience.d.ts +12 -0
  40. package/dist/src/commands/experience.js +288 -0
  41. package/dist/src/commands/files.js +1 -7
  42. package/dist/src/commands/messages.d.ts +2 -1
  43. package/dist/src/commands/messages.js +138 -16
  44. package/dist/src/commands/modes.js +28 -14
  45. package/dist/src/commands/registry.js +69 -7
  46. package/dist/src/commands/response-actions.js +1 -7
  47. package/dist/src/commands/sequence.js +24 -1
  48. package/dist/src/commands/session.d.ts +2 -0
  49. package/dist/src/commands/session.js +50 -1
  50. package/dist/src/commands/threads.js +1 -7
  51. package/dist/src/commands/work.d.ts +6 -0
  52. package/dist/src/commands/work.js +338 -0
  53. package/dist/src/dom/locale/am.d.ts +2 -1
  54. package/dist/src/dom/locale/am.js +2 -1
  55. package/dist/src/dom/locale/ar.d.ts +2 -1
  56. package/dist/src/dom/locale/ar.js +2 -1
  57. package/dist/src/dom/locale/bg.d.ts +2 -1
  58. package/dist/src/dom/locale/bg.js +2 -1
  59. package/dist/src/dom/locale/bn.d.ts +2 -1
  60. package/dist/src/dom/locale/bn.js +2 -1
  61. package/dist/src/dom/locale/bs.d.ts +2 -1
  62. package/dist/src/dom/locale/bs.js +2 -1
  63. package/dist/src/dom/locale/ca.d.ts +2 -1
  64. package/dist/src/dom/locale/ca.js +2 -1
  65. package/dist/src/dom/locale/cs.d.ts +2 -1
  66. package/dist/src/dom/locale/cs.js +2 -1
  67. package/dist/src/dom/locale/da.d.ts +2 -1
  68. package/dist/src/dom/locale/da.js +2 -1
  69. package/dist/src/dom/locale/de.d.ts +4 -3
  70. package/dist/src/dom/locale/de.js +4 -3
  71. package/dist/src/dom/locale/el.d.ts +2 -1
  72. package/dist/src/dom/locale/el.js +2 -1
  73. package/dist/src/dom/locale/en.d.ts +27 -2
  74. package/dist/src/dom/locale/en.js +28 -2
  75. package/dist/src/dom/locale/es-419.d.ts +2 -1
  76. package/dist/src/dom/locale/es-419.js +2 -1
  77. package/dist/src/dom/locale/es-ES.d.ts +2 -1
  78. package/dist/src/dom/locale/es-ES.js +2 -1
  79. package/dist/src/dom/locale/et.d.ts +2 -1
  80. package/dist/src/dom/locale/et.js +2 -1
  81. package/dist/src/dom/locale/fa.d.ts +2 -1
  82. package/dist/src/dom/locale/fa.js +2 -1
  83. package/dist/src/dom/locale/fi.d.ts +2 -1
  84. package/dist/src/dom/locale/fi.js +2 -1
  85. package/dist/src/dom/locale/fr-CA.d.ts +2 -1
  86. package/dist/src/dom/locale/fr-CA.js +2 -1
  87. package/dist/src/dom/locale/fr-FR.d.ts +3 -3
  88. package/dist/src/dom/locale/fr-FR.js +3 -3
  89. package/dist/src/dom/locale/gu.d.ts +2 -1
  90. package/dist/src/dom/locale/gu.js +2 -1
  91. package/dist/src/dom/locale/hi.d.ts +2 -1
  92. package/dist/src/dom/locale/hi.js +2 -1
  93. package/dist/src/dom/locale/hr.d.ts +2 -1
  94. package/dist/src/dom/locale/hr.js +2 -1
  95. package/dist/src/dom/locale/hu.d.ts +2 -1
  96. package/dist/src/dom/locale/hu.js +2 -1
  97. package/dist/src/dom/locale/hy.d.ts +2 -1
  98. package/dist/src/dom/locale/hy.js +2 -1
  99. package/dist/src/dom/locale/id.d.ts +2 -1
  100. package/dist/src/dom/locale/id.js +2 -1
  101. package/dist/src/dom/locale/index.d.ts +17 -2
  102. package/dist/src/dom/locale/index.js +65 -0
  103. package/dist/src/dom/locale/is.d.ts +2 -1
  104. package/dist/src/dom/locale/is.js +2 -1
  105. package/dist/src/dom/locale/it.d.ts +2 -1
  106. package/dist/src/dom/locale/it.js +2 -1
  107. package/dist/src/dom/locale/ja.d.ts +2 -1
  108. package/dist/src/dom/locale/ja.js +2 -1
  109. package/dist/src/dom/locale/ka.d.ts +2 -1
  110. package/dist/src/dom/locale/ka.js +2 -1
  111. package/dist/src/dom/locale/kk.d.ts +2 -1
  112. package/dist/src/dom/locale/kk.js +2 -1
  113. package/dist/src/dom/locale/kn.d.ts +2 -1
  114. package/dist/src/dom/locale/kn.js +2 -1
  115. package/dist/src/dom/locale/ko.d.ts +2 -1
  116. package/dist/src/dom/locale/ko.js +2 -1
  117. package/dist/src/dom/locale/lt.d.ts +2 -1
  118. package/dist/src/dom/locale/lt.js +2 -1
  119. package/dist/src/dom/locale/lv.d.ts +2 -1
  120. package/dist/src/dom/locale/lv.js +2 -1
  121. package/dist/src/dom/locale/mk.d.ts +2 -1
  122. package/dist/src/dom/locale/mk.js +2 -1
  123. package/dist/src/dom/locale/ml.d.ts +2 -1
  124. package/dist/src/dom/locale/ml.js +2 -1
  125. package/dist/src/dom/locale/mn.d.ts +2 -1
  126. package/dist/src/dom/locale/mn.js +2 -1
  127. package/dist/src/dom/locale/mr.d.ts +2 -1
  128. package/dist/src/dom/locale/mr.js +2 -1
  129. package/dist/src/dom/locale/ms.d.ts +2 -1
  130. package/dist/src/dom/locale/ms.js +2 -1
  131. package/dist/src/dom/locale/my.d.ts +2 -1
  132. package/dist/src/dom/locale/my.js +2 -1
  133. package/dist/src/dom/locale/nb.d.ts +2 -1
  134. package/dist/src/dom/locale/nb.js +2 -1
  135. package/dist/src/dom/locale/nl.d.ts +2 -1
  136. package/dist/src/dom/locale/nl.js +2 -1
  137. package/dist/src/dom/locale/pa.d.ts +2 -1
  138. package/dist/src/dom/locale/pa.js +2 -1
  139. package/dist/src/dom/locale/pl.d.ts +2 -1
  140. package/dist/src/dom/locale/pl.js +2 -1
  141. package/dist/src/dom/locale/pt-BR.d.ts +2 -1
  142. package/dist/src/dom/locale/pt-BR.js +2 -1
  143. package/dist/src/dom/locale/pt-PT.d.ts +2 -1
  144. package/dist/src/dom/locale/pt-PT.js +2 -1
  145. package/dist/src/dom/locale/ro.d.ts +2 -1
  146. package/dist/src/dom/locale/ro.js +2 -1
  147. package/dist/src/dom/locale/ru.d.ts +2 -1
  148. package/dist/src/dom/locale/ru.js +2 -1
  149. package/dist/src/dom/locale/sk.d.ts +2 -1
  150. package/dist/src/dom/locale/sk.js +2 -1
  151. package/dist/src/dom/locale/sl.d.ts +2 -1
  152. package/dist/src/dom/locale/sl.js +2 -1
  153. package/dist/src/dom/locale/so.d.ts +2 -1
  154. package/dist/src/dom/locale/so.js +2 -1
  155. package/dist/src/dom/locale/sq.d.ts +2 -1
  156. package/dist/src/dom/locale/sq.js +2 -1
  157. package/dist/src/dom/locale/sr.d.ts +2 -1
  158. package/dist/src/dom/locale/sr.js +2 -1
  159. package/dist/src/dom/locale/sv.d.ts +2 -1
  160. package/dist/src/dom/locale/sv.js +2 -1
  161. package/dist/src/dom/locale/sw.d.ts +2 -1
  162. package/dist/src/dom/locale/sw.js +2 -1
  163. package/dist/src/dom/locale/ta.d.ts +2 -1
  164. package/dist/src/dom/locale/ta.js +2 -1
  165. package/dist/src/dom/locale/te.d.ts +2 -1
  166. package/dist/src/dom/locale/te.js +2 -1
  167. package/dist/src/dom/locale/th.d.ts +2 -1
  168. package/dist/src/dom/locale/th.js +2 -1
  169. package/dist/src/dom/locale/tl.d.ts +4 -5
  170. package/dist/src/dom/locale/tl.js +4 -5
  171. package/dist/src/dom/locale/tr.d.ts +2 -1
  172. package/dist/src/dom/locale/tr.js +2 -1
  173. package/dist/src/dom/locale/types.d.ts +18 -1
  174. package/dist/src/dom/locale/uk.d.ts +2 -1
  175. package/dist/src/dom/locale/uk.js +2 -1
  176. package/dist/src/dom/locale/ur.d.ts +2 -1
  177. package/dist/src/dom/locale/ur.js +2 -1
  178. package/dist/src/dom/locale/vi.d.ts +2 -1
  179. package/dist/src/dom/locale/vi.js +2 -1
  180. package/dist/src/dom/locale/zh-HK.d.ts +2 -1
  181. package/dist/src/dom/locale/zh-HK.js +4 -3
  182. package/dist/src/dom/locale/zh-Hans.d.ts +2 -1
  183. package/dist/src/dom/locale/zh-Hans.js +2 -1
  184. package/dist/src/dom/locale/zh-TW.d.ts +2 -1
  185. package/dist/src/dom/locale/zh-TW.js +2 -1
  186. package/dist/src/dom/selectors.js +6 -1
  187. package/dist/src/index.d.ts +3 -0
  188. package/dist/src/index.js +3 -0
  189. package/dist/src/runner/responses.d.ts +3 -1
  190. package/dist/src/runner/responses.js +16 -1
  191. package/dist/src/runner/result.js +129 -4
  192. package/dist/src/runner/stream.d.ts +1 -1
  193. package/dist/src/runner/stream.js +6 -0
  194. package/dist/src/runner/types.d.ts +35 -1
  195. package/dist/src/safety/risk.d.ts +10 -0
  196. package/dist/src/safety/risk.js +10 -0
  197. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +6 -0
  198. package/dist/src/scripts/apply-intelligence-locale-captures.js +53 -7
  199. package/dist/src/scripts/capture-intelligence-locales.d.ts +3 -0
  200. package/dist/src/scripts/capture-intelligence-locales.js +372 -3
  201. package/dist/src/scripts/capture-surface-profile.d.ts +23 -0
  202. package/dist/src/scripts/capture-surface-profile.js +304 -0
  203. package/dist/src/scripts/live-smoke/scenarios.js +34 -1
  204. package/dist/src/scripts/live-smoke.js +1 -0
  205. package/dist/src/types.d.ts +245 -2
  206. package/package.json +3 -2
  207. package/references/2026-07-16-chat-work-surfaces.md +87 -0
  208. package/references/agents-runner.md +13 -2
  209. package/references/backend-protocol.md +15 -5
  210. package/references/language-coverage.md +40 -3
  211. package/references/localization.md +34 -6
  212. package/references/python-parity.md +11 -1
  213. package/references/responses-adapter.md +12 -1
  214. 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 { bootstrap } from "./session.js";
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, await countPageMessages(page).catch(() => undefined)), await contextFromPage(page));
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 data = waitDataFromText(args, false, normalizeWhitespace(partialText), latestAssistantCount, Date.now() - started);
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: args.text, mode: "replace" };
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: args.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: args.text };
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 { bootstrap } from "./session.js";
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 = findExactMenuItem(candidates, requestedVersion);
506
+ let exact = findExactSelectableMenuItem(candidates, requestedVersion);
512
507
  if (exact !== undefined) {
513
- return await clickMenuItem(page, exact.label)
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 = findExactMenuItem(candidates, requestedVersion);
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 clickMenuItem(page, exact.label)
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 findExactMenuItem(items, wanted) {
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
- if (!normalizedModeLabels.some(modeLabel => tokenMatches(normalized, modeLabel)))
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 set mode, ask, wait, and read.", [
11
- `await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, mode: { model: "Pro" }, files: ["/absolute/host/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`
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 browser-control agent against input, files, thread, existing-tab, mode, and response options.", [
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", "Select a visible model, intelligence, effort, or nested model-version candidate when unambiguous.", 30000),
86
- primitive("modes.get", "Read the mode labels shown on the visible composer controls without changing them.", 30000),
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 { bootstrap } from "./session.js";
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>;