skillscript-runtime 0.2.4
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/ARCHITECTURE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/audit.d.ts +33 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +76 -0
- package/dist/audit.js.map +1 -0
- package/dist/bootstrap.d.ts +69 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +117 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +805 -0
- package/dist/cli.js.map +1 -0
- package/dist/compile.d.ts +88 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +544 -0
- package/dist/compile.js.map +1 -0
- package/dist/connectors/agent-noop.d.ts +23 -0
- package/dist/connectors/agent-noop.d.ts.map +1 -0
- package/dist/connectors/agent-noop.js +43 -0
- package/dist/connectors/agent-noop.js.map +1 -0
- package/dist/connectors/agent.d.ts +54 -0
- package/dist/connectors/agent.d.ts.map +1 -0
- package/dist/connectors/agent.js +21 -0
- package/dist/connectors/agent.js.map +1 -0
- package/dist/connectors/index.d.ts +13 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +17 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/local-model.d.ts +41 -0
- package/dist/connectors/local-model.d.ts.map +1 -0
- package/dist/connectors/local-model.js +106 -0
- package/dist/connectors/local-model.js.map +1 -0
- package/dist/connectors/mcp.d.ts +22 -0
- package/dist/connectors/mcp.d.ts.map +1 -0
- package/dist/connectors/mcp.js +31 -0
- package/dist/connectors/mcp.js.map +1 -0
- package/dist/connectors/memory-store.d.ts +53 -0
- package/dist/connectors/memory-store.d.ts.map +1 -0
- package/dist/connectors/memory-store.js +169 -0
- package/dist/connectors/memory-store.js.map +1 -0
- package/dist/connectors/registry.d.ts +74 -0
- package/dist/connectors/registry.d.ts.map +1 -0
- package/dist/connectors/registry.js +127 -0
- package/dist/connectors/registry.js.map +1 -0
- package/dist/connectors/skill-store.d.ts +38 -0
- package/dist/connectors/skill-store.d.ts.map +1 -0
- package/dist/connectors/skill-store.js +314 -0
- package/dist/connectors/skill-store.js.map +1 -0
- package/dist/connectors/types.d.ts +188 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +35 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/dashboard/server.d.ts +40 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +122 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/dashboard/spa/app.js +375 -0
- package/dist/dashboard/spa/index.html +26 -0
- package/dist/dashboard/spa/styles.css +99 -0
- package/dist/errors.d.ts +111 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +187 -0
- package/dist/errors.js.map +1 -0
- package/dist/filters.d.ts +17 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +40 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.d.ts +97 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +990 -0
- package/dist/lint.js.map +1 -0
- package/dist/mcp-server.d.ts +93 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +505 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/metrics.d.ts +51 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +107 -0
- package/dist/metrics.js.map +1 -0
- package/dist/parser.d.ts +160 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +991 -0
- package/dist/parser.js.map +1 -0
- package/dist/provenance.d.ts +43 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +58 -0
- package/dist/provenance.js.map +1 -0
- package/dist/runtime.d.ts +145 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1071 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scheduler.d.ts +121 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +271 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/skill-manager.d.ts +121 -0
- package/dist/skill-manager.d.ts.map +1 -0
- package/dist/skill-manager.js +251 -0
- package/dist/skill-manager.js.map +1 -0
- package/dist/testing/conformance.d.ts +57 -0
- package/dist/testing/conformance.d.ts.map +1 -0
- package/dist/testing/conformance.js +365 -0
- package/dist/testing/conformance.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/trace.d.ts +141 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +226 -0
- package/dist/trace.js.map +1 -0
- package/examples/README.md +56 -0
- package/examples/classify-support-ticket.skill.md +30 -0
- package/examples/cut-release-tag.skill.md +40 -0
- package/examples/doc-qa-with-citations.skill.md +12 -0
- package/examples/feedback-sentiment-scan.skill.md +29 -0
- package/examples/hello.skill.md +9 -0
- package/examples/hello.skill.provenance.json +10 -0
- package/examples/morning-brief.skill.md +24 -0
- package/examples/programmatic-trace-demo.mjs +89 -0
- package/examples/service-health-watch.skill.md +18 -0
- package/package.json +100 -0
- package/scaffold/config.toml +35 -0
- package/scaffold/connectors.json +19 -0
- package/scaffold/examples/hello.skill.md +9 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// Dashboard SPA — vanilla JS + template literals. No framework, no bundler.
|
|
2
|
+
// Talks to the runtime's MCP server via POST /rpc.
|
|
3
|
+
|
|
4
|
+
const POLL_INTERVAL_MS = 30_000;
|
|
5
|
+
const RPC_ENDPOINT = "/rpc";
|
|
6
|
+
|
|
7
|
+
let nextRpcId = 1;
|
|
8
|
+
let pollTimer = null;
|
|
9
|
+
let currentView = null;
|
|
10
|
+
const state = {
|
|
11
|
+
skills: [],
|
|
12
|
+
triggers: [],
|
|
13
|
+
metrics: null,
|
|
14
|
+
lastUpdate: null,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ─── RPC client ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
async function rpc(method, params) {
|
|
20
|
+
const body = { jsonrpc: "2.0", id: nextRpcId++, method, ...(params ? { params } : {}) };
|
|
21
|
+
const res = await fetch(RPC_ENDPOINT, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "content-type": "application/json" },
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
});
|
|
26
|
+
const json = await res.json();
|
|
27
|
+
if (json.error) throw new Error(`${json.error.code}: ${json.error.message}`);
|
|
28
|
+
return json.result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function callTool(name, args) {
|
|
32
|
+
const result = await rpc("tools/call", { name, arguments: args ?? {} });
|
|
33
|
+
// tools/call returns { content: [{ type: "text", text: "..." }] }
|
|
34
|
+
if (!result?.content?.[0]?.text) throw new Error("Tool returned no content");
|
|
35
|
+
return JSON.parse(result.content[0].text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── State refresh (polling) ────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
async function refresh() {
|
|
41
|
+
const ts = new Date();
|
|
42
|
+
document.getElementById("poll-status").textContent = `polling…`;
|
|
43
|
+
try {
|
|
44
|
+
const [skills, triggers, metrics] = await Promise.all([
|
|
45
|
+
callTool("skill_list", {}),
|
|
46
|
+
callTool("list_triggers", {}),
|
|
47
|
+
callTool("health_metrics", {}),
|
|
48
|
+
]);
|
|
49
|
+
state.skills = skills;
|
|
50
|
+
state.triggers = triggers;
|
|
51
|
+
state.metrics = metrics;
|
|
52
|
+
state.lastUpdate = ts;
|
|
53
|
+
document.getElementById("poll-status").textContent = `last updated ${ts.toLocaleTimeString()}`;
|
|
54
|
+
renderCurrentView();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
document.getElementById("poll-status").textContent = `poll failed: ${err.message}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function startPolling() {
|
|
61
|
+
refresh();
|
|
62
|
+
pollTimer = setInterval(refresh, POLL_INTERVAL_MS);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Views ──────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function renderOverview() {
|
|
68
|
+
const m = state.metrics;
|
|
69
|
+
const totalFires = m?.totalFires ?? 0;
|
|
70
|
+
const skillCount = state.skills.length;
|
|
71
|
+
const statusCounts = state.skills.reduce((acc, s) => {
|
|
72
|
+
acc[s.status] = (acc[s.status] ?? 0) + 1;
|
|
73
|
+
return acc;
|
|
74
|
+
}, {});
|
|
75
|
+
const triggerCount = state.triggers.length;
|
|
76
|
+
const connectors = m ? Object.entries(m.perConnector) : [];
|
|
77
|
+
|
|
78
|
+
// Compute top errors by class across all skills
|
|
79
|
+
const errorTotals = {};
|
|
80
|
+
if (m) {
|
|
81
|
+
for (const skill of Object.values(m.perSkill)) {
|
|
82
|
+
for (const opKind in skill.errorCategories) {
|
|
83
|
+
for (const cls in skill.errorCategories[opKind]) {
|
|
84
|
+
errorTotals[cls] = (errorTotals[cls] ?? 0) + skill.errorCategories[opKind][cls];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const topErrors = Object.entries(errorTotals).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
90
|
+
|
|
91
|
+
return `
|
|
92
|
+
<h2>Overview</h2>
|
|
93
|
+
<section>
|
|
94
|
+
<div class="kpi-row">
|
|
95
|
+
<div class="kpi"><div class="label">Skills</div><div class="value">${skillCount}</div></div>
|
|
96
|
+
<div class="kpi"><div class="label">Approved</div><div class="value">${statusCounts.Approved ?? 0}</div></div>
|
|
97
|
+
<div class="kpi"><div class="label">Triggers</div><div class="value">${triggerCount}</div></div>
|
|
98
|
+
<div class="kpi"><div class="label">Fires (24h)</div><div class="value">${totalFires}</div></div>
|
|
99
|
+
</div>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<section>
|
|
103
|
+
<h2>Top errors (24h)</h2>
|
|
104
|
+
${topErrors.length === 0
|
|
105
|
+
? `<div class="empty">No errors observed.</div>`
|
|
106
|
+
: `<table><thead><tr><th>Error class</th><th>Count</th></tr></thead><tbody>
|
|
107
|
+
${topErrors.map(([cls, n]) => `<tr><td>${esc(cls)}</td><td>${n}</td></tr>`).join("")}
|
|
108
|
+
</tbody></table>`}
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section>
|
|
112
|
+
<h2>Connector health (24h)</h2>
|
|
113
|
+
${connectors.length === 0
|
|
114
|
+
? `<div class="empty">No connector activity observed.</div>`
|
|
115
|
+
: `<table><thead><tr><th>Connector</th><th>Calls</th><th>Error rate</th><th>p50</th><th>p95</th><th>p99</th></tr></thead><tbody>
|
|
116
|
+
${connectors.map(([name, c]) => {
|
|
117
|
+
const errRate = `${(c.errorRate * 100).toFixed(1)}%`;
|
|
118
|
+
const flag = c.errorRate > 0.05 ? ` <span class="badge error">degraded</span>` : "";
|
|
119
|
+
return `<tr><td>${esc(name)}${flag}</td><td>${c.callCount}</td><td>${errRate}</td><td>${c.latencyMs.p50}ms</td><td>${c.latencyMs.p95}ms</td><td>${c.latencyMs.p99}ms</td></tr>`;
|
|
120
|
+
}).join("")}
|
|
121
|
+
</tbody></table>`}
|
|
122
|
+
</section>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderSkills() {
|
|
127
|
+
if (state.skills.length === 0) {
|
|
128
|
+
return `<h2>Skills</h2><section><div class="empty">No skills in store. Use <code>skillfile init</code> + <code>skillfile run</code> to populate.</div></section>`;
|
|
129
|
+
}
|
|
130
|
+
return `
|
|
131
|
+
<h2>Skills (${state.skills.length})</h2>
|
|
132
|
+
<section>
|
|
133
|
+
<table>
|
|
134
|
+
<thead>
|
|
135
|
+
<tr><th>Name</th><th>Status</th><th>Description</th><th>Version</th></tr>
|
|
136
|
+
</thead>
|
|
137
|
+
<tbody>
|
|
138
|
+
${state.skills.map((s) => `
|
|
139
|
+
<tr onclick="window.location.hash='#skill/${encodeURIComponent(s.name)}'">
|
|
140
|
+
<td><strong>${esc(s.name)}</strong></td>
|
|
141
|
+
<td><span class="badge ${esc(s.status)}">${esc(s.status)}</span></td>
|
|
142
|
+
<td>${esc(s.description ?? "—")}</td>
|
|
143
|
+
<td><code>${esc(s.version?.slice(0, 8) ?? "—")}</code></td>
|
|
144
|
+
</tr>
|
|
145
|
+
`).join("")}
|
|
146
|
+
</tbody>
|
|
147
|
+
</table>
|
|
148
|
+
</section>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function renderSkillDetail(name) {
|
|
153
|
+
try {
|
|
154
|
+
const { metadata, versions, source, recent_fires } = await callTool("skill_metadata", { name });
|
|
155
|
+
const metrics = state.metrics?.perSkill?.[name];
|
|
156
|
+
const triggersForSkill = state.triggers.filter((t) => t.skillName === name);
|
|
157
|
+
return `
|
|
158
|
+
<h2>Skill: ${esc(metadata.name)} <span class="badge ${esc(metadata.status)}">${esc(metadata.status)}</span></h2>
|
|
159
|
+
|
|
160
|
+
<section>
|
|
161
|
+
<h2>Status</h2>
|
|
162
|
+
<p>${esc(metadata.description ?? "(no description)")}</p>
|
|
163
|
+
<div style="margin-top: 12px; display: flex; gap: 8px;">
|
|
164
|
+
${["Draft", "Approved", "Disabled"].filter((s) => s !== metadata.status).map((s) => `
|
|
165
|
+
<button class="${s === "Disabled" ? "danger" : ""}" onclick="updateStatus('${esc(name)}','${s}')">
|
|
166
|
+
Transition to ${s}
|
|
167
|
+
</button>
|
|
168
|
+
`).join("")}
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section>
|
|
173
|
+
<h2>Source</h2>
|
|
174
|
+
${source ? `<pre>${esc(source)}</pre>` : `<div class="empty">Source not available.</div>`}
|
|
175
|
+
</section>
|
|
176
|
+
|
|
177
|
+
<section>
|
|
178
|
+
<h2>Triggers (${triggersForSkill.length})</h2>
|
|
179
|
+
${triggersForSkill.length === 0
|
|
180
|
+
? `<div class="empty">No triggers registered for this skill.</div>`
|
|
181
|
+
: `<table>
|
|
182
|
+
<thead><tr><th>Source</th><th>Name</th><th>Registered</th></tr></thead>
|
|
183
|
+
<tbody>
|
|
184
|
+
${triggersForSkill.map((t) => `
|
|
185
|
+
<tr>
|
|
186
|
+
<td>${esc(t.source)}</td>
|
|
187
|
+
<td><code>${esc(t.name)}</code></td>
|
|
188
|
+
<td>${new Date(t.registeredAt * 1000).toLocaleString()}</td>
|
|
189
|
+
</tr>
|
|
190
|
+
`).join("")}
|
|
191
|
+
</tbody>
|
|
192
|
+
</table>`}
|
|
193
|
+
</section>
|
|
194
|
+
|
|
195
|
+
<section>
|
|
196
|
+
<h2>Metrics (24h)</h2>
|
|
197
|
+
${metrics
|
|
198
|
+
? `<dl class="kv">
|
|
199
|
+
<dt>Fires</dt><dd>${metrics.fireCount}</dd>
|
|
200
|
+
<dt>Success</dt><dd>${metrics.successCount}</dd>
|
|
201
|
+
<dt>Errors</dt><dd>${metrics.errorCount}</dd>
|
|
202
|
+
<dt>Success rate</dt><dd>${(metrics.successRate * 100).toFixed(1)}%</dd>
|
|
203
|
+
</dl>`
|
|
204
|
+
: `<div class="empty">No traces recorded in window.</div>`}
|
|
205
|
+
</section>
|
|
206
|
+
|
|
207
|
+
<section>
|
|
208
|
+
<h2>Recent fires (${recent_fires.length})</h2>
|
|
209
|
+
${recent_fires.length === 0
|
|
210
|
+
? `<div class="empty">No fires recorded.</div>`
|
|
211
|
+
: recent_fires.map((fire) => {
|
|
212
|
+
const ts = new Date(fire.fired_at_ms).toLocaleString();
|
|
213
|
+
const status = fire.errors.length === 0
|
|
214
|
+
? `<span class="badge ok">ok</span>`
|
|
215
|
+
: `<span class="badge error">err</span>`;
|
|
216
|
+
return `
|
|
217
|
+
<div style="border-bottom: 1px solid #e6e8eb; padding: 12px 0;">
|
|
218
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
219
|
+
${status}
|
|
220
|
+
<code style="font-size: 11px; color: #6c757d;">${esc(fire.trace_id.slice(0, 8))}</code>
|
|
221
|
+
<span>${ts}</span>
|
|
222
|
+
<span style="color: #6c757d; margin-left: auto;">${fire.duration_ms}ms · ${fire.ops.length} ops</span>
|
|
223
|
+
</div>
|
|
224
|
+
${fire.errors.map((e) => `
|
|
225
|
+
<div class="remediation">
|
|
226
|
+
<strong>${esc(e.class)}</strong> in ${esc(e.target)}/${esc(e.opKind)}: ${esc(e.message)}
|
|
227
|
+
${e.remediation ? `<div style="margin-top: 4px; color: #4a5158;">→ ${esc(e.remediation)}</div>` : ""}
|
|
228
|
+
</div>
|
|
229
|
+
`).join("")}
|
|
230
|
+
</div>
|
|
231
|
+
`;
|
|
232
|
+
}).join("")}
|
|
233
|
+
</section>
|
|
234
|
+
|
|
235
|
+
<section>
|
|
236
|
+
<h2>Version history (${versions.length})</h2>
|
|
237
|
+
<table>
|
|
238
|
+
<thead><tr><th>Version</th><th>Status</th><th>Changed at</th></tr></thead>
|
|
239
|
+
<tbody>
|
|
240
|
+
${versions.slice().reverse().map((v) => `
|
|
241
|
+
<tr>
|
|
242
|
+
<td><code>${esc(v.version)}</code></td>
|
|
243
|
+
<td><span class="badge ${esc(v.status)}">${esc(v.status)}</span></td>
|
|
244
|
+
<td>${new Date(v.changed_at * 1000).toLocaleString()}</td>
|
|
245
|
+
</tr>
|
|
246
|
+
`).join("")}
|
|
247
|
+
</tbody>
|
|
248
|
+
</table>
|
|
249
|
+
</section>
|
|
250
|
+
`;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
return `<h2>Skill: ${esc(name)}</h2><section><div class="empty">Failed to load: ${esc(err.message)}</div></section>`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function renderTriggers() {
|
|
257
|
+
return `
|
|
258
|
+
<h2>Triggers</h2>
|
|
259
|
+
|
|
260
|
+
<section>
|
|
261
|
+
${state.triggers.length === 0
|
|
262
|
+
? `<div class="empty">No triggers registered.</div>`
|
|
263
|
+
: `<table>
|
|
264
|
+
<thead><tr><th>Skill</th><th>Source</th><th>Name</th><th>Registered</th><th></th></tr></thead>
|
|
265
|
+
<tbody>
|
|
266
|
+
${state.triggers.map((t) => `
|
|
267
|
+
<tr>
|
|
268
|
+
<td><strong>${esc(t.skillName)}</strong></td>
|
|
269
|
+
<td>${esc(t.source)}</td>
|
|
270
|
+
<td><code>${esc(t.name)}</code></td>
|
|
271
|
+
<td>${new Date(t.registeredAt * 1000).toLocaleString()}</td>
|
|
272
|
+
<td><button class="danger" onclick="unregisterTrigger('${esc(t.id)}')">Unregister</button></td>
|
|
273
|
+
</tr>
|
|
274
|
+
`).join("")}
|
|
275
|
+
</tbody>
|
|
276
|
+
</table>`}
|
|
277
|
+
</section>
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function renderConnectors() {
|
|
282
|
+
const connectors = state.metrics ? Object.entries(state.metrics.perConnector) : [];
|
|
283
|
+
return `
|
|
284
|
+
<h2>Connectors</h2>
|
|
285
|
+
<section>
|
|
286
|
+
${connectors.length === 0
|
|
287
|
+
? `<div class="empty">No connector activity yet. Run a skill that uses <code>$</code>/<code>~</code>/<code>></code> ops.</div>`
|
|
288
|
+
: `<table>
|
|
289
|
+
<thead><tr><th>Connector</th><th>Calls</th><th>Errors</th><th>p50</th><th>p95</th><th>p99</th><th>Last success</th></tr></thead>
|
|
290
|
+
<tbody>
|
|
291
|
+
${connectors.map(([name, c]) => `
|
|
292
|
+
<tr>
|
|
293
|
+
<td><strong>${esc(name)}</strong></td>
|
|
294
|
+
<td>${c.callCount}</td>
|
|
295
|
+
<td>${c.errorCount} (${(c.errorRate * 100).toFixed(1)}%)</td>
|
|
296
|
+
<td>${c.latencyMs.p50}ms</td>
|
|
297
|
+
<td>${c.latencyMs.p95}ms</td>
|
|
298
|
+
<td>${c.latencyMs.p99}ms</td>
|
|
299
|
+
<td>${c.lastSuccess_ms ? new Date(c.lastSuccess_ms).toLocaleString() : "—"}</td>
|
|
300
|
+
</tr>
|
|
301
|
+
`).join("")}
|
|
302
|
+
</tbody>
|
|
303
|
+
</table>`}
|
|
304
|
+
</section>
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ─── Write paths ────────────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
window.updateStatus = async function (name, newState) {
|
|
311
|
+
if (newState === "Disabled" && !confirm(`Disable '${name}'? Its triggers will stop firing.`)) return;
|
|
312
|
+
try {
|
|
313
|
+
await callTool("skill_status", { name, new_state: newState });
|
|
314
|
+
await refresh();
|
|
315
|
+
} catch (err) {
|
|
316
|
+
alert(`Status update failed: ${err.message}`);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Note: register-trigger intentionally NOT exposed in the SPA — creates
|
|
321
|
+
// new autonomous dispatch surface that doesn't appear in the skill source.
|
|
322
|
+
// CLI-only (`skillfile register-trigger`) keeps intent explicit. Unregister
|
|
323
|
+
// stays in the UI; it removes existing surface (safety, not weapon).
|
|
324
|
+
|
|
325
|
+
window.unregisterTrigger = async function (id) {
|
|
326
|
+
if (!confirm("Unregister this trigger?")) return;
|
|
327
|
+
try {
|
|
328
|
+
await callTool("unregister_trigger", { trigger_id: id });
|
|
329
|
+
await refresh();
|
|
330
|
+
} catch (err) {
|
|
331
|
+
alert(`Unregister failed: ${err.message}`);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// ─── Routing ────────────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
async function renderCurrentView() {
|
|
338
|
+
const main = document.getElementById("main");
|
|
339
|
+
const hash = window.location.hash.replace(/^#/, "") || "overview";
|
|
340
|
+
// Update nav active state
|
|
341
|
+
for (const link of document.querySelectorAll("nav a")) {
|
|
342
|
+
link.classList.toggle("active", link.getAttribute("href") === `#${hash}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (hash.startsWith("skill/")) {
|
|
346
|
+
const name = decodeURIComponent(hash.slice("skill/".length));
|
|
347
|
+
currentView = `skill/${name}`;
|
|
348
|
+
main.innerHTML = "Loading…";
|
|
349
|
+
main.innerHTML = await renderSkillDetail(name);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
currentView = hash;
|
|
354
|
+
switch (hash) {
|
|
355
|
+
case "overview": main.innerHTML = renderOverview(); break;
|
|
356
|
+
case "skills": main.innerHTML = renderSkills(); break;
|
|
357
|
+
case "triggers": main.innerHTML = renderTriggers(); break;
|
|
358
|
+
case "connectors": main.innerHTML = renderConnectors(); break;
|
|
359
|
+
default: main.innerHTML = `<section><div class="empty">Unknown view: ${esc(hash)}</div></section>`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
window.addEventListener("hashchange", renderCurrentView);
|
|
364
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
365
|
+
startPolling();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ─── Utils ──────────────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
function esc(s) {
|
|
371
|
+
if (s === undefined || s === null) return "";
|
|
372
|
+
return String(s).replace(/[&<>"']/g, (c) => ({
|
|
373
|
+
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
|
|
374
|
+
})[c]);
|
|
375
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>skillscript-runtime · dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header>
|
|
11
|
+
<h1>skillscript-runtime</h1>
|
|
12
|
+
<nav id="nav">
|
|
13
|
+
<a href="#overview">Overview</a>
|
|
14
|
+
<a href="#skills">Skills</a>
|
|
15
|
+
<a href="#triggers">Triggers</a>
|
|
16
|
+
<a href="#connectors">Connectors</a>
|
|
17
|
+
</nav>
|
|
18
|
+
<div id="poll-status" aria-live="polite"></div>
|
|
19
|
+
</header>
|
|
20
|
+
<main id="main"></main>
|
|
21
|
+
<footer>
|
|
22
|
+
<small>Polling every 30s · localhost-only</small>
|
|
23
|
+
</footer>
|
|
24
|
+
<script src="/app.js" type="module"></script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Dashboard styles — minimal, readable, no framework. */
|
|
2
|
+
|
|
3
|
+
* { box-sizing: border-box; }
|
|
4
|
+
body {
|
|
5
|
+
font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif;
|
|
6
|
+
margin: 0;
|
|
7
|
+
color: #1a1d21;
|
|
8
|
+
background: #f6f7f9;
|
|
9
|
+
}
|
|
10
|
+
header {
|
|
11
|
+
background: #1a1d21;
|
|
12
|
+
color: #f6f7f9;
|
|
13
|
+
padding: 12px 20px;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 24px;
|
|
17
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.12);
|
|
18
|
+
}
|
|
19
|
+
header h1 {
|
|
20
|
+
font-size: 16px;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
nav { display: flex; gap: 16px; }
|
|
25
|
+
nav a {
|
|
26
|
+
color: #b8c0c8;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
padding: 4px 8px;
|
|
30
|
+
border-radius: 4px;
|
|
31
|
+
}
|
|
32
|
+
nav a:hover, nav a.active { color: #f6f7f9; background: #2a2e34; }
|
|
33
|
+
#poll-status {
|
|
34
|
+
margin-left: auto;
|
|
35
|
+
font-size: 12px;
|
|
36
|
+
color: #b8c0c8;
|
|
37
|
+
}
|
|
38
|
+
main { padding: 24px; max-width: 1200px; margin: 0 auto; }
|
|
39
|
+
footer { padding: 16px 24px; color: #6c757d; text-align: center; font-size: 12px; }
|
|
40
|
+
|
|
41
|
+
h2 { font-size: 18px; margin: 0 0 16px; }
|
|
42
|
+
section { background: #fff; border: 1px solid #e6e8eb; border-radius: 6px; padding: 20px; margin-bottom: 16px; }
|
|
43
|
+
|
|
44
|
+
table { width: 100%; border-collapse: collapse; }
|
|
45
|
+
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #e6e8eb; font-size: 13px; }
|
|
46
|
+
th { font-weight: 600; color: #4a5158; background: #fafbfc; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
47
|
+
tbody tr:hover { background: #fafbfc; cursor: pointer; }
|
|
48
|
+
|
|
49
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; }
|
|
50
|
+
.badge.Draft { background: #fff3cd; color: #6c5500; }
|
|
51
|
+
.badge.Approved { background: #d1f0d6; color: #1a5a23; }
|
|
52
|
+
.badge.Disabled { background: #f0d6d1; color: #8b1d1d; }
|
|
53
|
+
.badge.error { background: #f0d6d1; color: #8b1d1d; }
|
|
54
|
+
.badge.ok { background: #d1f0d6; color: #1a5a23; }
|
|
55
|
+
|
|
56
|
+
button {
|
|
57
|
+
font: inherit;
|
|
58
|
+
padding: 6px 12px;
|
|
59
|
+
border: 1px solid #c4cad0;
|
|
60
|
+
background: #fff;
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
}
|
|
64
|
+
button:hover { background: #f6f7f9; }
|
|
65
|
+
button.primary { background: #1a1d21; color: #fff; border-color: #1a1d21; }
|
|
66
|
+
button.danger { background: #b91c1c; color: #fff; border-color: #b91c1c; }
|
|
67
|
+
|
|
68
|
+
.kv { display: grid; grid-template-columns: 200px 1fr; gap: 8px 16px; font-size: 13px; }
|
|
69
|
+
.kv dt { color: #4a5158; font-weight: 500; }
|
|
70
|
+
.kv dd { margin: 0; }
|
|
71
|
+
|
|
72
|
+
.kpi-row { display: flex; gap: 16px; flex-wrap: wrap; }
|
|
73
|
+
.kpi {
|
|
74
|
+
flex: 1 1 180px;
|
|
75
|
+
background: #fafbfc;
|
|
76
|
+
padding: 12px 16px;
|
|
77
|
+
border-radius: 4px;
|
|
78
|
+
border: 1px solid #e6e8eb;
|
|
79
|
+
}
|
|
80
|
+
.kpi .label { font-size: 11px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.04em; }
|
|
81
|
+
.kpi .value { font-size: 24px; font-weight: 600; margin-top: 4px; }
|
|
82
|
+
|
|
83
|
+
pre {
|
|
84
|
+
background: #1a1d21; color: #d4d4d4;
|
|
85
|
+
padding: 12px;
|
|
86
|
+
border-radius: 4px;
|
|
87
|
+
font: 12px/1.4 ui-monospace, SFMono-Regular, monospace;
|
|
88
|
+
overflow-x: auto;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.remediation {
|
|
92
|
+
border-left: 3px solid #b91c1c;
|
|
93
|
+
padding: 8px 12px;
|
|
94
|
+
background: #fff5f5;
|
|
95
|
+
margin: 8px 0;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.empty { color: #6c757d; padding: 24px; text-align: center; font-style: italic; }
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ConnectorType } from "./connectors/types.js";
|
|
2
|
+
export interface LintDiagnostic {
|
|
3
|
+
rule: string;
|
|
4
|
+
message: string;
|
|
5
|
+
block?: string;
|
|
6
|
+
/** Tier-1 violations carry "error"; tier-2 "warning"; tier-3 "info". Defaults to "error" when omitted (legacy shape). */
|
|
7
|
+
severity?: "error" | "warning" | "info";
|
|
8
|
+
/** Canned remediation guidance per rule. */
|
|
9
|
+
remediation?: string;
|
|
10
|
+
/** Rule-specific structured extras. */
|
|
11
|
+
extras?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Base for any error thrown by a connector implementation. Carries the
|
|
15
|
+
* connector kind + implementation name so dispatch consumers can attribute
|
|
16
|
+
* failures precisely.
|
|
17
|
+
*/
|
|
18
|
+
export declare class ConnectorError extends Error {
|
|
19
|
+
readonly connector_type: ConnectorType;
|
|
20
|
+
readonly implementation: string;
|
|
21
|
+
constructor(message: string, connector_type: ConnectorType, implementation: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class SkillNotFoundError extends ConnectorError {
|
|
24
|
+
readonly skill_name: string;
|
|
25
|
+
constructor(skill_name: string, implementation: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class VersionNotFoundError extends ConnectorError {
|
|
28
|
+
readonly skill_name: string;
|
|
29
|
+
readonly version: string;
|
|
30
|
+
constructor(skill_name: string, version: string, implementation: string);
|
|
31
|
+
}
|
|
32
|
+
export declare class LintFailureError extends ConnectorError {
|
|
33
|
+
readonly diagnostics: LintDiagnostic[];
|
|
34
|
+
constructor(diagnostics: LintDiagnostic[], implementation: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class StorageConflictError extends ConnectorError {
|
|
37
|
+
readonly skill_name: string;
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
constructor(skill_name: string, reason: string, implementation: string);
|
|
40
|
+
}
|
|
41
|
+
export declare class QueryError extends ConnectorError {
|
|
42
|
+
readonly mode?: string | undefined;
|
|
43
|
+
constructor(message: string, connector_type: ConnectorType, implementation: string, mode?: string | undefined);
|
|
44
|
+
}
|
|
45
|
+
export declare class DispatchError extends ConnectorError {
|
|
46
|
+
readonly tool?: string | undefined;
|
|
47
|
+
constructor(message: string, implementation: string, tool?: string | undefined);
|
|
48
|
+
}
|
|
49
|
+
export declare class ModelError extends ConnectorError {
|
|
50
|
+
readonly model?: string | undefined;
|
|
51
|
+
constructor(message: string, implementation: string, model?: string | undefined);
|
|
52
|
+
}
|
|
53
|
+
export declare class TimeoutError extends ConnectorError {
|
|
54
|
+
readonly timeout_ms: number;
|
|
55
|
+
constructor(connector_type: ConnectorType, implementation: string, timeout_ms: number);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Base class for any error thrown during op dispatch. Carries the op kind,
|
|
59
|
+
* the target where the op lived, an optional inner cause (preserved when
|
|
60
|
+
* an underlying connector / spawn / etc. error propagates upward), and an
|
|
61
|
+
* actionable remediation string per `a3ba4149`.
|
|
62
|
+
*/
|
|
63
|
+
export declare class OpError extends Error {
|
|
64
|
+
readonly opKind: string;
|
|
65
|
+
readonly remediation: string;
|
|
66
|
+
readonly target?: string | undefined;
|
|
67
|
+
readonly innerCause?: string | undefined;
|
|
68
|
+
constructor(message: string, opKind: string, remediation: string, target?: string | undefined, innerCause?: string | undefined);
|
|
69
|
+
}
|
|
70
|
+
/** A `$` / `~` / `>` op references a connector name not registered with the runtime. */
|
|
71
|
+
export declare class ConnectorNotFoundError extends OpError {
|
|
72
|
+
readonly connectorName: string;
|
|
73
|
+
readonly connectorType: ConnectorType;
|
|
74
|
+
constructor(connectorName: string, connectorType: ConnectorType, opKind: string, target?: string);
|
|
75
|
+
}
|
|
76
|
+
/** An op exceeded its resolved timeout (per-op > skill > built-in). */
|
|
77
|
+
export declare class OpTimeoutError extends OpError {
|
|
78
|
+
readonly timeoutMs: number;
|
|
79
|
+
constructor(timeoutMs: number, opKind: string, target?: string);
|
|
80
|
+
}
|
|
81
|
+
/** A `??` ask-user op fired in autonomous mode (no `askUser` callback wired). */
|
|
82
|
+
export declare class InteractiveOpInAutonomousModeError extends OpError {
|
|
83
|
+
readonly prompt: string;
|
|
84
|
+
constructor(prompt: string, target?: string);
|
|
85
|
+
}
|
|
86
|
+
/** An `@ unsafe` op fired with `runtime.enable_unsafe_shell = false` (default). */
|
|
87
|
+
export declare class UnsafeShellDisabledError extends OpError {
|
|
88
|
+
readonly command: string;
|
|
89
|
+
constructor(command: string, target?: string);
|
|
90
|
+
}
|
|
91
|
+
/** A `$(VAR)` reference couldn't be resolved at runtime. */
|
|
92
|
+
export declare class UnresolvedVariableError extends OpError {
|
|
93
|
+
readonly varRef: string;
|
|
94
|
+
constructor(varRef: string, opKind: string, target?: string);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Structured JSON shape for entries in `result.errors[]`. Surfaces in
|
|
98
|
+
* dispatch trace records, CLI diagnostics, and dashboard error views.
|
|
99
|
+
*/
|
|
100
|
+
export interface OpErrorMetadata {
|
|
101
|
+
class: string;
|
|
102
|
+
opKind: string;
|
|
103
|
+
target: string;
|
|
104
|
+
message: string;
|
|
105
|
+
remediation?: string;
|
|
106
|
+
innerCause?: string;
|
|
107
|
+
connector?: string;
|
|
108
|
+
skill?: string;
|
|
109
|
+
trace_id?: string;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|