clankerbend 0.1.0 → 0.1.2
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/DEVELOPERS.md +6 -6
- package/README.md +31 -14
- package/apps/README.md +3 -3
- package/apps/sticky-notes/README.md +2 -2
- package/apps/sticky-notes/{onewhack.manifest.json → clankerbend.manifest.json} +1 -1
- package/apps/sticky-notes/package.json +3 -3
- package/apps/sticky-notes/public/app.js +42 -9
- package/apps/sticky-notes/src/sticky-notes-app.js +17 -21
- package/apps/vim-nav/README.md +22 -22
- package/apps/vim-nav/{onewhack.manifest.json → clankerbend.manifest.json} +4 -4
- package/apps/vim-nav/package.json +3 -3
- package/apps/vim-nav/public/app.js +44 -11
- package/apps/vim-nav/public/index.html +2 -2
- package/apps/vim-nav/server.mjs +2 -2
- package/apps/vim-nav/src/vim-nav-app.js +5 -5
- package/assets/clankerbend-banner.webp +0 -0
- package/assets/clankerbend-demo-thumbnail.webp +0 -0
- package/assets/clankerbend.svg +26 -0
- package/cli.mjs +22 -11
- package/docs/app-lifecycle.md +9 -9
- package/docs/app-manifest.md +4 -4
- package/docs/author-guide.md +5 -5
- package/docs/launcher-profiles.md +45 -10
- package/docs/protocol.md +248 -241
- package/host/README.md +4 -4
- package/host/src/app-registry.js +8 -6
- package/host/src/codex-desktop-cdp-adapter.js +85 -65
- package/host/src/codex-desktop-renderer-bridge.js +839 -270
- package/host/src/index.js +186 -34
- package/launch/accounts.mjs +360 -0
- package/launch/codex-mux-adapter.mjs +175 -0
- package/launch/profiles.mjs +40 -16
- package/launch/runtime-paths.mjs +9 -6
- package/launch/test-accounts.mjs +71 -0
- package/package.json +12 -10
- package/scripts/release-npm.mjs +1 -2
- package/server.mjs +21 -14
- package/assets/onewhack.jpg +0 -0
package/host/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ClankerBend Host
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ClankerBend is an independent OneWill project compatible with OpenAI Codex
|
|
4
4
|
Desktop. It is not affiliated with or endorsed by OpenAI.
|
|
5
5
|
|
|
6
|
-
This package contains reusable host primitives for
|
|
6
|
+
This package contains reusable host primitives for ClankerBend `0.1`:
|
|
7
7
|
|
|
8
8
|
- loopback HTTP server
|
|
9
9
|
- JSON response envelopes and error envelopes
|
|
@@ -14,5 +14,5 @@ This package contains reusable host primitives for OneWhack `0.1`:
|
|
|
14
14
|
- monotonic selection handling
|
|
15
15
|
- mock transcript adapter for tests and local development
|
|
16
16
|
|
|
17
|
-
The host does not contain
|
|
17
|
+
The host does not contain VimNav-specific logic. Apps register a
|
|
18
18
|
manifest, static panel directory, state function, and action handler.
|
package/host/src/app-registry.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const CLANKERBEND_APP_MANIFEST_VERSION = "0.1";
|
|
6
6
|
|
|
7
7
|
const VALID_DISTRIBUTION_KINDS = new Set(["local", "npm", "tarball", "binary"]);
|
|
8
8
|
const VALID_PLATFORM_OS = new Set(["darwin", "linux", "win32", "any"]);
|
|
@@ -51,9 +51,9 @@ export function validateAppManifest(manifest, options = {}) {
|
|
|
51
51
|
if (!isPlainObject(manifest)) errors.push("manifest must be a JSON object");
|
|
52
52
|
if (errors.length) throwManifestError(errors, options);
|
|
53
53
|
|
|
54
|
-
requireString(manifest, "
|
|
55
|
-
if (manifest.
|
|
56
|
-
errors.push(`
|
|
54
|
+
requireString(manifest, "clankerbendVersion", errors);
|
|
55
|
+
if (manifest.clankerbendVersion !== CLANKERBEND_APP_MANIFEST_VERSION) {
|
|
56
|
+
errors.push(`clankerbendVersion must be ${CLANKERBEND_APP_MANIFEST_VERSION}`);
|
|
57
57
|
}
|
|
58
58
|
requireString(manifest, "appId", errors);
|
|
59
59
|
requireString(manifest, "version", errors);
|
|
@@ -144,6 +144,8 @@ export async function loadProfileFromManifests(options) {
|
|
|
144
144
|
hostId: options.hostId,
|
|
145
145
|
hostName: options.hostName,
|
|
146
146
|
runDir: options.runDir,
|
|
147
|
+
runtimePaths: options.runtimePaths,
|
|
148
|
+
accountRegistry: options.accountRegistry,
|
|
147
149
|
defaultPanelAppId,
|
|
148
150
|
apps,
|
|
149
151
|
manifests: loadedApps.map((loaded) => loaded.manifest),
|
|
@@ -202,7 +204,7 @@ export function normalizeAppFromManifest(app, manifest) {
|
|
|
202
204
|
|
|
203
205
|
export function defaultRegistryConfig() {
|
|
204
206
|
return {
|
|
205
|
-
|
|
207
|
+
clankerbendVersion: CLANKERBEND_APP_MANIFEST_VERSION,
|
|
206
208
|
installedApps: {},
|
|
207
209
|
profiles: {
|
|
208
210
|
default: {
|
|
@@ -307,7 +309,7 @@ function requireString(object, key, errors, label = key) {
|
|
|
307
309
|
|
|
308
310
|
function throwManifestError(errors, options = {}) {
|
|
309
311
|
const prefix = options.manifestPath ? `${options.manifestPath}: ` : "";
|
|
310
|
-
throw new Error(`${prefix}invalid
|
|
312
|
+
throw new Error(`${prefix}invalid ClankerBend app manifest:\n${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
function isPlainObject(value) {
|
|
@@ -52,7 +52,7 @@ function normalizeProviders(options = {}, rendererBridges = []) {
|
|
|
52
52
|
|
|
53
53
|
function userPanelError(error) {
|
|
54
54
|
if (/native Browser panel controls not found|panel app did not load|panel unavailable/i.test(String(error || ""))) {
|
|
55
|
-
return "Waiting for Codex Desktop to expose the Browser side panel. Open a Codex thread, then
|
|
55
|
+
return "Waiting for Codex Desktop to expose the Browser side panel. Open a Codex thread, then ClankerBend will load the selected app automatically.";
|
|
56
56
|
}
|
|
57
57
|
return error || "Waiting for Codex Desktop to expose the Browser side panel.";
|
|
58
58
|
}
|
|
@@ -67,6 +67,8 @@ class CodexDesktopCdpAdapter {
|
|
|
67
67
|
this.codexCli = options.codexCli || DEFAULT_CODEX_CLI;
|
|
68
68
|
this.runDir = options.runDir ? resolve(options.runDir) : null;
|
|
69
69
|
this.profileDir = options.profileDir || (this.runDir ? join(this.runDir, "codex-profile") : null);
|
|
70
|
+
this.codexHome = options.codexHome ? resolve(options.codexHome) : null;
|
|
71
|
+
this.resetProfileDir = options.resetProfileDir === true;
|
|
70
72
|
this.rendererBridges = normalizeRendererBridges(options);
|
|
71
73
|
this.providers = normalizeProviders(options, this.rendererBridges);
|
|
72
74
|
this.snapshotToTranscript = options.snapshotToTranscript || defaultSnapshotToTranscript;
|
|
@@ -93,9 +95,10 @@ class CodexDesktopCdpAdapter {
|
|
|
93
95
|
}
|
|
94
96
|
if (this.runDir) mkdirSync(this.runDir, { recursive: true });
|
|
95
97
|
if (this.profileDir) {
|
|
96
|
-
rmSync(this.profileDir, { recursive: true, force: true });
|
|
98
|
+
if (this.resetProfileDir) rmSync(this.profileDir, { recursive: true, force: true });
|
|
97
99
|
mkdirSync(this.profileDir, { recursive: true });
|
|
98
100
|
}
|
|
101
|
+
if (this.codexHome) mkdirSync(this.codexHome, { recursive: true });
|
|
99
102
|
|
|
100
103
|
this.cdpPort = await freePort();
|
|
101
104
|
this.child = spawn(this.codexApp, [
|
|
@@ -105,6 +108,7 @@ class CodexDesktopCdpAdapter {
|
|
|
105
108
|
], {
|
|
106
109
|
env: {
|
|
107
110
|
...process.env,
|
|
111
|
+
...(this.codexHome ? { CODEX_HOME: this.codexHome } : {}),
|
|
108
112
|
...(this.profileDir ? { CODEX_ELECTRON_USER_DATA_PATH: this.profileDir } : {})
|
|
109
113
|
},
|
|
110
114
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -219,7 +223,7 @@ class CodexDesktopCdpAdapter {
|
|
|
219
223
|
const bridge = this.bridgeForProvider("transcriptHighlight");
|
|
220
224
|
const appId = this.bridgeAppId(bridge);
|
|
221
225
|
return this.evaluate(`(() => {
|
|
222
|
-
const runtime = window.
|
|
226
|
+
const runtime = window.__clankerbendRuntime;
|
|
223
227
|
const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
|
|
224
228
|
if (bridge?.highlightRange) return bridge.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
|
|
225
229
|
if (runtime?.highlightRange) return runtime.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
|
|
@@ -230,7 +234,7 @@ class CodexDesktopCdpAdapter {
|
|
|
230
234
|
|
|
231
235
|
async setComposerDraft(draft, options = {}) {
|
|
232
236
|
return this.evaluate(`(() => {
|
|
233
|
-
const runtime = window.
|
|
237
|
+
const runtime = window.__clankerbendRuntime;
|
|
234
238
|
if (!runtime?.setComposerDraft) return { ok: false, error: "composer draft unavailable" };
|
|
235
239
|
return runtime.setComposerDraft(${JSON.stringify(draft)}, ${JSON.stringify(options)});
|
|
236
240
|
})()`);
|
|
@@ -238,7 +242,7 @@ class CodexDesktopCdpAdapter {
|
|
|
238
242
|
|
|
239
243
|
async submitComposer(draft, options = {}) {
|
|
240
244
|
return this.evaluate(`(() => {
|
|
241
|
-
const runtime = window.
|
|
245
|
+
const runtime = window.__clankerbendRuntime;
|
|
242
246
|
if (!runtime?.submitComposer) return { ok: false, error: "composer submit unavailable" };
|
|
243
247
|
return runtime.submitComposer(${JSON.stringify(draft)}, ${JSON.stringify(options)});
|
|
244
248
|
})()`);
|
|
@@ -347,7 +351,7 @@ class CodexDesktopCdpAdapter {
|
|
|
347
351
|
return { ok: false, error: "app-server active thread is unknown", mode: "app-server-inject-items", diagnostic: diagnostics };
|
|
348
352
|
}
|
|
349
353
|
const items = buildAttachmentResponseItems(files, {
|
|
350
|
-
appName: options.appName || "
|
|
354
|
+
appName: options.appName || "ClankerBend",
|
|
351
355
|
createdAt: new Date().toISOString()
|
|
352
356
|
});
|
|
353
357
|
const response = await client.threadInjectItems(thread.threadId, items);
|
|
@@ -491,20 +495,20 @@ class CodexDesktopCdpAdapter {
|
|
|
491
495
|
const protocolVersion = ${JSON.stringify(this.host.protocolVersion)};
|
|
492
496
|
const hostUrl = ${JSON.stringify(this.host.state.host.url)};
|
|
493
497
|
const seededApps = ${JSON.stringify(apps)};
|
|
494
|
-
const previous = window.
|
|
498
|
+
const previous = window.__clankerbendRuntime;
|
|
495
499
|
const slots = previous && typeof previous.apps === "object" ? previous.apps : {};
|
|
496
500
|
const cssEscape = (value) => window.CSS?.escape ? window.CSS.escape(String(value)) : String(value).replace(/["\\\\]/g, "\\\\$&");
|
|
497
501
|
const ensureAnnotationLayoutStyle = () => {
|
|
498
|
-
const styleId = "
|
|
502
|
+
const styleId = "clankerbend-annotation-layout-style";
|
|
499
503
|
if (document.getElementById(styleId)) return;
|
|
500
504
|
const style = document.createElement("style");
|
|
501
505
|
style.id = styleId;
|
|
502
506
|
style.textContent = \`
|
|
503
|
-
.
|
|
507
|
+
.clankerbend-transcript-anchor {
|
|
504
508
|
position: relative !important;
|
|
505
509
|
overflow: visible !important;
|
|
506
510
|
}
|
|
507
|
-
.
|
|
511
|
+
.clankerbend-transcript-annotations {
|
|
508
512
|
position: absolute !important;
|
|
509
513
|
left: -62px !important;
|
|
510
514
|
top: 4px !important;
|
|
@@ -515,37 +519,35 @@ class CodexDesktopCdpAdapter {
|
|
|
515
519
|
gap: 6px !important;
|
|
516
520
|
pointer-events: none !important;
|
|
517
521
|
}
|
|
518
|
-
.
|
|
522
|
+
.clankerbend-transcript-annotation-slot {
|
|
519
523
|
display: flex !important;
|
|
520
524
|
align-items: center !important;
|
|
521
525
|
justify-content: center !important;
|
|
522
526
|
pointer-events: auto !important;
|
|
523
527
|
}
|
|
524
528
|
@media (max-width: 900px) {
|
|
525
|
-
.
|
|
526
|
-
position:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
align-items: center !important;
|
|
530
|
-
margin: 0 0 6px 0 !important;
|
|
529
|
+
.clankerbend-transcript-annotations {
|
|
530
|
+
position: absolute !important;
|
|
531
|
+
left: -44px !important;
|
|
532
|
+
top: 4px !important;
|
|
531
533
|
}
|
|
532
534
|
}
|
|
533
535
|
\`;
|
|
534
536
|
document.head.appendChild(style);
|
|
535
537
|
};
|
|
536
538
|
const annotationHost = (anchor, anchorId) => {
|
|
537
|
-
if (!(anchor instanceof HTMLElement)) throw new Error("
|
|
539
|
+
if (!(anchor instanceof HTMLElement)) throw new Error("ClankerBend annotation anchor must be an HTMLElement");
|
|
538
540
|
ensureAnnotationLayoutStyle();
|
|
539
|
-
anchor.classList.add("
|
|
541
|
+
anchor.classList.add("clankerbend-transcript-anchor");
|
|
540
542
|
let host = Array.from(anchor.children).find((child) =>
|
|
541
543
|
child instanceof HTMLElement &&
|
|
542
|
-
child.classList.contains("
|
|
543
|
-
child.dataset.
|
|
544
|
+
child.classList.contains("clankerbend-transcript-annotations") &&
|
|
545
|
+
child.dataset.clankerbendAnchorId === anchorId
|
|
544
546
|
);
|
|
545
547
|
if (!host) {
|
|
546
548
|
host = document.createElement("div");
|
|
547
|
-
host.className = "
|
|
548
|
-
host.dataset.
|
|
549
|
+
host.className = "clankerbend-transcript-annotations";
|
|
550
|
+
host.dataset.clankerbendAnchorId = anchorId;
|
|
549
551
|
anchor.insertAdjacentElement("afterbegin", host);
|
|
550
552
|
}
|
|
551
553
|
return host;
|
|
@@ -565,15 +567,15 @@ class CodexDesktopCdpAdapter {
|
|
|
565
567
|
}
|
|
566
568
|
return left;
|
|
567
569
|
})();
|
|
568
|
-
|
|
569
|
-
host.style.setProperty("
|
|
570
|
+
host.style.setProperty("left", desiredLeft + "px", "important");
|
|
571
|
+
host.style.setProperty("visibility", rect.left + desiredLeft < clippingLeft ? "hidden" : "visible", "important");
|
|
570
572
|
};
|
|
571
573
|
const sortAnnotationSlots = (host) => {
|
|
572
574
|
[...host.children]
|
|
573
575
|
.sort((a, b) =>
|
|
574
|
-
Number(a.dataset.
|
|
575
|
-
String(a.dataset.
|
|
576
|
-
String(a.dataset.
|
|
576
|
+
Number(a.dataset.clankerbendPriority || 100) - Number(b.dataset.clankerbendPriority || 100) ||
|
|
577
|
+
String(a.dataset.clankerbendAppId || "").localeCompare(String(b.dataset.clankerbendAppId || "")) ||
|
|
578
|
+
String(a.dataset.clankerbendMarkerId || "").localeCompare(String(b.dataset.clankerbendMarkerId || ""))
|
|
577
579
|
)
|
|
578
580
|
.forEach((child) => host.appendChild(child));
|
|
579
581
|
};
|
|
@@ -589,8 +591,8 @@ class CodexDesktopCdpAdapter {
|
|
|
589
591
|
const findAnchorElement = (anchorId) => document.querySelector(transcriptAnchorSelector(anchorId));
|
|
590
592
|
const isHostUiElement = (el) => {
|
|
591
593
|
for (let node = el; node; node = node.parentElement) {
|
|
592
|
-
if (node.classList?.contains?.("
|
|
593
|
-
if (/^
|
|
594
|
+
if (node.classList?.contains?.("clankerbend-host-ui")) return true;
|
|
595
|
+
if (/^clankerbend-/.test(String(node.id || ""))) return true;
|
|
594
596
|
}
|
|
595
597
|
return false;
|
|
596
598
|
};
|
|
@@ -633,16 +635,16 @@ class CodexDesktopCdpAdapter {
|
|
|
633
635
|
);
|
|
634
636
|
return candidates[0]?.el || null;
|
|
635
637
|
};
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
const
|
|
638
|
+
const CLANKERBEND_CONTEXT_START = "--- ClankerBend context ---";
|
|
639
|
+
const CLANKERBEND_CONTEXT_END = "--- End ClankerBend context ---";
|
|
640
|
+
const stripContextBlock = (text) => {
|
|
639
641
|
let output = String(text || "");
|
|
640
642
|
while (true) {
|
|
641
|
-
const start = output.indexOf(
|
|
643
|
+
const start = output.indexOf(CLANKERBEND_CONTEXT_START);
|
|
642
644
|
if (start < 0) return output.trimStart();
|
|
643
|
-
const end = output.indexOf(
|
|
645
|
+
const end = output.indexOf(CLANKERBEND_CONTEXT_END, start + CLANKERBEND_CONTEXT_START.length);
|
|
644
646
|
if (end < 0) return output.slice(0, start).trimEnd();
|
|
645
|
-
output = output.slice(0, start) + output.slice(end +
|
|
647
|
+
output = output.slice(0, start) + output.slice(end + CLANKERBEND_CONTEXT_END.length);
|
|
646
648
|
}
|
|
647
649
|
};
|
|
648
650
|
const setComposerValue = (el, value) => {
|
|
@@ -678,7 +680,7 @@ class CodexDesktopCdpAdapter {
|
|
|
678
680
|
hostUrl,
|
|
679
681
|
apps: slots,
|
|
680
682
|
registerApp(app) {
|
|
681
|
-
if (!app || !app.appId) throw new Error("
|
|
683
|
+
if (!app || !app.appId) throw new Error("ClankerBend appId is required");
|
|
682
684
|
const seeded = seededApps[app.appId] || {};
|
|
683
685
|
const current = this.apps[app.appId] || {};
|
|
684
686
|
const currentVersion = Number(current.bridge?.version);
|
|
@@ -690,7 +692,7 @@ class CodexDesktopCdpAdapter {
|
|
|
690
692
|
Number.isFinite(nextVersion) &&
|
|
691
693
|
nextVersion < currentVersion
|
|
692
694
|
) {
|
|
693
|
-
throw new Error("
|
|
695
|
+
throw new Error("ClankerBend app bridge version regressed for " + app.appId);
|
|
694
696
|
}
|
|
695
697
|
const slot = {
|
|
696
698
|
...current,
|
|
@@ -713,22 +715,22 @@ class CodexDesktopCdpAdapter {
|
|
|
713
715
|
return this.getApp(appId)?.entryUrl || null;
|
|
714
716
|
},
|
|
715
717
|
placeAnnotation(anchor, annotation) {
|
|
716
|
-
if (!annotation?.appId) throw new Error("
|
|
717
|
-
if (!annotation?.anchorId) throw new Error("
|
|
718
|
-
if (!(annotation.element instanceof HTMLElement)) throw new Error("
|
|
718
|
+
if (!annotation?.appId) throw new Error("ClankerBend annotation appId is required");
|
|
719
|
+
if (!annotation?.anchorId) throw new Error("ClankerBend annotation anchorId is required");
|
|
720
|
+
if (!(annotation.element instanceof HTMLElement)) throw new Error("ClankerBend annotation element is required");
|
|
719
721
|
const markerId = annotation.markerId || annotation.anchorId;
|
|
720
722
|
const host = annotationHost(anchor, annotation.anchorId);
|
|
721
|
-
const selector = '.
|
|
723
|
+
const selector = '.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(annotation.appId) + '"][data-clankerbend-marker-id="' + cssEscape(markerId) + '"]';
|
|
722
724
|
let slot = host.querySelector(selector);
|
|
723
725
|
if (!slot) {
|
|
724
726
|
slot = document.createElement("div");
|
|
725
|
-
slot.className = "
|
|
726
|
-
slot.dataset.
|
|
727
|
-
slot.dataset.
|
|
727
|
+
slot.className = "clankerbend-transcript-annotation-slot";
|
|
728
|
+
slot.dataset.clankerbendAppId = annotation.appId;
|
|
729
|
+
slot.dataset.clankerbendMarkerId = markerId;
|
|
728
730
|
host.appendChild(slot);
|
|
729
731
|
}
|
|
730
|
-
slot.dataset.
|
|
731
|
-
slot.dataset.
|
|
732
|
+
slot.dataset.clankerbendAnchorId = annotation.anchorId;
|
|
733
|
+
slot.dataset.clankerbendPriority = String(annotation.priority ?? 100);
|
|
732
734
|
if (slot.firstElementChild !== annotation.element) slot.replaceChildren(annotation.element);
|
|
733
735
|
sortAnnotationSlots(host);
|
|
734
736
|
positionAnnotationHost(host, anchor);
|
|
@@ -736,10 +738,10 @@ class CodexDesktopCdpAdapter {
|
|
|
736
738
|
},
|
|
737
739
|
removeAnnotations(appId, liveAnchorIds = null) {
|
|
738
740
|
const live = liveAnchorIds ? new Set([...liveAnchorIds].map(String)) : null;
|
|
739
|
-
document.querySelectorAll('.
|
|
740
|
-
if (!live || !live.has(slot.dataset.
|
|
741
|
+
document.querySelectorAll('.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(appId) + '"]').forEach((slot) => {
|
|
742
|
+
if (!live || !live.has(slot.dataset.clankerbendAnchorId || "")) slot.remove();
|
|
741
743
|
});
|
|
742
|
-
document.querySelectorAll(".
|
|
744
|
+
document.querySelectorAll(".clankerbend-transcript-annotations").forEach((host) => {
|
|
743
745
|
if (!host.children.length) host.remove();
|
|
744
746
|
});
|
|
745
747
|
},
|
|
@@ -747,14 +749,14 @@ class CodexDesktopCdpAdapter {
|
|
|
747
749
|
if (!range?.anchorId) return { ok: false, error: "range.anchorId is required" };
|
|
748
750
|
const anchor = findAnchorElement(range.anchorId);
|
|
749
751
|
if (!anchor) return { ok: false, error: "anchor not found", anchorId: range.anchorId };
|
|
750
|
-
anchor.classList.add("
|
|
752
|
+
anchor.classList.add("clankerbend-range-highlight");
|
|
751
753
|
const previousOutline = anchor.style.outline;
|
|
752
754
|
const previousOffset = anchor.style.outlineOffset;
|
|
753
755
|
anchor.style.outline = "3px solid #f6c945";
|
|
754
756
|
anchor.style.outlineOffset = "3px";
|
|
755
757
|
anchor.scrollIntoView({ block: options.block || "center", behavior: options.behavior || "smooth" });
|
|
756
758
|
setTimeout(() => {
|
|
757
|
-
anchor.classList.remove("
|
|
759
|
+
anchor.classList.remove("clankerbend-range-highlight");
|
|
758
760
|
anchor.style.outline = previousOutline;
|
|
759
761
|
anchor.style.outlineOffset = previousOffset;
|
|
760
762
|
}, Number(options.durationMs || 1200));
|
|
@@ -764,8 +766,8 @@ class CodexDesktopCdpAdapter {
|
|
|
764
766
|
const el = findComposer();
|
|
765
767
|
if (!el) return { ok: false, error: "composer not found" };
|
|
766
768
|
const currentText = composerText(el);
|
|
767
|
-
const text = draft.
|
|
768
|
-
? (draft.text ? mergeDraftText(
|
|
769
|
+
const text = draft.clankerbendContext
|
|
770
|
+
? (draft.text ? mergeDraftText(stripContextBlock(currentText), String(draft.text || ""), "prepend") : stripContextBlock(currentText))
|
|
769
771
|
: mergeDraftText(currentText, String(draft.text || ""), draft.mode || "replace");
|
|
770
772
|
if (!setComposerValue(el, text)) return { ok: false, error: "composer cannot be updated" };
|
|
771
773
|
return { ok: true, draft: { ...draft, text } };
|
|
@@ -790,7 +792,7 @@ class CodexDesktopCdpAdapter {
|
|
|
790
792
|
injectedAt: runtime.apps[appId]?.injectedAt || null
|
|
791
793
|
};
|
|
792
794
|
}
|
|
793
|
-
window.
|
|
795
|
+
window.__clankerbendRuntime = runtime;
|
|
794
796
|
})()`);
|
|
795
797
|
for (const bridge of this.rendererBridges) {
|
|
796
798
|
await this.evaluate(bridge.injectedSource);
|
|
@@ -887,6 +889,24 @@ class CodexDesktopCdpAdapter {
|
|
|
887
889
|
await this.host.highlightRange(event.range, { durationMs: 1200 });
|
|
888
890
|
} else if (event.kind === "highlightAnchor" && event.anchorId) {
|
|
889
891
|
await this.host.highlightAnchor(event.anchorId, { durationMs: 1200 });
|
|
892
|
+
} else if (event.kind === "codexAccountSwitch" && event.accountId) {
|
|
893
|
+
if (typeof this.host.transcriptAdapter.switchTo !== "function") throw new Error("Codex account switching is unavailable");
|
|
894
|
+
await this.host.transcriptAdapter.switchTo(String(event.accountId));
|
|
895
|
+
} else if (event.kind === "codexAccountCreateAndSwitch") {
|
|
896
|
+
if (typeof this.host.transcriptAdapter.createAccount !== "function" || typeof this.host.transcriptAdapter.switchTo !== "function") {
|
|
897
|
+
throw new Error("Codex account creation is unavailable");
|
|
898
|
+
}
|
|
899
|
+
const account = await this.host.transcriptAdapter.createAccount({ label: String(event.label || "Account") });
|
|
900
|
+
await this.host.transcriptAdapter.switchTo(account.id);
|
|
901
|
+
} else if (event.kind === "codexAccountSetDefault" && event.accountId) {
|
|
902
|
+
if (typeof this.host.transcriptAdapter.setDefault !== "function") throw new Error("Codex account defaults are unavailable");
|
|
903
|
+
await this.host.transcriptAdapter.setDefault(String(event.accountId));
|
|
904
|
+
} else if (event.kind === "codexAccountAdoptAsPrimary" && event.accountId) {
|
|
905
|
+
if (typeof this.host.transcriptAdapter.adoptAsPrimary !== "function") throw new Error("Codex primary adoption is unavailable");
|
|
906
|
+
await this.host.transcriptAdapter.adoptAsPrimary(String(event.accountId));
|
|
907
|
+
} else if (event.kind === "codexAccountDelete" && event.accountId) {
|
|
908
|
+
if (typeof this.host.transcriptAdapter.deleteAccount !== "function") throw new Error("Codex account removal is unavailable");
|
|
909
|
+
await this.host.transcriptAdapter.deleteAccount(String(event.accountId));
|
|
890
910
|
}
|
|
891
911
|
}
|
|
892
912
|
}
|
|
@@ -1042,9 +1062,9 @@ class CodexDesktopCdpAdapter {
|
|
|
1042
1062
|
async evalBridge(expression, bridge = this.primaryBridge()) {
|
|
1043
1063
|
const appId = this.bridgeAppId(bridge);
|
|
1044
1064
|
return this.evaluate(`(() => {
|
|
1045
|
-
const runtime = window.
|
|
1065
|
+
const runtime = window.__clankerbendRuntime;
|
|
1046
1066
|
const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
|
|
1047
|
-
if (!bridge) return { ok: false, error: "
|
|
1067
|
+
if (!bridge) return { ok: false, error: "ClankerBend bridge is not installed for " + ${JSON.stringify(appId)} };
|
|
1048
1068
|
return bridge.${expression};
|
|
1049
1069
|
})()`);
|
|
1050
1070
|
}
|
|
@@ -1240,8 +1260,8 @@ class AppServerClient {
|
|
|
1240
1260
|
async initialize() {
|
|
1241
1261
|
const response = await this.call("initialize", {
|
|
1242
1262
|
clientInfo: {
|
|
1243
|
-
name: "
|
|
1244
|
-
title: "
|
|
1263
|
+
name: "clankerbend-host",
|
|
1264
|
+
title: "ClankerBend Host",
|
|
1245
1265
|
version: "0.1"
|
|
1246
1266
|
},
|
|
1247
1267
|
capabilities: {
|
|
@@ -1448,7 +1468,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
|
|
|
1448
1468
|
const createdAt = options.createdAt || new Date().toISOString();
|
|
1449
1469
|
return files.map((file, index) => {
|
|
1450
1470
|
const text = [
|
|
1451
|
-
"
|
|
1471
|
+
"ClankerBend attached runtime file context.",
|
|
1452
1472
|
"",
|
|
1453
1473
|
`Attached at: ${createdAt}`,
|
|
1454
1474
|
`File ${index + 1}: ${file.name || basename(file.path)}`,
|
|
@@ -1456,7 +1476,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
|
|
|
1456
1476
|
file.relativePath ? `Runtime path: ${file.relativePath}` : null,
|
|
1457
1477
|
file.mimeType ? `MIME type: ${file.mimeType}` : null,
|
|
1458
1478
|
"",
|
|
1459
|
-
"The following file was created by a
|
|
1479
|
+
"The following file was created by a ClankerBend app and should be treated as attached context for the user's next prompt.",
|
|
1460
1480
|
"",
|
|
1461
1481
|
"```markdown",
|
|
1462
1482
|
readFileSync(file.path, "utf8"),
|
|
@@ -1731,14 +1751,14 @@ function nativeAttachmentDiagnosticExpression(paths) {
|
|
|
1731
1751
|
(() => {
|
|
1732
1752
|
const paths = ${JSON.stringify(paths)};
|
|
1733
1753
|
const basenames = paths.map((path) => String(path).split(/[\\\\/]/).pop()).filter(Boolean);
|
|
1734
|
-
const
|
|
1754
|
+
const clankerbendChipCount = document.querySelectorAll("#clankerbend-composer-chips .clankerbend-context-chip").length;
|
|
1735
1755
|
const composerInputs = [...document.querySelectorAll("textarea,[contenteditable='true'],[role='textbox']")]
|
|
1736
1756
|
.map((el) => ({ el, rect: el.getBoundingClientRect() }))
|
|
1737
1757
|
.filter((item) => item.rect.width > 180 && item.rect.height > 18 && item.rect.bottom > window.innerHeight * 0.45)
|
|
1738
1758
|
.sort((a, b) => b.rect.bottom - a.rect.bottom);
|
|
1739
1759
|
const composerRect = composerInputs[0]?.rect || null;
|
|
1740
1760
|
const candidates = [...document.querySelectorAll("button,[role='button'],span,div")]
|
|
1741
|
-
.filter((el) => !el.closest("#
|
|
1761
|
+
.filter((el) => !el.closest("#clankerbend-composer-chips"))
|
|
1742
1762
|
.map((el) => {
|
|
1743
1763
|
const text = (el.innerText || el.textContent || "").replace(/\\s+/g, " ").trim();
|
|
1744
1764
|
const rect = el.getBoundingClientRect();
|
|
@@ -1760,10 +1780,10 @@ function nativeAttachmentDiagnosticExpression(paths) {
|
|
|
1760
1780
|
);
|
|
1761
1781
|
const matched = basenames.filter((name) => candidates.some((item) => item.text.includes(name)));
|
|
1762
1782
|
return {
|
|
1763
|
-
ok: matched.length === basenames.length &&
|
|
1783
|
+
ok: matched.length === basenames.length && clankerbendChipCount === 0,
|
|
1764
1784
|
matched,
|
|
1765
1785
|
basenames,
|
|
1766
|
-
|
|
1786
|
+
clankerbendChipCount,
|
|
1767
1787
|
candidates: candidates.slice(0, 12)
|
|
1768
1788
|
};
|
|
1769
1789
|
})()
|