@tomflow/proflow-execution-browser-extension 0.1.36 → 0.1.38
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/dist/deployment/adapter.d.ts +10 -10
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/extension/background.js +687 -19
- package/dist/extension/content.js +340 -80
- package/dist/extension/tasks.js +74 -3
- package/dist/src/bridge.js +59 -6
- package/dist/src/carrier-attention-view.d.ts +13 -0
- package/dist/src/carrier-attention-view.js +54 -0
- package/dist/src/carrier-attention.d.ts +26 -0
- package/dist/src/carrier-attention.js +44 -0
- package/dist/src/carrier-continuation-control.d.ts +44 -0
- package/dist/src/carrier-continuation-control.js +114 -0
- package/dist/src/carrier-permission-attempt.d.ts +18 -0
- package/dist/src/carrier-permission-attempt.js +68 -0
- package/dist/src/carrier-permission-lifecycle.d.ts +34 -0
- package/dist/src/carrier-permission-lifecycle.js +73 -0
- package/dist/src/carrier-permission.d.ts +16 -0
- package/dist/src/carrier-permission.js +65 -0
- package/dist/src/chatgpt-runtime-adapter.d.ts +16 -0
- package/dist/src/chatgpt-runtime-adapter.js +173 -0
- package/dist/src/composer-submit.d.ts +12 -0
- package/dist/src/composer-submit.js +22 -0
- package/extension/background.ts +622 -17
- package/extension/content.ts +40 -39
- package/extension/tasks.html +6 -0
- package/extension/tasks.ts +93 -5
- package/manifest.json +1 -1
- package/package.json +3 -3
- package/proflow.module.json +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { detectActionPermission, permissionActionAllowed, permissionSemanticAction, } from "./carrier-permission.js";
|
|
2
|
+
import { submitControlledComposer } from "./composer-submit.js";
|
|
3
|
+
export function classifyChatGptPageSignals(input) {
|
|
4
|
+
if (input.permission)
|
|
5
|
+
return {
|
|
6
|
+
pageState: "BLOCKED",
|
|
7
|
+
activityKind: "ACTION_PERMISSION",
|
|
8
|
+
blockerFacts: input.permission,
|
|
9
|
+
};
|
|
10
|
+
if (input.hasDialog)
|
|
11
|
+
return { pageState: "BLOCKED", activityKind: "WAITING_HUMAN" };
|
|
12
|
+
if (input.isGenerating)
|
|
13
|
+
return { pageState: "BUSY", activityKind: "GENERATING" };
|
|
14
|
+
if (input.hasComposer)
|
|
15
|
+
return { pageState: "IDLE", activityKind: null };
|
|
16
|
+
return { pageState: "UNKNOWN", activityKind: null };
|
|
17
|
+
}
|
|
18
|
+
const composerSelector = '#prompt-textarea, textarea, [contenteditable="true"]';
|
|
19
|
+
const sendSelector = 'button[data-testid="send-button"], button[aria-label*="Send"], button[aria-label*="发送"]';
|
|
20
|
+
function buttonLabel(button) {
|
|
21
|
+
return (button.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
22
|
+
}
|
|
23
|
+
function actionPermissionDom(document) {
|
|
24
|
+
const view = document.defaultView;
|
|
25
|
+
if (!view)
|
|
26
|
+
return null;
|
|
27
|
+
const semanticButtons = [...document.querySelectorAll("button")].filter((button) => button instanceof view.HTMLButtonElement &&
|
|
28
|
+
permissionSemanticAction(buttonLabel(button)) !== null);
|
|
29
|
+
for (const seed of semanticButtons) {
|
|
30
|
+
let root = seed.parentElement;
|
|
31
|
+
for (let depth = 0; root && depth < 8; depth += 1, root = root.parentElement) {
|
|
32
|
+
const buttons = [...root.querySelectorAll("button")].filter((button) => button instanceof view.HTMLButtonElement);
|
|
33
|
+
const facts = detectActionPermission([
|
|
34
|
+
{
|
|
35
|
+
text: root.textContent ?? "",
|
|
36
|
+
buttonLabels: buttons.map(buttonLabel),
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
if (!facts)
|
|
40
|
+
continue;
|
|
41
|
+
const mapped = new Map();
|
|
42
|
+
for (const button of buttons) {
|
|
43
|
+
const action = permissionSemanticAction(buttonLabel(button));
|
|
44
|
+
if (action && !mapped.has(action))
|
|
45
|
+
mapped.set(action, button);
|
|
46
|
+
}
|
|
47
|
+
return { facts, buttons: mapped };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
export function observeChatGptPage(document) {
|
|
53
|
+
const permission = actionPermissionDom(document);
|
|
54
|
+
return classifyChatGptPageSignals({
|
|
55
|
+
permission: permission?.facts ?? null,
|
|
56
|
+
hasDialog: document.querySelector('[role="dialog"]') !== null,
|
|
57
|
+
isGenerating: document.querySelector('[data-testid="stop-button"], button[aria-label*="Stop"], button[aria-label*="停止"]') !== null,
|
|
58
|
+
hasComposer: document.querySelector(composerSelector) !== null,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function performChatGptPermissionAction(document, expectedFingerprint, action) {
|
|
62
|
+
const permission = actionPermissionDom(document);
|
|
63
|
+
if (!permission)
|
|
64
|
+
throw new Error("ACTION_PERMISSION_NOT_FOUND");
|
|
65
|
+
if (!permissionActionAllowed(permission.facts, expectedFingerprint, action))
|
|
66
|
+
throw new Error("STALE_PERMISSION");
|
|
67
|
+
const button = permission.buttons.get(action);
|
|
68
|
+
if (!button ||
|
|
69
|
+
button.disabled ||
|
|
70
|
+
button.getAttribute("aria-disabled") === "true")
|
|
71
|
+
throw new Error("PERMISSION_ACTION_NOT_READY");
|
|
72
|
+
button.click();
|
|
73
|
+
return permission.facts;
|
|
74
|
+
}
|
|
75
|
+
function composerElement(document) {
|
|
76
|
+
const view = document.defaultView;
|
|
77
|
+
const element = document.querySelector(composerSelector);
|
|
78
|
+
if (!view || !(element instanceof view.HTMLElement))
|
|
79
|
+
throw new Error("COMPOSER_NOT_FOUND");
|
|
80
|
+
return element;
|
|
81
|
+
}
|
|
82
|
+
function readElementValue(element) {
|
|
83
|
+
const view = element.ownerDocument.defaultView;
|
|
84
|
+
if (!view)
|
|
85
|
+
return "";
|
|
86
|
+
if (element instanceof view.HTMLTextAreaElement ||
|
|
87
|
+
element instanceof view.HTMLInputElement)
|
|
88
|
+
return element.value;
|
|
89
|
+
return element.textContent ?? "";
|
|
90
|
+
}
|
|
91
|
+
function nativeWrite(element, value) {
|
|
92
|
+
const view = element.ownerDocument.defaultView;
|
|
93
|
+
if (!view)
|
|
94
|
+
throw new Error("DOM_WINDOW_NOT_READY");
|
|
95
|
+
if (element instanceof view.HTMLTextAreaElement ||
|
|
96
|
+
element instanceof view.HTMLInputElement) {
|
|
97
|
+
const prototype = element instanceof view.HTMLTextAreaElement
|
|
98
|
+
? view.HTMLTextAreaElement.prototype
|
|
99
|
+
: view.HTMLInputElement.prototype;
|
|
100
|
+
const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
|
|
101
|
+
if (!setter)
|
|
102
|
+
throw new Error("COMPOSER_NATIVE_SETTER_MISSING");
|
|
103
|
+
setter.call(element, value);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
element.textContent = value;
|
|
107
|
+
}
|
|
108
|
+
function dispatchComposerInput(element, value) {
|
|
109
|
+
const view = element.ownerDocument.defaultView;
|
|
110
|
+
if (!view)
|
|
111
|
+
throw new Error("DOM_WINDOW_NOT_READY");
|
|
112
|
+
element.dispatchEvent(new view.InputEvent("input", {
|
|
113
|
+
bubbles: true,
|
|
114
|
+
inputType: "insertText",
|
|
115
|
+
data: value,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
function nextComposerFrame(document) {
|
|
119
|
+
const view = document.defaultView;
|
|
120
|
+
if (!view)
|
|
121
|
+
return Promise.reject(new Error("DOM_WINDOW_NOT_READY"));
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
let settled = false;
|
|
124
|
+
const finish = () => {
|
|
125
|
+
if (settled)
|
|
126
|
+
return;
|
|
127
|
+
settled = true;
|
|
128
|
+
resolve();
|
|
129
|
+
};
|
|
130
|
+
view.requestAnimationFrame(finish);
|
|
131
|
+
view.setTimeout(finish, 50);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function sendButton(document) {
|
|
135
|
+
const view = document.defaultView;
|
|
136
|
+
const element = document.querySelector(sendSelector);
|
|
137
|
+
return view && element instanceof view.HTMLButtonElement ? element : null;
|
|
138
|
+
}
|
|
139
|
+
export async function submitChatGptComposer(document, value) {
|
|
140
|
+
const composer = composerElement(document);
|
|
141
|
+
composer.focus();
|
|
142
|
+
await submitControlledComposer({
|
|
143
|
+
write: (next) => nativeWrite(composer, next),
|
|
144
|
+
dispatchInput: (next) => dispatchComposerInput(composer, next),
|
|
145
|
+
readValue: () => readElementValue(composer),
|
|
146
|
+
submitReady: () => {
|
|
147
|
+
const button = sendButton(document);
|
|
148
|
+
return Boolean(button &&
|
|
149
|
+
!button.disabled &&
|
|
150
|
+
button.getAttribute("aria-disabled") !== "true");
|
|
151
|
+
},
|
|
152
|
+
nextFrame: () => nextComposerFrame(document),
|
|
153
|
+
clickSubmit: () => {
|
|
154
|
+
const button = sendButton(document);
|
|
155
|
+
if (!button ||
|
|
156
|
+
button.disabled ||
|
|
157
|
+
button.getAttribute("aria-disabled") === "true")
|
|
158
|
+
throw new Error("COMPOSER_SUBMIT_NOT_READY");
|
|
159
|
+
button.click();
|
|
160
|
+
},
|
|
161
|
+
}, value);
|
|
162
|
+
}
|
|
163
|
+
export function writeChatGptInput(document, selector, value) {
|
|
164
|
+
if (!selector || selector.length > 512)
|
|
165
|
+
throw new Error("SELECTOR_INVALID");
|
|
166
|
+
const view = document.defaultView;
|
|
167
|
+
const element = document.querySelector(selector);
|
|
168
|
+
if (!view || !(element instanceof view.HTMLElement))
|
|
169
|
+
throw new Error("ELEMENT_NOT_FOUND");
|
|
170
|
+
element.focus();
|
|
171
|
+
nativeWrite(element, value);
|
|
172
|
+
dispatchComposerInput(element, value);
|
|
173
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ComposerSubmitPort {
|
|
2
|
+
readValue(): string;
|
|
3
|
+
submitReady(): boolean;
|
|
4
|
+
nextFrame(): Promise<void>;
|
|
5
|
+
clickSubmit(): void;
|
|
6
|
+
}
|
|
7
|
+
export interface ControlledComposerPort extends ComposerSubmitPort {
|
|
8
|
+
write(value: string): void;
|
|
9
|
+
dispatchInput(value: string): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function submitAfterComposerCommit(port: ComposerSubmitPort, expectedValue: string, maxFrames?: number): Promise<void>;
|
|
12
|
+
export declare function submitControlledComposer(port: ControlledComposerPort, expectedValue: string, maxFrames?: number): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function submitAfterComposerCommit(port, expectedValue, maxFrames = 180) {
|
|
2
|
+
let stableFrames = 0;
|
|
3
|
+
for (let frame = 0; frame < maxFrames; frame += 1) {
|
|
4
|
+
await port.nextFrame();
|
|
5
|
+
if (port.readValue() === expectedValue && port.submitReady()) {
|
|
6
|
+
stableFrames += 1;
|
|
7
|
+
if (stableFrames >= 2) {
|
|
8
|
+
port.clickSubmit();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
stableFrames = 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error("COMPOSER_SUBMIT_NOT_READY");
|
|
17
|
+
}
|
|
18
|
+
export async function submitControlledComposer(port, expectedValue, maxFrames = 180) {
|
|
19
|
+
port.write(expectedValue);
|
|
20
|
+
port.dispatchInput(expectedValue);
|
|
21
|
+
await submitAfterComposerCommit(port, expectedValue, maxFrames);
|
|
22
|
+
}
|