@runcore-sh/runcore 0.1.9 → 0.1.10

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.
Files changed (63) hide show
  1. package/dist/cli.js +232 -11
  2. package/dist/cli.js.map +1 -1
  3. package/dist/files/deep-index.d.ts +59 -0
  4. package/dist/files/deep-index.d.ts.map +1 -0
  5. package/dist/files/deep-index.js +337 -0
  6. package/dist/files/deep-index.js.map +1 -0
  7. package/dist/files/import.d.ts +44 -0
  8. package/dist/files/import.d.ts.map +1 -0
  9. package/dist/files/import.js +213 -0
  10. package/dist/files/import.js.map +1 -0
  11. package/dist/files/index-local.d.ts +37 -0
  12. package/dist/files/index-local.d.ts.map +1 -0
  13. package/dist/files/index-local.js +198 -0
  14. package/dist/files/index-local.js.map +1 -0
  15. package/dist/nerve/state.d.ts +1 -1
  16. package/dist/nerve/state.d.ts.map +1 -1
  17. package/dist/nerve/state.js +1 -1
  18. package/dist/nerve/state.js.map +1 -1
  19. package/dist/posture/engine.d.ts +41 -0
  20. package/dist/posture/engine.d.ts.map +1 -0
  21. package/dist/posture/engine.js +217 -0
  22. package/dist/posture/engine.js.map +1 -0
  23. package/dist/posture/index.d.ts +11 -0
  24. package/dist/posture/index.d.ts.map +1 -0
  25. package/dist/posture/index.js +10 -0
  26. package/dist/posture/index.js.map +1 -0
  27. package/dist/posture/middleware.d.ts +30 -0
  28. package/dist/posture/middleware.d.ts.map +1 -0
  29. package/dist/posture/middleware.js +92 -0
  30. package/dist/posture/middleware.js.map +1 -0
  31. package/dist/posture/types.d.ts +61 -0
  32. package/dist/posture/types.d.ts.map +1 -0
  33. package/dist/posture/types.js +48 -0
  34. package/dist/posture/types.js.map +1 -0
  35. package/dist/server.d.ts +3 -1
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +191 -45
  38. package/dist/server.js.map +1 -1
  39. package/dist/tier/bond.d.ts +51 -0
  40. package/dist/tier/bond.d.ts.map +1 -0
  41. package/dist/tier/bond.js +154 -0
  42. package/dist/tier/bond.js.map +1 -0
  43. package/dist/tier/freeze.d.ts +21 -0
  44. package/dist/tier/freeze.d.ts.map +1 -0
  45. package/dist/tier/freeze.js +73 -0
  46. package/dist/tier/freeze.js.map +1 -0
  47. package/dist/tier/gate.d.ts +11 -0
  48. package/dist/tier/gate.d.ts.map +1 -0
  49. package/dist/tier/gate.js +25 -0
  50. package/dist/tier/gate.js.map +1 -0
  51. package/dist/tier/heartbeat.d.ts +22 -0
  52. package/dist/tier/heartbeat.d.ts.map +1 -0
  53. package/dist/tier/heartbeat.js +128 -0
  54. package/dist/tier/heartbeat.js.map +1 -0
  55. package/dist/tier/token.d.ts +22 -0
  56. package/dist/tier/token.d.ts.map +1 -0
  57. package/dist/tier/token.js +100 -0
  58. package/dist/tier/token.js.map +1 -0
  59. package/dist/tier/types.d.ts +44 -0
  60. package/dist/tier/types.d.ts.map +1 -0
  61. package/dist/tier/types.js +61 -0
  62. package/dist/tier/types.js.map +1 -0
  63. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Posture middleware — gates routes by current UI surface level
