skykoi 2026.3.278 → 2026.3.280
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/assets/chrome-extension/background.js +212 -210
- package/assets/chrome-extension/options.html +31 -11
- package/assets/chrome-extension/options.js +34 -34
- package/dist/{acp-cli-By3Qrc0T.js → acp-cli-B6Y_fz5U.js} +6 -6
- package/dist/{archive-D-nIFDvI.js → archive-BpTDgDTy.js} +1 -1
- package/dist/{audit-ZCofKzBh.js → audit-D1fpejRg.js} +8 -8
- package/dist/{auth-BzM5UpTJ.js → auth-D0z_AJK6.js} +1 -1
- package/dist/{auth-health-BC19VMJe.js → auth-health-CEfawBgz.js} +1 -1
- package/dist/build-info.json +3 -3
- package/dist/{call-Bos6YLrN.js → call-DCuOjRUG.js} +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/canvas-host/a2ui/a2ui.bundle.js +6 -4
- package/dist/{channel-options-BRwi2S1q.js → channel-options-CqprncCD.js} +2 -2
- package/dist/{channel-summary-BB3bwKMa.js → channel-summary-CQFJJfiX.js} +4 -4
- package/dist/{channels-cli-BP9FkXtR.js → channels-cli-BSoRJh9j.js} +28 -28
- package/dist/cli/daemon-cli.js +1 -1
- package/dist/{cli-DWW95jJe.js → cli-Ce2MWW7B.js} +22 -22
- package/dist/{completion-cli-Bk5t5c97.js → completion-cli-BWSlYCwr.js} +1 -1
- package/dist/{config-B1La6DxS.js → config-DFSuRu_s.js} +3 -3
- package/dist/{config-guard-BLj9NwR0.js → config-guard-DaFM3Eof.js} +20 -20
- package/dist/{configure-Ci7m67M0.js → configure-tON2C9Zo.js} +7 -7
- package/dist/{control-service-D30sRnYY.js → control-service-B1LQv-jp.js} +2 -2
- package/dist/control-ui/assets/index-C7Jts3SS.js.map +1 -1
- package/dist/{cron-cli-_UjEtRij.js → cron-cli-CIsurmrl.js} +6 -6
- package/dist/{daemon-cli-P1M4WfxO.js → daemon-cli-BxVVzYet.js} +9 -9
- package/dist/{daemon-runtime-Ct4U4ixT.js → daemon-runtime-DUhZ0AE3.js} +1 -1
- package/dist/{deliver-B-J5hhwR.js → deliver-QH8nLFB5.js} +7 -7
- package/dist/{deps-OwHcOs6U.js → deps-CQov5sWA.js} +2 -2
- package/dist/{devices-cli-C9pNVqZ7.js → devices-cli-Bn9pQ5ky.js} +5 -5
- package/dist/{directory-cli-BPMP0Zk7.js → directory-cli-GOnzyOoc.js} +3 -3
- package/dist/{dispatcher-QEMerbmH.js → dispatcher-SB6YDznX.js} +1 -1
- package/dist/{dist-BfJYv-iR.js → dist-9wkUrskD.js} +2 -2
- package/dist/{dist-2N9GeOCZ.js → dist-CLm8TPvp.js} +3 -3
- package/dist/{dns-cli-CVVLxo5W.js → dns-cli-CyvmFPia.js} +3 -3
- package/dist/{docs-cli-DnVsm2sD.js → docs-cli-nLD4utdW.js} +3 -3
- package/dist/{doctor-C26BWayN.js → doctor-4O-p5ipj.js} +16 -16
- package/dist/entry.js +1 -1
- package/dist/{exec-approvals-cli-DsmzGEdE.js → exec-approvals-cli-BNElx2Vq.js} +7 -7
- package/dist/extension-api.js +22 -22
- package/dist/{gateway-cli-CCLbtMBI.js → gateway-cli-CjwJynIz.js} +50 -50
- package/dist/{gateway-rpc-CxxLj5l6.js → gateway-rpc-C630PmXq.js} +1 -1
- package/dist/{github-copilot-auth-ae1ycEGH.js → github-copilot-auth-CfebkeVF.js} +4 -4
- package/dist/{gmail-setup-utils-C47vy-M_.js → gmail-setup-utils-DcuIAwKK.js} +1 -1
- package/dist/{health-format-DevJEyuH.js → health-format-C-5YUjyN.js} +6 -6
- package/dist/{hooks-cli-Bk-8PCu1.js → hooks-cli-DHv47L8h.js} +25 -25
- package/dist/{hooks-status-ONt2WSFD.js → hooks-status-BI-Kg4Yg.js} +1 -1
- package/dist/{image-Db_oz8I6.js → image-tWMcAovq.js} +5 -5
- package/dist/index.js +50 -50
- package/dist/{installs-m9gMtTy1.js → installs-Bg5c6KoO.js} +1 -1
- package/dist/{koi-BenZ-HcC.js → koi-ktvnaIfS.js} +8 -8
- package/dist/{login-qr-BUNnQgv9.js → login-qr-DKqrrAZR.js} +1 -1
- package/dist/{logs-cli-DjmYGwDw.js → logs-cli-DmAmSxEQ.js} +6 -6
- package/dist/{manager-CofU7sxR.js → manager-BjhAM06t.js} +1 -1
- package/dist/{model-selection-BdujDB_E.js → model-selection-mCem7UcZ.js} +23 -8
- package/dist/{models-cli-6PPNkmAm.js → models-cli-BizaZ067.js} +24 -24
- package/dist/{net-DG4ItObd.js → net-dAXg-DbE.js} +2 -2
- package/dist/{node-cli-C_clt6lE.js → node-cli-B0bHqkX-.js} +11 -11
- package/dist/{nodes-cli-BzVkLBFe.js → nodes-cli-DEPAIC6K.js} +6 -6
- package/dist/{onboard-channels-M4xOz6Tx.js → onboard-channels-DvwX0NAI.js} +3 -3
- package/dist/{onboard-skills-DfOGA4RH.js → onboard-skills-B1A80W7q.js} +8 -8
- package/dist/{onboarding-BMf2ucWp.js → onboarding-1427o2ED.js} +18 -17
- package/dist/{pairing-cli-Zm24Bwa3.js → pairing-cli-69QjJ5Xa.js} +3 -3
- package/dist/{pi-embedded-helpers-BR1OjovY.js → pi-embedded-helpers-CCBbyQJT.js} +1 -1
- package/dist/{pi-model-discovery-Cbn1GZwb.js → pi-model-discovery-C2vnlBAx.js} +1 -1
- package/dist/{pi-tools.policy-BTylCL58.js → pi-tools.policy-CeUHuvkF.js} +1 -1
- package/dist/{plugin-auto-enable-Bx_BHwcy.js → plugin-auto-enable-DPugTU6E.js} +1 -1
- package/dist/{plugin-registry-DkvBeUxp.js → plugin-registry-CW8wr4Y-.js} +2 -2
- package/dist/plugin-sdk/browser/config.d.ts +1 -0
- package/dist/plugin-sdk/compat/legacy-names.d.ts +1 -0
- package/dist/plugin-sdk/index.js +29 -14
- package/dist/{plugins-cli-DCjlrnJy.js → plugins-cli-CucfnMF8.js} +26 -26
- package/dist/{program-C3SbZ8xp.js → program-qtsOE9Wj.js} +6 -6
- package/dist/{register.subclis-BJPL11BX.js → register.subclis-Dab8Usr4.js} +28 -28
- package/dist/{reply-CQKbNkW1.js → reply-DbSDQiIc.js} +87 -37
- package/dist/{routes-vNI4VWn-.js → routes-DTCo5sqi.js} +17 -14
- package/dist/{rpc-DykocUdv.js → rpc-BPzU2GEK.js} +1 -1
- package/dist/{run-main-BUlzecP7.js → run-main-CuRcoDQM.js} +52 -52
- package/dist/{runner-DRI8-OuM.js → runner-BH0V-ihf.js} +5 -5
- package/dist/{sandbox-COQl76qj.js → sandbox-C18TfibJ.js} +5 -5
- package/dist/{sandbox-cli-D_WC6tOL.js → sandbox-cli-B3MrFcpb.js} +9 -9
- package/dist/{security-cli-Bg751BmY.js → security-cli-COu1DzpE.js} +14 -14
- package/dist/{server-context-BG4YK6Ll.js → server-context-By4m-ZR-.js} +14 -5
- package/dist/{server-node-events-B-RuKYD2.js → server-node-events-DtB7jTdC.js} +24 -24
- package/dist/{service-audit-DMmKqL8q.js → service-audit-DAp2zv_8.js} +1 -1
- package/dist/{sessions-LG_t6M1u.js → sessions-BG9-1nv9.js} +2 -2
- package/dist/{shared-ih2ZCRlQ.js → shared-DGIWPJP2.js} +1 -1
- package/dist/{skills-C0SvpGTU.js → skills-DgBgB325.js} +1 -1
- package/dist/{skills-cli-COUXiho3.js → skills-cli-EHDty6AK.js} +6 -6
- package/dist/{skills-status-PpCs1M-j.js → skills-status-QmdDW96R.js} +2 -2
- package/dist/{status-CSccNDO5.js → status-DlYCO3S-.js} +2 -2
- package/dist/{system-cli-BwHARoov.js → system-cli-CpjU8A_6.js} +6 -6
- package/dist/{tui-cli-Bzwfs5KA.js → tui-cli-jI_TGgaX.js} +14 -14
- package/dist/{tui-bwRb_Ht8.js → tui-ltTLDIiD.js} +6 -6
- package/dist/{update-DYs42iFB.js → update-D5nDnc-5.js} +1 -1
- package/dist/{update-cli-7_iySREb.js → update-cli-BgiWzE6D.js} +39 -39
- package/dist/{update-runner-CTtv4PVN.js → update-runner-DhEGhTtX.js} +5 -5
- package/dist/{webhooks-cli-BuHqy_S0.js → webhooks-cli-DM7fc19r.js} +6 -6
- package/docs/automation/cron-vs-heartbeat.md +1 -1
- package/docs/channels/feishu.md +2 -2
- package/docs/channels/msteams.md +7 -7
- package/docs/channels/troubleshooting.md +15 -15
- package/docs/concepts/typebox.md +8 -8
- package/docs/gateway/configuration.md +2 -2
- package/docs/gateway/remote-gateway-readme.md +1 -1
- package/docs/help/faq.md +8 -8
- package/docs/index.md +1 -1
- package/docs/install/fly.md +7 -7
- package/docs/install/gcp.md +12 -12
- package/docs/install/hetzner.md +12 -12
- package/docs/install/installer.md +37 -37
- package/docs/install/macos-vm.md +4 -4
- package/docs/pi.md +12 -12
- package/docs/prose.md +1 -1
- package/docs/reference/templates/AGENTS.md +26 -1
- package/docs/reference/templates/BOOTSTRAP.md +3 -0
- package/docs/reference/templates/HEARTBEAT.md +12 -3
- package/docs/reference/templates/KOIS.md +26 -1
- package/docs/reference/templates/SOUL.md +1 -0
- package/docs/zh-CN/channels/feishu.md +3 -3
- package/docs/zh-CN/channels/msteams.md +7 -7
- package/docs/zh-CN/concepts/typebox.md +8 -8
- package/docs/zh-CN/gateway/security/index.md +1 -1
- package/docs/zh-CN/gateway/troubleshooting.md +8 -8
- package/docs/zh-CN/help/faq.md +8 -8
- package/docs/zh-CN/index.md +1 -1
- package/docs/zh-CN/install/fly.md +7 -7
- package/docs/zh-CN/install/gcp.md +7 -7
- package/docs/zh-CN/install/hetzner.md +7 -7
- package/docs/zh-CN/install/macos-vm.md +4 -4
- package/docs/zh-CN/pi.md +11 -11
- package/docs/zh-CN/prose.md +1 -1
- package/extensions/bluebubbles/src/monitor.ts +1 -4
- package/extensions/diagnostics-otel/src/service.test.ts +3 -9
- package/extensions/google-gemini-cli-auth/oauth.ts +1 -4
- package/extensions/matrix/CHANGELOG.md +1 -0
- package/extensions/matrix/src/actions.ts +1 -1
- package/extensions/matrix/src/channel.ts +3 -3
- package/extensions/matrix/src/directory-live.ts +1 -1
- package/extensions/matrix/src/matrix/accounts.ts +1 -1
- package/extensions/matrix/src/matrix/client/config.ts +2 -2
- package/extensions/matrix/src/matrix/client.test.ts +1 -1
- package/extensions/matrix/src/matrix/monitor/location.ts +1 -5
- package/extensions/matrix/src/matrix/probe.ts +2 -1
- package/extensions/matrix/src/outbound.ts +3 -3
- package/extensions/msteams/CHANGELOG.md +1 -0
- package/extensions/msteams/package.json +2 -2
- package/extensions/nostr/CHANGELOG.md +1 -0
- package/extensions/open-prose/skills/prose/SKILL.md +8 -8
- package/extensions/open-prose/skills/prose/alt-borges.md +1 -1
- package/extensions/open-prose/skills/prose/alts/arabian-nights.md +2 -2
- package/extensions/open-prose/skills/prose/alts/borges.md +1 -1
- package/extensions/open-prose/skills/prose/alts/folk.md +1 -1
- package/extensions/open-prose/skills/prose/alts/homer.md +1 -1
- package/extensions/open-prose/skills/prose/alts/kafka.md +2 -2
- package/extensions/open-prose/skills/prose/compiler.md +52 -52
- package/extensions/open-prose/skills/prose/examples/README.md +8 -8
- package/extensions/open-prose/skills/prose/examples/roadmap/README.md +1 -1
- package/extensions/open-prose/skills/prose/help.md +2 -2
- package/extensions/open-prose/skills/prose/primitives/session.md +3 -3
- package/extensions/open-prose/skills/prose/prose.md +10 -10
- package/extensions/open-prose/skills/prose/state/filesystem.md +6 -6
- package/extensions/open-prose/skills/prose/state/in-context.md +1 -1
- package/extensions/open-prose/skills/prose/state/postgres.md +9 -9
- package/extensions/open-prose/skills/prose/state/sqlite.md +9 -9
- package/extensions/twitch/CHANGELOG.md +1 -0
- package/extensions/twitch/src/onboarding.ts +1 -4
- package/extensions/voice-call/CHANGELOG.md +1 -0
- package/extensions/voice-call/src/core-bridge.ts +1 -5
- package/extensions/zalo/CHANGELOG.md +1 -0
- package/extensions/zalo/src/accounts.ts +1 -4
- package/extensions/zalouser/CHANGELOG.md +1 -0
- package/package.json +1 -1
- package/skills/chrome-e2e/SKILL.md +23 -17
- package/skills/coding-koi/SKILL.md +8 -10
|
@@ -1,438 +1,440 @@
|
|
|
1
|
-
const DEFAULT_PORT = 18792
|
|
1
|
+
const DEFAULT_PORT = 18792;
|
|
2
2
|
|
|
3
3
|
const BADGE = {
|
|
4
|
-
on: { text:
|
|
5
|
-
off: { text:
|
|
6
|
-
connecting: { text:
|
|
7
|
-
error: { text:
|
|
8
|
-
}
|
|
4
|
+
on: { text: "ON", color: "#FF5A36" },
|
|
5
|
+
off: { text: "", color: "#000000" },
|
|
6
|
+
connecting: { text: "…", color: "#F59E0B" },
|
|
7
|
+
error: { text: "!", color: "#B91C1C" },
|
|
8
|
+
};
|
|
9
9
|
|
|
10
10
|
/** @type {WebSocket|null} */
|
|
11
|
-
let relayWs = null
|
|
11
|
+
let relayWs = null;
|
|
12
12
|
/** @type {Promise<void>|null} */
|
|
13
|
-
let relayConnectPromise = null
|
|
13
|
+
let relayConnectPromise = null;
|
|
14
14
|
|
|
15
|
-
let debuggerListenersInstalled = false
|
|
15
|
+
let debuggerListenersInstalled = false;
|
|
16
16
|
|
|
17
|
-
let nextSession = 1
|
|
17
|
+
let nextSession = 1;
|
|
18
18
|
|
|
19
19
|
/** @type {Map<number, {state:'connecting'|'connected', sessionId?:string, targetId?:string, attachOrder?:number}>} */
|
|
20
|
-
const tabs = new Map()
|
|
20
|
+
const tabs = new Map();
|
|
21
21
|
/** @type {Map<string, number>} */
|
|
22
|
-
const tabBySession = new Map()
|
|
22
|
+
const tabBySession = new Map();
|
|
23
23
|
/** @type {Map<string, number>} */
|
|
24
|
-
const childSessionToTab = new Map()
|
|
24
|
+
const childSessionToTab = new Map();
|
|
25
25
|
|
|
26
26
|
/** @type {Map<number, {resolve:(v:any)=>void, reject:(e:Error)=>void}>} */
|
|
27
|
-
const pending = new Map()
|
|
27
|
+
const pending = new Map();
|
|
28
28
|
|
|
29
29
|
function nowStack() {
|
|
30
30
|
try {
|
|
31
|
-
return new Error().stack ||
|
|
31
|
+
return new Error().stack || "";
|
|
32
32
|
} catch {
|
|
33
|
-
return
|
|
33
|
+
return "";
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async function getRelayPort() {
|
|
38
|
-
const stored = await chrome.storage.local.get([
|
|
39
|
-
const raw = stored.relayPort
|
|
40
|
-
const n = Number.parseInt(String(raw ||
|
|
41
|
-
if (!Number.isFinite(n) || n <= 0 || n > 65535) return DEFAULT_PORT
|
|
42
|
-
return n
|
|
38
|
+
const stored = await chrome.storage.local.get(["relayPort"]);
|
|
39
|
+
const raw = stored.relayPort;
|
|
40
|
+
const n = Number.parseInt(String(raw || ""), 10);
|
|
41
|
+
if (!Number.isFinite(n) || n <= 0 || n > 65535) return DEFAULT_PORT;
|
|
42
|
+
return n;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function setBadge(tabId, kind) {
|
|
46
|
-
const cfg = BADGE[kind]
|
|
47
|
-
void chrome.action.setBadgeText({ tabId, text: cfg.text })
|
|
48
|
-
void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color })
|
|
49
|
-
void chrome.action.setBadgeTextColor({ tabId, color:
|
|
46
|
+
const cfg = BADGE[kind];
|
|
47
|
+
void chrome.action.setBadgeText({ tabId, text: cfg.text });
|
|
48
|
+
void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color });
|
|
49
|
+
void chrome.action.setBadgeTextColor({ tabId, color: "#FFFFFF" }).catch(() => {});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
async function ensureRelayConnection() {
|
|
53
|
-
if (relayWs && relayWs.readyState === WebSocket.OPEN) return
|
|
54
|
-
if (relayConnectPromise) return await relayConnectPromise
|
|
53
|
+
if (relayWs && relayWs.readyState === WebSocket.OPEN) return;
|
|
54
|
+
if (relayConnectPromise) return await relayConnectPromise;
|
|
55
55
|
|
|
56
56
|
relayConnectPromise = (async () => {
|
|
57
|
-
const port = await getRelayPort()
|
|
58
|
-
const httpBase = `http://127.0.0.1:${port}
|
|
59
|
-
const wsUrl = `ws://127.0.0.1:${port}/extension
|
|
57
|
+
const port = await getRelayPort();
|
|
58
|
+
const httpBase = `http://127.0.0.1:${port}`;
|
|
59
|
+
const wsUrl = `ws://127.0.0.1:${port}/extension`;
|
|
60
60
|
|
|
61
61
|
// Fast preflight: is the relay server up?
|
|
62
62
|
try {
|
|
63
|
-
await fetch(`${httpBase}/`, { method:
|
|
63
|
+
await fetch(`${httpBase}/`, { method: "HEAD", signal: AbortSignal.timeout(2000) });
|
|
64
64
|
} catch (err) {
|
|
65
|
-
throw new Error(`Relay server not reachable at ${httpBase} (${String(err)})`)
|
|
65
|
+
throw new Error(`Relay server not reachable at ${httpBase} (${String(err)})`);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const ws = new WebSocket(wsUrl)
|
|
69
|
-
relayWs = ws
|
|
68
|
+
const ws = new WebSocket(wsUrl);
|
|
69
|
+
relayWs = ws;
|
|
70
70
|
|
|
71
71
|
await new Promise((resolve, reject) => {
|
|
72
|
-
const t = setTimeout(() => reject(new Error(
|
|
72
|
+
const t = setTimeout(() => reject(new Error("WebSocket connect timeout")), 5000);
|
|
73
73
|
ws.onopen = () => {
|
|
74
|
-
clearTimeout(t)
|
|
75
|
-
resolve()
|
|
76
|
-
}
|
|
74
|
+
clearTimeout(t);
|
|
75
|
+
resolve();
|
|
76
|
+
};
|
|
77
77
|
ws.onerror = () => {
|
|
78
|
-
clearTimeout(t)
|
|
79
|
-
reject(new Error(
|
|
80
|
-
}
|
|
78
|
+
clearTimeout(t);
|
|
79
|
+
reject(new Error("WebSocket connect failed"));
|
|
80
|
+
};
|
|
81
81
|
ws.onclose = (ev) => {
|
|
82
|
-
clearTimeout(t)
|
|
83
|
-
reject(new Error(`WebSocket closed (${ev.code} ${ev.reason ||
|
|
84
|
-
}
|
|
85
|
-
})
|
|
82
|
+
clearTimeout(t);
|
|
83
|
+
reject(new Error(`WebSocket closed (${ev.code} ${ev.reason || "no reason"})`));
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
86
|
|
|
87
|
-
ws.onmessage = (event) => void onRelayMessage(String(event.data ||
|
|
88
|
-
ws.onclose = () => onRelayClosed(
|
|
89
|
-
ws.onerror = () => onRelayClosed(
|
|
87
|
+
ws.onmessage = (event) => void onRelayMessage(String(event.data || ""));
|
|
88
|
+
ws.onclose = () => onRelayClosed("closed");
|
|
89
|
+
ws.onerror = () => onRelayClosed("error");
|
|
90
90
|
|
|
91
91
|
if (!debuggerListenersInstalled) {
|
|
92
|
-
debuggerListenersInstalled = true
|
|
93
|
-
chrome.debugger.onEvent.addListener(onDebuggerEvent)
|
|
94
|
-
chrome.debugger.onDetach.addListener(onDebuggerDetach)
|
|
92
|
+
debuggerListenersInstalled = true;
|
|
93
|
+
chrome.debugger.onEvent.addListener(onDebuggerEvent);
|
|
94
|
+
chrome.debugger.onDetach.addListener(onDebuggerDetach);
|
|
95
95
|
}
|
|
96
|
-
})()
|
|
96
|
+
})();
|
|
97
97
|
|
|
98
98
|
try {
|
|
99
|
-
await relayConnectPromise
|
|
99
|
+
await relayConnectPromise;
|
|
100
100
|
} finally {
|
|
101
|
-
relayConnectPromise = null
|
|
101
|
+
relayConnectPromise = null;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function onRelayClosed(reason) {
|
|
106
|
-
relayWs = null
|
|
106
|
+
relayWs = null;
|
|
107
107
|
for (const [id, p] of pending.entries()) {
|
|
108
|
-
pending.delete(id)
|
|
109
|
-
p.reject(new Error(`Relay disconnected (${reason})`))
|
|
108
|
+
pending.delete(id);
|
|
109
|
+
p.reject(new Error(`Relay disconnected (${reason})`));
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
for (const tabId of tabs.keys()) {
|
|
113
|
-
void chrome.debugger.detach({ tabId }).catch(() => {})
|
|
114
|
-
setBadge(tabId,
|
|
113
|
+
void chrome.debugger.detach({ tabId }).catch(() => {});
|
|
114
|
+
setBadge(tabId, "connecting");
|
|
115
115
|
void chrome.action.setTitle({
|
|
116
116
|
tabId,
|
|
117
|
-
title:
|
|
118
|
-
})
|
|
117
|
+
title: "SKYKOI Browser Relay: disconnected (click to re-attach)",
|
|
118
|
+
});
|
|
119
119
|
}
|
|
120
|
-
tabs.clear()
|
|
121
|
-
tabBySession.clear()
|
|
122
|
-
childSessionToTab.clear()
|
|
120
|
+
tabs.clear();
|
|
121
|
+
tabBySession.clear();
|
|
122
|
+
childSessionToTab.clear();
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function sendToRelay(payload) {
|
|
126
|
-
const ws = relayWs
|
|
126
|
+
const ws = relayWs;
|
|
127
127
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
128
|
-
throw new Error(
|
|
128
|
+
throw new Error("Relay not connected");
|
|
129
129
|
}
|
|
130
|
-
ws.send(JSON.stringify(payload))
|
|
130
|
+
ws.send(JSON.stringify(payload));
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
async function maybeOpenHelpOnce() {
|
|
134
134
|
try {
|
|
135
|
-
const stored = await chrome.storage.local.get([
|
|
136
|
-
if (stored.helpOnErrorShown === true) return
|
|
137
|
-
await chrome.storage.local.set({ helpOnErrorShown: true })
|
|
138
|
-
await chrome.runtime.openOptionsPage()
|
|
135
|
+
const stored = await chrome.storage.local.get(["helpOnErrorShown"]);
|
|
136
|
+
if (stored.helpOnErrorShown === true) return;
|
|
137
|
+
await chrome.storage.local.set({ helpOnErrorShown: true });
|
|
138
|
+
await chrome.runtime.openOptionsPage();
|
|
139
139
|
} catch {
|
|
140
140
|
// ignore
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
function requestFromRelay(command) {
|
|
145
|
-
const id = command.id
|
|
145
|
+
const id = command.id;
|
|
146
146
|
return new Promise((resolve, reject) => {
|
|
147
|
-
pending.set(id, { resolve, reject })
|
|
147
|
+
pending.set(id, { resolve, reject });
|
|
148
148
|
try {
|
|
149
|
-
sendToRelay(command)
|
|
149
|
+
sendToRelay(command);
|
|
150
150
|
} catch (err) {
|
|
151
|
-
pending.delete(id)
|
|
152
|
-
reject(err instanceof Error ? err : new Error(String(err)))
|
|
151
|
+
pending.delete(id);
|
|
152
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
153
153
|
}
|
|
154
|
-
})
|
|
154
|
+
});
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
async function onRelayMessage(text) {
|
|
158
158
|
/** @type {any} */
|
|
159
|
-
let msg
|
|
159
|
+
let msg;
|
|
160
160
|
try {
|
|
161
|
-
msg = JSON.parse(text)
|
|
161
|
+
msg = JSON.parse(text);
|
|
162
162
|
} catch {
|
|
163
|
-
return
|
|
163
|
+
return;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
if (msg && msg.method ===
|
|
166
|
+
if (msg && msg.method === "ping") {
|
|
167
167
|
try {
|
|
168
|
-
sendToRelay({ method:
|
|
168
|
+
sendToRelay({ method: "pong" });
|
|
169
169
|
} catch {
|
|
170
170
|
// ignore
|
|
171
171
|
}
|
|
172
|
-
return
|
|
172
|
+
return;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
if (msg && typeof msg.id ===
|
|
176
|
-
const p = pending.get(msg.id)
|
|
177
|
-
if (!p) return
|
|
178
|
-
pending.delete(msg.id)
|
|
179
|
-
if (msg.error) p.reject(new Error(String(msg.error)))
|
|
180
|
-
else p.resolve(msg.result)
|
|
181
|
-
return
|
|
175
|
+
if (msg && typeof msg.id === "number" && (msg.result !== undefined || msg.error !== undefined)) {
|
|
176
|
+
const p = pending.get(msg.id);
|
|
177
|
+
if (!p) return;
|
|
178
|
+
pending.delete(msg.id);
|
|
179
|
+
if (msg.error) p.reject(new Error(String(msg.error)));
|
|
180
|
+
else p.resolve(msg.result);
|
|
181
|
+
return;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
if (msg && typeof msg.id ===
|
|
184
|
+
if (msg && typeof msg.id === "number" && msg.method === "forwardCDPCommand") {
|
|
185
185
|
try {
|
|
186
|
-
const result = await handleForwardCdpCommand(msg)
|
|
187
|
-
sendToRelay({ id: msg.id, result })
|
|
186
|
+
const result = await handleForwardCdpCommand(msg);
|
|
187
|
+
sendToRelay({ id: msg.id, result });
|
|
188
188
|
} catch (err) {
|
|
189
|
-
sendToRelay({ id: msg.id, error: err instanceof Error ? err.message : String(err) })
|
|
189
|
+
sendToRelay({ id: msg.id, error: err instanceof Error ? err.message : String(err) });
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
function getTabBySessionId(sessionId) {
|
|
195
|
-
const direct = tabBySession.get(sessionId)
|
|
196
|
-
if (direct) return { tabId: direct, kind:
|
|
197
|
-
const child = childSessionToTab.get(sessionId)
|
|
198
|
-
if (child) return { tabId: child, kind:
|
|
199
|
-
return null
|
|
195
|
+
const direct = tabBySession.get(sessionId);
|
|
196
|
+
if (direct) return { tabId: direct, kind: "main" };
|
|
197
|
+
const child = childSessionToTab.get(sessionId);
|
|
198
|
+
if (child) return { tabId: child, kind: "child" };
|
|
199
|
+
return null;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
function getTabByTargetId(targetId) {
|
|
203
203
|
for (const [tabId, tab] of tabs.entries()) {
|
|
204
|
-
if (tab.targetId === targetId) return tabId
|
|
204
|
+
if (tab.targetId === targetId) return tabId;
|
|
205
205
|
}
|
|
206
|
-
return null
|
|
206
|
+
return null;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
async function attachTab(tabId, opts = {}) {
|
|
210
|
-
const debuggee = { tabId }
|
|
211
|
-
await chrome.debugger.attach(debuggee,
|
|
212
|
-
await chrome.debugger.sendCommand(debuggee,
|
|
213
|
-
|
|
214
|
-
const info = /** @type {any} */ (
|
|
215
|
-
|
|
216
|
-
|
|
210
|
+
const debuggee = { tabId };
|
|
211
|
+
await chrome.debugger.attach(debuggee, "1.3");
|
|
212
|
+
await chrome.debugger.sendCommand(debuggee, "Page.enable").catch(() => {});
|
|
213
|
+
|
|
214
|
+
const info = /** @type {any} */ (
|
|
215
|
+
await chrome.debugger.sendCommand(debuggee, "Target.getTargetInfo")
|
|
216
|
+
);
|
|
217
|
+
const targetInfo = info?.targetInfo;
|
|
218
|
+
const targetId = String(targetInfo?.targetId || "").trim();
|
|
217
219
|
if (!targetId) {
|
|
218
|
-
throw new Error(
|
|
220
|
+
throw new Error("Target.getTargetInfo returned no targetId");
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
const sessionId = `cb-tab-${nextSession++}
|
|
222
|
-
const attachOrder = nextSession
|
|
223
|
+
const sessionId = `cb-tab-${nextSession++}`;
|
|
224
|
+
const attachOrder = nextSession;
|
|
223
225
|
|
|
224
|
-
tabs.set(tabId, { state:
|
|
225
|
-
tabBySession.set(sessionId, tabId)
|
|
226
|
+
tabs.set(tabId, { state: "connected", sessionId, targetId, attachOrder });
|
|
227
|
+
tabBySession.set(sessionId, tabId);
|
|
226
228
|
void chrome.action.setTitle({
|
|
227
229
|
tabId,
|
|
228
|
-
title:
|
|
229
|
-
})
|
|
230
|
+
title: "SKYKOI Browser Relay: attached (click to detach)",
|
|
231
|
+
});
|
|
230
232
|
|
|
231
233
|
if (!opts.skipAttachedEvent) {
|
|
232
234
|
sendToRelay({
|
|
233
|
-
method:
|
|
235
|
+
method: "forwardCDPEvent",
|
|
234
236
|
params: {
|
|
235
|
-
method:
|
|
237
|
+
method: "Target.attachedToTarget",
|
|
236
238
|
params: {
|
|
237
239
|
sessionId,
|
|
238
240
|
targetInfo: { ...targetInfo, attached: true },
|
|
239
241
|
waitingForDebugger: false,
|
|
240
242
|
},
|
|
241
243
|
},
|
|
242
|
-
})
|
|
244
|
+
});
|
|
243
245
|
}
|
|
244
246
|
|
|
245
|
-
setBadge(tabId,
|
|
246
|
-
return { sessionId, targetId }
|
|
247
|
+
setBadge(tabId, "on");
|
|
248
|
+
return { sessionId, targetId };
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
async function detachTab(tabId, reason) {
|
|
250
|
-
const tab = tabs.get(tabId)
|
|
252
|
+
const tab = tabs.get(tabId);
|
|
251
253
|
if (tab?.sessionId && tab?.targetId) {
|
|
252
254
|
try {
|
|
253
255
|
sendToRelay({
|
|
254
|
-
method:
|
|
256
|
+
method: "forwardCDPEvent",
|
|
255
257
|
params: {
|
|
256
|
-
method:
|
|
258
|
+
method: "Target.detachedFromTarget",
|
|
257
259
|
params: { sessionId: tab.sessionId, targetId: tab.targetId, reason },
|
|
258
260
|
},
|
|
259
|
-
})
|
|
261
|
+
});
|
|
260
262
|
} catch {
|
|
261
263
|
// ignore
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
|
|
265
|
-
if (tab?.sessionId) tabBySession.delete(tab.sessionId)
|
|
266
|
-
tabs.delete(tabId)
|
|
267
|
+
if (tab?.sessionId) tabBySession.delete(tab.sessionId);
|
|
268
|
+
tabs.delete(tabId);
|
|
267
269
|
|
|
268
270
|
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
|
269
|
-
if (parentTabId === tabId) childSessionToTab.delete(childSessionId)
|
|
271
|
+
if (parentTabId === tabId) childSessionToTab.delete(childSessionId);
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
try {
|
|
273
|
-
await chrome.debugger.detach({ tabId })
|
|
275
|
+
await chrome.debugger.detach({ tabId });
|
|
274
276
|
} catch {
|
|
275
277
|
// ignore
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
setBadge(tabId,
|
|
280
|
+
setBadge(tabId, "off");
|
|
279
281
|
void chrome.action.setTitle({
|
|
280
282
|
tabId,
|
|
281
|
-
title:
|
|
282
|
-
})
|
|
283
|
+
title: "SKYKOI Browser Relay (click to attach/detach)",
|
|
284
|
+
});
|
|
283
285
|
}
|
|
284
286
|
|
|
285
287
|
async function connectOrToggleForActiveTab() {
|
|
286
|
-
const [active] = await chrome.tabs.query({ active: true, currentWindow: true })
|
|
287
|
-
const tabId = active?.id
|
|
288
|
-
if (!tabId) return
|
|
289
|
-
|
|
290
|
-
const existing = tabs.get(tabId)
|
|
291
|
-
if (existing?.state ===
|
|
292
|
-
await detachTab(tabId,
|
|
293
|
-
return
|
|
288
|
+
const [active] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
289
|
+
const tabId = active?.id;
|
|
290
|
+
if (!tabId) return;
|
|
291
|
+
|
|
292
|
+
const existing = tabs.get(tabId);
|
|
293
|
+
if (existing?.state === "connected") {
|
|
294
|
+
await detachTab(tabId, "toggle");
|
|
295
|
+
return;
|
|
294
296
|
}
|
|
295
297
|
|
|
296
|
-
tabs.set(tabId, { state:
|
|
297
|
-
setBadge(tabId,
|
|
298
|
+
tabs.set(tabId, { state: "connecting" });
|
|
299
|
+
setBadge(tabId, "connecting");
|
|
298
300
|
void chrome.action.setTitle({
|
|
299
301
|
tabId,
|
|
300
|
-
title:
|
|
301
|
-
})
|
|
302
|
+
title: "SKYKOI Browser Relay: connecting to local relay…",
|
|
303
|
+
});
|
|
302
304
|
|
|
303
305
|
try {
|
|
304
|
-
await ensureRelayConnection()
|
|
305
|
-
await attachTab(tabId)
|
|
306
|
+
await ensureRelayConnection();
|
|
307
|
+
await attachTab(tabId);
|
|
306
308
|
} catch (err) {
|
|
307
|
-
tabs.delete(tabId)
|
|
308
|
-
setBadge(tabId,
|
|
309
|
+
tabs.delete(tabId);
|
|
310
|
+
setBadge(tabId, "error");
|
|
309
311
|
void chrome.action.setTitle({
|
|
310
312
|
tabId,
|
|
311
|
-
title:
|
|
312
|
-
})
|
|
313
|
-
void maybeOpenHelpOnce()
|
|
313
|
+
title: "SKYKOI Browser Relay: relay not running (open options for setup)",
|
|
314
|
+
});
|
|
315
|
+
void maybeOpenHelpOnce();
|
|
314
316
|
// Extra breadcrumbs in chrome://extensions service worker logs.
|
|
315
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
316
|
-
console.warn(
|
|
317
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
318
|
+
console.warn("attach failed", message, nowStack());
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
async function handleForwardCdpCommand(msg) {
|
|
321
|
-
const method = String(msg?.params?.method ||
|
|
322
|
-
const params = msg?.params?.params || undefined
|
|
323
|
-
const sessionId = typeof msg?.params?.sessionId ===
|
|
323
|
+
const method = String(msg?.params?.method || "").trim();
|
|
324
|
+
const params = msg?.params?.params || undefined;
|
|
325
|
+
const sessionId = typeof msg?.params?.sessionId === "string" ? msg.params.sessionId : undefined;
|
|
324
326
|
|
|
325
327
|
// Map command to tab
|
|
326
|
-
const bySession = sessionId ? getTabBySessionId(sessionId) : null
|
|
327
|
-
const targetId = typeof params?.targetId ===
|
|
328
|
+
const bySession = sessionId ? getTabBySessionId(sessionId) : null;
|
|
329
|
+
const targetId = typeof params?.targetId === "string" ? params.targetId : undefined;
|
|
328
330
|
const tabId =
|
|
329
331
|
bySession?.tabId ||
|
|
330
332
|
(targetId ? getTabByTargetId(targetId) : null) ||
|
|
331
333
|
(() => {
|
|
332
334
|
// No sessionId: pick the first connected tab (stable-ish).
|
|
333
335
|
for (const [id, tab] of tabs.entries()) {
|
|
334
|
-
if (tab.state ===
|
|
336
|
+
if (tab.state === "connected") return id;
|
|
335
337
|
}
|
|
336
|
-
return null
|
|
337
|
-
})()
|
|
338
|
+
return null;
|
|
339
|
+
})();
|
|
338
340
|
|
|
339
|
-
if (!tabId) throw new Error(`No attached tab for method ${method}`)
|
|
341
|
+
if (!tabId) throw new Error(`No attached tab for method ${method}`);
|
|
340
342
|
|
|
341
343
|
/** @type {chrome.debugger.DebuggerSession} */
|
|
342
|
-
const debuggee = { tabId }
|
|
344
|
+
const debuggee = { tabId };
|
|
343
345
|
|
|
344
|
-
if (method ===
|
|
346
|
+
if (method === "Runtime.enable") {
|
|
345
347
|
try {
|
|
346
|
-
await chrome.debugger.sendCommand(debuggee,
|
|
347
|
-
await new Promise((r) => setTimeout(r, 50))
|
|
348
|
+
await chrome.debugger.sendCommand(debuggee, "Runtime.disable");
|
|
349
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
348
350
|
} catch {
|
|
349
351
|
// ignore
|
|
350
352
|
}
|
|
351
|
-
return await chrome.debugger.sendCommand(debuggee,
|
|
353
|
+
return await chrome.debugger.sendCommand(debuggee, "Runtime.enable", params);
|
|
352
354
|
}
|
|
353
355
|
|
|
354
|
-
if (method ===
|
|
355
|
-
const url = typeof params?.url ===
|
|
356
|
-
const tab = await chrome.tabs.create({ url, active: false })
|
|
357
|
-
if (!tab.id) throw new Error(
|
|
358
|
-
await new Promise((r) => setTimeout(r, 100))
|
|
359
|
-
const attached = await attachTab(tab.id)
|
|
360
|
-
return { targetId: attached.targetId }
|
|
356
|
+
if (method === "Target.createTarget") {
|
|
357
|
+
const url = typeof params?.url === "string" ? params.url : "about:blank";
|
|
358
|
+
const tab = await chrome.tabs.create({ url, active: false });
|
|
359
|
+
if (!tab.id) throw new Error("Failed to create tab");
|
|
360
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
361
|
+
const attached = await attachTab(tab.id);
|
|
362
|
+
return { targetId: attached.targetId };
|
|
361
363
|
}
|
|
362
364
|
|
|
363
|
-
if (method ===
|
|
364
|
-
const target = typeof params?.targetId ===
|
|
365
|
-
const toClose = target ? getTabByTargetId(target) : tabId
|
|
366
|
-
if (!toClose) return { success: false }
|
|
365
|
+
if (method === "Target.closeTarget") {
|
|
366
|
+
const target = typeof params?.targetId === "string" ? params.targetId : "";
|
|
367
|
+
const toClose = target ? getTabByTargetId(target) : tabId;
|
|
368
|
+
if (!toClose) return { success: false };
|
|
367
369
|
try {
|
|
368
|
-
await chrome.tabs.remove(toClose)
|
|
370
|
+
await chrome.tabs.remove(toClose);
|
|
369
371
|
} catch {
|
|
370
|
-
return { success: false }
|
|
372
|
+
return { success: false };
|
|
371
373
|
}
|
|
372
|
-
return { success: true }
|
|
374
|
+
return { success: true };
|
|
373
375
|
}
|
|
374
376
|
|
|
375
|
-
if (method ===
|
|
376
|
-
const target = typeof params?.targetId ===
|
|
377
|
-
const toActivate = target ? getTabByTargetId(target) : tabId
|
|
378
|
-
if (!toActivate) return {}
|
|
379
|
-
const tab = await chrome.tabs.get(toActivate).catch(() => null)
|
|
380
|
-
if (!tab) return {}
|
|
377
|
+
if (method === "Target.activateTarget") {
|
|
378
|
+
const target = typeof params?.targetId === "string" ? params.targetId : "";
|
|
379
|
+
const toActivate = target ? getTabByTargetId(target) : tabId;
|
|
380
|
+
if (!toActivate) return {};
|
|
381
|
+
const tab = await chrome.tabs.get(toActivate).catch(() => null);
|
|
382
|
+
if (!tab) return {};
|
|
381
383
|
if (tab.windowId) {
|
|
382
|
-
await chrome.windows.update(tab.windowId, { focused: true }).catch(() => {})
|
|
384
|
+
await chrome.windows.update(tab.windowId, { focused: true }).catch(() => {});
|
|
383
385
|
}
|
|
384
|
-
await chrome.tabs.update(toActivate, { active: true }).catch(() => {})
|
|
385
|
-
return {}
|
|
386
|
+
await chrome.tabs.update(toActivate, { active: true }).catch(() => {});
|
|
387
|
+
return {};
|
|
386
388
|
}
|
|
387
389
|
|
|
388
|
-
const tabState = tabs.get(tabId)
|
|
389
|
-
const mainSessionId = tabState?.sessionId
|
|
390
|
+
const tabState = tabs.get(tabId);
|
|
391
|
+
const mainSessionId = tabState?.sessionId;
|
|
390
392
|
const debuggerSession =
|
|
391
393
|
sessionId && mainSessionId && sessionId !== mainSessionId
|
|
392
394
|
? { ...debuggee, sessionId }
|
|
393
|
-
: debuggee
|
|
395
|
+
: debuggee;
|
|
394
396
|
|
|
395
|
-
return await chrome.debugger.sendCommand(debuggerSession, method, params)
|
|
397
|
+
return await chrome.debugger.sendCommand(debuggerSession, method, params);
|
|
396
398
|
}
|
|
397
399
|
|
|
398
400
|
function onDebuggerEvent(source, method, params) {
|
|
399
|
-
const tabId = source.tabId
|
|
400
|
-
if (!tabId) return
|
|
401
|
-
const tab = tabs.get(tabId)
|
|
402
|
-
if (!tab?.sessionId) return
|
|
401
|
+
const tabId = source.tabId;
|
|
402
|
+
if (!tabId) return;
|
|
403
|
+
const tab = tabs.get(tabId);
|
|
404
|
+
if (!tab?.sessionId) return;
|
|
403
405
|
|
|
404
|
-
if (method ===
|
|
405
|
-
childSessionToTab.set(String(params.sessionId), tabId)
|
|
406
|
+
if (method === "Target.attachedToTarget" && params?.sessionId) {
|
|
407
|
+
childSessionToTab.set(String(params.sessionId), tabId);
|
|
406
408
|
}
|
|
407
409
|
|
|
408
|
-
if (method ===
|
|
409
|
-
childSessionToTab.delete(String(params.sessionId))
|
|
410
|
+
if (method === "Target.detachedFromTarget" && params?.sessionId) {
|
|
411
|
+
childSessionToTab.delete(String(params.sessionId));
|
|
410
412
|
}
|
|
411
413
|
|
|
412
414
|
try {
|
|
413
415
|
sendToRelay({
|
|
414
|
-
method:
|
|
416
|
+
method: "forwardCDPEvent",
|
|
415
417
|
params: {
|
|
416
418
|
sessionId: source.sessionId || tab.sessionId,
|
|
417
419
|
method,
|
|
418
420
|
params,
|
|
419
421
|
},
|
|
420
|
-
})
|
|
422
|
+
});
|
|
421
423
|
} catch {
|
|
422
424
|
// ignore
|
|
423
425
|
}
|
|
424
426
|
}
|
|
425
427
|
|
|
426
428
|
function onDebuggerDetach(source, reason) {
|
|
427
|
-
const tabId = source.tabId
|
|
428
|
-
if (!tabId) return
|
|
429
|
-
if (!tabs.has(tabId)) return
|
|
430
|
-
void detachTab(tabId, reason)
|
|
429
|
+
const tabId = source.tabId;
|
|
430
|
+
if (!tabId) return;
|
|
431
|
+
if (!tabs.has(tabId)) return;
|
|
432
|
+
void detachTab(tabId, reason);
|
|
431
433
|
}
|
|
432
434
|
|
|
433
|
-
chrome.action.onClicked.addListener(() => void connectOrToggleForActiveTab())
|
|
435
|
+
chrome.action.onClicked.addListener(() => void connectOrToggleForActiveTab());
|
|
434
436
|
|
|
435
437
|
chrome.runtime.onInstalled.addListener(() => {
|
|
436
438
|
// Useful: first-time instructions.
|
|
437
|
-
void chrome.runtime.openOptionsPage()
|
|
438
|
-
})
|
|
439
|
+
void chrome.runtime.openOptionsPage();
|
|
440
|
+
});
|