@runcore-sh/runcore 0.3.1 → 0.3.2
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/dictionary.json +2 -2
- package/dist/agents/runtime/driver.d.ts.map +1 -1
- package/dist/agents/runtime/driver.js +1 -2
- package/dist/agents/runtime/driver.js.map +1 -1
- package/dist/agents/runtime/manager.d.ts.map +1 -1
- package/dist/agents/runtime/manager.js +1 -0
- package/dist/agents/runtime/manager.js.map +1 -1
- package/dist/agents/runtime/types.d.ts +2 -0
- package/dist/agents/runtime/types.d.ts.map +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +2 -0
- package/dist/auth/middleware.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/llm/ollama.d.ts +5 -0
- package/dist/llm/ollama.d.ts.map +1 -1
- package/dist/llm/ollama.js +22 -1
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/providers/ollama.d.ts +2 -2
- package/dist/llm/providers/ollama.d.ts.map +1 -1
- package/dist/llm/providers/ollama.js +63 -17
- package/dist/llm/providers/ollama.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +85 -29
- package/dist/server.js.map +1 -1
- package/dist/ui-sync.d.ts +34 -0
- package/dist/ui-sync.d.ts.map +1 -0
- package/dist/ui-sync.js +108 -0
- package/dist/ui-sync.js.map +1 -0
- package/package.json +5 -5
- package/public/avatar/Hey-Dash_en_windows_v4_0_0.zip +0 -0
- package/public/avatar/README.md +43 -0
- package/public/avatar/dash_headhshot_v1.png +0 -0
- package/public/avatar/idle.mp4 +0 -0
- package/public/avatar/photo.png +0 -0
- package/public/index.html +114 -64
- package/public/nerve/icon-192.svg +6 -0
- package/public/nerve/icon-512.svg +6 -0
- package/public/nerve/index.html +698 -0
- package/public/nerve/manifest.json +24 -0
- package/public/nerve/sw.js +84 -0
package/dist/server.js
CHANGED
|
@@ -9,12 +9,15 @@ import { serveStatic } from "@hono/node-server/serve-static";
|
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
12
|
-
// Package root
|
|
12
|
+
// Package root — works whether run from CWD or npx
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
15
|
const PKG_ROOT = join(__dirname, "..");
|
|
16
|
+
// UI directory — resolved at startup. Prefers CDN-synced, falls back to bundled.
|
|
17
|
+
let UI_DIR = getUiPublicDir(PKG_ROOT);
|
|
16
18
|
import { writeFileSync } from "node:fs";
|
|
17
19
|
import { initInstanceName, getInstanceName, setInstanceName, getInstanceNameLower, resolveEnv, getAlertEmailFrom } from "./instance.js";
|
|
20
|
+
import { syncUi, getUiPublicDir } from "./ui-sync.js";
|
|
18
21
|
import { readBrainFile, writeBrainFile, appendBrainLine } from "./lib/brain-io.js";
|
|
19
22
|
import { runWithAuditContext } from "./lib/audit.js";
|
|
20
23
|
import { Brain } from "./brain.js";
|
|
@@ -186,6 +189,13 @@ async function getOrCreateChatSession(sessionId, name) {
|
|
|
186
189
|
let cs = chatSessions.get(sessionId);
|
|
187
190
|
if (cs)
|
|
188
191
|
return cs;
|
|
192
|
+
// Single-user system: reuse existing chat session from any prior session ID.
|
|
193
|
+
// This ensures all tabs/devices see the same conversation history.
|
|
194
|
+
if (chatSessions.size > 0) {
|
|
195
|
+
const [existingId, existingCs] = chatSessions.entries().next().value;
|
|
196
|
+
chatSessions.set(sessionId, existingCs);
|
|
197
|
+
return existingCs;
|
|
198
|
+
}
|
|
189
199
|
// Read custom personality instructions (empty string if file doesn't exist)
|
|
190
200
|
let personality = "";
|
|
191
201
|
try {
|
|
@@ -207,18 +217,17 @@ async function getOrCreateChatSession(sessionId, name) {
|
|
|
207
217
|
await ltm.init();
|
|
208
218
|
const brain = new Brain({
|
|
209
219
|
systemPrompt: [
|
|
210
|
-
`
|
|
220
|
+
`IDENTITY:`,
|
|
221
|
+
`- Your name is ${getInstanceName()}.`,
|
|
222
|
+
`- The human you are talking to is named ${name}. When they say "my name" they mean "${name}".`,
|
|
223
|
+
`- You are ${name}'s personal AI agent, running locally on their machine. This conversation is private.`,
|
|
211
224
|
``,
|
|
212
|
-
`
|
|
213
|
-
`- NEVER invent information. You have no knowledge of reports, accounts, schedules, or tasks unless they appear in the context below.`,
|
|
214
|
-
`- If context is provided below, reference ONLY that. If no context is provided, you know nothing yet — and that's okay.`,
|
|
215
|
-
`- This is a new relationship. You and ${name} are just getting to know each other. Be curious. Ask real questions.`,
|
|
225
|
+
`RULES:`,
|
|
216
226
|
`- Be warm, honest, and direct. Have personality. Don't be a corporate assistant.`,
|
|
217
|
-
`- If you don't know something, say so
|
|
218
|
-
`-
|
|
219
|
-
`- NEVER
|
|
220
|
-
|
|
221
|
-
`You are running locally on ${name}'s machine. This conversation is private.`,
|
|
227
|
+
`- If you don't know something, say so. Never invent information.`,
|
|
228
|
+
`- Only reference data that appears in the context below. If nothing is provided, you know nothing yet.`,
|
|
229
|
+
`- NEVER reference board items, tasks, or project work unless they appear verbatim below.`,
|
|
230
|
+
`- NEVER claim you searched the web unless search results appear in your context.`,
|
|
222
231
|
...(personality ? [``, `--- Custom personality ---`, personality, `--- End custom personality ---`] : []),
|
|
223
232
|
isSearchAvailable()
|
|
224
233
|
? `You have web search capability. When search results appear in your context, use them to answer. You don't control when searches happen — the system handles that automatically.`
|
|
@@ -354,8 +363,8 @@ app.onError((err, c) => {
|
|
|
354
363
|
log.error("Unhandled route error", { error: msg, stack, path: c.req.path, method: c.req.method });
|
|
355
364
|
return c.json({ error: msg }, 500);
|
|
356
365
|
});
|
|
357
|
-
// Serve static files from
|
|
358
|
-
app.use("/public/*", serveStatic({ root:
|
|
366
|
+
// Serve static files from UI directory (CDN-synced or bundled fallback)
|
|
367
|
+
app.use("/public/*", serveStatic({ root: join(UI_DIR, ".."), rewriteRequestPath: (p) => p }));
|
|
359
368
|
// --- HTML template cache (replaces {{INSTANCE_NAME}} with configured name) ---
|
|
360
369
|
const htmlCache = new Map();
|
|
361
370
|
async function serveHtmlTemplate(filePath) {
|
|
@@ -373,12 +382,12 @@ async function serveHtmlTemplate(filePath) {
|
|
|
373
382
|
app.use("/api/*", requireSession());
|
|
374
383
|
// Serve index.html at root
|
|
375
384
|
app.get("/", async (c) => {
|
|
376
|
-
const html = await serveHtmlTemplate(join(
|
|
385
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "index.html"));
|
|
377
386
|
return c.html(html);
|
|
378
387
|
});
|
|
379
388
|
// --- Nerve endpoint (PWA) ---
|
|
380
389
|
app.get("/nerve", async (c) => {
|
|
381
|
-
const html = await serveHtmlTemplate(join(
|
|
390
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "nerve", "index.html"));
|
|
382
391
|
return c.html(html);
|
|
383
392
|
});
|
|
384
393
|
// --- Audit context middleware ---
|
|
@@ -1267,6 +1276,25 @@ app.put("/api/prompt", async (c) => {
|
|
|
1267
1276
|
await writeBrainFile(PERSONALITY_PATH, prompt ?? "");
|
|
1268
1277
|
return c.json({ ok: true });
|
|
1269
1278
|
});
|
|
1279
|
+
// --- Model discovery ---
|
|
1280
|
+
app.get("/api/models", async (c) => {
|
|
1281
|
+
const ollamaUrl = process.env.OLLAMA_URL ?? "http://localhost:11434";
|
|
1282
|
+
try {
|
|
1283
|
+
const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
|
1284
|
+
if (!res.ok)
|
|
1285
|
+
return c.json({ models: [], error: "Ollama not responding" });
|
|
1286
|
+
const data = await res.json();
|
|
1287
|
+
const models = (data.models ?? []).map((m) => ({
|
|
1288
|
+
name: m.name,
|
|
1289
|
+
size: m.size,
|
|
1290
|
+
modified: m.modified_at,
|
|
1291
|
+
}));
|
|
1292
|
+
return c.json({ models });
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
return c.json({ models: [], error: "Ollama not reachable" });
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1270
1298
|
// --- Settings routes ---
|
|
1271
1299
|
app.get("/api/settings", async (c) => {
|
|
1272
1300
|
const settings = getSettings();
|
|
@@ -1391,7 +1419,7 @@ app.get("/api/avatar/video/:hash", async (c) => {
|
|
|
1391
1419
|
if (!/^[a-f0-9]+\.mp4$/.test(hash)) {
|
|
1392
1420
|
return c.json({ error: "Invalid hash" }, 400);
|
|
1393
1421
|
}
|
|
1394
|
-
const filePath = join(
|
|
1422
|
+
const filePath = join(UI_DIR, "avatar", "cache", hash);
|
|
1395
1423
|
try {
|
|
1396
1424
|
const mp4 = await readFile(filePath);
|
|
1397
1425
|
return new Response(mp4, {
|
|
@@ -1421,7 +1449,7 @@ app.post("/api/avatar/photo", async (c) => {
|
|
|
1421
1449
|
return c.json({ error: "Photo body required" }, 400);
|
|
1422
1450
|
const avatarConfig = getAvatarConfig();
|
|
1423
1451
|
const photoPath = join(process.cwd(), avatarConfig.photoPath);
|
|
1424
|
-
await mkdir(join(
|
|
1452
|
+
await mkdir(join(UI_DIR, "avatar"), { recursive: true });
|
|
1425
1453
|
await writeFile(photoPath, Buffer.from(body));
|
|
1426
1454
|
const ok = await preparePhoto(photoPath);
|
|
1427
1455
|
if (ok) {
|
|
@@ -1480,6 +1508,22 @@ app.get("/api/history", async (c) => {
|
|
|
1480
1508
|
.map((m) => ({ role: m.role, content: m.content }));
|
|
1481
1509
|
return c.json({ messages });
|
|
1482
1510
|
});
|
|
1511
|
+
// Persist intro message so it appears in all tabs/devices
|
|
1512
|
+
app.post("/api/history/intro", async (c) => {
|
|
1513
|
+
const body = await c.req.json();
|
|
1514
|
+
const { sessionId, message } = body;
|
|
1515
|
+
if (!sessionId || !message)
|
|
1516
|
+
return c.json({ error: "sessionId and message required" }, 400);
|
|
1517
|
+
const session = validateSession(sessionId);
|
|
1518
|
+
if (!session)
|
|
1519
|
+
return c.json({ error: "Invalid session" }, 401);
|
|
1520
|
+
const cs = await getOrCreateChatSession(sessionId, session.name);
|
|
1521
|
+
// Only add if history is empty (first run)
|
|
1522
|
+
if (cs.history.length === 0) {
|
|
1523
|
+
cs.history.push({ role: "assistant", content: message });
|
|
1524
|
+
}
|
|
1525
|
+
return c.json({ ok: true });
|
|
1526
|
+
});
|
|
1483
1527
|
// Activity log: poll for background actions
|
|
1484
1528
|
app.get("/api/activity", async (c) => {
|
|
1485
1529
|
const sessionId = c.req.query("sessionId");
|
|
@@ -1553,7 +1597,7 @@ app.post("/api/branch", async (c) => {
|
|
|
1553
1597
|
data: JSON.stringify({
|
|
1554
1598
|
meta: {
|
|
1555
1599
|
provider: activeProvider,
|
|
1556
|
-
model: activeChatModel ?? (activeProvider === "ollama" ? "llama3.
|
|
1600
|
+
model: activeChatModel ?? (activeProvider === "ollama" ? "llama3.2:3b" : "claude-sonnet-4"),
|
|
1557
1601
|
traceId: branchTraceId,
|
|
1558
1602
|
backref: primaryBackref,
|
|
1559
1603
|
},
|
|
@@ -3233,7 +3277,7 @@ app.get("/api/cache", (c) => {
|
|
|
3233
3277
|
});
|
|
3234
3278
|
// --- Help page routes (no auth — knowledge exchange for other AIs/humans) ---
|
|
3235
3279
|
app.get("/help", async (c) => {
|
|
3236
|
-
const html = await serveHtmlTemplate(join(
|
|
3280
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "help.html"));
|
|
3237
3281
|
return c.html(html);
|
|
3238
3282
|
});
|
|
3239
3283
|
app.get("/api/help/context", async (c) => {
|
|
@@ -3276,33 +3320,33 @@ app.get("/api/help/context", async (c) => {
|
|
|
3276
3320
|
// --- Ops dashboard routes (posture-gated: board level) ---
|
|
3277
3321
|
// Board-level pages — only assembled when user has shown intent for full visibility
|
|
3278
3322
|
app.get("/observatory", requireSurface("pages"), async (c) => {
|
|
3279
|
-
const html = await serveHtmlTemplate(join(
|
|
3323
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "observatory.html"));
|
|
3280
3324
|
return c.html(html);
|
|
3281
3325
|
});
|
|
3282
3326
|
app.get("/ops", requireSurface("pages"), async (c) => {
|
|
3283
|
-
const html = await serveHtmlTemplate(join(
|
|
3327
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "ops.html"));
|
|
3284
3328
|
return c.html(html);
|
|
3285
3329
|
});
|
|
3286
3330
|
app.get("/board", requireSurface("pages"), async (c) => {
|
|
3287
|
-
const html = await serveHtmlTemplate(join(
|
|
3331
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "board.html"));
|
|
3288
3332
|
return c.html(html);
|
|
3289
3333
|
});
|
|
3290
3334
|
app.get("/library", requireSurface("pages"), async (c) => {
|
|
3291
|
-
const html = await serveHtmlTemplate(join(
|
|
3335
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "library.html"));
|
|
3292
3336
|
return c.html(html);
|
|
3293
3337
|
});
|
|
3294
3338
|
app.get("/browser", requireSurface("pages"), async (c) => {
|
|
3295
|
-
const html = await serveHtmlTemplate(join(
|
|
3339
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "browser.html"));
|
|
3296
3340
|
return c.html(html);
|
|
3297
3341
|
});
|
|
3298
3342
|
// Registry is always available — it's the entry point
|
|
3299
3343
|
app.get("/registry", async (c) => {
|
|
3300
|
-
const html = await serveHtmlTemplate(join(
|
|
3344
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "registry.html"));
|
|
3301
3345
|
return c.html(html);
|
|
3302
3346
|
});
|
|
3303
3347
|
// Serve roadmap.html (strategic roadmap & rearview)
|
|
3304
3348
|
app.get("/roadmap", async (c) => {
|
|
3305
|
-
const html = await serveHtmlTemplate(join(
|
|
3349
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "roadmap.html"));
|
|
3306
3350
|
return c.html(html);
|
|
3307
3351
|
});
|
|
3308
3352
|
// Roadmap API — parse brain/operations/roadmap.yaml and return as JSON
|
|
@@ -3364,7 +3408,7 @@ app.get("/api/roadmap/recent", async (c) => {
|
|
|
3364
3408
|
// Serve personal.html (placeholder — instances populate this)
|
|
3365
3409
|
app.get("/personal", async (c) => {
|
|
3366
3410
|
try {
|
|
3367
|
-
const html = await serveHtmlTemplate(join(
|
|
3411
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "personal.html"));
|
|
3368
3412
|
return c.html(html);
|
|
3369
3413
|
}
|
|
3370
3414
|
catch {
|
|
@@ -3374,7 +3418,7 @@ app.get("/personal", async (c) => {
|
|
|
3374
3418
|
// Serve life.html (placeholder — instances populate this)
|
|
3375
3419
|
app.get("/life", async (c) => {
|
|
3376
3420
|
try {
|
|
3377
|
-
const html = await serveHtmlTemplate(join(
|
|
3421
|
+
const html = await serveHtmlTemplate(join(UI_DIR, "life.html"));
|
|
3378
3422
|
return c.html(html);
|
|
3379
3423
|
}
|
|
3380
3424
|
catch {
|
|
@@ -4545,7 +4589,7 @@ app.post("/api/chat", async (c) => {
|
|
|
4545
4589
|
const reqSignal = c.req.raw.signal;
|
|
4546
4590
|
return streamSSE(c, async (stream) => {
|
|
4547
4591
|
// Send metadata first so UI can show which model is responding
|
|
4548
|
-
await stream.writeSSE({ data: JSON.stringify({ meta: { provider: activeProvider, model: activeChatModel ?? (activeProvider === "ollama" ? "llama3.
|
|
4592
|
+
await stream.writeSSE({ data: JSON.stringify({ meta: { provider: activeProvider, model: activeChatModel ?? (activeProvider === "ollama" ? "llama3.2:3b" : "claude-sonnet-4") } }) });
|
|
4549
4593
|
let fullResponse = "";
|
|
4550
4594
|
const savePartial = () => {
|
|
4551
4595
|
if (fullResponse) {
|
|
@@ -5294,9 +5338,21 @@ async function start(opts) {
|
|
|
5294
5338
|
const taskCount = await queueProvider.getStore().count();
|
|
5295
5339
|
log.info(`Board: ${board.name} (local, ${taskCount} tasks)`);
|
|
5296
5340
|
}
|
|
5341
|
+
// Sync UI from CDN (non-blocking — falls back to bundled if offline)
|
|
5342
|
+
syncUi().then(({ source, revision }) => {
|
|
5343
|
+
if (source === "cdn") {
|
|
5344
|
+
UI_DIR = getUiPublicDir(PKG_ROOT);
|
|
5345
|
+
log.info(`UI synced from CDN: revision ${revision}`);
|
|
5346
|
+
}
|
|
5347
|
+
else {
|
|
5348
|
+
log.info(`UI source: ${source}${revision ? ` (revision ${revision})` : ""}`);
|
|
5349
|
+
}
|
|
5350
|
+
}).catch(() => { });
|
|
5297
5351
|
// Generate startup token for zero-friction local auth
|
|
5298
5352
|
const { randomBytes: rng } = await import("node:crypto");
|
|
5299
5353
|
startupToken = rng(32).toString("hex");
|
|
5354
|
+
// Warm up local model in background (non-blocking)
|
|
5355
|
+
import("./llm/ollama.js").then(({ warmupOllama }) => warmupOllama()).catch(() => { });
|
|
5300
5356
|
if (code) {
|
|
5301
5357
|
log.info(`First launch detected. Pairing code: ${code}`);
|
|
5302
5358
|
}
|