instar 1.2.61 → 1.2.63

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 (42) hide show
  1. package/README.md +10 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +115 -24
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +13 -0
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/SessionManager.d.ts +18 -0
  9. package/dist/core/SessionManager.d.ts.map +1 -1
  10. package/dist/core/SessionManager.js +34 -0
  11. package/dist/core/SessionManager.js.map +1 -1
  12. package/dist/core/TopicIntent.d.ts +62 -1
  13. package/dist/core/TopicIntent.d.ts.map +1 -1
  14. package/dist/core/TopicIntent.js +131 -2
  15. package/dist/core/TopicIntent.js.map +1 -1
  16. package/dist/core/TopicIntentCapture.d.ts +124 -0
  17. package/dist/core/TopicIntentCapture.d.ts.map +1 -0
  18. package/dist/core/TopicIntentCapture.js +232 -0
  19. package/dist/core/TopicIntentCapture.js.map +1 -0
  20. package/dist/core/TopicIntentExtractor.d.ts +32 -0
  21. package/dist/core/TopicIntentExtractor.d.ts.map +1 -1
  22. package/dist/core/TopicIntentExtractor.js +52 -3
  23. package/dist/core/TopicIntentExtractor.js.map +1 -1
  24. package/dist/core/types.d.ts +10 -0
  25. package/dist/core/types.d.ts.map +1 -1
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/monitoring/sentinelWiring.d.ts +28 -0
  28. package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
  29. package/dist/monitoring/sentinelWiring.js +48 -0
  30. package/dist/monitoring/sentinelWiring.js.map +1 -1
  31. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  32. package/dist/server/CapabilityIndex.js +1 -0
  33. package/dist/server/CapabilityIndex.js.map +1 -1
  34. package/dist/server/topicIntentRoutes.d.ts.map +1 -1
  35. package/dist/server/topicIntentRoutes.js +61 -1
  36. package/dist/server/topicIntentRoutes.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/data/builtin-manifest.json +3 -3
  39. package/upgrades/1.2.62.md +75 -0
  40. package/upgrades/1.2.63.md +84 -0
  41. package/upgrades/side-effects/rate-limit-recovery-reachability.md +116 -0
  42. package/upgrades/side-effects/topic-intent-capture-loop.md +100 -0
package/README.md CHANGED
@@ -200,6 +200,16 @@ The AI systems we build today set precedents for how AI is treated tomorrow. **T
200
200
 
