rewritable 0.11.0 → 0.12.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rewritable",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for re-writeable: emit and import single-file rwa documents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7991,13 +7991,98 @@ function showAgentInstallDialog(envelope) {
7991
7991
  const ib = card.querySelector('[data-act=install]'); if (ib) ib.onclick = async () => close(await runtimeInstallAgent(envelope));
7992
7992
  }));
7993
7993
  }
7994
- // §1.3 / §12 — install trigger: pick a .rwa-skill.json OR .rwa-agent.json, parse, dispatch by format.
7994
+ // §1.3 / §12 — install trigger: pick a .rwa-skill.json / .rwa-agent.json envelope OR an
7995
+ // intelligence carrier .html (a rewritable carrying a signed rwa-agent/1 record), then route.
7995
7996
  function runtimePromptInstall() {
7996
- const inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.rwa-skill.json,.rwa-agent.json,application/json,.json';
7997
- inp.onchange = () => { const f = inp.files && inp.files[0]; if (!f) return; const rd = new FileReader(); rd.onload = () => { let env; try { env = JSON.parse(rd.result); } catch (_) { setStatus && setStatus('err', 'invalid skill/agent JSON'); return; } if (env && env.format === 'rwa-agent/1') showAgentInstallDialog(env); else showSkillInstallDialog(env); }; rd.readAsText(f); };
7997
+ const inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.rwa-skill.json,.rwa-agent.json,application/json,.json,.html,.htm,text/html';
7998
+ inp.onchange = () => { const f = inp.files && inp.files[0]; if (!f) return; const rd = new FileReader(); rd.onload = () => { routeInstallFromText(String(rd.result || '')); }; rd.readAsText(f); };
7998
7999
  inp.click();
7999
8000
  }
8000
8001
 
