rewritable 0.11.0 → 0.13.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 +1 -1
- package/seeds/rewritable.html +168 -4
package/package.json
CHANGED
package/seeds/rewritable.html
CHANGED
|
@@ -2322,6 +2322,18 @@ async function renderActionsModePanel(panel) {
|
|
|
2322
2322
|
const skillRows = skillAffs.length ? skillAffs.map(a =>
|
|
2323
2323
|
'<div class="rwa-mode-row"><div><div class="rwa-mode-title">' + escRuntimeHtml(a.name) + '</div><div class="rwa-mode-meta">' + escRuntimeHtml(a.kind || 'skill') + ' · ' + escRuntimeHtml(a.skillId) + '</div></div><button type="button" data-action-skill="' + escRuntimeHtml(a.skillId) + '">Open in Skills</button></div>'
|
|
2324
2324
|
).join('') : '<div class="rwa-mode-empty">No installed skills.</div>';
|
|
2325
|
+
// Intelligences (rwa-agent/1 roles) — list + activate/deactivate. Activating offers the role's
|
|
2326
|
+
// recommended model (intelligence/0.2 I-A). A verified role can be activated; unverified cannot.
|
|
2327
|
+
const agentList = (typeof runtimeListAgents === 'function') ? runtimeListAgents() : [];
|
|
2328
|
+
const activeRole = (runtimeAgentActive() || {}).role || null;
|
|
2329
|
+
const agentRows = agentList.length ? agentList.map(a => {
|
|
2330
|
+
const isActive = a.role === activeRole;
|
|
2331
|
+
const meta = (a.verified ? 'verified' : 'unverified') + (isActive ? ' · active' : '');
|
|
2332
|
+
const btn = !a.verified ? '' : (isActive
|
|
2333
|
+
? '<button type="button" data-agent-off="1">Deactivate</button>'
|
|
2334
|
+
: '<button type="button" data-agent-on="' + escRuntimeHtml(a.role) + '">Activate</button>');
|
|
2335
|
+
return '<div class="rwa-mode-row"><div><div class="rwa-mode-title">' + escRuntimeHtml(a.role) + '</div><div class="rwa-mode-meta">' + escRuntimeHtml('intelligence · ' + meta) + '</div></div>' + btn + '</div>';
|
|
2336
|
+
}).join('') : '<div class="rwa-mode-empty">No intelligences installed.</div>';
|
|
2325
2337
|
panel.innerHTML = [
|
|
2326
2338
|
'<div class="rwa-mode-section">',
|
|
2327
2339
|
'<div class="rwa-mode-kicker">Activity</div>',
|
|
@@ -2336,6 +2348,7 @@ async function renderActionsModePanel(panel) {
|
|
|
2336
2348
|
'<div class="rwa-mode-section"><div class="rwa-mode-kicker">Recent runs</div>' + histRows + '</div>',
|
|
2337
2349
|
'<div class="rwa-mode-section"><div class="rwa-mode-kicker">Live affordances</div>' + affRows + '</div>',
|
|
2338
2350
|
'<div class="rwa-mode-section"><div class="rwa-mode-kicker">Installed skill actions</div>' + skillRows + '</div>',
|
|
2351
|
+
'<div class="rwa-mode-section"><div class="rwa-mode-kicker">Intelligences</div>' + agentRows + '</div>',
|
|
2339
2352
|
].join('');
|
|
2340
2353
|
const undoBtn = panel.querySelector('#rwa-actions-undo');
|
|
2341
2354
|
if (undoBtn) undoBtn.addEventListener('click', () => runtimeUndo());
|
|
@@ -2352,6 +2365,13 @@ async function renderActionsModePanel(panel) {
|
|
|
2352
2365
|
panel.querySelectorAll('[data-action-skill]').forEach(btn => {
|
|
2353
2366
|
btn.addEventListener('click', () => runtimeSetMode('skills'));
|
|
2354
2367
|
});
|
|
2368
|
+
panel.querySelectorAll('[data-agent-on]').forEach(btn => btn.addEventListener('click', () => {
|
|
2369
|
+
try { runtimeActivateAgent(btn.getAttribute('data-agent-on')); } // setActive + offer recommended model
|
|
2370
|
+
catch (e) { if (typeof setStatus === 'function') setStatus('err', '✗ ' + (e && (e.code || e.message))); }
|
|
2371
|
+
renderActionsModePanel(panel);
|
|
2372
|
+
}));
|
|
2373
|
+
const agentOff = panel.querySelector('[data-agent-off]');
|
|
2374
|
+
if (agentOff) agentOff.addEventListener('click', () => { runtimeSetActiveAgent(null); renderActionsModePanel(panel); });
|
|
2355
2375
|
}
|
|
2356
2376
|
|
|
2357
2377
|
// ─── Agent (rwa-edit/1) ─────────────────────────────────────────────
|
|
@@ -7991,13 +8011,157 @@ function showAgentInstallDialog(envelope) {
|
|
|
7991
8011
|
const ib = card.querySelector('[data-act=install]'); if (ib) ib.onclick = async () => close(await runtimeInstallAgent(envelope));
|
|
7992
8012
|
}));
|
|
7993
8013
|
}
|
|
7994
|
-
// §1.3 / §12 — install trigger: pick a .rwa-skill.json
|
|
8014
|
+
// §1.3 / §12 — install trigger: pick a .rwa-skill.json / .rwa-agent.json envelope OR an
|
|
8015
|
+
// intelligence carrier .html (a rewritable carrying a signed rwa-agent/1 record), then route.
|
|
7995
8016
|
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 = () => {
|
|
8017
|
+
const inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.rwa-skill.json,.rwa-agent.json,application/json,.json,.html,.htm,text/html';
|
|
8018
|
+
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
8019
|
inp.click();
|
|
7999
8020
|
}
|
|
8000
8021
|
|
|
8022
|
+
// ── intelligence/0.2 (docs/specs/rwa-intelligence-spec.md §5) — the file-drop bridge.
|
|
8023
|
+
// An "intelligence" ships as a CARRIER: a (skill-host) rewritable carrying a signed rwa-agent/1
|
|
8024
|
+
// record in its frozen #rwa-agents zone (the overlay half is the v0.9 agent role — see
|
|
8025
|
+
// runtime.agents / buildAgentZone above). Dropping a carrier onto a target extracts that record
|
|
8026
|
+
// and routes it to the existing consent dialog + runtime.agents.install. In a carrier's RAW bytes
|
|
8027
|
+
// the record lives inside INLINE_DOC, so its closing script tag is backslash-escaped (the zone
|
|
8028
|
+
// <div>/</div> delimiters are not). We extract + un-escape INLINE_DOC (the inverse of buildFile's
|
|
8029
|
+
// escapeTL — every escape is one backslash + one char), then parse the zone exactly as
|
|
8030
|
+
// readTrustworthyAgents does. The signature stays the trust anchor: the dialog re-verifies.
|
|
8031
|
+
function _carrierDoc(html) {
|
|
8032
|
+
const marker = 'const INLINE_DOC = `';
|
|
8033
|
+
const i = String(html || '').indexOf(marker);
|
|
8034
|
+
if (i < 0) return String(html || ''); // not a full container — treat the text itself as the doc
|
|
8035
|
+
let j = i + marker.length;
|
|
8036
|
+
for (; j < html.length; j++) { const c = html[j]; if (c === '\\') { j++; continue; } if (c === '`') break; }
|
|
8037
|
+
return html.slice(i + marker.length, j).replace(/\\([\s\S])/g, '$1');
|
|
8038
|
+
}
|
|
8039
|
+
function extractAgentEnvelopesFromCarrier(html) {
|
|
8040
|
+
const zone = _agExtractZone(_carrierDoc(html));
|
|
8041
|
+
if (!zone) return [];
|
|
8042
|
+
const out = [];
|
|
8043
|
+
for (const m of zone.matchAll(/<script\s+type="application\/rwa-agent\+json">([\s\S]*?)<\/script>/g)) {
|
|
8044
|
+
let env; try { env = JSON.parse(new TextDecoder().decode(_skFromB64(m[1].trim()))); } catch (_) { continue; }
|
|
8045
|
+
if (env && env.agent && typeof env.agent.role === 'string') out.push(env);
|
|
8046
|
+
}
|
|
8047
|
+
return out;
|
|
8048
|
+
}
|
|
8049
|
+
// Classify a dropped/picked file's text: a carrier .html, a bare envelope JSON, or nothing.
|
|
8050
|
+
function classifyInstallText(text) {
|
|
8051
|
+
const s = String(text || '');
|
|
8052
|
+
let obj = null; try { obj = JSON.parse(s); } catch (_) {}
|
|
8053
|
+
if (obj && typeof obj === 'object') {
|
|
8054
|
+
if (obj.agent || obj.format === 'rwa-agent/1') return { kind: 'json-agent', envelope: obj };
|
|
8055
|
+
if (obj.skill || obj.format === 'rwa-skill/1') return { kind: 'json-skill', envelope: obj };
|
|
8056
|
+
return { kind: 'none' };
|
|
8057
|
+
}
|
|
8058
|
+
const envelopes = extractAgentEnvelopesFromCarrier(s);
|
|
8059
|
+
if (envelopes.length) return { kind: 'agent-carrier', envelopes };
|
|
8060
|
+
return { kind: 'none' };
|
|
8061
|
+
}
|
|
8062
|
+
// Route extracted content to the right consent dialog. Install stays behind the dialog (the trust
|
|
8063
|
+
// anchor); the dialog is fire-and-forget so the drop handler returns immediately. Multiple records
|
|
8064
|
+
// in one carrier are queued (each dialog awaits the previous close).
|
|
8065
|
+
async function routeInstallFromText(text) {
|
|
8066
|
+
const c = classifyInstallText(text);
|
|
8067
|
+
if (c.kind === 'json-agent') showAgentInstallDialog(c.envelope);
|
|
8068
|
+
else if (c.kind === 'json-skill') showSkillInstallDialog(c.envelope);
|
|
8069
|
+
else if (c.kind === 'agent-carrier') { (async () => { for (const env of c.envelopes) { await showAgentInstallDialog(env); } })(); }
|
|
8070
|
+
else if (typeof setStatus === 'function') setStatus('err', 'no installable skill or intelligence found in that file');
|
|
8071
|
+
return c;
|
|
8072
|
+
}
|
|
8073
|
+
async function _readDroppedText(file) {
|
|
8074
|
+
if (file && typeof file.text === 'function') return file.text();
|
|
8075
|
+
return new Promise((res, rej) => { const rd = new FileReader(); rd.onload = () => res(String(rd.result || '')); rd.onerror = rej; rd.readAsText(file); });
|
|
8076
|
+
}
|
|
8077
|
+
// Drop gesture — capture phase, so a carrier is claimed before the Edit-mode image-mount drop.
|
|
8078
|
+
// Acts only on an .html/.htm file; any other drop flows through untouched to existing handlers.
|
|
8079
|
+
// A carrier is a self-contained .html (seed + doc, ~0.6 MB; larger with embedded images); cap the
|
|
8080
|
+
// read so a wildly oversized drop can't be slurped into memory (mirrors the image-ingest size cap).
|
|
8081
|
+
const CARRIER_MAX_BYTES = 32 * 1024 * 1024;
|
|
8082
|
+
async function handleCarrierDrop(e) {
|
|
8083
|
+
const files = Array.from((e && e.dataTransfer && e.dataTransfer.files) || []);
|
|
8084
|
+
const carrier = files.find(f => /text\/html/i.test(f.type || '') || /\.html?$/i.test(f.name || ''));
|
|
8085
|
+
if (!carrier) return; // not a carrier — let the image/other drop handlers run
|
|
8086
|
+
if (e.preventDefault) e.preventDefault();
|
|
8087
|
+
if (e.stopPropagation) e.stopPropagation();
|
|
8088
|
+
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; }
|
|
8089
|
+
try { await routeInstallFromText(await _readDroppedText(carrier)); }
|
|
8090
|
+
catch (_) { if (typeof setStatus === 'function') setStatus('err', 'could not read the dropped file'); }
|
|
8091
|
+
}
|
|
8092
|
+
function handleCarrierDragOver(e) {
|
|
8093
|
+
// Let a file drop fire anywhere on the page (a carrier can be dropped onto the document, not only
|
|
8094
|
+
// the edit mount). Idempotent with the mount's own dragover; the drop handler decides whether to claim it.
|
|
8095
|
+
const t = e && e.dataTransfer;
|
|
8096
|
+
if (t && (Array.from(t.items || []).some(i => i.kind === 'file') || Array.from(t.types || []).includes('Files'))) e.preventDefault();
|
|
8097
|
+
}
|
|
8098
|
+
window.addEventListener('dragover', handleCarrierDragOver, true);
|
|
8099
|
+
window.addEventListener('drop', handleCarrierDrop, true);
|
|
8100
|
+
// Automation/test hooks (mirror window.__ingestImageFile).
|
|
8101
|
+
window.__rwaExtractAgentCarrier = extractAgentEnvelopesFromCarrier;
|
|
8102
|
+
window.__rwaClassifyInstallText = classifyInstallText;
|
|
8103
|
+
window.__rwaInstallFromText = routeInstallFromText;
|
|
8104
|
+
window.__rwaHandleCarrierDrop = handleCarrierDrop;
|
|
8105
|
+
|
|
8106
|
+
// ── intelligence/0.2 I-A (docs/specs/rwa-intelligence-spec.md §6) — recommended model on activation.
|
|
8107
|
+
// A carrier may carry a NON-SECRET recommended_model / recommended_backend on its rwa-agent/1
|
|
8108
|
+
// ENVELOPE (OUTSIDE the signed `agent`, so canonicalAgent is unchanged and the signature still
|
|
8109
|
+
// verifies — this stays seed-only). On activation the runtime OFFERS to apply it to sessionStorage
|
|
8110
|
+
// behind a one-line consent: it never auto-applies, never sets a base-URL, never touches the API
|
|
8111
|
+
// key. A recommendation is a suggestion, not a stored credential (key/model are sessionStorage-only).
|
|
8112
|
+
const REC_MODEL_RE = /^[A-Za-z0-9._:\/-]{1,200}$/; // a model id: provider/name, dots, colon (ollama), hyphen
|
|
8113
|
+
const REC_BACKENDS = ['openrouter', 'ollama', 'lmstudio', 'atomic', 'bridge', 'bridge-session'];
|
|
8114
|
+
function getRecommendation(envelope) {
|
|
8115
|
+
const e = envelope || {};
|
|
8116
|
+
const out = {};
|
|
8117
|
+
if (typeof e.recommended_model === 'string' && REC_MODEL_RE.test(e.recommended_model.trim())) out.model = e.recommended_model.trim();
|
|
8118
|
+
if (typeof e.recommended_backend === 'string' && REC_BACKENDS.includes(e.recommended_backend.trim())) out.backend = e.recommended_backend.trim();
|
|
8119
|
+
return (out.model || out.backend) ? out : null;
|
|
8120
|
+
}
|
|
8121
|
+
function applyRecommendation(rec) {
|
|
8122
|
+
const r = rec || {};
|
|
8123
|
+
const applied = {};
|
|
8124
|
+
if (r.model && REC_MODEL_RE.test(String(r.model))) { sessionStorage.setItem(RWA.K_MODEL, String(r.model)); applied.model = String(r.model); }
|
|
8125
|
+
if (r.backend && REC_BACKENDS.includes(String(r.backend))) { sessionStorage.setItem(RWA.K_BACKEND, String(r.backend)); applied.backend = String(r.backend); }
|
|
8126
|
+
try { const m = document.getElementById('rwa-model'); if (m && applied.model) m.value = applied.model; const b = document.getElementById('rwa-backend'); if (b && applied.backend) { b.value = applied.backend; b.dispatchEvent(new Event('change')); } } catch (_) {}
|
|
8127
|
+
return applied;
|
|
8128
|
+
}
|
|
8129
|
+
// Offer the role's recommended model, if any and if it differs from the current session. Fire-and-
|
|
8130
|
+
// forget consent dialog; resolves with the choice. Activation already happened — this only proposes.
|
|
8131
|
+
function offerRecommendedModel(role) {
|
|
8132
|
+
const recRec = Array.from(installedAgents.values()).find(a => a.role === role);
|
|
8133
|
+
const rec = recRec && getRecommendation(recRec.envelope);
|
|
8134
|
+
if (!rec) return Promise.resolve({ offered: false });
|
|
8135
|
+
const curModel = sessionStorage.getItem(RWA.K_MODEL) || RWA.MODEL;
|
|
8136
|
+
const curBackend = sessionStorage.getItem(RWA.K_BACKEND) || 'openrouter';
|
|
8137
|
+
if ((!rec.model || rec.model === curModel) && (!rec.backend || rec.backend === curBackend)) return Promise.resolve({ offered: false });
|
|
8138
|
+
return new Promise(resolve => {
|
|
8139
|
+
const prev = document.getElementById('rwa-model-offer'); if (prev) prev.remove();
|
|
8140
|
+
const overlay = document.createElement('div'); overlay.id = 'rwa-model-offer';
|
|
8141
|
+
overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;';
|
|
8142
|
+
const card = document.createElement('div');
|
|
8143
|
+
card.style.cssText = 'background:#fff;max-width:480px;width:92%;border-radius:18px;padding:24px 26px;font:14px/1.5 var(--font-ui,system-ui);box-shadow:0 12px 48px rgba(0,0,0,.28);';
|
|
8144
|
+
const what = [rec.model ? 'model <code>' + _skEsc(rec.model) + '</code>' : '', rec.backend ? 'backend <code>' + _skEsc(rec.backend) + '</code>' : ''].filter(Boolean).join(' on ');
|
|
8145
|
+
card.innerHTML =
|
|
8146
|
+
'<h2 style="margin:0 0 .4em;font-size:1.15rem">Use this intelligence’s recommended ' + (rec.model ? 'model' : 'backend') + '?</h2>' +
|
|
8147
|
+
'<p style="margin:.2em 0;color:#444">The <strong>' + _skEsc(role) + '</strong> role suggests ' + what + ' for ⌘K. This is a <em>recommendation</em> (not part of the signed role) — applying it changes only your session’s model/backend selection. Your API key is untouched.</p>' +
|
|
8148
|
+
'<div style="display:flex;gap:10px;margin-top:1.2em;justify-content:flex-end">' +
|
|
8149
|
+
'<button data-act="keep" style="padding:9px 16px;border:1px solid #ccc;border-radius:10px;background:#fff;cursor:pointer">Keep current</button>' +
|
|
8150
|
+
'<button data-act="apply" style="padding:9px 16px;border:none;border-radius:10px;background:var(--gray-900,#111);color:#fff;cursor:pointer">Use it</button>' +
|
|
8151
|
+
'</div>';
|
|
8152
|
+
overlay.appendChild(card); document.body.appendChild(overlay);
|
|
8153
|
+
const close = (choice) => { overlay.remove(); resolve(choice); };
|
|
8154
|
+
card.querySelector('[data-act=keep]').onclick = () => close({ offered: true, applied: null });
|
|
8155
|
+
card.querySelector('[data-act=apply]').onclick = () => close({ offered: true, applied: applyRecommendation(rec) });
|
|
8156
|
+
});
|
|
8157
|
+
}
|
|
8158
|
+
// Activate a role AND offer its recommended model — the "on activation" entry the UI calls.
|
|
8159
|
+
function runtimeActivateAgent(role) { runtimeSetActiveAgent(role); return offerRecommendedModel(role); }
|
|
8160
|
+
window.__rwaGetRecommendation = getRecommendation;
|
|
8161
|
+
window.__rwaApplyRecommendation = applyRecommendation;
|
|
8162
|
+
window.__rwaOfferRecommendedModel = offerRecommendedModel;
|
|
8163
|
+
window.__rwaActivateAgent = runtimeActivateAgent;
|
|
8164
|
+
|
|
8001
8165
|
// §4/§5a — does a network: host pattern admit a host? Mirror of cli/src/skill-manifest.mjs
|
|
8002
8166
|
// matchNetworkOrigin (keep in step). The bridge's per-call origin check.
|
|
8003
8167
|
function _skMatchNetworkOrigin(pattern, host) {
|
|
@@ -9530,7 +9694,7 @@ document.addEventListener('keydown', e => {
|
|
|
9530
9694
|
discoverSkills: runtimeDiscoverSkills, // v0.9 §11 (I6) — GET the marketplace index (opt-in network)
|
|
9531
9695
|
fetchSkillFromIndex: runtimeFetchSkillFromIndex, // v0.9 §11 (I6) — fetch + client-side-verify an indexed skill
|
|
9532
9696
|
vault: { get: runtimeVaultGet, set: runtimeVaultSet, has: runtimeVaultHas, namespaces: runtimeVaultNamespaces, unlock: runtimeVaultUnlock, lock: runtimeVaultLock, isLocked: runtimeVaultIsLocked, export: runtimeVaultExport, import: runtimeVaultImport }, // v0.8 §6 + v0.9 §14 (I13) portable export/import
|
|
9533
|
-
agents: { list: runtimeListAgents, active: runtimeAgentActive, setActive: runtimeSetActiveAgent, install: runtimeInstallAgent, uninstall: runtimeUninstallAgent, message: runtimeAgentMessage, showInstallDialog: showAgentInstallDialog }, // v0.9 §12 — multi-agent roles
|
|
9697
|
+
agents: { list: runtimeListAgents, active: runtimeAgentActive, setActive: runtimeSetActiveAgent, activate: runtimeActivateAgent, install: runtimeInstallAgent, uninstall: runtimeUninstallAgent, message: runtimeAgentMessage, showInstallDialog: showAgentInstallDialog, offerModel: offerRecommendedModel }, // v0.9 §12 — multi-agent roles; `activate` = setActive + offer recommended model (intelligence/0.2 I-A)
|
|
9534
9698
|
hookLog: runtimeHookLog, // v0.9 §9 — the hook audit trail (rwa_hook_log)
|
|
9535
9699
|
};
|
|
9536
9700
|
// `status` is a getter so each read returns a fresh snapshot of
|