3
+ * and records interaction signals for intent accumulation.
4
+ *
5
+ * Routes return 404 (not 403) when posture is below threshold.
6
+ * The surface doesn't exist yet — it's not forbidden, it's not assembled.
7
+ */
8
+ import type { MiddlewareHandler } from "hono";
9
+ import type { PostureSurface } from "./types.js";
10
+ /**
11
+ * Middleware: record every API interaction as a signal.
12
+ * Mounted early — runs for all /api/* routes.
13
+ */
14
+ export declare function postureTracker(): MiddlewareHandler;
15
+ /**
16
+ * Middleware: record page views (HTML page loads).
17
+ * Mounted on page routes like /board, /ops, /observatory, etc.
18
+ */
19
+ export declare function pageViewTracker(pageName: string): MiddlewareHandler;
20
+ /**
21
+ * Require a minimum posture level for a route group.
22
+ * Returns 404 if the surface isn't assembled yet.
23
+ */
24
+ export declare function requireSurface(surface: keyof PostureSurface): MiddlewareHandler;
25
+ /**
26
+ * Add posture info to API responses via header.
27
+ * Clients use this to know what to render.
28
+ */
29
+ export declare function postureHeader(): MiddlewareHandler;
30
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/posture/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAsClD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CASnE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,cAAc,GAAG,iBAAiB,CAO/E;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,iBAAiB,CAMjD"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Posture middleware — gates routes by current UI surface level
3
+ * and records interaction signals for intent accumulation.
4
+ *
5
+ * Routes return 404 (not 403) when posture is below threshold.
6
+ * The surface doesn't exist yet — it's not forbidden, it's not assembled.
7
+ */
8
+ import { hasSurface, recordInteraction, getSurface } from "./engine.js";
9
+ /**
10
+ * Middleware: record every API interaction as a signal.
11
+ * Mounted early — runs for all /api/* routes.
12
+ */
13
+ export function postureTracker() {
14
+ return async (c, next) => {
15
+ const path = c.req.path;
16
+ // Classify the interaction
17
+ let surface = "chat";
18
+ let detail;
19
+ if (path.startsWith("/api/nerve") || path.startsWith("/api/pulse")) {
20
+ surface = "pulse";
21
+ }
22
+ else if (path.startsWith("/api/board")) {
23
+ surface = "pages";
24
+ detail = "board";
25
+ }
26
+ else if (path.startsWith("/api/ops")) {
27
+ surface = "pages";
28
+ detail = "ops";
29
+ }
30
+ else if (path.startsWith("/api/agents")) {
31
+ surface = "agents";
32
+ }
33
+ else if (path.startsWith("/api/settings") || path.startsWith("/api/admin")) {
34
+ surface = "settings";
35
+ }
36
+ else if (path.startsWith("/api/open-loops") || path.startsWith("/api/insights") || path.startsWith("/api/metrics")) {
37
+ surface = "pages";
38
+ detail = "observatory";
39
+ }
40
+ else if (path.startsWith("/api/chat")) {
41
+ surface = "chat";
42
+ }
43
+ else if (path.startsWith("/api/browse") || path.startsWith("/api/search")) {
44
+ surface = "pages";
45
+ detail = "browser";
46
+ }
47
+ recordInteraction({
48
+ timestamp: new Date().toISOString(),
49
+ surface,
50
+ detail,
51
+ });
52
+ await next();
53
+ };
54
+ }
55
+ /**
56
+ * Middleware: record page views (HTML page loads).
57
+ * Mounted on page routes like /board, /ops, /observatory, etc.
58
+ */
59
+ export function pageViewTracker(pageName) {
60
+ return async (c, next) => {
61
+ recordInteraction({
62
+ timestamp: new Date().toISOString(),
63
+ surface: "page-view",
64
+ detail: pageName,
65
+ });
66
+ await next();
67
+ };
68
+ }
69
+ /**
70
+ * Require a minimum posture level for a route group.
71
+ * Returns 404 if the surface isn't assembled yet.
72
+ */
73
+ export function requireSurface(surface) {
74
+ return async (c, next) => {
75
+ if (!hasSurface(surface)) {
76
+ return c.json({ error: "not_available", posture: "surface not assembled" }, 404);
77
+ }
78
+ await next();
79
+ };
80
+ }
81
+ /**
82
+ * Add posture info to API responses via header.
83
+ * Clients use this to know what to render.
84
+ */
85
+ export function postureHeader() {
86
+ return async (c, next) => {
87
+ await next();
88
+ const surface = getSurface();
89
+ c.header("X-Posture-Surface", JSON.stringify(surface));
90
+ };
91
+ }
92
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/posture/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Posture — how much UI surface assembles around the user.
3
+ *
4
+ * Three modes:
5
+ * - silent: No UI. Core works in background. Reaches out via chat/SMS/email.
6
+ * - pulse: Three dots only. Tap for drill-down. Zero-to-one interaction.
7
+ * - board: Full visibility. Threads, agents, memory, operations.
8
+ *
9
+ * Posture is orthogonal to tier (capabilities) and bonding (trust).
10
+ * A BYOK user might be silent. A Spawn user might only want pulse.
11
+ * UI is a symptom of unresolved autonomy — buttons exist because
12
+ * Core couldn't handle it alone yet.
13
+ *
14
+ * Posture is observed, not configured. Intent accumulation drives transitions.
15
+ * The system records interaction patterns and adapts surface area.
16
+ */
17
+ export type PostureName = "silent" | "pulse" | "board";
18
+ export declare const POSTURE_LEVEL: Record<PostureName, number>;
19
+ /** What UI surfaces are available at each posture. */
20
+ export interface PostureSurface {
21
+ /** Chat channel (always available — the minimum) */
22
+ chat: boolean;
23
+ /** Three-dot pulse strip */
24
+ pulse: boolean;
25
+ /** Drill-down panels when tapping dots */
26
+ drilldown: boolean;
27
+ /** Full page views: observatory, ops, board, library, etc. */
28
+ pages: boolean;
29
+ /** Agent task management UI */
30
+ agents: boolean;
31
+ /** Settings/configuration UI */
32
+ settings: boolean;
33
+ }
34
+ export declare const POSTURE_SURFACE: Record<PostureName, PostureSurface>;
35
+ /**
36
+ * An interaction signal — recorded every time the user does something.
37
+ * Intent accumulation uses these to decide posture transitions.
38
+ */
39
+ export interface InteractionSignal {
40
+ timestamp: string;
41
+ /** What surface the user touched */
42
+ surface: keyof PostureSurface | "page-view";
43
+ /** Specific detail — page name, dot tapped, etc. */
44
+ detail?: string;
45
+ }
46
+ /**
47
+ * Posture state — persisted in brain/settings.json alongside other settings.
48
+ */
49
+ export interface PostureState {
50
+ /** Current posture */
51
+ current: PostureName;
52
+ /** When the posture last changed */
53
+ changedAt: string;
54
+ /** Whether the user explicitly set this (locks auto-transition) */
55
+ pinned: boolean;
56
+ /** Interaction count per surface in the current window */
57
+ interactions: Record<string, number>;
58
+ /** When the interaction window started */
59
+ windowStart: string;
60
+ }
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/posture/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAIrD,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,IAAI,EAAE,OAAO,CAAC;IACd,4BAA4B;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,CAAC;IACf,+BAA+B;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,CAyB/D,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,MAAM,cAAc,GAAG,WAAW,CAAC;IAC5C,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,OAAO,EAAE,WAAW,CAAC;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,EAAE,OAAO,CAAC;IAChB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Posture — how much UI surface assembles around the user.
3
+ *
4
+ * Three modes:
5
+ * - silent: No UI. Core works in background. Reaches out via chat/SMS/email.
6
+ * - pulse: Three dots only. Tap for drill-down. Zero-to-one interaction.
7
+ * - board: Full visibility. Threads, agents, memory, operations.
8
+ *
9
+ * Posture is orthogonal to tier (capabilities) and bonding (trust).
10
+ * A BYOK user might be silent. A Spawn user might only want pulse.
11
+ * UI is a symptom of unresolved autonomy — buttons exist because
12
+ * Core couldn't handle it alone yet.
13
+ *
14
+ * Posture is observed, not configured. Intent accumulation drives transitions.
15
+ * The system records interaction patterns and adapts surface area.
16
+ */
17
+ export const POSTURE_LEVEL = {
18
+ silent: 0,
19
+ pulse: 1,
20
+ board: 2,
21
+ };
22
+ export const POSTURE_SURFACE = {
23
+ silent: {
24
+ chat: true,
25
+ pulse: false,
26
+ drilldown: false,
27
+ pages: false,
28
+ agents: false,
29
+ settings: false,
30
+ },
31
+ pulse: {
32
+ chat: true,
33
+ pulse: true,
34
+ drilldown: true,
35
+ pages: false,
36
+ agents: false,
37
+ settings: false,
38
+ },
39
+ board: {
40
+ chat: true,
41
+ pulse: true,
42
+ drilldown: true,
43
+ pages: true,
44
+ agents: true,
45
+ settings: true,
46
+ },
47
+ };
48
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/posture/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,CAAC,MAAM,aAAa,GAAgC;IACxD,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;CACT,CAAC;AAkBF,MAAM,CAAC,MAAM,eAAe,GAAwC;IAClE,MAAM,EAAE;QACN,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,KAAK;QACZ,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,KAAK;KAChB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,KAAK;KAChB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;KACf;CACF,CAAC"}
package/dist/server.d.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  * Hono app: serves static UI, handles pairing/auth, streams chat via Ollama (local) or OpenRouter (cloud).
4
4
  */