8002
+ // ── intelligence/0.2 (docs/specs/rwa-intelligence-spec.md §5) — the file-drop bridge.
8003
+ // An "intelligence" ships as a CARRIER: a (skill-host) rewritable carrying a signed rwa-agent/1
8004
+ // record in its frozen #rwa-agents zone (the overlay half is the v0.9 agent role — see
8005
+ // runtime.agents / buildAgentZone above). Dropping a carrier onto a target extracts that record
8006
+ // and routes it to the existing consent dialog + runtime.agents.install. In a carrier's RAW bytes
8007
+ // the record lives inside INLINE_DOC, so its closing script tag is backslash-escaped (the zone
8008
+ // <div>/</div> delimiters are not). We extract + un-escape INLINE_DOC (the inverse of buildFile's
8009
+ // escapeTL — every escape is one backslash + one char), then parse the zone exactly as
8010
+ // readTrustworthyAgents does. The signature stays the trust anchor: the dialog re-verifies.
8011
+ function _carrierDoc(html) {
8012
+ const marker = 'const INLINE_DOC = `';
8013
+ const i = String(html || '').indexOf(marker);
8014
+ if (i < 0) return String(html || ''); // not a full container — treat the text itself as the doc
8015
+ let j = i + marker.length;
8016
+ for (; j < html.length; j++) { const c = html[j]; if (c === '\\') { j++; continue; } if (c === '`') break; }
8017
+ return html.slice(i + marker.length, j).replace(/\\([\s\S])/g, '$1');
8018
+ }
8019
+ function extractAgentEnvelopesFromCarrier(html) {
8020
+ const zone = _agExtractZone(_carrierDoc(html));
8021
+ if (!zone) return [];
8022
+ const out = [];
8023
+ for (const m of zone.matchAll(/<script\s+type="application\/rwa-agent\+json">([\s\S]*?)<\/script>/g)) {
8024
+ let env; try { env = JSON.parse(new TextDecoder().decode(_skFromB64(m[1].trim()))); } catch (_) { continue; }
8025
+ if (env && env.agent && typeof env.agent.role === 'string') out.push(env);
8026
+ }
8027
+ return out;
8028
+ }
8029
+ // Classify a dropped/picked file's text: a carrier .html, a bare envelope JSON, or nothing.
8030
+ function classifyInstallText(text) {
8031
+ const s = String(text || '');
8032
+ let obj = null; try { obj = JSON.parse(s); } catch (_) {}
8033
+ if (obj && typeof obj === 'object') {
8034
+ if (obj.agent || obj.format === 'rwa-agent/1') return { kind: 'json-agent', envelope: obj };
8035
+ if (obj.skill || obj.format === 'rwa-skill/1') return { kind: 'json-skill', envelope: obj };
8036
+ return { kind: 'none' };
8037
+ }
8038
+ const envelopes = extractAgentEnvelopesFromCarrier(s);
8039
+ if (envelopes.length) return { kind: 'agent-carrier', envelopes };
8040
+ return { kind: 'none' };
8041
+ }
8042
+ // Route extracted content to the right consent dialog. Install stays behind the dialog (the trust
8043
+ // anchor); the dialog is fire-and-forget so the drop handler returns immediately. Multiple records
8044
+ // in one carrier are queued (each dialog awaits the previous close).
8045
+ async function routeInstallFromText(text) {
8046
+ const c = classifyInstallText(text);
8047
+ if (c.kind === 'json-agent') showAgentInstallDialog(c.envelope);
8048
+ else if (c.kind === 'json-skill') showSkillInstallDialog(c.envelope);
8049
+ else if (c.kind === 'agent-carrier') { (async () => { for (const env of c.envelopes) { await showAgentInstallDialog(env); } })(); }
8050
+ else if (typeof setStatus === 'function') setStatus('err', 'no installable skill or intelligence found in that file');
8051
+ return c;
8052
+ }
8053
+ async function _readDroppedText(file) {
8054
+ if (file && typeof file.text === 'function') return file.text();
8055
+ return new Promise((res, rej) => { const rd = new FileReader(); rd.onload = () => res(String(rd.result || '')); rd.onerror = rej; rd.readAsText(file); });
8056
+ }
8057
+ // Drop gesture — capture phase, so a carrier is claimed before the Edit-mode image-mount drop.
8058
+ // Acts only on an .html/.htm file; any other drop flows through untouched to existing handlers.
8059
+ // A carrier is a self-contained .html (seed + doc, ~0.6 MB; larger with embedded images); cap the
8060
+ // read so a wildly oversized drop can't be slurped into memory (mirrors the image-ingest size cap).
8061
+ const CARRIER_MAX_BYTES = 32 * 1024 * 1024;
8062
+ async function handleCarrierDrop(e) {
8063
+ const files = Array.from((e && e.dataTransfer && e.dataTransfer.files) || []);
8064
+ const carrier = files.find(f => /text\/html/i.test(f.type || '') || /\.html?$/i.test(f.name || ''));
8065
+ if (!carrier) return; // not a carrier — let the image/other drop handlers run
8066
+ if (e.preventDefault) e.preventDefault();
8067
+ if (e.stopPropagation) e.stopPropagation();
8068
+ if (carrier.size > CARRIER_MAX_BYTES) { if (typeof setStatus === 'function') setStatus('err', 'that file is too large to be an intelligence carrier (' + Math.round(carrier.size / 1048576) + ' MB)'); return; }
8069
+ try { await routeInstallFromText(await _readDroppedText(carrier)); }
8070
+ catch (_) { if (typeof setStatus === 'function') setStatus('err', 'could not read the dropped file'); }
8071
+ }
8072
+ function handleCarrierDragOver(e) {
8073
+ // Let a file drop fire anywhere on the page (a carrier can be dropped onto the document, not only
8074
+ // the edit mount). Idempotent with the mount's own dragover; the drop handler decides whether to claim it.
8075
+ const t = e && e.dataTransfer;
8076
+ if (t && (Array.from(t.items || []).some(i => i.kind === 'file') || Array.from(t.types || []).includes('Files'))) e.preventDefault();
8077
+ }
8078
+ window.addEventListener('dragover', handleCarrierDragOver, true);
8079
+ window.addEventListener('drop', handleCarrierDrop, true);
8080
+ // Automation/test hooks (mirror window.__ingestImageFile).
8081
+ window.__rwaExtractAgentCarrier = extractAgentEnvelopesFromCarrier;
8082
+ window.__rwaClassifyInstallText = classifyInstallText;
8083
+ window.__rwaInstallFromText = routeInstallFromText;
8084
+ window.__rwaHandleCarrierDrop = handleCarrierDrop;
8085
+
8001
8086
  // §4/§5a — does a network: host pattern admit a host? Mirror of cli/src/skill-manifest.mjs
8002
8087
  // matchNetworkOrigin (keep in step). The bridge's per-call origin check.
8003
8088
  function _skMatchNetworkOrigin(pattern, host) {