instar 1.3.310 → 1.3.312

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 (67) hide show
  1. package/dist/commands/init.d.ts.map +1 -1
  2. package/dist/commands/init.js +32 -0
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/server.d.ts.map +1 -1
  5. package/dist/commands/server.js +8 -5
  6. package/dist/commands/server.js.map +1 -1
  7. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  8. package/dist/config/ConfigDefaults.js +17 -0
  9. package/dist/config/ConfigDefaults.js.map +1 -1
  10. package/dist/core/AgentWorktreeDetector.d.ts +27 -3
  11. package/dist/core/AgentWorktreeDetector.d.ts.map +1 -1
  12. package/dist/core/AgentWorktreeDetector.js +77 -19
  13. package/dist/core/AgentWorktreeDetector.js.map +1 -1
  14. package/dist/core/BootSelfKnowledge.d.ts +121 -0
  15. package/dist/core/BootSelfKnowledge.d.ts.map +1 -0
  16. package/dist/core/BootSelfKnowledge.js +285 -0
  17. package/dist/core/BootSelfKnowledge.js.map +1 -0
  18. package/dist/core/InstarWorktreeManager.d.ts +7 -0
  19. package/dist/core/InstarWorktreeManager.d.ts.map +1 -1
  20. package/dist/core/InstarWorktreeManager.js +47 -1
  21. package/dist/core/InstarWorktreeManager.js.map +1 -1
  22. package/dist/core/PostUpdateMigrator.d.ts +10 -0
  23. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  24. package/dist/core/PostUpdateMigrator.js +161 -0
  25. package/dist/core/PostUpdateMigrator.js.map +1 -1
  26. package/dist/core/types.d.ts +26 -0
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/core/types.js.map +1 -1
  29. package/dist/messaging/AttentionTopicGuard.d.ts +14 -0
  30. package/dist/messaging/AttentionTopicGuard.d.ts.map +1 -1
  31. package/dist/messaging/AttentionTopicGuard.js +21 -0
  32. package/dist/messaging/AttentionTopicGuard.js.map +1 -1
  33. package/dist/messaging/TelegramAdapter.d.ts +40 -3
  34. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  35. package/dist/messaging/TelegramAdapter.js +61 -11
  36. package/dist/messaging/TelegramAdapter.js.map +1 -1
  37. package/dist/scaffold/templates.d.ts.map +1 -1
  38. package/dist/scaffold/templates.js +8 -0
  39. package/dist/scaffold/templates.js.map +1 -1
  40. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  41. package/dist/scheduler/JobScheduler.js +3 -3
  42. package/dist/scheduler/JobScheduler.js.map +1 -1
  43. package/dist/server/routes.d.ts.map +1 -1
  44. package/dist/server/routes.js +137 -2
  45. package/dist/server/routes.js.map +1 -1
  46. package/dist/threadline/CollaborationSurfacer.d.ts +4 -1
  47. package/dist/threadline/CollaborationSurfacer.d.ts.map +1 -1
  48. package/dist/threadline/CollaborationSurfacer.js +1 -1
  49. package/dist/threadline/CollaborationSurfacer.js.map +1 -1
  50. package/dist/threadline/TelegramBridge.d.ts +4 -1
  51. package/dist/threadline/TelegramBridge.d.ts.map +1 -1
  52. package/dist/threadline/TelegramBridge.js +1 -1
  53. package/dist/threadline/TelegramBridge.js.map +1 -1
  54. package/dist/threadline/hubCommands.d.ts +4 -1
  55. package/dist/threadline/hubCommands.d.ts.map +1 -1
  56. package/dist/threadline/hubCommands.js +3 -3
  57. package/dist/threadline/hubCommands.js.map +1 -1
  58. package/package.json +2 -2
  59. package/scripts/lint-no-direct-destructive.js +4 -0
  60. package/scripts/lint-no-unfunneled-topic-creation.js +129 -0
  61. package/src/data/builtin-manifest.json +73 -65
  62. package/src/scaffold/templates.ts +8 -0
  63. package/src/templates/scripts/secret-get.mjs +143 -0
  64. package/upgrades/1.3.311.md +20 -0
  65. package/upgrades/1.3.312.md +31 -0
  66. package/upgrades/side-effects/bounded-notification-surface.md +34 -0
  67. package/upgrades/side-effects/session-boot-self-knowledge.md +78 -0