5
5
  export declare function getStartupToken(): string | null;
6
- declare function start(): Promise<void>;
6
+ declare function start(opts?: {
7
+ tier?: import("./tier/types.js").TierName;
8
+ }): Promise<void>;
7
9
  /** Returns the port the server is actually listening on (resolves port 0). */
8
10
  export declare function getActualPort(): number;
9
11
  export { start };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuTH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AA62JD,iBAAe,KAAK,kBAkgBnB;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuTH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AA2gKD,iBAAe,KAAK,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,iBAAiB,EAAE,QAAQ,CAAA;CAAE,iBAghBxE;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
package/dist/server.js CHANGED
@@ -406,6 +406,11 @@ app.use("/api/*", async (c, next) => {
406
406
  }
407
407
  return generalLimiter(c, next);
408
408
  });
409
+ // --- Posture middleware (intent accumulation + surface gating) ---
410
+ // Track all API interactions for posture engine
411
+ app.use("/api/*", postureTracker());
412
+ // Attach posture surface header to all responses
413
+ app.use("/api/*", postureHeader());
409
414
  // --- Webhook initialization (batch registration + config + admin routes) ---
410
415
  const webhookInitStart = performance.now();
411
416
  // Phase 1: Batch-register all webhook providers (deferred from module imports to avoid