201
201
  > **Deep dive:** [Philosophy](https://instar.sh/concepts/philosophy/)
202
202
 
203
+ ## The Living Constitution
204
+
205
+ Instar's engineering principles aren't a static style guide — they're a **living constitution**. The [Standards Registry](https://instar.sh/foundations/standards-registry/) codifies each one as a rule, what it means in practice, the *failure it was earned from*, and its trace back to the one founding goal: a coherent, self-evolving agent. Nineteen articles across five families (Root, Substrate, Building, Shipping, Interaction), plus the Genesis story and the AWG positioning on the ethics of instantiating agents.
206
+
207
+ It's not decoration — it's a working part of the machine. The spec-review conformance gate checks every draft against it, and the registry grows the same way the framework was built: the agent proposes a new standard with its story, the operator ratifies it.
208
+
209
+ The registry is the first tangible artifact of a larger vision: the [North Star — Continuous Working Awareness](https://instar.sh/foundations/north-star/). The aim is an agent that never silently loses track of something that mattered — capturing relevant context automatically, keeping it warm while it matters, re-surfacing it the moment it's needed, and letting it fade when it stops — across three facets that are really one: awareness of the world, of itself, and of its own standards.
210
+
211
+ > **Read the constitution:** [Standards Registry](https://instar.sh/foundations/standards-registry/) · [North Star](https://instar.sh/foundations/north-star/)
212
+
203
213
  ## iMessage Setup (macOS)
204
214
 
205
215
  iMessage support lets your agent send and receive iMessages on macOS. Messages are read directly from the native Messages database and sent via the [`imsg`](https://github.com/steipete/imsg) CLI.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiqDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA60LtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiqDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+6LtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -4750,31 +4750,67 @@ export async function startServer(options) {
4750
4750
  // docs/specs/rate-limit-sentinel.md.
4751
4751
  const { RateLimitSentinel } = await import('../monitoring/RateLimitSentinel.js');
4752
4752
  const getClaudeSessionIdForName = (sessionName) => sessionManager.listRunningSessions().find(s => s.tmuxSession === sessionName)?.claudeSessionId;
4753
- // Neutral "continue" nudge NOT the compaction-resume payload (which would
4754
- // falsely tell the agent its memory was reset). Topic-tagged so InputGuard
4755
- // accepts it.
4756
- const rateLimitResume = async (sessionName) => {
4757
- if (!sessionManager.isSessionAlive(sessionName))
4758
- return false;
4759
- const topicId = telegram?.getTopicForSession(sessionName);
4760
- if (topicId == null)
4761
- return false;
4762
- const tagged = `[telegram:${topicId}] The temporary server throttle should have cleared — please continue where you left off.`;
4763
- return sessionManager.injectMessage(sessionName, tagged);
4764
- };
4765
- // Route a user-facing notice for the session's topic (Telegram).
4766
- const rateLimitNotify = async (sessionName, text) => {
4767
- const topicId = telegram?.getTopicForSession(sessionName);
4768
- if (topicId == null)
4769
- return;
4770
- const resp = await fetch(`http://localhost:${config.port}/telegram/reply/${topicId}`, {
4771
- method: 'POST',
4772
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.authToken}` },
4773
- body: JSON.stringify({ text }),
4774
- });
4775
- if (!resp.ok)
4776
- throw new Error(`rate-limit notify failed: ${resp.status}`);
4753
+ // Recovery-reachability audit trail. Both recovery paths below ALWAYS leave
4754
+ // a record: a recovery-reached/recovery-unreachable line in the sentinel
4755
+ // audit log, and — when delivery to the user fails entirely — an entry in
4756
+ // .instar/sentinel-alerts.json so the dashboard surfaces it even without
4757
+ // Telegram. The defining bug this closes: a non-topic-bound session (e.g. a
4758
+ // developer's interactive Claude Code window) used to make both recovery
4759
+ // paths silently no-op, so the throttle never recovered and nothing reached
4760
+ // the user. Reachability is now unconditional — see the Sentinel
4761
+ // Reachability spec.
4762
+ const rlReachLogPath = path.join(config.stateDir, '..', 'logs', 'sentinel-events.jsonl');
4763
+ const rlAlertsPath = path.join(config.stateDir, 'sentinel-alerts.json');
4764
+ const recordRecovery = (kind, sessionName, detail, fallbackTried) => {
4765
+ const entry = { timestamp: new Date().toISOString(), kind, sentinel: 'rate-limit', sessionName, detail, fallbackTried };
4766
+ console.log(`[sentinel:${kind}] rate-limit/${sessionName} ${detail}`);
4767
+ try {
4768
+ fs.appendFileSync(rlReachLogPath, JSON.stringify(entry) + '\n');
4769
+ }
4770
+ catch { /* best-effort */ }
4771
+ if (kind === 'recovery-unreachable') {
4772
+ // Append-only alert log the dashboard reads when Telegram can't be reached.
4773
+ try {
4774
+ let alerts = [];
4775
+ if (fs.existsSync(rlAlertsPath)) {
4776
+ try {
4777
+ alerts = JSON.parse(fs.readFileSync(rlAlertsPath, 'utf-8'));
4778
+ }
4779
+ catch {
4780
+ alerts = [];
4781
+ }
4782
+ if (!Array.isArray(alerts))
4783
+ alerts = [];
4784
+ }
4785
+ alerts.push(entry);
4786
+ fs.writeFileSync(rlAlertsPath, JSON.stringify(alerts.slice(-200), null, 2));
4787
+ }
4788
+ catch { /* best-effort */ }
4789
+ }
4777
4790
  };
4791
+ // Reachability deps (extracted to sentinelWiring.buildRateLimitRecoveryDeps
4792
+ // so the topic / lifeline / audit branching is unit-testable). Topic-bound
4793
+ // sessions get a topic-tagged nudge + topic notice; non-topic-bound sessions
4794
+ // (e.g. an interactive dev window) get a trusted internal nudge + a lifeline
4795
+ // notice; if no channel is reachable, a recovery-unreachable audit event is
4796
+ // recorded instead of a silent no-op.
4797
+ const { buildRateLimitRecoveryDeps } = await import('../monitoring/sentinelWiring.js');
4798
+ const { resumeFn: rateLimitResume, notifyFn: rateLimitNotify } = buildRateLimitRecoveryDeps({
4799
+ isSessionAlive: (name) => sessionManager.isSessionAlive(name),
4800
+ injectTopicNudge: (name, topicId, text) => sessionManager.injectMessage(name, `[telegram:${topicId}] ${text}`),
4801
+ injectInternalNudge: (name, text) => sessionManager.injectInternalMessage(name, text, 'sentinel-recovery'),
4802
+ getTopicForSession: (name) => telegram?.getTopicForSession(name),
4803
+ getLifelineTopicId: () => telegram?.getLifelineTopicId?.(),
4804
+ deliverNotice: async (topicId, text) => {
4805
+ const resp = await fetch(`http://localhost:${config.port}/telegram/reply/${topicId}`, {
4806
+ method: 'POST',
4807
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.authToken}` },
4808
+ body: JSON.stringify({ text }),
4809
+ });
4810
+ return resp.ok;
4811
+ },
4812
+ recordRecovery,
4813
+ });
4778
4814
  const rlsCfg = config.monitoring?.rateLimitSentinel ?? { enabled: true };
4779
4815
  const rateLimitSentinel = new RateLimitSentinel({
4780
4816
  resumeFn: rateLimitResume,
@@ -5362,6 +5398,61 @@ export async function startServer(options) {
5362
5398
  beforeHadCallback(entry);
5363
5399
  observeInboundMessage(humanAsDetectorLog, entry);
5364
5400
  };
5401
+ // ── Topic-Intent capture loop (rung 0 of continuous-working-awareness) ─
5402
+ // The "clerk" that fills the topic-intent store from live conversation so
5403
+ // the per-topic briefing + ArcCheck have real material (closes the
5404
+ // shipped-but-asleep gap — the store/routes/briefing all existed but
5405
+ // nothing ever invoked ingest()). Chains the prior callback; capture is
5406
+ // fire-and-forget + degrade-safe so it NEVER blocks or slows delivery.
5407
+ // Spec: docs/specs/topic-intent-capture-loop.md.
5408
+ if (sharedIntelligence && (config.topicIntent?.capture?.enabled ?? true)) {
5409
+ try {
5410
+ const { TopicIntentExtractor, createLlmExtractFn } = await import('../core/TopicIntentExtractor.js');
5411
+ const { createCaptureLoop, createQueuedIntelligence } = await import('../core/TopicIntentCapture.js');
5412
+ // Transport: route every extraction through sharedLlmQueue (background
5413
+ // lane → yields to interactive work, respects the daily cap), with the
5414
+ // call itself delegating to sharedIntelligence (subscription/REPL-pool,
5415
+ // never raw API — acceptance #6).
5416
+ const queuedIntelligence = createQueuedIntelligence(sharedIntelligence, (lane, fn, costCents) => sharedLlmQueue.enqueue(lane, fn, costCents));
5417
+ const captureExtractFn = createLlmExtractFn(queuedIntelligence, (reason, topicId) => {
5418
+ topicIntentStore.bumpCaptureCounters(topicId, reason === 'no-intelligence'
5419
+ ? { degraded_no_intelligence: 1 }
5420
+ : { degraded_cap_or_error: 1 });
5421
+ });
5422
+ const captureExtractor = new TopicIntentExtractor(topicIntentStore, captureExtractFn);
5423
+ const captureLoop = createCaptureLoop({
5424
+ extractor: captureExtractor,
5425
+ store: topicIntentStore,
5426
+ topicMemory,
5427
+ // Skip capture under sustained quota pressure (load-shedding).
5428
+ shouldShed: () => {
5429
+ const r = quotaTracker?.getRecommendation();
5430
+ return r === 'critical' || r === 'stop';
5431
+ },
5432
+ // Per-topic runaway guard beyond the pre-filter.
5433
+ rateCeiling: { maxPerWindow: 30, windowMs: 60_000 },
5434
+ });
5435
+ const beforeCaptureCb = telegram.onMessageLogged;
5436
+ telegram.onMessageLogged = (entry) => {
5437
+ if (beforeCaptureCb)
5438
+ beforeCaptureCb(entry);
5439
+ // Fire-and-forget — capture latency must never reach the delivery path.
5440
+ void captureLoop({
5441
+ messageId: entry.messageId,
5442
+ topicId: entry.topicId ?? undefined,
5443
+ text: entry.text,
5444
+ fromUser: entry.fromUser,
5445
+ timestamp: entry.timestamp,
5446
+ });
5447
+ };
5448
+ // Anti-"shipped-but-asleep" marker for the wiring-integrity test.
5449
+ globalThis.__instarTopicIntentCaptureWired = true;
5450
+ console.log(pc.green(' Topic-intent capture loop wired (per-turn extraction → store)'));
5451
+ }
5452
+ catch (err) {
5453
+ console.warn('[TopicIntentCapture] init failed:', err.message);
5454
+ }
5455
+ }
5365
5456
  presenceProxy.start();
5366
5457
  // ── PromiseBeacon ────────────────────────────────────────────────
5367
5458
  // Watches beacon-enabled commitments and emits ⏳ heartbeats so the