@tomflow/proflow-execution-browser-extension 0.1.12 → 0.1.13
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 +6 -0
- package/DOCS.md +12 -4
- package/SETUP.md +10 -14
- package/dist/deployment/adapter.d.ts +42 -50
- package/dist/deployment/adapter.js +125 -103
- package/dist/deployment/descriptor.d.ts +4 -1
- package/dist/deployment/descriptor.js +5 -1
- package/dist/extension/background.js +189 -0
- package/dist/extension/provisioning-content.d.ts +1 -0
- package/dist/extension/provisioning-content.js +936 -0
- package/dist/src/configure-args.d.ts +3 -0
- package/dist/src/configure-args.js +23 -0
- package/dist/src/configure.js +8 -71
- package/dist/src/custom-gpt-editor-driver.d.ts +51 -0
- package/dist/src/custom-gpt-editor-driver.js +116 -0
- package/dist/src/custom-gpt-knowledge.d.ts +21 -0
- package/dist/src/custom-gpt-knowledge.js +148 -0
- package/dist/src/custom-gpt-provisioner.d.ts +78 -0
- package/dist/src/custom-gpt-provisioner.js +225 -0
- package/dist/src/custom-gpt-role.d.ts +66 -0
- package/dist/src/custom-gpt-role.js +98 -0
- package/dist/src/install-workflow.d.ts +29 -0
- package/dist/src/install-workflow.js +64 -0
- package/dist/src/pairing.d.ts +20 -0
- package/dist/src/pairing.js +176 -0
- package/dist/src/provisioning-bridge.d.ts +46 -0
- package/dist/src/provisioning-bridge.js +314 -0
- package/extension/background.ts +264 -3
- package/extension/provisioning-content.ts +1033 -0
- package/manifest.json +9 -1
- package/package.json +7 -5
- package/proflow.module.json +5 -1
package/extension/background.ts
CHANGED
|
@@ -36,10 +36,12 @@ type RuntimeMessage = {
|
|
|
36
36
|
| "PROFLOW_CONTENT_OBSERVATION"
|
|
37
37
|
| "PROFLOW_SIDE_PANEL_SNAPSHOT"
|
|
38
38
|
| "PROFLOW_TASK_APPLICATION"
|
|
39
|
-
| "PROFLOW_APPROVAL_APPLICATION"
|
|
39
|
+
| "PROFLOW_APPROVAL_APPLICATION"
|
|
40
|
+
| "PROFLOW_PROVISIONING_RELAY_FETCH";
|
|
40
41
|
observation?: Omit<ContentObservation, "tabId" | "windowId">;
|
|
41
42
|
operation?: string;
|
|
42
43
|
input?: Record<string, unknown>;
|
|
44
|
+
url?: string;
|
|
43
45
|
};
|
|
44
46
|
type BridgeConfig = { endpoint: string; token: string };
|
|
45
47
|
type BridgeCommand = {
|
|
@@ -67,7 +69,25 @@ type ContentCommand = {
|
|
|
67
69
|
value?: string;
|
|
68
70
|
fingerprint?: string;
|
|
69
71
|
};
|
|
70
|
-
type
|
|
72
|
+
type ProvisioningOperation =
|
|
73
|
+
| "PROVISION_CUSTOM_GPT"
|
|
74
|
+
| "FINALIZE_CUSTOM_GPT_AUTH";
|
|
75
|
+
type ProvisioningBridgeCommand = {
|
|
76
|
+
commandId: string;
|
|
77
|
+
type: ProvisioningOperation;
|
|
78
|
+
request: Record<string, unknown>;
|
|
79
|
+
};
|
|
80
|
+
type ProvisioningContentCommand = {
|
|
81
|
+
type: "PROFLOW_PROVISIONING_COMMAND";
|
|
82
|
+
operation: ProvisioningOperation;
|
|
83
|
+
request: Record<string, unknown>;
|
|
84
|
+
};
|
|
85
|
+
type ChromeTab = {
|
|
86
|
+
id?: number;
|
|
87
|
+
windowId?: number;
|
|
88
|
+
url?: string;
|
|
89
|
+
status?: "loading" | "complete";
|
|
90
|
+
};
|
|
71
91
|
type ChromeRuntime = {
|
|
72
92
|
runtime: {
|
|
73
93
|
id: string;
|
|
@@ -98,12 +118,17 @@ type ChromeRuntime = {
|
|
|
98
118
|
};
|
|
99
119
|
tabs: {
|
|
100
120
|
query(query: { url?: string }): Promise<ChromeTab[]>;
|
|
121
|
+
get(tabId: number): Promise<ChromeTab>;
|
|
101
122
|
create(create: { url: string; active: boolean }): Promise<ChromeTab>;
|
|
123
|
+
reload(tabId: number): Promise<void>;
|
|
102
124
|
update(
|
|
103
125
|
tabId: number,
|
|
104
126
|
update: { url?: string; active?: boolean },
|
|
105
127
|
): Promise<ChromeTab>;
|
|
106
|
-
sendMessage(
|
|
128
|
+
sendMessage(
|
|
129
|
+
tabId: number,
|
|
130
|
+
message: ContentCommand | ProvisioningContentCommand,
|
|
131
|
+
): Promise<unknown>;
|
|
107
132
|
captureVisibleTab(
|
|
108
133
|
windowId: number,
|
|
109
134
|
options: { format: "png" },
|
|
@@ -158,6 +183,7 @@ function parseConfig(value: unknown): BridgeConfig | null {
|
|
|
158
183
|
|
|
159
184
|
type ManagedRuntimeConfig = {
|
|
160
185
|
proflowRuntimeBridge?: unknown;
|
|
186
|
+
proflowProvisioningBridge?: unknown;
|
|
161
187
|
proflowTaskApplication?: unknown;
|
|
162
188
|
proflowApprovalApplication?: unknown;
|
|
163
189
|
};
|
|
@@ -176,6 +202,7 @@ async function bootstrapManagedRuntimeConfig(): Promise<void> {
|
|
|
176
202
|
if (!isRecord(raw)) return;
|
|
177
203
|
const managed = raw as ManagedRuntimeConfig;
|
|
178
204
|
const bridge = parseConfig(managed.proflowRuntimeBridge);
|
|
205
|
+
const provisioning = parseConfig(managed.proflowProvisioningBridge);
|
|
179
206
|
const task = parseConfig(managed.proflowTaskApplication);
|
|
180
207
|
const approval = parseConfig(managed.proflowApprovalApplication);
|
|
181
208
|
if (!bridge || !task || !approval) {
|
|
@@ -183,6 +210,7 @@ async function bootstrapManagedRuntimeConfig(): Promise<void> {
|
|
|
183
210
|
}
|
|
184
211
|
await chrome.storage.local.set({
|
|
185
212
|
proflowRuntimeBridge: bridge,
|
|
213
|
+
...(provisioning ? { proflowProvisioningBridge: provisioning } : {}),
|
|
186
214
|
proflowTaskApplication: task,
|
|
187
215
|
proflowApprovalApplication: approval,
|
|
188
216
|
});
|
|
@@ -193,6 +221,11 @@ async function bridgeConfig(): Promise<BridgeConfig | null> {
|
|
|
193
221
|
return parseConfig(stored.proflowRuntimeBridge);
|
|
194
222
|
}
|
|
195
223
|
|
|
224
|
+
async function provisioningBridgeConfig(): Promise<BridgeConfig | null> {
|
|
225
|
+
const stored = await chrome.storage.local.get("proflowProvisioningBridge");
|
|
226
|
+
return parseConfig(stored.proflowProvisioningBridge);
|
|
227
|
+
}
|
|
228
|
+
|
|
196
229
|
async function taskApplicationConfig(): Promise<BridgeConfig | null> {
|
|
197
230
|
const stored = await chrome.storage.local.get("proflowTaskApplication");
|
|
198
231
|
return parseConfig(stored.proflowTaskApplication);
|
|
@@ -804,6 +837,96 @@ async function executeCommand(command: BridgeCommand): Promise<unknown> {
|
|
|
804
837
|
throw new Error("BROWSER_PRIMITIVE_UNAVAILABLE");
|
|
805
838
|
}
|
|
806
839
|
|
|
840
|
+
function missingProvisioningReceiver(error: unknown): boolean {
|
|
841
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
842
|
+
return (
|
|
843
|
+
message.includes("Could not establish connection") ||
|
|
844
|
+
message.includes("Receiving end does not exist")
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function isEditorUrl(url: string | undefined): boolean {
|
|
849
|
+
return (
|
|
850
|
+
url === "https://chatgpt.com/gpts/editor" ||
|
|
851
|
+
Boolean(url?.startsWith("https://chatgpt.com/gpts/editor/"))
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function provisioningContentCommand(
|
|
856
|
+
tabId: number,
|
|
857
|
+
operation: ProvisioningOperation,
|
|
858
|
+
request: Record<string, unknown>,
|
|
859
|
+
): Promise<unknown> {
|
|
860
|
+
let receiverReloaded = false;
|
|
861
|
+
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
862
|
+
let response: unknown;
|
|
863
|
+
try {
|
|
864
|
+
response = await chrome.tabs.sendMessage(tabId, {
|
|
865
|
+
type: "PROFLOW_PROVISIONING_COMMAND",
|
|
866
|
+
operation,
|
|
867
|
+
request,
|
|
868
|
+
});
|
|
869
|
+
} catch (error) {
|
|
870
|
+
if (!receiverReloaded && missingProvisioningReceiver(error)) {
|
|
871
|
+
try {
|
|
872
|
+
const tab = await chrome.tabs.get(tabId);
|
|
873
|
+
if (tab.status === "complete" && isEditorUrl(tab.url)) {
|
|
874
|
+
await chrome.tabs.reload(tabId);
|
|
875
|
+
receiverReloaded = true;
|
|
876
|
+
}
|
|
877
|
+
} catch {}
|
|
878
|
+
}
|
|
879
|
+
if (attempt === 59) throw error;
|
|
880
|
+
await sleep(250);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (!isRecord(response) || response.ok !== true) {
|
|
884
|
+
const detail =
|
|
885
|
+
isRecord(response) && typeof response.error === "string"
|
|
886
|
+
? response.error
|
|
887
|
+
: "PROVISIONING_CONTENT_FAILED";
|
|
888
|
+
if (
|
|
889
|
+
(detail === "PROVISIONING_SURFACE_NOT_READY" ||
|
|
890
|
+
detail === "GPT_EDITOR_CONFIGURE_SURFACE_NOT_READY") &&
|
|
891
|
+
attempt < 59
|
|
892
|
+
) {
|
|
893
|
+
await sleep(250);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
throw new Error(detail);
|
|
897
|
+
}
|
|
898
|
+
return response.value;
|
|
899
|
+
}
|
|
900
|
+
throw new Error("PROVISIONING_CONTENT_TIMEOUT");
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async function executeProvisioningCommand(
|
|
904
|
+
command: ProvisioningBridgeCommand,
|
|
905
|
+
): Promise<unknown> {
|
|
906
|
+
if (!isRecord(command.request))
|
|
907
|
+
throw new Error("PROVISIONING_COMMAND_INVALID");
|
|
908
|
+
let editorUrl = "https://chatgpt.com/gpts/editor";
|
|
909
|
+
if (command.type === "FINALIZE_CUSTOM_GPT_AUTH") {
|
|
910
|
+
const carrierUrl = new URL(text(command.request.carrierUrl, "CARRIER_URL"));
|
|
911
|
+
const match = /^\/g\/(g-[A-Za-z0-9_-]+)$/.exec(carrierUrl.pathname);
|
|
912
|
+
if (
|
|
913
|
+
carrierUrl.origin !== "https://chatgpt.com" ||
|
|
914
|
+
carrierUrl.username !== "" ||
|
|
915
|
+
carrierUrl.password !== "" ||
|
|
916
|
+
carrierUrl.search !== "" ||
|
|
917
|
+
carrierUrl.hash !== "" ||
|
|
918
|
+
!match?.[1]
|
|
919
|
+
)
|
|
920
|
+
throw new Error("PROVISIONING_CARRIER_URL_INVALID");
|
|
921
|
+
editorUrl = `https://chatgpt.com/gpts/editor/${match[1]}`;
|
|
922
|
+
}
|
|
923
|
+
const tab = await chrome.tabs.create({ url: editorUrl, active: true });
|
|
924
|
+
return provisioningContentCommand(
|
|
925
|
+
numeric(tab.id, "TAB_ID"),
|
|
926
|
+
command.type,
|
|
927
|
+
command.request,
|
|
928
|
+
);
|
|
929
|
+
}
|
|
807
930
|
async function bridgeFetch(
|
|
808
931
|
config: BridgeConfig,
|
|
809
932
|
path: string,
|
|
@@ -911,7 +1034,144 @@ async function runBridgeLoop() {
|
|
|
911
1034
|
}
|
|
912
1035
|
}
|
|
913
1036
|
|
|
1037
|
+
let provisioningBridgeLoopStarted = false;
|
|
1038
|
+
async function runProvisioningBridgeLoop() {
|
|
1039
|
+
if (provisioningBridgeLoopStarted) return;
|
|
1040
|
+
provisioningBridgeLoopStarted = true;
|
|
1041
|
+
while (true) {
|
|
1042
|
+
const config = await provisioningBridgeConfig();
|
|
1043
|
+
if (!config) {
|
|
1044
|
+
await sleep(1_000);
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
const query = `?extensionInstanceId=${encodeURIComponent(extensionInstanceId)}`;
|
|
1048
|
+
try {
|
|
1049
|
+
const hello = await bridgeFetch(
|
|
1050
|
+
config,
|
|
1051
|
+
"/v1/provisioning/session/hello",
|
|
1052
|
+
{
|
|
1053
|
+
method: "POST",
|
|
1054
|
+
body: JSON.stringify({
|
|
1055
|
+
extensionId: chrome.runtime.id,
|
|
1056
|
+
extensionInstanceId,
|
|
1057
|
+
}),
|
|
1058
|
+
},
|
|
1059
|
+
);
|
|
1060
|
+
if (!hello.ok) throw new Error("PROVISIONING_BRIDGE_HELLO_REJECTED");
|
|
1061
|
+
let lastHeartbeatAt = 0;
|
|
1062
|
+
while (true) {
|
|
1063
|
+
if (Date.now() - lastHeartbeatAt >= 5_000) {
|
|
1064
|
+
const heartbeat = await bridgeFetch(
|
|
1065
|
+
config,
|
|
1066
|
+
`/v1/provisioning/session/heartbeat${query}`,
|
|
1067
|
+
{ method: "POST", body: "{}" },
|
|
1068
|
+
);
|
|
1069
|
+
if (!heartbeat.ok)
|
|
1070
|
+
throw new Error("PROVISIONING_BRIDGE_HEARTBEAT_REJECTED");
|
|
1071
|
+
lastHeartbeatAt = Date.now();
|
|
1072
|
+
}
|
|
1073
|
+
const response = await bridgeFetch(
|
|
1074
|
+
config,
|
|
1075
|
+
`/v1/provisioning/commands/next${query}`,
|
|
1076
|
+
);
|
|
1077
|
+
if (response.status === 204) {
|
|
1078
|
+
await sleep(250);
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (!response.ok) throw new Error("PROVISIONING_BRIDGE_POLL_REJECTED");
|
|
1082
|
+
const command = (await response.json()) as ProvisioningBridgeCommand;
|
|
1083
|
+
const commandHeartbeat = setInterval(() => {
|
|
1084
|
+
void bridgeFetch(
|
|
1085
|
+
config,
|
|
1086
|
+
`/v1/provisioning/session/heartbeat${query}`,
|
|
1087
|
+
{ method: "POST", body: "{}" },
|
|
1088
|
+
).catch(() => undefined);
|
|
1089
|
+
}, 2_000);
|
|
1090
|
+
let result: Record<string, unknown>;
|
|
1091
|
+
try {
|
|
1092
|
+
result = {
|
|
1093
|
+
commandId: command.commandId,
|
|
1094
|
+
ok: true,
|
|
1095
|
+
value: await executeProvisioningCommand(command),
|
|
1096
|
+
};
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
result = {
|
|
1099
|
+
commandId: command.commandId,
|
|
1100
|
+
ok: false,
|
|
1101
|
+
error:
|
|
1102
|
+
error instanceof Error
|
|
1103
|
+
? error.message
|
|
1104
|
+
: "PROVISIONING_EXTENSION_COMMAND_FAILED",
|
|
1105
|
+
};
|
|
1106
|
+
} finally {
|
|
1107
|
+
clearInterval(commandHeartbeat);
|
|
1108
|
+
}
|
|
1109
|
+
const reported = await bridgeFetch(
|
|
1110
|
+
config,
|
|
1111
|
+
`/v1/provisioning/commands/result${query}`,
|
|
1112
|
+
{ method: "POST", body: JSON.stringify(result) },
|
|
1113
|
+
);
|
|
1114
|
+
if (!reported.ok)
|
|
1115
|
+
throw new Error("PROVISIONING_BRIDGE_RESULT_REJECTED");
|
|
1116
|
+
}
|
|
1117
|
+
} catch {
|
|
1118
|
+
await sleep(1_000);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function provisioningRelayBase64(bytes: Uint8Array): string {
|
|
1124
|
+
let binary = "";
|
|
1125
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000)
|
|
1126
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
1127
|
+
return btoa(binary);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
914
1130
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
1131
|
+
if (message.type === "PROFLOW_PROVISIONING_RELAY_FETCH") {
|
|
1132
|
+
const rawUrl = typeof message.url === "string" ? message.url : "";
|
|
1133
|
+
let parsed: URL;
|
|
1134
|
+
try {
|
|
1135
|
+
parsed = new URL(rawUrl);
|
|
1136
|
+
} catch {
|
|
1137
|
+
sendResponse({ ok: false, error: "KNOWLEDGE_RELAY_URL_INVALID" });
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (
|
|
1141
|
+
parsed.protocol !== "http:" ||
|
|
1142
|
+
parsed.hostname !== "127.0.0.1" ||
|
|
1143
|
+
!parsed.pathname.startsWith("/v1/provisioning/files/") ||
|
|
1144
|
+
parsed.username !== "" ||
|
|
1145
|
+
parsed.password !== "" ||
|
|
1146
|
+
parsed.search !== "" ||
|
|
1147
|
+
parsed.hash !== ""
|
|
1148
|
+
) {
|
|
1149
|
+
sendResponse({ ok: false, error: "KNOWLEDGE_RELAY_URL_INVALID" });
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
void fetch(parsed.toString(), { cache: "no-store" }).then(
|
|
1153
|
+
async (response) => {
|
|
1154
|
+
if (!response.ok) {
|
|
1155
|
+
sendResponse({
|
|
1156
|
+
ok: false,
|
|
1157
|
+
error: `KNOWLEDGE_RELAY_FETCH_FAILED:${response.status}`,
|
|
1158
|
+
});
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
1162
|
+
sendResponse({ ok: true, base64: provisioningRelayBase64(bytes) });
|
|
1163
|
+
},
|
|
1164
|
+
(error: unknown) =>
|
|
1165
|
+
sendResponse({
|
|
1166
|
+
ok: false,
|
|
1167
|
+
error:
|
|
1168
|
+
error instanceof Error
|
|
1169
|
+
? `KNOWLEDGE_RELAY_FETCH_FAILED:${error.message}`
|
|
1170
|
+
: "KNOWLEDGE_RELAY_FETCH_FAILED",
|
|
1171
|
+
}),
|
|
1172
|
+
);
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
915
1175
|
if (
|
|
916
1176
|
message.type === "PROFLOW_CONTENT_OBSERVATION" &&
|
|
917
1177
|
message.observation &&
|
|
@@ -1020,6 +1280,7 @@ async function startBackgroundRuntime(): Promise<void> {
|
|
|
1020
1280
|
await bootstrapManagedRuntimeConfig();
|
|
1021
1281
|
await persistSnapshot();
|
|
1022
1282
|
void runBridgeLoop();
|
|
1283
|
+
void runProvisioningBridgeLoop();
|
|
1023
1284
|
void runObserverRecovery();
|
|
1024
1285
|
}
|
|
1025
1286
|
|