@@ -195,6 +195,7 @@ export class PostUpdateMigrator {
195
195
  this.migrateStaleLifelineSignal(result);
196
196
  this.migrateThreadlineConversationStore(result);
197
197
  this.migrateThreadlineAgentInfoIdentity(result);
198
+ this.migrateWorktreeMisplacedFloodItems(result);
198
199
  return result;
199
200
  }
200
201
  /**
@@ -537,6 +538,42 @@ export class PostUpdateMigrator {
537
538
  result.errors.push(`threadline-agent-info-identity write: ${err instanceof Error ? err.message : String(err)}`);
538
539
  }
539
540
  }
541
+ /**
542
+ * 2026-06-05 worktree-misplaced flood cleanup (Bounded Notification
543
+ * Surface). The pre-fix AgentWorktreeDetector emitted one attention item
544
+ * PER worktree with ids `worktree-misplaced:<sha256>`; a transiently-wrong
545
+ * safe-root read mass-created 110 false-positive OPEN items on flooded
546
+ * agents. The fixed detector emits a single `worktree-misplaced-summary:*`
547
+ * item, so the old per-path items are permanently stale — purge them.
548
+ * Idempotent: a store with no old-format ids is left untouched.
549
+ */
550
+ migrateWorktreeMisplacedFloodItems(result) {
551
+ const storePath = path.join(this.config.stateDir, 'state', 'attention-items.json');
552
+ if (!fs.existsSync(storePath)) {
553
+ result.skipped.push('worktree-misplaced flood items: no attention store');
554
+ return;
555
+ }
556
+ try {
557
+ const data = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
558
+ const items = Array.isArray(data.items) ? data.items : [];
559
+ // Old per-path format only: `worktree-misplaced:<hash>`. The new
560
+ // aggregated format is `worktree-misplaced-summary:<hash>` — kept.
561
+ const isStale = (id) => typeof id === 'string' && id.startsWith('worktree-misplaced:');
562
+ const staleCount = items.filter((i) => isStale(i.id)).length;
563
+ if (staleCount === 0) {
564
+ result.skipped.push('worktree-misplaced flood items: none present');
565
+ return;
566
+ }
567
+ const kept = items.filter((i) => !isStale(i.id));
568
+ const tmp = `${storePath}.${process.pid}.tmp`;
569
+ fs.writeFileSync(tmp, JSON.stringify({ ...data, items: kept }, null, 2));
570
+ fs.renameSync(tmp, storePath);
571
+ result.upgraded.push(`worktree-misplaced flood items: purged ${staleCount} stale per-path item(s) from the attention store`);
572
+ }
573
+ catch (err) {
574
+ result.errors.push(`worktree-misplaced flood items: ${err instanceof Error ? err.message : String(err)}`);
575
+ }
576
+ }
540
577
  migrateBootWrapperToCjs(result) {
541
578
  if (process.platform !== 'darwin') {
542
579
  result.skipped.push('boot-wrapper .cjs: non-darwin, no plist to migrate');
@@ -2639,6 +2676,24 @@ Rule: I do not state that work landed inside another agent's state unless I have
2639
2676
  patched = true;
2640
2677
  result.upgraded.push('CLAUDE.md: added Cross-Agent Communication Discipline (anti-confabulation) section');
2641
2678
  }
2679
+ // Session Boot Self-Knowledge (spec: session-boot-self-knowledge.md).
2680
+ // Existing agents need the rule ("a secret named in your boot block is in
2681
+ // the vault — retrieve, don't re-ask") + the facts writer + the retrieval
2682
+ // script. Content-sniffed on the same heading the template emits.
2683
+ if (!content.includes('**Session Boot Self-Knowledge**')) {
2684
+ const bootSelfKnowledgeSection = `
2685
+ **Session Boot Self-Knowledge** — Your session-start context includes an auto-injected \`<session-self-knowledge>\` block: the NAMES of secrets in your encrypted vault (never values) + self-asserted operational facts about this agent/machine. (Rides the developmentAgent gate until the fleet flip.)
2686
+ - **The rule**: a secret named in your boot block is ALREADY in your vault — retrieve it with \`node .instar/scripts/secret-get.mjs <name>\` (pipe stdout straight into the consuming command, e.g. \`... github_token | gh auth login --with-token\` — NEVER echo the value into chat/transcripts) instead of asking the user to re-send it. Only re-ask if you have evidence it is invalid (expired/revoked/decrypt-failed).
2687
+ - Discover vault key names anytime: \`node .instar/scripts/secret-get.mjs --names\` (names+lengths to stderr) or \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/self-knowledge/session-context?full=1"\`.
2688
+ - **Record a durable operational fact** (a channel path, a logged-in seat, a machine-specific truth worth knowing at every boot): \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/self-knowledge/facts -H 'Content-Type: application/json' -d '{"fact":"..."}'\` (auto-stamped with date+machine). Remove: \`curl -X DELETE -H "Authorization: Bearer $AUTH" http://localhost:${port}/self-knowledge/facts -H 'Content-Type: application/json' -d '{"match":"substring"}'\`. Facts are per-machine and appear at the next session start.
2689
+ - **When to use** (PROACTIVE — this is the trigger): the moment you discover an operational fact future sessions will need (where a tool lives, which machine owns a seat, a non-obvious path), record it as a fact — never leave it to session memory.
2690
+ - If the boot block reports the vault as DECRYPT-FAILED: do NOT repair, rotate, or delete anything — a decrypt failure is usually recoverable; destructive action loses secrets permanently. Surface it to the operator and stop.
2691
+ - Off-switch: \`selfKnowledge.sessionContext.enabled: false\` in \`.instar/config.json\` (applies at the next session start).
2692
+ `;
2693
+ content += '\n' + bootSelfKnowledgeSection;
2694
+ patched = true;
2695
+ result.upgraded.push('CLAUDE.md: added Session Boot Self-Knowledge section');
2696
+ }
2642
2697
  // Apprenticeship Program (Step 1, APPRENTICESHIP-STEP1-PROGRAM-SCAFFOLD-SPEC.md).
2643
2698
  // Existing agents need to know the program registry + lifecycle gates exist —
2644
2699
  // an agent that doesn't know about a capability effectively doesn't have it.
@@ -2983,6 +3038,26 @@ The attention queue spawns ONE Telegram forum topic per item — right for a gen
2983
3038
  else {
2984
3039
  result.skipped.push('CLAUDE.md: Topic-Flood Guard section already present');
2985
3040
  }
3041
+ // Bounded Notification Surface (2026-06-05, flood #3) — extends the
3042
+ // flood-guard awareness with the universal last-resort budget INSIDE
3043
+ // createForumTopic (covers every caller, not just attention items) and the
3044
+ // aggregate-at-the-emitter rule. Idempotent via the unique marker phrase.
3045
+ if (!content.includes('Bounded Notification Surface')) {
3046
+ const section = `
3047
+ ### Bounded Notification Surface (universal auto-topic budget)
3048
+
3049
+ Beyond the attention-queue breaker above, the topic-creation primitive itself (\`TelegramAdapter.createForumTopic\`) enforces a LAST-RESORT budget on every automatically-created topic — covering every caller, current and future, no matter what source labels it passes (the 2026-06-05 worktree-detector flood dodged the per-source budget by giving every item a unique source; this ceiling is the layer that cannot be dodged). User-initiated and bounded create-once system topics are exempt; everything else is budgeted by default. Tune via \`messaging[].config.topicCreationBudget\` = \`{ "windowMs": 600000, "maxTopicsPerSource": 8, "maxTopicsGlobal": 12 }\`.
3050
+
3051
+ - If I am building a feature that notifies per-element over a collection: AGGREGATE — one summary item carrying the count and the list, never one item per element. The burst-invariant CI test (\`tests/integration/notification-flood-burst-invariant.test.ts\`) fails any build that violates the bound.
3052
+ - If a topic creation fails with "topic-creation budget exceeded": that is the flood ceiling doing its job — fix the calling feature's volume (aggregate), don't raise the budget.
3053
+ `;
3054
+ content += '\n' + section;
3055
+ patched = true;
3056
+ result.upgraded.push('CLAUDE.md: added Bounded Notification Surface section');
3057
+ }
3058
+ else {
3059
+ result.skipped.push('CLAUDE.md: Bounded Notification Surface section already present');
3060
+ }
2986
3061
  // Multi-Machine Session Pool (§L2) — tells the agent about the active-active
2987
3062
  // pool + the Machines tab + nickname-based placement/transfer. Without it an
2988
3063
  // agent asked "what machines am I on / move this to the mini / where is this
@@ -4202,6 +4277,11 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
4202
4277
  '**Coordination Mandate**',
4203
4278
  '**ReviewExchange (autonomous code review)**',
4204
4279
  '**Cutover Readiness**',
4280
+ // Session Boot Self-Knowledge (spec session-boot-self-knowledge): vault
4281
+ // secret NAMES + operational facts at boot. A Codex/Gemini agent that
4282
+ // never learns the facts writer + secret-get retrieval will re-ask the
4283
+ // user for stored credentials — the exact loop this feature closes.
4284
+ '**Session Boot Self-Knowledge**',
4205
4285
  ];