@@ -2438,6 +2443,10 @@ function issuesToCardPayload(issues) {
2438
2443
  };
2439
2444
  });
2440
2445
  }
2446
+ // Board API — gated to board posture
2447
+ app.use("/api/board/*", requireSurface("pages"));
2448
+ app.use("/api/ops/*", requireSurface("pages"));
2449
+ app.use("/api/agents/*", requireSurface("agents"));
2441
2450
  // Board status: is a provider configured, and who is the user?
2442
2451
  app.get("/api/board/status", async (c) => {
2443
2452
  const board = getBoardProvider();
@@ -3244,33 +3253,29 @@ app.get("/api/help/context", async (c) => {
3244
3253
  changelog,
3245
3254
  });
3246
3255
  });
3247
- // --- Ops dashboard routes (no auth — local-only diagnostics) ---
3248
- // Serve observatory.html
3249
- app.get("/observatory", async (c) => {
3256
+ // --- Ops dashboard routes (posture-gated: board level) ---
3257
+ // Board-level pages — only assembled when user has shown intent for full visibility
3258
+ app.get("/observatory", requireSurface("pages"), async (c) => {
3250
3259
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "observatory.html"));
3251
3260
  return c.html(html);
3252
3261
  });
3253
- // Serve ops.html
3254
- app.get("/ops", async (c) => {
3262
+ app.get("/ops", requireSurface("pages"), async (c) => {
3255
3263
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "ops.html"));
3256
3264
  return c.html(html);
3257
3265
  });
