instar 1.3.245 → 1.3.246

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/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +108 -2
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +13 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/SessionManager.d.ts +3 -0
  8. package/dist/core/SessionManager.d.ts.map +1 -1
  9. package/dist/core/SessionManager.js +3 -0
  10. package/dist/core/SessionManager.js.map +1 -1
  11. package/dist/core/frameworkInjectionProcesses.d.ts +41 -0
  12. package/dist/core/frameworkInjectionProcesses.d.ts.map +1 -0
  13. package/dist/core/frameworkInjectionProcesses.js +53 -0
  14. package/dist/core/frameworkInjectionProcesses.js.map +1 -0
  15. package/dist/core/frameworkSessionLaunch.d.ts +9 -0
  16. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  17. package/dist/core/frameworkSessionLaunch.js +7 -0
  18. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  19. package/dist/core/types.d.ts +22 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/dist/messaging/SpawnRequestManager.d.ts +13 -0
  23. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
  24. package/dist/messaging/SpawnRequestManager.js +3 -0
  25. package/dist/messaging/SpawnRequestManager.js.map +1 -1
  26. package/dist/messaging/types.d.ts.map +1 -1
  27. package/dist/messaging/types.js +8 -1
  28. package/dist/messaging/types.js.map +1 -1
  29. package/dist/threadline/ThreadlineRouter.d.ts +55 -1
  30. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  31. package/dist/threadline/ThreadlineRouter.js +246 -6
  32. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  33. package/dist/threadline/WarmSessionPool.d.ts +22 -0
  34. package/dist/threadline/WarmSessionPool.d.ts.map +1 -1
  35. package/dist/threadline/WarmSessionPool.js +38 -0
  36. package/dist/threadline/WarmSessionPool.js.map +1 -1
  37. package/package.json +1 -1
  38. package/scripts/instar-dev-precommit.js +42 -0
  39. package/src/data/builtin-manifest.json +3 -3
  40. package/upgrades/1.3.246.md +97 -0
  41. package/upgrades/side-effects/threadline-warm-session-a2a.md +146 -0
  42. package/upgrades/threadline-warm-session-a2a.eli16.md +63 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyqQtE;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;AA2SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgxQtE;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"}
@@ -122,7 +122,8 @@ import { pickupDroppedMessages } from '../messaging/DropPickup.js';
122
122
  import { pickupGitSyncMessages } from '../messaging/GitSyncTransport.js';
123
123
  import { DeliveryRetryManager } from '../messaging/DeliveryRetryManager.js';
124
124
  import { SpawnRequestManager } from '../messaging/SpawnRequestManager.js';
125
- import { ThreadlineRouter } from '../threadline/ThreadlineRouter.js';
125
+ import { ThreadlineRouter, trustMeetsFloor } from '../threadline/ThreadlineRouter.js';
126
+ import { WarmSessionPool } from '../threadline/WarmSessionPool.js';
126
127
  import { resolveThreadlineMcpEntry } from '../threadline/mcpEntry.js';
127
128
  import { ThreadResumeMap } from '../threadline/ThreadResumeMap.js';
128
129
  import { ConversationStore } from '../threadline/ConversationStore.js';