4206
4286
  for (const shadowName of ['AGENTS.md', 'GEMINI.md']) {
4207
4287
  const shadowPath = path.join(this.config.projectDir, shadowName);
@@ -4411,6 +4491,21 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
4411
4491
  catch (err) {
4412
4492
  result.errors.push(`secret-drop-retrieve.mjs: ${err instanceof Error ? err.message : String(err)}`);
4413
4493
  }
4494
+ // Vault retrieval helper — always overwrite (sibling of the above; spec
4495
+ // session-boot-self-knowledge §Retrieval affordance). The boot block names
4496
+ // vault secrets; this is the hardened read path it points at (value →
4497
+ // stdout for piping, names/diagnostics → stderr, never echoed). Without
4498
+ // it, "a secret named here is in your vault" is aspirational.
4499
+ try {
4500
+ const secretGetContent = this.loadRelayTemplate('secret-get.mjs');
4501
+ if (secretGetContent) {
4502
+ fs.writeFileSync(path.join(instarScriptsDir, 'secret-get.mjs'), secretGetContent, { mode: 0o755 });
4503
+ result.upgraded.push('scripts/secret-get.mjs (hardened vault retrieval)');
4504
+ }
4505
+ }
4506
+ catch (err) {
4507
+ result.errors.push(`secret-get.mjs: ${err instanceof Error ? err.message : String(err)}`);
4508
+ }
4414
4509
  // Session-clock injector — always overwrite. New, non-customizable shared
4415
4510
  // routine (docs/specs/ROBUST-SESSION-TIME-AWARENESS-SPEC.md Component 2):
4416
4511
  // renders the SESSION CLOCK line (render mode for the autonomous-stop-hook,
@@ -5811,6 +5906,37 @@ except Exception:
5811
5906
  fi
5812
5907
  fi
5813
5908
 
5909
+ # SESSION BOOT SELF-KNOWLEDGE injection (spec: session-boot-self-knowledge.md).
5910
+ # Fetches /self-knowledge/session-context and injects the deterministic "what I
5911
+ # already have" block: vault secret NAMES (never values) + self-asserted
5912
+ # operational facts — so the agent never re-asks the user for a secret it
5913
+ # already holds and never claims ignorance of a channel it owns. Placed AFTER
5914
+ # the org-intent + preferences blocks (authoritative contract first — this is
5915
+ # background signal; the server wraps it in a <session-self-knowledge
5916
+ # src='boot'> envelope). Fail-open: 503 (dark / disabled) / 404 (version skew:
5917
+ # old server) / unreachable / empty -> silent skip; curl -sf is what makes a
5918
+ # non-2xx emit nothing, and the Bearer token travels ONLY in the header.
5919
+ if [ -n "\$PORT" ] && [ -n "\$TOKEN" ]; then
5920
+ BOOT_SK_RESPONSE=\$(curl -sf --max-time 4 --connect-timeout 1 -H "Authorization: Bearer \$TOKEN" \\
5921
+ "http://localhost:\${PORT}/self-knowledge/session-context" 2>/dev/null)
5922
+ if [ -n "\$BOOT_SK_RESPONSE" ]; then
5923
+ BOOT_SK_BLOCK=\$(echo "\$BOOT_SK_RESPONSE" | python3 -c "
5924
+ import sys, json
5925
+ try:
5926
+ d = json.load(sys.stdin)
5927
+ if d.get('present') and d.get('block'):
5928
+ print(d['block'])
5929
+ except Exception:
5930
+ pass
5931
+ " 2>/dev/null)
5932
+ if [ -n "\$BOOT_SK_BLOCK" ]; then
5933
+ echo ""
5934
+ echo "\$BOOT_SK_BLOCK"
5935
+ echo ""
5936
+ fi
5937
+ fi
5938
+ fi
5939
+
5814
5940
  # BEGIN integrated-being-v2
5815
5941
  # INTEGRATED-BEING V2 — session-write binding (see docs/specs/integrated-being-ledger-v2.md §3)
5816
5942
  # Generates a session UUID, registers with /shared-state/session-bind, writes the
@@ -6851,6 +6977,41 @@ except Exception:
6851
6977
  fi
6852
6978
  fi
6853
6979
 
6980
+ # SESSION BOOT SELF-KNOWLEDGE re-injection (spec: session-boot-self-knowledge.md).
6981
+ # A days-long session compacts; the boot block injected at session start only
6982
+ # survives if the compaction summary happens to carry it — willpower, not
6983
+ # structure. Re-fetching here makes the block durable across compaction AND
6984
+ # fresher than the original: a secret stored mid-session appears in the
6985
+ # post-compaction context. Same fail-open contract as the boot fetch: dark /
6986
+ # unreachable / version-skew -> silent skip, header-only Bearer.
6987
+ if [ -f "$INSTAR_DIR/config.json" ]; then
6988
+ BOOT_SK_PORT=\${PORT:-\$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$INSTAR_DIR/config.json" | head -1 | grep -oE '[0-9]+' | head -1)}
6989
+ BOOT_SK_TOKEN="\${INSTAR_AUTH_TOKEN:-}"
6990
+ if [ -z "\$BOOT_SK_TOKEN" ]; then
6991
+ BOOT_SK_TOKEN=\$(python3 -c "import json; v=json.load(open('$INSTAR_DIR/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
6992
+ fi
6993
+ if [ -n "\$BOOT_SK_PORT" ] && [ -n "\$BOOT_SK_TOKEN" ]; then
6994
+ BOOT_SK_RESPONSE=\$(curl -sf --max-time 4 --connect-timeout 1 -H "Authorization: Bearer \$BOOT_SK_TOKEN" \
6995
+ "http://localhost:\${BOOT_SK_PORT}/self-knowledge/session-context" 2>/dev/null)
6996
+ if [ -n "\$BOOT_SK_RESPONSE" ]; then
6997
+ BOOT_SK_BLOCK=\$(echo "\$BOOT_SK_RESPONSE" | python3 -c "
6998
+ import sys, json
6999
+ try:
7000
+ d = json.load(sys.stdin)
7001
+ if d.get('present') and d.get('block'):
7002
+ print(d['block'])
7003
+ except Exception:
7004
+ pass
7005
+ " 2>/dev/null)
7006
+ if [ -n "\$BOOT_SK_BLOCK" ]; then
7007
+ echo ""
7008
+ echo "\$BOOT_SK_BLOCK"
7009
+ echo ""
7010
+ fi
7011
+ fi
7012
+ fi
7013
+ fi
7014
+
6854
7015
  echo "=== END IDENTITY RECOVERY ==="
6855
7016
  `;
6856
7017
  }