3258
- // Serve board.html (kanban view)
3259
- app.get("/board", async (c) => {
3266
+ app.get("/board", requireSurface("pages"), async (c) => {
3260
3267
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "board.html"));
3261
3268
  return c.html(html);
3262
3269
  });
3263
- // Serve library.html (file explorer)
3264
- app.get("/library", async (c) => {
3270
+ app.get("/library", requireSurface("pages"), async (c) => {
3265
3271
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "library.html"));
3266
3272
  return c.html(html);
3267
3273
  });
3268
- // Serve browser.html (agent's-eye view of web pages)
3269
- app.get("/browser", async (c) => {
3274
+ app.get("/browser", requireSurface("pages"), async (c) => {
3270
3275
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "browser.html"));
3271
3276
  return c.html(html);
3272
3277
  });
3273
- // Serve registry.html (service & capability dashboard)
3278
+ // Registry is always available it's the entry point
3274
3279
  app.get("/registry", async (c) => {
3275
3280
  const html = await serveHtmlTemplate(join(PKG_ROOT, "public", "registry.html"));
3276
3281
  return c.html(html);
@@ -3689,6 +3694,30 @@ app.delete("/api/ops/projects/:id", async (c) => {
3689
3694
  return c.json({ error: "Project not found" }, 404);
3690
3695
  return c.json({ ok: true });
3691
3696
  });
3697
+ // --- Posture API (UI surface assembly) ---
3698
+ app.get("/api/posture", (c) => {
3699
+ return c.json({
3700
+ posture: getPosture(),
3701
+ surface: getSurface(),
3702
+ state: getPostureState(),
3703
+ });
3704
+ });
3705
+ app.put("/api/posture", async (c) => {
3706
+ const body = await c.req.json();
3707
+ if (body.posture && ["silent", "pulse", "board"].includes(body.posture)) {
3708
+ if (body.pinned !== false) {
3709
+ pinPosture(body.posture);
3710
+ }
3711
+ else {
3712
+ pinPosture(body.posture);
3713
+ unpinPosture();
3714
+ }
3715
+ }
3716
+ else if (body.pinned === false) {
3717
+ unpinPosture();
3718
+ }
3719
+ return c.json({ posture: getPosture(), surface: getSurface(), state: getPostureState() });
3720
+ });
3692
3721
  // --- Pulse (nervous system) endpoint ---
3693
3722
  app.get("/api/pulse/status", (c) => {
3694
3723
  const integrator = getPressureIntegrator();
@@ -3707,6 +3736,8 @@ app.get("/api/pulse/history", (c) => {
3707
3736
  // --- Nerve API (three-dot goo) ---
3708
3737
  import { getNerveState } from "./nerve/state.js";
3709
3738
  import { initPush, getVapidPublicKey, addSubscription, checkAndNotify, startPushMonitor, stopPushMonitor } from "./nerve/push.js";
3739
+ import { loadPosture, startDecayTimer, getPosture, getPostureState, getSurface, pinPosture, unpinPosture, } from "./posture/engine.js";
3740
+ import { postureTracker, requireSurface, postureHeader } from "./posture/middleware.js";
3710
3741
  // State endpoint — three dots
3711
3742
  app.get("/api/nerve/state", async (c) => {
3712
3743
  const state = await getNerveState();
@@ -4781,8 +4812,111 @@ app.post("/api/chat", async (c) => {
4781
4812
  });
4782
4813
  });
4783
4814
  });
4815
+ // --- Freeze endpoint (operator clicks freeze link) ---
4816
+ app.post("/api/freeze", async (c) => {
4817
+ const body = await c.req.json().catch(() => null);
4818
+ if (!body?.signal)
4819
+ return c.json({ error: "Missing freeze signal" }, 400);
4820
+ const { freeze, isFrozen } = await import("./tier/freeze.js");
4821
+ if (isFrozen())
4822
+ return c.json({ error: "Already frozen" }, 409);
4823
+ await freeze(body.signal, process.cwd());
4824
+ return c.json({ status: "frozen", message: "All agents dormant." });
4825
+ });
4826
+ app.post("/api/thaw", async (c) => {
4827
+ const { thaw } = await import("./tier/freeze.js");
4828
+ await thaw(process.cwd());
4829
+ return c.json({ status: "thawed", message: "Operations resuming." });
4830
+ });
4831
+ app.get("/api/freeze/status", async (c) => {
4832
+ const { isFrozen, getFreezeSignal } = await import("./tier/freeze.js");
4833
+ return c.json({ frozen: isFrozen(), signal: getFreezeSignal() });
4834
+ });
4835
+ // --- Brain import API ---
4836
+ app.post("/api/import/folder", async (c) => {
4837
+ const body = await c.req.json();
4838
+ if (!body.paths || !Array.isArray(body.paths) || body.paths.length === 0) {
4839
+ return c.json({ error: "paths[] required" }, 400);
4840
+ }
4841
+ const { resolve } = await import("node:path");
4842
+ const { importToBrain } = await import("./files/import.js");
4843
+ const result = await importToBrain({
4844
+ sources: body.paths.map(p => resolve(p)),
4845
+ brainRoot: process.cwd(),
4846
+ dryRun: body.dryRun ?? false,
4847
+ });
4848
+ // After import: fast pass → deep pass (both background, chained)
4849
+ if (!body.dryRun && result.imported > 0) {
4850
+ import("./files/index-local.js").then(({ indexImportedFiles }) => {
4851
+ indexImportedFiles({ localOnly: true })
4852
+ .then(() => import("./files/deep-index.js"))
4853
+ .then(({ runDeepIndex }) => runDeepIndex())
4854
+ .catch(() => { });
4855
+ });
4856
+ }
4857
+ return c.json(result);
4858
+ });
4859
+ app.post("/api/import/index", async (c) => {
4860
+ const { indexImportedFiles } = await import("./files/index-local.js");
4861
+ const result = await indexImportedFiles({ localOnly: true });
4862
+ // After fast pass, kick off deep index in background
4863
+ import("./files/deep-index.js").then(({ runDeepIndex }) => {
4864
+ runDeepIndex().catch(() => { });
4865
+ });
4866
+ return c.json(result);
4867
+ });
4868
+ app.post("/api/import/deep-index", async (c) => {
4869
+ const { runDeepIndex } = await import("./files/deep-index.js");
4870
+ const result = await runDeepIndex();
4871
+ return c.json(result);
4872
+ });
4873
+ app.get("/api/import/deep-index/progress", async (c) => {
4874
+ const { getDeepIndexProgress } = await import("./files/deep-index.js");
4875
+ return c.json(getDeepIndexProgress());
4876
+ });
4877
+ app.get("/api/import/deep-index/results", async (c) => {
4878
+ const { readFile: rf } = await import("node:fs/promises");
4879
+ const { join: jp } = await import("node:path");
4880
+ try {
4881
+ const raw = await rf(jp(process.cwd(), "brain", ".core", "deep-index.json"), "utf-8");
4882
+ return c.json(JSON.parse(raw));
4883
+ }
4884
+ catch {
4885
+ return c.json({ entities: [], themes: [], crossRefs: [], flags: [], deepIndexed: [] });
4886
+ }
4887
+ });
4888
+ app.post("/api/import/files", async (c) => {
4889
+ const formData = await c.req.formData();
4890
+ const files = formData.getAll("files");
4891
+ if (files.length === 0)
4892
+ return c.json({ error: "No files provided" }, 400);
4893
+ const { mkdir: mkdirFs, writeFile: writeFs } = await import("node:fs/promises");
4894
+ const { join: joinPath } = await import("node:path");
4895
+ const ingestDir = joinPath(process.cwd(), "ingest");
4896
+ await mkdirFs(ingestDir, { recursive: true });
4897
+ const saved = [];
4898
+ for (const file of files) {
4899
+ if (!file.name)
4900
+ continue;
4901
+ const buffer = Buffer.from(await file.arrayBuffer());
4902
+ const dest = joinPath(ingestDir, file.name);
4903
+ await writeFs(dest, buffer);
4904
+ saved.push(file.name);
4905
+ }
4906
+ // Process the ingest folder immediately
4907
+ const { processIngestFolder } = await import("./files/ingest-folder.js");
4908
+ const ingestedDir = joinPath(process.cwd(), "ingested");
4909
+ const result = await processIngestFolder(ingestDir, ingestedDir);
4910
+ return c.json({
4911
+ saved: saved.length,
4912
+ files: saved,
4913
+ ingested: result.newFiles,
4914
+ });
4915
+ });
4784
4916
  // --- Startup ---
4785
- async function start() {
4917
+ async function start(opts) {
4918
+ const tier = opts?.tier ?? "byok";
4919
+ const tierGate = await import("./tier/gate.js");
4786
4920
  // Initialize instance name before anything else
4787
4921
  initInstanceName();
4788
4922
  // Initialize OpenTelemetry tracing (must be early, before instrumented code)
@@ -4793,6 +4927,9 @@ async function start() {
4793
4927
  });
4794
4928
  // Load settings (airplane mode, model selection)
4795
4929
  const settings = await loadSettings();
4930
+ // Initialize posture system (UI surface assembly)
4931
+ await loadPosture();
4932
+ startDecayTimer();
4796
4933
  // Install fetch guard before any routes or outbound calls
4797
4934
  installFetchGuard();
4798
4935
  // Initialize PrivacyMembrane for reversible redaction
@@ -4963,17 +5100,24 @@ async function start() {
4963
5100
  logActivity({ source: "agent", summary: `Continuation error: ${msg}` });
4964
5101
  }
4965
5102
  });
4966
- // Initialize instance manager (GC, health checks, load balancing)
4967
- instanceManager = new AgentInstanceManager(runtime);
4968
- await instanceManager.init();
4969
- // Initialize agent pool (circuit breakers, isolation, resource management)
4970
- agentPool = AgentPool.fromExisting(runtime, instanceManager);
4971
- setAgentPool(agentPool);
4972
- // Initialize workflow engine for multi-agent coordination
4973
- workflowEngine = new WorkflowEngine(agentPool);
4974
- await workflowEngine.loadAllDefinitions().catch((err) => {
4975
- log.warn("Failed to load workflow definitions", { error: err instanceof Error ? err.message : String(err) });
4976
- });
5103
+ // Initialize agent spawning tier >= spawn only
5104
+ if (tierGate.canSpawn(tier)) {
5105
+ // Initialize instance manager (GC, health checks, load balancing)
5106
+ instanceManager = new AgentInstanceManager(runtime);
5107
+ await instanceManager.init();
5108
+ // Initialize agent pool (circuit breakers, isolation, resource management)
5109
+ agentPool = AgentPool.fromExisting(runtime, instanceManager);
5110
+ setAgentPool(agentPool);
5111
+ // Initialize workflow engine for multi-agent coordination
5112
+ workflowEngine = new WorkflowEngine(agentPool);
5113
+ await workflowEngine.loadAllDefinitions().catch((err) => {
5114
+ log.warn("Failed to load workflow definitions", { error: err instanceof Error ? err.message : String(err) });
5115
+ });
5116
+ log.info(`Agent spawning enabled (tier: ${tier})`);
5117
+ }
5118
+ else {
5119
+ log.info(`Agent spawning disabled (tier: ${tier} — requires spawn tier)`);
5120
+ }
4977
5121
  // --- Register component health checks (after all systems initialized) ---
4978
5122
  // Queue store: can we read the JSONL file?
4979
5123
  health.register("queue", queueStoreCheck(() => queueProvider.getStore()), { critical: false });
@@ -4997,25 +5141,27 @@ async function start() {
4997
5141
  recovery.start();
4998
5142
  // Start alert evaluation loop (evaluates every 30s)
4999
5143
  alertManager.start();
5000
- // Wire notification channels — email (Resend) and phone (Twilio voice)
5001
- const resendKey = process.env.RESEND_API_KEY;
5002
- if (resendKey) {
5003
- alertDispatcher.add(new EmailChannel({
5004
- endpoint: "https://api.resend.com/emails",
5005
- apiKey: resendKey,
5006
- from: `${getInstanceName()} <${getAlertEmailFrom()}>`,
5007
- to: [resolveEnv("ALERT_EMAIL_TO") ?? ""].filter(Boolean),
5008
- }));
5009
- log.info("Alert channel: email (Resend)");
5010
- }
5011
- if (process.env.TWILIO_ACCOUNT_SID) {
5012
- alertDispatcher.add(new PhoneChannel());
5013
- log.info("Alert channel: phone (Twilio voice)");
5014
- }
5015
- alertManager.updateNotifications([
5016
- { channel: "email", minSeverity: "warning" },
5017
- { channel: "phone", minSeverity: "critical" },
5018
- ]);
5144
+ // Wire notification channels — tier >= byok only (requires BYOK API keys)
5145
+ if (tierGate.canAlert(tier)) {
5146
+ const resendKey = process.env.RESEND_API_KEY;
5147
+ if (resendKey) {
5148
+ alertDispatcher.add(new EmailChannel({
5149
+ endpoint: "https://api.resend.com/emails",
5150
+ apiKey: resendKey,
5151
+ from: `${getInstanceName()} <${getAlertEmailFrom()}>`,
5152
+ to: [resolveEnv("ALERT_EMAIL_TO") ?? ""].filter(Boolean),
5153
+ }));
5154
+ log.info("Alert channel: email (Resend)");
5155
+ }
5156
+ if (process.env.TWILIO_ACCOUNT_SID) {
5157
+ alertDispatcher.add(new PhoneChannel());
5158
+ log.info("Alert channel: phone (Twilio voice)");
5159
+ }
5160
+ alertManager.updateNotifications([
5161
+ { channel: "email", minSeverity: "warning" },
5162
+ { channel: "phone", minSeverity: "critical" },
5163
+ ]);
5164
+ }
5019
5165
  // Start credit monitoring (checks every 5 min, configurable via CORE_CREDIT_CHECK_INTERVAL_MS)
5020
5166
  startCreditMonitor(health, alertManager);
5021
5167
  // Avatar sidecar launches in background — slow (model loading) but non-blocking
@@ -5238,8 +5384,8 @@ async function start() {
5238
5384
  });
5239
5385
  }
5240
5386
  catch { /* ok */ }
5241
- // Announce on LAN if mesh.lanAnnounce is enabled
5242
- if (getMeshConfig().lanAnnounce) {
5387
+ // Announce on LAN if mesh.lanAnnounce is enabled AND tier >= byok
5388
+ if (tierGate.canMesh(tier) && getMeshConfig().lanAnnounce) {
5243
5389
  try {
5244
5390
  startMdns(actualPort);
5245
5391
  }