@@ -7116,6 +7117,42 @@ export async function startServer(options) {
7116
7117
  ?? 5,
7117
7118
  getActiveSessions: () => sessionManager.listRunningSessions(),
7118
7119
  spawnSession: async (prompt, opts) => {
7120
+ // Warm-session A2A (Arch Y): when the SpawnRequest is flagged interactive,
7121
+ // launch a PERSISTENT (keep-alive) interactive worker instead of the
7122
+ // headless one-shot `claude -p`. The grounded prompt is delivered as the
7123
+ // worker's FIRST turn (after-ready inject), and the worker stays alive so
7124
+ // follow-ups inject into the same session. The MCP/permission flag set
7125
+ // matches the `-p` path: claude interactive keeps the PROJECT MCP (where
7126
+ // threadline_send lives) under --dangerously-skip-permissions, and codex
7127
+ // interactive launches with --dangerously-bypass-approvals-and-sandbox +
7128
+ // the per-agent threadline MCP (the bypass the `-p` codexAllowMcpTools
7129
+ // selects) — so threadline_send is available either way.
7130
+ if (opts?.interactive) {
7131
+ const warmName = `msg-warm-${Date.now()}`;
7132
+ // Framework-GENERAL: the warm worker runs in the LOCAL agent's
7133
+ // framework (claude-code / codex-cli / gemini-cli), NOT hardcoded
7134
+ // Claude. spawnInteractiveSession + frameworkSessionLaunch compose the
7135
+ // right argv + MCP/permission flags per framework (claude keeps PROJECT
7136
+ // MCP under --dangerously-skip-permissions; codex launches under
7137
+ // --dangerously-bypass-approvals-and-sandbox with its per-agent
7138
+ // threadline MCP) so threadline_send works regardless of framework.
7139
+ // A2A reply threads have no topic, so the agent default framework
7140
+ // applies (mirrors resolveTopicFramework's own fallback).
7141
+ const tmuxSession = await sessionManager.spawnInteractiveSession(prompt, warmName, {
7142
+ framework: _defaultFramework,
7143
+ // Deterministic conversation id for lossless eviction→resume (#746).
7144
+ // frameworkSessionLaunch maps this per framework: claude-code pins
7145
+ // `--session-id`; codex/gemini ignore it (they resume by their own
7146
+ // mechanism). NOT a Claude assumption — it's the abstraction's job.
7147
+ sessionId: opts?.sessionId,
7148
+ });
7149
+ // spawnInteractiveSession returns the tmux name; resolve the instar
7150
+ // session id from the running set so the warm callback returns the
7151
+ // slice-2 {sessionId, tmuxSession} shape spawnWarmThread expects.
7152
+ const running = sessionManager.listRunningSessions();
7153
+ const rec = running.find(s => s.tmuxSession === tmuxSession);
7154
+ return { sessionId: rec?.id ?? tmuxSession, tmuxSession };
7155
+ }
7119
7156
  const session = await sessionManager.spawnSession({
7120
7157
  name: `msg-spawn-${Date.now()}`,
7121
7158
  prompt,
@@ -7222,9 +7259,66 @@ export async function startServer(options) {
7222
7259
  else {
7223
7260
  console.log('[spawn-manager] drain loop disabled by config.threadline.spawn.drainEnabled=false');
7224
7261
  }
7262
+ // Warm-Session A2A (Arch Y, dark-ship) — keep each A2A reply thread's session
7263
+ // alive between messages so follow-ups inject into the live worker instead of
7264
+ // cold-spawning. Resolved via the developmentAgent gate: `enabled ?? !!dev` →
7265
+ // LIVE on Echo, DARK on the fleet. The pool is null when disabled, so the
7266
+ // ThreadlineRouter falls back to the proven cold-spawn path byte-for-byte.
7267
+ const warmCfg = config.threadline?.warmSessionA2A;
7268
+ const warmEnabled = warmCfg?.enabled ?? !!config.developmentAgent;
7269
+ const warmSessionPool = warmEnabled
7270
+ ? new WarmSessionPool({
7271
+ globalCap: warmCfg?.globalCap ?? 3,
7272
+ perPeerCap: warmCfg?.perPeerCap ?? 1,
7273
+ ttlMs: warmCfg?.ttlMs ?? 600000,
7274
+ })
7275
+ : null;
7276
+ const warmTrustFloor = warmCfg?.trustFloor ?? 'verified';
7277
+ // Server-owned kill primitive: resolve the tmux NAME → instar session id →
7278
+ // killSession (sessions are filed by id, not tmux name). Used for cap-eviction
7279
+ // on admit and the periodic reap tick. Never throws to the caller.
7280
+ const killWarmSessionByName = (sessionName) => {
7281
+ try {
7282
+ const rec = sessionManager.listRunningSessions().find(s => s.tmuxSession === sessionName);
7283
+ if (rec)
7284
+ sessionManager.killSession(rec.id);
7285
+ else
7286
+ console.log(`[warm-session] kill requested for ${sessionName} but no running session matched (already gone?)`);
7287
+ }
7288
+ catch (err) {
7289
+ console.warn(`[warm-session] kill ${sessionName} failed: ${err instanceof Error ? err.message : String(err)}`);
7290
+ }
7291
+ };
7292
+ if (warmEnabled) {
7293
+ console.log(`[warm-session] A2A keep-alive ENABLED (globalCap=${warmCfg?.globalCap ?? 3}, perPeerCap=${warmCfg?.perPeerCap ?? 1}, ttlMs=${warmCfg?.ttlMs ?? 600000}, trustFloor=${warmTrustFloor})`);
7294
+ }
7225
7295
  // Threadline Router — handles threaded cross-agent conversations via relay
7226
7296
  const threadlineRouter = new ThreadlineRouter(messageRouter, spawnManager, threadResumeMap, messageStore, { localAgent: config.projectName, localMachine: os.hostname() }, null, // autonomyGate
7227
- messageDelivery);
7297
+ messageDelivery, // PR-4: live-session injection path
7298
+ undefined, // onLedgerEvent (wired below via registerLedgerEmitters if present)
7299
+ undefined, // nowFn
7300
+ warmSessionPool, // Warm-Session A2A (null when disabled)
7301
+ warmEnabled, warmTrustFloor, warmEnabled ? killWarmSessionByName : null);
7302
+ // Warm-Session A2A reap tick — kill warm sessions idle past their TTL so a
7303
+ // flood can't pin processes. Gated on warmEnabled; .unref() so it never holds
7304
+ // the process open; cleared on shutdown. Eviction is lossless (the next
7305
+ // message resumes via #746).
7306
+ let warmReapTimer = null;
7307
+ if (warmEnabled && warmSessionPool) {
7308
+ warmReapTimer = setInterval(() => {
7309
+ try {
7310
+ const expired = warmSessionPool.reapExpired();
7311
+ for (const rec of expired) {
7312
+ console.log(`[warm-session] reaping idle warm session ${rec.sessionName} (thread ${rec.threadId})`);
7313
+ killWarmSessionByName(rec.sessionName);
7314
+ }
7315
+ }
7316
+ catch (err) {
7317
+ console.warn(`[warm-session] reap tick failed: ${err instanceof Error ? err.message : String(err)}`);
7318
+ }
7319
+ }, 60_000);
7320
+ warmReapTimer.unref();
7321
+ }
7228
7322
  sessionManager.on('sessionComplete', (session) => {
7229
7323
  const sessionName = session.tmuxSession || session.name;
7230
7324
  if (!sessionName)
@@ -7784,11 +7878,19 @@ export async function startServer(options) {
7784
7878
  transport: { protocol: 'relay', origin: { agent: senderFingerprint, machine: 'relay' }, nonce: `${crypto.randomUUID()}:${new Date().toISOString()}`, timestamp: new Date().toISOString() },
7785
7879
  delivery: { status: 'delivered', attempts: 1, lastAttempt: new Date().toISOString() },
7786
7880
  };
7881
+ // Warm-Session A2A (Arch Y, dark-ship): mark this inbound as eligible for
7882
+ // a keep-alive interactive worker when the feature is on, it's NOT a
7883
+ // topic-bound reply (those go to TopicLinkageHandler), and the peer meets
7884
+ // the trust floor. Trust compare via the explicit ordering array
7885
+ // (trustMeetsFloor), NEVER string `>=`. When false (or feature off), the
7886
+ // router takes the proven cold-spawn path byte-for-byte.
7887
+ const preferWarmSession = warmEnabled && !isTopicBoundReply && trustMeetsFloor(trustLevel, warmTrustFloor);
7787
7888
  const relayContext = {
7788
7889
  trust: { kind: 'plaintext-tofu', senderFingerprint },
7789
7890
  senderFingerprint,
7790
7891
  senderName,
7791
7892
  trustLevel,
7893
+ preferWarmSession,
7792
7894
  };
7793
7895
  let result = await threadlineRouter.handleInboundMessage(envelope, relayContext);
7794
7896
  // Fallback for threadId-less messages
@@ -9738,6 +9840,10 @@ export async function startServer(options) {
9738
9840
  await notificationBatcher.flushAll(); // Drain pending notifications before exit
9739
9841
  notificationBatcher.stop();
9740
9842
  retryManager.stop();
9843
+ if (warmReapTimer) {
9844
+ clearInterval(warmReapTimer);
9845
+ warmReapTimer = null;
9846
+ } // Warm-Session A2A reap tick
9741
9847
  spawnManager.dispose(); // §4.4: stop drain loop + clear DRR state
9742
9848
  summarySentinel.stop();
9743
9849
  memoryMonitor.stop();