clankerbend 0.1.0 → 0.1.1
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 +17 -13
- 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 +8 -8
- 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 +10 -10
- 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 +17 -9
- 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 +13 -10
- package/docs/protocol.md +234 -234
- package/host/README.md +4 -4
- package/host/src/app-registry.js +6 -6
- package/host/src/codex-desktop-cdp-adapter.js +62 -64
- package/host/src/codex-desktop-renderer-bridge.js +207 -270
- package/host/src/index.js +30 -30
- package/launch/profiles.mjs +13 -13
- package/launch/runtime-paths.mjs +6 -6
- package/package.json +12 -10
- package/scripts/release-npm.mjs +1 -2
- package/server.mjs +7 -7
- 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);
|
|
@@ -202,7 +202,7 @@ export function normalizeAppFromManifest(app, manifest) {
|
|
|
202
202
|
|
|
203
203
|
export function defaultRegistryConfig() {
|
|
204
204
|
return {
|
|
205
|
-
|
|
205
|
+
clankerbendVersion: CLANKERBEND_APP_MANIFEST_VERSION,
|
|
206
206
|
installedApps: {},
|
|
207
207
|
profiles: {
|
|
208
208
|
default: {
|
|
@@ -307,7 +307,7 @@ function requireString(object, key, errors, label = key) {
|
|
|
307
307
|
|
|
308
308
|
function throwManifestError(errors, options = {}) {
|
|
309
309
|
const prefix = options.manifestPath ? `${options.manifestPath}: ` : "";
|
|
310
|
-
throw new Error(`${prefix}invalid
|
|
310
|
+
throw new Error(`${prefix}invalid ClankerBend app manifest:\n${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
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
|
}
|
|
@@ -219,7 +219,7 @@ class CodexDesktopCdpAdapter {
|
|
|
219
219
|
const bridge = this.bridgeForProvider("transcriptHighlight");
|
|
220
220
|
const appId = this.bridgeAppId(bridge);
|
|
221
221
|
return this.evaluate(`(() => {
|
|
222
|
-
const runtime = window.
|
|
222
|
+
const runtime = window.__clankerbendRuntime;
|
|
223
223
|
const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
|
|
224
224
|
if (bridge?.highlightRange) return bridge.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
|
|
225
225
|
if (runtime?.highlightRange) return runtime.highlightRange(${JSON.stringify(range)}, ${JSON.stringify(options)});
|
|
@@ -230,7 +230,7 @@ class CodexDesktopCdpAdapter {
|
|
|
230
230
|
|
|
231
231
|
async setComposerDraft(draft, options = {}) {
|
|
232
232
|
return this.evaluate(`(() => {
|
|
233
|
-
const runtime = window.
|
|
233
|
+
const runtime = window.__clankerbendRuntime;
|
|
234
234
|
if (!runtime?.setComposerDraft) return { ok: false, error: "composer draft unavailable" };
|
|
235
235
|
return runtime.setComposerDraft(${JSON.stringify(draft)}, ${JSON.stringify(options)});
|
|
236
236
|
})()`);
|
|
@@ -238,7 +238,7 @@ class CodexDesktopCdpAdapter {
|
|
|
238
238
|
|
|
239
239
|
async submitComposer(draft, options = {}) {
|
|
240
240
|
return this.evaluate(`(() => {
|
|
241
|
-
const runtime = window.
|
|
241
|
+
const runtime = window.__clankerbendRuntime;
|
|
242
242
|
if (!runtime?.submitComposer) return { ok: false, error: "composer submit unavailable" };
|
|
243
243
|
return runtime.submitComposer(${JSON.stringify(draft)}, ${JSON.stringify(options)});
|
|
244
244
|
})()`);
|
|
@@ -347,7 +347,7 @@ class CodexDesktopCdpAdapter {
|
|
|
347
347
|
return { ok: false, error: "app-server active thread is unknown", mode: "app-server-inject-items", diagnostic: diagnostics };
|
|
348
348
|
}
|
|
349
349
|
const items = buildAttachmentResponseItems(files, {
|
|
350
|
-
appName: options.appName || "
|
|
350
|
+
appName: options.appName || "ClankerBend",
|
|
351
351
|
createdAt: new Date().toISOString()
|
|
352
352
|
});
|
|
353
353
|
const response = await client.threadInjectItems(thread.threadId, items);
|
|
@@ -491,20 +491,20 @@ class CodexDesktopCdpAdapter {
|
|
|
491
491
|
const protocolVersion = ${JSON.stringify(this.host.protocolVersion)};
|
|
492
492
|
const hostUrl = ${JSON.stringify(this.host.state.host.url)};
|
|
493
493
|
const seededApps = ${JSON.stringify(apps)};
|
|
494
|
-
const previous = window.
|
|
494
|
+
const previous = window.__clankerbendRuntime;
|
|
495
495
|
const slots = previous && typeof previous.apps === "object" ? previous.apps : {};
|
|
496
496
|
const cssEscape = (value) => window.CSS?.escape ? window.CSS.escape(String(value)) : String(value).replace(/["\\\\]/g, "\\\\$&");
|
|
497
497
|
const ensureAnnotationLayoutStyle = () => {
|
|
498
|
-
const styleId = "
|
|
498
|
+
const styleId = "clankerbend-annotation-layout-style";
|
|
499
499
|
if (document.getElementById(styleId)) return;
|
|
500
500
|
const style = document.createElement("style");
|
|
501
501
|
style.id = styleId;
|
|
502
502
|
style.textContent = \`
|
|
503
|
-
.
|
|
503
|
+
.clankerbend-transcript-anchor {
|
|
504
504
|
position: relative !important;
|
|
505
505
|
overflow: visible !important;
|
|
506
506
|
}
|
|
507
|
-
.
|
|
507
|
+
.clankerbend-transcript-annotations {
|
|
508
508
|
position: absolute !important;
|
|
509
509
|
left: -62px !important;
|
|
510
510
|
top: 4px !important;
|
|
@@ -515,37 +515,35 @@ class CodexDesktopCdpAdapter {
|
|
|
515
515
|
gap: 6px !important;
|
|
516
516
|
pointer-events: none !important;
|
|
517
517
|
}
|
|
518
|
-
.
|
|
518
|
+
.clankerbend-transcript-annotation-slot {
|
|
519
519
|
display: flex !important;
|
|
520
520
|
align-items: center !important;
|
|
521
521
|
justify-content: center !important;
|
|
522
522
|
pointer-events: auto !important;
|
|
523
523
|
}
|
|
524
524
|
@media (max-width: 900px) {
|
|
525
|
-
.
|
|
526
|
-
position:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
align-items: center !important;
|
|
530
|
-
margin: 0 0 6px 0 !important;
|
|
525
|
+
.clankerbend-transcript-annotations {
|
|
526
|
+
position: absolute !important;
|
|
527
|
+
left: -44px !important;
|
|
528
|
+
top: 4px !important;
|
|
531
529
|
}
|
|
532
530
|
}
|
|
533
531
|
\`;
|
|
534
532
|
document.head.appendChild(style);
|
|
535
533
|
};
|
|
536
534
|
const annotationHost = (anchor, anchorId) => {
|
|
537
|
-
if (!(anchor instanceof HTMLElement)) throw new Error("
|
|
535
|
+
if (!(anchor instanceof HTMLElement)) throw new Error("ClankerBend annotation anchor must be an HTMLElement");
|
|
538
536
|
ensureAnnotationLayoutStyle();
|
|
539
|
-
anchor.classList.add("
|
|
537
|
+
anchor.classList.add("clankerbend-transcript-anchor");
|
|
540
538
|
let host = Array.from(anchor.children).find((child) =>
|
|
541
539
|
child instanceof HTMLElement &&
|
|
542
|
-
child.classList.contains("
|
|
543
|
-
child.dataset.
|
|
540
|
+
child.classList.contains("clankerbend-transcript-annotations") &&
|
|
541
|
+
child.dataset.clankerbendAnchorId === anchorId
|
|
544
542
|
);
|
|
545
543
|
if (!host) {
|
|
546
544
|
host = document.createElement("div");
|
|
547
|
-
host.className = "
|
|
548
|
-
host.dataset.
|
|
545
|
+
host.className = "clankerbend-transcript-annotations";
|
|
546
|
+
host.dataset.clankerbendAnchorId = anchorId;
|
|
549
547
|
anchor.insertAdjacentElement("afterbegin", host);
|
|
550
548
|
}
|
|
551
549
|
return host;
|
|
@@ -565,15 +563,15 @@ class CodexDesktopCdpAdapter {
|
|
|
565
563
|
}
|
|
566
564
|
return left;
|
|
567
565
|
})();
|
|
568
|
-
|
|
569
|
-
host.style.setProperty("
|
|
566
|
+
host.style.setProperty("left", desiredLeft + "px", "important");
|
|
567
|
+
host.style.setProperty("visibility", rect.left + desiredLeft < clippingLeft ? "hidden" : "visible", "important");
|
|
570
568
|
};
|
|
571
569
|
const sortAnnotationSlots = (host) => {
|
|
572
570
|
[...host.children]
|
|
573
571
|
.sort((a, b) =>
|
|
574
|
-
Number(a.dataset.
|
|
575
|
-
String(a.dataset.
|
|
576
|
-
String(a.dataset.
|
|
572
|
+
Number(a.dataset.clankerbendPriority || 100) - Number(b.dataset.clankerbendPriority || 100) ||
|
|
573
|
+
String(a.dataset.clankerbendAppId || "").localeCompare(String(b.dataset.clankerbendAppId || "")) ||
|
|
574
|
+
String(a.dataset.clankerbendMarkerId || "").localeCompare(String(b.dataset.clankerbendMarkerId || ""))
|
|
577
575
|
)
|
|
578
576
|
.forEach((child) => host.appendChild(child));
|
|
579
577
|
};
|
|
@@ -589,8 +587,8 @@ class CodexDesktopCdpAdapter {
|
|
|
589
587
|
const findAnchorElement = (anchorId) => document.querySelector(transcriptAnchorSelector(anchorId));
|
|
590
588
|
const isHostUiElement = (el) => {
|
|
591
589
|
for (let node = el; node; node = node.parentElement) {
|
|
592
|
-
if (node.classList?.contains?.("
|
|
593
|
-
if (/^
|
|
590
|
+
if (node.classList?.contains?.("clankerbend-host-ui")) return true;
|
|
591
|
+
if (/^clankerbend-/.test(String(node.id || ""))) return true;
|
|
594
592
|
}
|
|
595
593
|
return false;
|
|
596
594
|
};
|
|
@@ -633,16 +631,16 @@ class CodexDesktopCdpAdapter {
|
|
|
633
631
|
);
|
|
634
632
|
return candidates[0]?.el || null;
|
|
635
633
|
};
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
const
|
|
634
|
+
const CLANKERBEND_CONTEXT_START = "--- ClankerBend context ---";
|
|
635
|
+
const CLANKERBEND_CONTEXT_END = "--- End ClankerBend context ---";
|
|
636
|
+
const stripContextBlock = (text) => {
|
|
639
637
|
let output = String(text || "");
|
|
640
638
|
while (true) {
|
|
641
|
-
const start = output.indexOf(
|
|
639
|
+
const start = output.indexOf(CLANKERBEND_CONTEXT_START);
|
|
642
640
|
if (start < 0) return output.trimStart();
|
|
643
|
-
const end = output.indexOf(
|
|
641
|
+
const end = output.indexOf(CLANKERBEND_CONTEXT_END, start + CLANKERBEND_CONTEXT_START.length);
|
|
644
642
|
if (end < 0) return output.slice(0, start).trimEnd();
|
|
645
|
-
output = output.slice(0, start) + output.slice(end +
|
|
643
|
+
output = output.slice(0, start) + output.slice(end + CLANKERBEND_CONTEXT_END.length);
|
|
646
644
|
}
|
|
647
645
|
};
|
|
648
646
|
const setComposerValue = (el, value) => {
|
|
@@ -678,7 +676,7 @@ class CodexDesktopCdpAdapter {
|
|
|
678
676
|
hostUrl,
|
|
679
677
|
apps: slots,
|
|
680
678
|
registerApp(app) {
|
|
681
|
-
if (!app || !app.appId) throw new Error("
|
|
679
|
+
if (!app || !app.appId) throw new Error("ClankerBend appId is required");
|
|
682
680
|
const seeded = seededApps[app.appId] || {};
|
|
683
681
|
const current = this.apps[app.appId] || {};
|
|
684
682
|
const currentVersion = Number(current.bridge?.version);
|
|
@@ -690,7 +688,7 @@ class CodexDesktopCdpAdapter {
|
|
|
690
688
|
Number.isFinite(nextVersion) &&
|
|
691
689
|
nextVersion < currentVersion
|
|
692
690
|
) {
|
|
693
|
-
throw new Error("
|
|
691
|
+
throw new Error("ClankerBend app bridge version regressed for " + app.appId);
|
|
694
692
|
}
|
|
695
693
|
const slot = {
|
|
696
694
|
...current,
|
|
@@ -713,22 +711,22 @@ class CodexDesktopCdpAdapter {
|
|
|
713
711
|
return this.getApp(appId)?.entryUrl || null;
|
|
714
712
|
},
|
|
715
713
|
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("
|
|
714
|
+
if (!annotation?.appId) throw new Error("ClankerBend annotation appId is required");
|
|
715
|
+
if (!annotation?.anchorId) throw new Error("ClankerBend annotation anchorId is required");
|
|
716
|
+
if (!(annotation.element instanceof HTMLElement)) throw new Error("ClankerBend annotation element is required");
|
|
719
717
|
const markerId = annotation.markerId || annotation.anchorId;
|
|
720
718
|
const host = annotationHost(anchor, annotation.anchorId);
|
|
721
|
-
const selector = '.
|
|
719
|
+
const selector = '.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(annotation.appId) + '"][data-clankerbend-marker-id="' + cssEscape(markerId) + '"]';
|
|
722
720
|
let slot = host.querySelector(selector);
|
|
723
721
|
if (!slot) {
|
|
724
722
|
slot = document.createElement("div");
|
|
725
|
-
slot.className = "
|
|
726
|
-
slot.dataset.
|
|
727
|
-
slot.dataset.
|
|
723
|
+
slot.className = "clankerbend-transcript-annotation-slot";
|
|
724
|
+
slot.dataset.clankerbendAppId = annotation.appId;
|
|
725
|
+
slot.dataset.clankerbendMarkerId = markerId;
|
|
728
726
|
host.appendChild(slot);
|
|
729
727
|
}
|
|
730
|
-
slot.dataset.
|
|
731
|
-
slot.dataset.
|
|
728
|
+
slot.dataset.clankerbendAnchorId = annotation.anchorId;
|
|
729
|
+
slot.dataset.clankerbendPriority = String(annotation.priority ?? 100);
|
|
732
730
|
if (slot.firstElementChild !== annotation.element) slot.replaceChildren(annotation.element);
|
|
733
731
|
sortAnnotationSlots(host);
|
|
734
732
|
positionAnnotationHost(host, anchor);
|
|
@@ -736,10 +734,10 @@ class CodexDesktopCdpAdapter {
|
|
|
736
734
|
},
|
|
737
735
|
removeAnnotations(appId, liveAnchorIds = null) {
|
|
738
736
|
const live = liveAnchorIds ? new Set([...liveAnchorIds].map(String)) : null;
|
|
739
|
-
document.querySelectorAll('.
|
|
740
|
-
if (!live || !live.has(slot.dataset.
|
|
737
|
+
document.querySelectorAll('.clankerbend-transcript-annotation-slot[data-clankerbend-app-id="' + cssEscape(appId) + '"]').forEach((slot) => {
|
|
738
|
+
if (!live || !live.has(slot.dataset.clankerbendAnchorId || "")) slot.remove();
|
|
741
739
|
});
|
|
742
|
-
document.querySelectorAll(".
|
|
740
|
+
document.querySelectorAll(".clankerbend-transcript-annotations").forEach((host) => {
|
|
743
741
|
if (!host.children.length) host.remove();
|
|
744
742
|
});
|
|
745
743
|
},
|
|
@@ -747,14 +745,14 @@ class CodexDesktopCdpAdapter {
|
|
|
747
745
|
if (!range?.anchorId) return { ok: false, error: "range.anchorId is required" };
|
|
748
746
|
const anchor = findAnchorElement(range.anchorId);
|
|
749
747
|
if (!anchor) return { ok: false, error: "anchor not found", anchorId: range.anchorId };
|
|
750
|
-
anchor.classList.add("
|
|
748
|
+
anchor.classList.add("clankerbend-range-highlight");
|
|
751
749
|
const previousOutline = anchor.style.outline;
|
|
752
750
|
const previousOffset = anchor.style.outlineOffset;
|
|
753
751
|
anchor.style.outline = "3px solid #f6c945";
|
|
754
752
|
anchor.style.outlineOffset = "3px";
|
|
755
753
|
anchor.scrollIntoView({ block: options.block || "center", behavior: options.behavior || "smooth" });
|
|
756
754
|
setTimeout(() => {
|
|
757
|
-
anchor.classList.remove("
|
|
755
|
+
anchor.classList.remove("clankerbend-range-highlight");
|
|
758
756
|
anchor.style.outline = previousOutline;
|
|
759
757
|
anchor.style.outlineOffset = previousOffset;
|
|
760
758
|
}, Number(options.durationMs || 1200));
|
|
@@ -764,8 +762,8 @@ class CodexDesktopCdpAdapter {
|
|
|
764
762
|
const el = findComposer();
|
|
765
763
|
if (!el) return { ok: false, error: "composer not found" };
|
|
766
764
|
const currentText = composerText(el);
|
|
767
|
-
const text = draft.
|
|
768
|
-
? (draft.text ? mergeDraftText(
|
|
765
|
+
const text = draft.clankerbendContext
|
|
766
|
+
? (draft.text ? mergeDraftText(stripContextBlock(currentText), String(draft.text || ""), "prepend") : stripContextBlock(currentText))
|
|
769
767
|
: mergeDraftText(currentText, String(draft.text || ""), draft.mode || "replace");
|
|
770
768
|
if (!setComposerValue(el, text)) return { ok: false, error: "composer cannot be updated" };
|
|
771
769
|
return { ok: true, draft: { ...draft, text } };
|
|
@@ -790,7 +788,7 @@ class CodexDesktopCdpAdapter {
|
|
|
790
788
|
injectedAt: runtime.apps[appId]?.injectedAt || null
|
|
791
789
|
};
|
|
792
790
|
}
|
|
793
|
-
window.
|
|
791
|
+
window.__clankerbendRuntime = runtime;
|
|
794
792
|
})()`);
|
|
795
793
|
for (const bridge of this.rendererBridges) {
|
|
796
794
|
await this.evaluate(bridge.injectedSource);
|
|
@@ -1042,9 +1040,9 @@ class CodexDesktopCdpAdapter {
|
|
|
1042
1040
|
async evalBridge(expression, bridge = this.primaryBridge()) {
|
|
1043
1041
|
const appId = this.bridgeAppId(bridge);
|
|
1044
1042
|
return this.evaluate(`(() => {
|
|
1045
|
-
const runtime = window.
|
|
1043
|
+
const runtime = window.__clankerbendRuntime;
|
|
1046
1044
|
const bridge = runtime?.getBridge?.(${JSON.stringify(appId)});
|
|
1047
|
-
if (!bridge) return { ok: false, error: "
|
|
1045
|
+
if (!bridge) return { ok: false, error: "ClankerBend bridge is not installed for " + ${JSON.stringify(appId)} };
|
|
1048
1046
|
return bridge.${expression};
|
|
1049
1047
|
})()`);
|
|
1050
1048
|
}
|
|
@@ -1240,8 +1238,8 @@ class AppServerClient {
|
|
|
1240
1238
|
async initialize() {
|
|
1241
1239
|
const response = await this.call("initialize", {
|
|
1242
1240
|
clientInfo: {
|
|
1243
|
-
name: "
|
|
1244
|
-
title: "
|
|
1241
|
+
name: "clankerbend-host",
|
|
1242
|
+
title: "ClankerBend Host",
|
|
1245
1243
|
version: "0.1"
|
|
1246
1244
|
},
|
|
1247
1245
|
capabilities: {
|
|
@@ -1448,7 +1446,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
|
|
|
1448
1446
|
const createdAt = options.createdAt || new Date().toISOString();
|
|
1449
1447
|
return files.map((file, index) => {
|
|
1450
1448
|
const text = [
|
|
1451
|
-
"
|
|
1449
|
+
"ClankerBend attached runtime file context.",
|
|
1452
1450
|
"",
|
|
1453
1451
|
`Attached at: ${createdAt}`,
|
|
1454
1452
|
`File ${index + 1}: ${file.name || basename(file.path)}`,
|
|
@@ -1456,7 +1454,7 @@ function buildAttachmentResponseItems(files = [], options = {}) {
|
|
|
1456
1454
|
file.relativePath ? `Runtime path: ${file.relativePath}` : null,
|
|
1457
1455
|
file.mimeType ? `MIME type: ${file.mimeType}` : null,
|
|
1458
1456
|
"",
|
|
1459
|
-
"The following file was created by a
|
|
1457
|
+
"The following file was created by a ClankerBend app and should be treated as attached context for the user's next prompt.",
|
|
1460
1458
|
"",
|
|
1461
1459
|
"```markdown",
|
|
1462
1460
|
readFileSync(file.path, "utf8"),
|
|
@@ -1731,14 +1729,14 @@ function nativeAttachmentDiagnosticExpression(paths) {
|
|
|
1731
1729
|
(() => {
|
|
1732
1730
|
const paths = ${JSON.stringify(paths)};
|
|
1733
1731
|
const basenames = paths.map((path) => String(path).split(/[\\\\/]/).pop()).filter(Boolean);
|
|
1734
|
-
const
|
|
1732
|
+
const clankerbendChipCount = document.querySelectorAll("#clankerbend-composer-chips .clankerbend-context-chip").length;
|
|
1735
1733
|
const composerInputs = [...document.querySelectorAll("textarea,[contenteditable='true'],[role='textbox']")]
|
|
1736
1734
|
.map((el) => ({ el, rect: el.getBoundingClientRect() }))
|
|
1737
1735
|
.filter((item) => item.rect.width > 180 && item.rect.height > 18 && item.rect.bottom > window.innerHeight * 0.45)
|
|
1738
1736
|
.sort((a, b) => b.rect.bottom - a.rect.bottom);
|
|
1739
1737
|
const composerRect = composerInputs[0]?.rect || null;
|
|
1740
1738
|
const candidates = [...document.querySelectorAll("button,[role='button'],span,div")]
|
|
1741
|
-
.filter((el) => !el.closest("#
|
|
1739
|
+
.filter((el) => !el.closest("#clankerbend-composer-chips"))
|
|
1742
1740
|
.map((el) => {
|
|
1743
1741
|
const text = (el.innerText || el.textContent || "").replace(/\\s+/g, " ").trim();
|
|
1744
1742
|
const rect = el.getBoundingClientRect();
|
|
@@ -1760,10 +1758,10 @@ function nativeAttachmentDiagnosticExpression(paths) {
|
|
|
1760
1758
|
);
|
|
1761
1759
|
const matched = basenames.filter((name) => candidates.some((item) => item.text.includes(name)));
|
|
1762
1760
|
return {
|
|
1763
|
-
ok: matched.length === basenames.length &&
|
|
1761
|
+
ok: matched.length === basenames.length && clankerbendChipCount === 0,
|
|
1764
1762
|
matched,
|
|
1765
1763
|
basenames,
|
|
1766
|
-
|
|
1764
|
+
clankerbendChipCount,
|
|
1767
1765
|
candidates: candidates.slice(0, 12)
|
|
1768
1766
|
};
|
|
1769
1767
|
})()
|