@steipete/oracle 0.8.3 → 0.8.4

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Peter Steinberger
3
+ \g<1>2026 Peter Steinberger
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -570,7 +570,7 @@ export async function uploadAttachmentFile(deps, attachment, logger, options) {
570
570
  // keep it as a fallback, but strongly prefer visible (even sr-only 1x1) inputs.
571
571
  const localSet = new Set(localInputs);
572
572
  let idx = 0;
573
- const candidates = inputs.map((el) => {
573
+ let candidates = inputs.map((el) => {
574
574
  const accept = el.getAttribute('accept') || '';
575
575
  const imageOnly = acceptIsImageOnly(accept);
576
576
  const rect = el instanceof HTMLElement ? el.getBoundingClientRect() : { width: 0, height: 0 };
@@ -583,9 +583,18 @@ export async function uploadAttachmentFile(deps, attachment, logger, options) {
583
583
  (!imageOnly ? 30 : isImageAttachment ? 20 : 5);
584
584
  el.setAttribute('data-oracle-upload-candidate', 'true');
585
585
  el.setAttribute('data-oracle-upload-idx', String(idx));
586
- return { idx: idx++, score, imageOnly, visible, local };
586
+ return { idx: idx++, score, imageOnly };
587
587
  });
588
588
 
589
+ // When the attachment isn't an image, avoid inputs that only accept images.
590
+ // Some ChatGPT surfaces expose multiple file inputs (e.g. image-only vs generic upload).
591
+ if (!isImageAttachment) {
592
+ const nonImage = candidates.filter((candidate) => !candidate.imageOnly);
593
+ if (nonImage.length > 0) {
594
+ candidates = nonImage;
595
+ }
596
+ }
597
+
589
598
  // Prefer higher scores first.
590
599
  candidates.sort((a, b) => b.score - a.score);
591
600
  return {
@@ -829,8 +838,8 @@ export async function uploadAttachmentFile(deps, attachment, logger, options) {
829
838
  continue;
830
839
  }
831
840
  const baselineInputSnapshot = await readInputSnapshot(idx);
832
- const gatherSignals = async () => {
833
- const signalResult = await waitForAttachmentUiSignal(attachmentUiSignalWaitMs);
841
+ const gatherSignals = async (waitMs = attachmentUiSignalWaitMs) => {
842
+ const signalResult = await waitForAttachmentUiSignal(waitMs);
834
843
  const postInputSnapshot = await readInputSnapshot(idx);
835
844
  const postInputSignals = inputSignalsFor(baselineInputSnapshot, postInputSnapshot);
836
845
  const snapshot = await runtime
@@ -890,22 +899,6 @@ export async function uploadAttachmentFile(deps, attachment, logger, options) {
890
899
  if (!hasExpectedFile) {
891
900
  if (mode === 'set') {
892
901
  await dom.setFileInputFiles({ nodeId: resultNode.nodeId, files: [attachment.path] });
893
- await runtime
894
- .evaluate({
895
- expression: `(() => {
896
- const input = document.querySelector('input[type="file"][data-oracle-upload-idx="${idx}"]');
897
- if (!(input instanceof HTMLInputElement)) return false;
898
- try {
899
- input.dispatchEvent(new Event('input', { bubbles: true }));
900
- input.dispatchEvent(new Event('change', { bubbles: true }));
901
- return true;
902
- } catch {
903
- return false;
904
- }
905
- })()`,
906
- returnByValue: true,
907
- })
908
- .catch(() => undefined);
909
902
  }
910
903
  else {
911
904
  const selector = `input[type="file"][data-oracle-upload-idx="${idx}"]`;
@@ -930,12 +923,43 @@ export async function uploadAttachmentFile(deps, attachment, logger, options) {
930
923
  const evaluation = await evaluateSignals(signalState.signalResult, signalState.postInputSignals, immediateInputMatch);
931
924
  return { evaluation, signalState, immediateInputMatch };
932
925
  };
926
+ const dispatchInputEvents = async () => {
927
+ await runtime
928
+ .evaluate({
929
+ expression: `(() => {
930
+ const input = document.querySelector('input[type="file"][data-oracle-upload-idx="${idx}"]');
931
+ if (!(input instanceof HTMLInputElement)) return false;
932
+ try {
933
+ input.dispatchEvent(new Event('input', { bubbles: true }));
934
+ input.dispatchEvent(new Event('change', { bubbles: true }));
935
+ return true;
936
+ } catch {
937
+ return false;
938
+ }
939
+ })()`,
940
+ returnByValue: true,
941
+ })
942
+ .catch(() => undefined);
943
+ };
933
944
  let result = await runInputAttempt('set');
934
945
  if (result.evaluation.status === 'ui') {
935
946
  confirmedAttachment = true;
936
947
  break;
937
948
  }
938
949
  if (result.evaluation.status === 'input') {
950
+ await dispatchInputEvents();
951
+ await delay(150);
952
+ const forcedState = await gatherSignals(1_500);
953
+ const forcedEvaluation = await evaluateSignals(forcedState.signalResult, forcedState.postInputSignals, result.immediateInputMatch);
954
+ if (forcedEvaluation.status === 'ui') {
955
+ confirmedAttachment = true;
956
+ break;
957
+ }
958
+ if (forcedEvaluation.status === 'input') {
959
+ logger('Attachment input set; proceeding without UI confirmation.');
960
+ inputConfirmed = true;
961
+ break;
962
+ }
939
963
  logger('Attachment input set; retrying with data transfer to trigger ChatGPT upload.');
940
964
  await dom.setFileInputFiles({ nodeId: resultNode.nodeId, files: [] }).catch(() => undefined);
941
965
  await delay(150);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5.2 Pro (via gpt-5.1-pro alias), GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
5
5
  "type": "module",
6
6
  "main": "dist/bin/oracle-cli.js",
@@ -80,14 +80,15 @@
80
80
  "markdansi": "0.2.0",
81
81
  "openai": "^6.15.0",
82
82
  "osc-progress": "^0.2.0",
83
+ "qs": "^6.14.1",
83
84
  "shiki": "^3.20.0",
84
85
  "toasted-notifier": "^10.1.0",
85
86
  "tokentally": "^0.1.1",
86
- "zod": "^4.2.1"
87
+ "zod": "^4.3.5"
87
88
  },
88
89
  "devDependencies": {
89
90
  "@anthropic-ai/tokenizer": "^0.0.4",
90
- "@biomejs/biome": "^2.3.10",
91
+ "@biomejs/biome": "^2.3.11",
91
92
  "@cdktf/node-pty-prebuilt-multiarch": "0.10.2",
92
93
  "@types/chrome-remote-interface": "^0.33.0",
93
94
  "@types/inquirer": "^9.0.9",