rewritable 0.12.0 → 0.14.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.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "CLI for re-writeable: emit and import single-file rwa documents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2322,6 +2322,27 @@ 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 advisorSet = (typeof runtimeListAdvisors === 'function') ? runtimeListAdvisors() : [];
2330
+ const advisorFull = advisorSet.length >= 3;
2331
+ const agentRows = agentList.length ? agentList.map(a => {
2332
+ const isActive = a.role === activeRole;
2333
+ const isAdvisor = advisorSet.indexOf(a.role) >= 0;
2334
+ const state = isActive ? 'primary' : (isAdvisor ? 'advisor' : '');
2335
+ const meta = (a.verified ? 'verified' : 'unverified') + (state ? ' · ' + state : '');
2336
+ let btns = '';
2337
+ if (a.verified) {
2338
+ const r = escRuntimeHtml(a.role);
2339
+ if (isActive) btns = '<button type="button" data-agent-off="1">Deactivate</button>';
2340
+ else if (isAdvisor) btns = '<button type="button" data-agent-advoff="' + r + '">Remove advisor</button>';
2341
+ else btns = '<button type="button" data-agent-on="' + r + '">Activate</button>'
2342
+ + (advisorFull ? '<button type="button" disabled title="advisor limit reached">Add advisor</button>' : '<button type="button" data-agent-advon="' + r + '">Add advisor</button>');
2343
+ }
2344
+ 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>' + btns + '</div>';
2345
+ }).join('') : '<div class="rwa-mode-empty">No intelligences installed.</div>';
2325
2346
  panel.innerHTML = [
2326
2347
  '<div class="rwa-mode-section">',
2327
2348
  '<div class="rwa-mode-kicker">Activity</div>',
@@ -2336,6 +2357,7 @@ async function renderActionsModePanel(panel) {
2336
2357
  '<div class="rwa-mode-section"><div class="rwa-mode-kicker">Recent runs</div>' + histRows + '</div>',
2337
2358
  '<div class="rwa-mode-section"><div class="rwa-mode-kicker">Live affordances</div>' + affRows + '</div>',
2338
2359
  '<div class="rwa-mode-section"><div class="rwa-mode-kicker">Installed skill actions</div>' + skillRows + '</div>',
2360
+ '<div class="rwa-mode-section"><div class="rwa-mode-kicker">Intelligences</div>' + agentRows + '</div>',
2339
2361
  ].join('');
2340
2362
  const undoBtn = panel.querySelector('#rwa-actions-undo');
2341
2363
  if (undoBtn) undoBtn.addEventListener('click', () => runtimeUndo());
@@ -2352,6 +2374,19 @@ async function renderActionsModePanel(panel) {
2352
2374
  panel.querySelectorAll('[data-action-skill]').forEach(btn => {
2353
2375
  btn.addEventListener('click', () => runtimeSetMode('skills'));
2354
2376
  });
2377
+ panel.querySelectorAll('[data-agent-on]').forEach(btn => btn.addEventListener('click', () => {
2378
+ try { runtimeActivateAgent(btn.getAttribute('data-agent-on')); } // setActive + offer recommended model
2379
+ catch (e) { if (typeof setStatus === 'function') setStatus('err', '✗ ' + (e && (e.code || e.message))); }
2380
+ renderActionsModePanel(panel);
2381
+ }));
2382
+ const agentOff = panel.querySelector('[data-agent-off]');
2383
+ if (agentOff) agentOff.addEventListener('click', () => { runtimeSetActiveAgent(null); renderActionsModePanel(panel); });
2384
+ panel.querySelectorAll('[data-agent-advon]').forEach(btn => btn.addEventListener('click', () => {
2385
+ try { runtimeAddAdvisor(btn.getAttribute('data-agent-advon')); } // I-E: layer an advisory lens
2386
+ catch (e) { if (typeof setStatus === 'function') setStatus('err', '✗ ' + (e && (e.code || e.message))); }
2387
+ renderActionsModePanel(panel);
2388
+ }));
2389
+ panel.querySelectorAll('[data-agent-advoff]').forEach(btn => btn.addEventListener('click', () => { runtimeRemoveAdvisor(btn.getAttribute('data-agent-advoff')); renderActionsModePanel(panel); }));
2355
2390
  }
2356
2391
 
2357
2392
  // ─── Agent (rwa-edit/1) ─────────────────────────────────────────────
@@ -5593,11 +5628,26 @@ function getActiveActor() {
5593
5628
  // system_prompt swaps the per-kind FRAMING; the shared SYSTEM_PROMPT_RULES (tool protocol, frozen-
5594
5629
  // zone rules, data-rwa-id) are always appended so editing still works. No agent → the singleton.
5595
5630
  function resolveSystemPrompt() {
5631
+ let base = SYSTEM_PROMPT;
5596
5632
  if (activeAgentRole) {
5597
5633
  const rec = Array.from(installedAgents.values()).find(a => a.role === activeAgentRole && a.verified);
5598
- if (rec && typeof rec.manifest.system_prompt === 'string') return rec.manifest.system_prompt + '\n' + SYSTEM_PROMPT_RULES;
5634
+ if (rec && typeof rec.manifest.system_prompt === 'string') base = rec.manifest.system_prompt + '\n' + SYSTEM_PROMPT_RULES;
5635
+ }
5636
+ return base + _agAdvisorBlock(); // I-E: append any advisory lenses (secondary; empty → byte-identical)
5637
+ }
5638
+ // I-E (intelligence/0.2 §6) — blended overlays. The PRIMARY is activeAgentRole (framing/actor/vault,
5639
+ // unchanged). advisorRoles add advisory PROSE only — never capabilities, so the vault gate
5640
+ // (_agVaultAllowed, keyed on the active record) stays primary-only by construction. Verified-only,
5641
+ // ephemeral (never serialized), capped. Block is omitted when empty (single-role prompt unchanged).
5642
+ function _agAdvisorBlock() {
5643
+ if (!advisorRoles.size) return '';
5644
+ const lines = [];
5645
+ for (const role of advisorRoles) {
5646
+ if (role === activeAgentRole) continue; // a role is primary XOR advisor, never both
5647
+ const rec = Array.from(installedAgents.values()).find(a => a.role === role && a.verified);
5648
+ if (rec && typeof rec.manifest.system_prompt === 'string') lines.push('- ' + role + ': ' + rec.manifest.system_prompt);
5599
5649
  }
5600
- return SYSTEM_PROMPT;
5650
+ return lines.length ? '\n\nAdditional advisory lenses (secondary — apply only where they don\'t conflict with the above):\n' + lines.join('\n') : '';
5601
5651
  }
5602
5652
  // I12 — the role-scoped vault gate: exact vault:<ns> membership in the agent's vault_namespace_set
5603
5653
  // (mirrors _skVaultAllowed against the agent record instead of the skill's permissions).
@@ -6812,6 +6862,10 @@ const installedSkills = new Map();
6812
6862
  // starts with no agent active per §12).
6813
6863
  const installedAgents = new Map();
6814
6864
  let activeAgentRole = null;
6865
+ // I-E — advisory roles layered atop the primary (in-memory, ephemeral, never serialized — like the
6866
+ // active role). Capped for prompt coherence/size. Advisors contribute prose only (see _agAdvisorBlock).
6867
+ const advisorRoles = new Set();
6868
+ const ADVISOR_CAP = 3;
6815
6869
  // I8 (v0.9 §9) — hook firing state. activeHooks holds skillIds currently executing (re-entrancy
6816
6870
  // guard, Inv 23); _lastHookMode tracks the prior mode for on-mode-change's `previous`; _hookSeq
6817
6871
  // makes rwa_hook_log keys unique within a millisecond.
@@ -7806,6 +7860,19 @@ function runtimeSetActiveAgent(role) {
7806
7860
  if (!rec.verified) throw new RwaEditError('unverified_agent', null, { role });
7807
7861
  activeAgentRole = role;
7808
7862
  }
7863
+ // I-E — add/remove an advisory role. Verified-only (its prompt drives modify), not-the-primary,
7864
+ // capped. Checks order so unverified/not-found beat the cap. Returns the current advisor list.
7865
+ function runtimeAddAdvisor(role) {
7866
+ const rec = Array.from(installedAgents.values()).find(a => a.role === role);
7867
+ if (!rec) throw new RwaEditError('agent_not_found', null, { role });
7868
+ if (!rec.verified) throw new RwaEditError('unverified_agent', null, { role });
7869
+ if (role === activeAgentRole || advisorRoles.has(role)) return Array.from(advisorRoles); // primary / already → no-op
7870
+ if (advisorRoles.size >= ADVISOR_CAP) throw new RwaEditError('advisor_cap_reached', null, { cap: ADVISOR_CAP });
7871
+ advisorRoles.add(role);
7872
+ return Array.from(advisorRoles);
7873
+ }
7874
+ function runtimeRemoveAdvisor(role) { advisorRoles.delete(role); return Array.from(advisorRoles); }
7875
+ function runtimeListAdvisors() { return Array.from(advisorRoles); }
7809
7876
  // §12 — validate + verify + register an agent, then persist the #rwa-agents zone (same discipline as
7810
7877
  // runtimeInstallSkill: rollback the in-memory map on a persist failure). A signed-but-unverified
7811
7878
  // agent registers (verified:false) but can't activate.
@@ -7831,6 +7898,7 @@ async function runtimeUninstallAgent(agentId) {
7831
7898
  if (!prev) return { ok: false, errors: ['not_installed'] };
7832
7899
  installedAgents.delete(agentId);
7833
7900
  if (activeAgentRole && prev.role === activeAgentRole && !Array.from(installedAgents.values()).some(a => a.role === activeAgentRole)) activeAgentRole = null;
7901
+ if (prev.role && !Array.from(installedAgents.values()).some(a => a.role === prev.role)) advisorRoles.delete(prev.role); // I-E: drop a removed role from advisors
7834
7902
  try {
7835
7903
  await runtimeRegionCommit({ regions: [_agAgentsRegion()], actor: 'agent:uninstall', reachability: 'frozen' });
7836
7904
  } catch (e) {
@@ -8083,6 +8151,67 @@ window.__rwaClassifyInstallText = classifyInstallText;
8083
8151
  window.__rwaInstallFromText = routeInstallFromText;
8084
8152
  window.__rwaHandleCarrierDrop = handleCarrierDrop;
8085
8153
 
8154
+ // ── intelligence/0.2 I-A (docs/specs/rwa-intelligence-spec.md §6) — recommended model on activation.
8155
+ // A carrier may carry a NON-SECRET recommended_model / recommended_backend on its rwa-agent/1
8156
+ // ENVELOPE (OUTSIDE the signed `agent`, so canonicalAgent is unchanged and the signature still
8157
+ // verifies — this stays seed-only). On activation the runtime OFFERS to apply it to sessionStorage
8158
+ // behind a one-line consent: it never auto-applies, never sets a base-URL, never touches the API
8159
+ // key. A recommendation is a suggestion, not a stored credential (key/model are sessionStorage-only).
8160
+ const REC_MODEL_RE = /^[A-Za-z0-9._:\/-]{1,200}$/; // a model id: provider/name, dots, colon (ollama), hyphen
8161
+ const REC_BACKENDS = ['openrouter', 'ollama', 'lmstudio', 'atomic', 'bridge', 'bridge-session'];
8162
+ function getRecommendation(envelope) {
8163
+ const e = envelope || {};
8164
+ const out = {};
8165
+ if (typeof e.recommended_model === 'string' && REC_MODEL_RE.test(e.recommended_model.trim())) out.model = e.recommended_model.trim();
8166
+ if (typeof e.recommended_backend === 'string' && REC_BACKENDS.includes(e.recommended_backend.trim())) out.backend = e.recommended_backend.trim();
8167
+ return (out.model || out.backend) ? out : null;
8168
+ }
8169
+ function applyRecommendation(rec) {
8170
+ const r = rec || {};
8171
+ const applied = {};
8172
+ if (r.model && REC_MODEL_RE.test(String(r.model))) { sessionStorage.setItem(RWA.K_MODEL, String(r.model)); applied.model = String(r.model); }
8173
+ if (r.backend && REC_BACKENDS.includes(String(r.backend))) { sessionStorage.setItem(RWA.K_BACKEND, String(r.backend)); applied.backend = String(r.backend); }
8174
+ 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 (_) {}
8175
+ return applied;
8176
+ }
8177
+ // Offer the role's recommended model, if any and if it differs from the current session. Fire-and-
8178
+ // forget consent dialog; resolves with the choice. Activation already happened — this only proposes.
8179
+ function offerRecommendedModel(role) {
8180
+ const recRec = Array.from(installedAgents.values()).find(a => a.role === role);
8181
+ const rec = recRec && getRecommendation(recRec.envelope);
8182
+ if (!rec) return Promise.resolve({ offered: false });
8183
+ const curModel = sessionStorage.getItem(RWA.K_MODEL) || RWA.MODEL;
8184
+ const curBackend = sessionStorage.getItem(RWA.K_BACKEND) || 'openrouter';
8185
+ if ((!rec.model || rec.model === curModel) && (!rec.backend || rec.backend === curBackend)) return Promise.resolve({ offered: false });
8186
+ return new Promise(resolve => {
8187
+ const prev = document.getElementById('rwa-model-offer'); if (prev) prev.remove();
8188
+ const overlay = document.createElement('div'); overlay.id = 'rwa-model-offer';
8189
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;';
8190
+ const card = document.createElement('div');
8191
+ 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);';
8192
+ const what = [rec.model ? 'model <code>' + _skEsc(rec.model) + '</code>' : '', rec.backend ? 'backend <code>' + _skEsc(rec.backend) + '</code>' : ''].filter(Boolean).join(' on ');
8193
+ card.innerHTML =
8194
+ '<h2 style="margin:0 0 .4em;font-size:1.15rem">Use this intelligence’s recommended ' + (rec.model ? 'model' : 'backend') + '?</h2>' +
8195
+ '<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>' +
8196
+ '<div style="display:flex;gap:10px;margin-top:1.2em;justify-content:flex-end">' +
8197
+ '<button data-act="keep" style="padding:9px 16px;border:1px solid #ccc;border-radius:10px;background:#fff;cursor:pointer">Keep current</button>' +
8198
+ '<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>' +
8199
+ '</div>';
8200
+ overlay.appendChild(card); document.body.appendChild(overlay);
8201
+ const close = (choice) => { overlay.remove(); resolve(choice); };
8202
+ card.querySelector('[data-act=keep]').onclick = () => close({ offered: true, applied: null });
8203
+ card.querySelector('[data-act=apply]').onclick = () => close({ offered: true, applied: applyRecommendation(rec) });
8204
+ });
8205
+ }
8206
+ // Activate a role AND offer its recommended model — the "on activation" entry the UI calls.
8207
+ function runtimeActivateAgent(role) { runtimeSetActiveAgent(role); return offerRecommendedModel(role); }
8208
+ window.__rwaGetRecommendation = getRecommendation;
8209
+ window.__rwaApplyRecommendation = applyRecommendation;
8210
+ window.__rwaOfferRecommendedModel = offerRecommendedModel;
8211
+ window.__rwaActivateAgent = runtimeActivateAgent;
8212
+ window.__rwaResolveSystemPrompt = resolveSystemPrompt; // I-E test hook — the assembled modify() prompt
8213
+ window.__rwaGetActiveActor = getActiveActor;
8214
+
8086
8215
  // §4/§5a — does a network: host pattern admit a host? Mirror of cli/src/skill-manifest.mjs
8087
8216
  // matchNetworkOrigin (keep in step). The bridge's per-call origin check.
8088
8217
  function _skMatchNetworkOrigin(pattern, host) {
@@ -9615,7 +9744,7 @@ document.addEventListener('keydown', e => {
9615
9744
  discoverSkills: runtimeDiscoverSkills, // v0.9 §11 (I6) — GET the marketplace index (opt-in network)
9616
9745
  fetchSkillFromIndex: runtimeFetchSkillFromIndex, // v0.9 §11 (I6) — fetch + client-side-verify an indexed skill
9617
9746
  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
9618
- agents: { list: runtimeListAgents, active: runtimeAgentActive, setActive: runtimeSetActiveAgent, install: runtimeInstallAgent, uninstall: runtimeUninstallAgent, message: runtimeAgentMessage, showInstallDialog: showAgentInstallDialog }, // v0.9 §12 — multi-agent roles
9747
+ agents: { list: runtimeListAgents, active: runtimeAgentActive, setActive: runtimeSetActiveAgent, activate: runtimeActivateAgent, install: runtimeInstallAgent, uninstall: runtimeUninstallAgent, message: runtimeAgentMessage, showInstallDialog: showAgentInstallDialog, offerModel: offerRecommendedModel, addAdvisor: runtimeAddAdvisor, removeAdvisor: runtimeRemoveAdvisor, advisors: runtimeListAdvisors }, // v0.9 §12 — multi-agent roles; `activate` = setActive + offer recommended model (I-A); addAdvisor/removeAdvisor/advisors = blended overlays (I-E)
9619
9748
  hookLog: runtimeHookLog, // v0.9 §9 — the hook audit trail (rwa_hook_log)
9620
9749
  };
9621
9750
  // `status` is a getter so each read returns a fresh snapshot of