instar 0.28.57 → 0.28.59

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/README.md CHANGED
@@ -118,6 +118,7 @@ Instar solves the six dimensions of agent coherence:
118
118
  | **AutoUpdater** | Built-in update engine. Checks npm, auto-applies, self-restarts | [→](https://instar.sh/features/autoupdater/) |
119
119
  | **Build Pipeline** | `/build` skill with worktree isolation, 6-phase pipeline, quality gates, stop-hook enforcement | |
120
120
  | **Behavioral Hooks** | 9 automatic hooks: command guards, safety gates, identity grounding, topic context | [→](https://instar.sh/reference/hooks/) |
121
+ | **Initiative Tracker** | Persisted multi-phase long-running work tracker. Phases, blockers, links, digest alerts. HTTP API at `/initiatives/*` | |
121
122
  | **Default Jobs** | Health checks, reflection, evolution, relationship maintenance | [→](https://instar.sh/reference/default-jobs/) |
122
123
 
123
124
  > **Reference:** [CLI Commands](https://instar.sh/reference/cli/) · [API Endpoints](https://instar.sh/reference/api/) · [Configuration](https://instar.sh/reference/configuration/) · [File Structure](https://instar.sh/reference/file-structure/)
@@ -2415,6 +2415,7 @@
2415
2415
  <button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
2416
2416
  <button class="tab" data-tab="integrated-being" onclick="switchTab('integrated-being')">Integrated-Being</button>
2417
2417
  <button class="tab" data-tab="pr-pipeline" onclick="switchTab('pr-pipeline')">PR Pipeline</button>
2418
+ <button class="tab" data-tab="initiatives" onclick="switchTab('initiatives')">Initiatives <span class="tab-count" id="tabInitiativeCount">0</span></button>
2418
2419
  </nav>
2419
2420
  </div>
2420
2421
  <div class="vital-signs" id="vitalSigns">
@@ -2779,6 +2780,34 @@
2779
2780
  <div id="prPipelineList" style="display:flex;flex-direction:column;gap:12px"></div>
2780
2781
  </div>
2781
2782
 
2783
+ <!-- Initiatives Tab — multi-phase long-running work -->
2784
+ <div id="initiativesPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
2785
+ <div style="display:flex;justify-content:space-between;align-items:center">
2786
+ <h2 style="margin:0">Initiatives</h2>
2787
+ <div style="display:flex;gap:8px;align-items:center">
2788
+ <select id="initiativesFilter" style="padding:6px 10px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px" onchange="loadInitiatives()">
2789
+ <option value="active">Active</option>
2790
+ <option value="">All</option>
2791
+ <option value="completed">Completed</option>
2792
+ <option value="archived">Archived</option>
2793
+ <option value="abandoned">Abandoned</option>
2794
+ </select>
2795
+ <button onclick="loadInitiatives()" style="padding:6px 12px">Refresh</button>
2796
+ </div>
2797
+ </div>
2798
+ <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
2799
+ Multi-phase long-running work. Signals: <span style="color:#e27d3b">needs-user</span>,
2800
+ <span style="color:#d65f5f">next-check-due</span>,
2801
+ <span style="color:#4a9a4a">ready-to-advance</span>,
2802
+ <span style="color:#888">stale (&gt;7d)</span>.
2803
+ </div>
2804
+ <div id="initiativesDigest" style="display:none;padding:12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-dim)"></div>
2805
+ <div id="initiativesEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
2806
+ No initiatives to display. Create one via <code>POST /initiatives</code>.
2807
+ </div>
2808
+ <div id="initiativesList" style="display:flex;flex-direction:column;gap:12px"></div>
2809
+ </div>
2810
+
2782
2811
  <!-- Health Tab (was Systems) -->
2783
2812
  <div class="systems-container" id="systemsTab" style="display:none">
2784
2813
  <div class="systems-main">
@@ -3192,6 +3221,7 @@
3192
3221
 
3193
3222
  activeSession = tmuxSession;
3194
3223
  userIsFollowing = true; // Reset scroll tracking on session switch
3224
+ hideResumeButton(); // Carry-over button from prior session would be misleading
3195
3225
  historyLinesLoaded = 2000; // Reset history state for new session
3196
3226
  historyLoading = false;
3197
3227
  historyExhausted = false;
@@ -3305,15 +3335,20 @@
3305
3335
  if (atBottom && !userIsFollowing) {
3306
3336
  // User scrolled back to bottom — resume following and apply pending output
3307
3337
  userIsFollowing = true;
3338
+ hideResumeButton();
3308
3339
  if (pendingOutputData) {
3309
3340
  const data = pendingOutputData;
3310
3341
  pendingOutputData = null;
3311
3342
  term.clear();
3312
- term.write(data);
3313
- term.scrollToBottom();
3343
+ // term.write is async — scroll after write completes so the buffer is populated.
3344
+ term.write(data, () => { term.scrollToBottom(); });
3314
3345
  }
3315
- } else if (!atBottom) {
3346
+ } else if (!atBottom && userIsFollowing) {
3347
+ // User scrolled up — pause follow and surface the resume button
3348
+ // even if no new output is pending. The button is the always-available
3349
+ // "take me back to live" control.
3316
3350
  userIsFollowing = false;
3351
+ showResumeButton();
3317
3352
  }
3318
3353
 
3319
3354
  // Infinite scroll: load more history when scrolled near the top
@@ -3341,12 +3376,13 @@
3341
3376
  const atBottom = buf.baseY === 0 || (buf.baseY - buf.viewportY) <= 5;
3342
3377
  if (atBottom) {
3343
3378
  userIsFollowing = true;
3379
+ hideResumeButton();
3344
3380
  if (pendingOutputData) {
3345
3381
  const data = pendingOutputData;
3346
3382
  pendingOutputData = null;
3347
3383
  term.clear();
3348
- term.write(data);
3349
- term.scrollToBottom();
3384
+ // term.write is async — scroll after write completes so the buffer is populated.
3385
+ term.write(data, () => { term.scrollToBottom(); });
3350
3386
  }
3351
3387
  }
3352
3388
  }
@@ -3420,23 +3456,20 @@
3420
3456
  btn.style.cssText = 'position:absolute;bottom:12px;left:50%;transform:translateX(-50%);z-index:10;background:var(--accent);color:#000;border:none;padding:6px 16px;border-radius:4px;font-size:12px;cursor:pointer;font-weight:600;opacity:0.95;box-shadow:0 2px 8px rgba(0,0,0,0.3);';
3421
3457
  btn.onclick = () => {
3422
3458
  userIsFollowing = true;
3459
+ const snapToBottom = () => {
3460
+ term.scrollToBottom();
3461
+ const container = document.getElementById('terminalContainer');
3462
+ if (container) container.scrollIntoView({ block: 'end', behavior: 'instant' });
3463
+ };
3423
3464
  if (pendingOutputData) {
3424
3465
  const data = pendingOutputData;
3425
3466
  pendingOutputData = null;
3426
3467
  term.clear();
3427
- term.write(data);
3428
- // Delay scrollToBottom until after xterm renders the written data
3429
- requestAnimationFrame(() => {
3430
- term.scrollToBottom();
3431
- // Also scroll the browser viewport to show the terminal bottom
3432
- const container = document.getElementById('terminalContainer');
3433
- if (container) container.scrollIntoView({ block: 'end', behavior: 'instant' });
3434
- });
3468
+ // term.write is async — use its completion callback so scroll runs
3469
+ // after the buffer is populated, not against a stale viewport.
3470
+ term.write(data, snapToBottom);
3435
3471
  } else {
3436
- // No pending data — just snap to bottom of existing content
3437
- term.scrollToBottom();
3438
- const container = document.getElementById('terminalContainer');
3439
- if (container) container.scrollIntoView({ block: 'end', behavior: 'instant' });
3472
+ snapToBottom();
3440
3473
  }
3441
3474
  hideResumeButton();
3442
3475
  };
@@ -3809,6 +3842,12 @@
3809
3842
  display: ['flex'],
3810
3843
  onActivate: () => { if (typeof loadPrPipeline === 'function') loadPrPipeline(); },
3811
3844
  },
3845
+ {
3846
+ id: 'initiatives',
3847
+ panels: ['initiativesPanel'],
3848
+ display: ['flex'],
3849
+ onActivate: () => { if (typeof loadInitiatives === 'function') loadInitiatives(); },
3850
+ },
3812
3851
  ];
3813
3852
 
3814
3853
  function switchTab(tabName) {
@@ -5699,6 +5738,176 @@
5699
5738
  }
5700
5739
  }
5701
5740
 
5741
+ // ── Initiatives Tab (INITIATIVE-TRACKER-SPEC v1) ─────────────
5742
+ //
5743
+ // Fetches /initiatives and /initiatives/digest on panel activation.
5744
+ // Read-only: no buttons here mutate the tracker. All content goes
5745
+ // through textContent; no innerHTML. Matches the PR Pipeline tab's
5746
+ // XSS-safe pattern.
5747
+ async function loadInitiatives() {
5748
+ const list = document.getElementById('initiativesList');
5749
+ const empty = document.getElementById('initiativesEmpty');
5750
+ const digestBox = document.getElementById('initiativesDigest');
5751
+ const filter = document.getElementById('initiativesFilter');
5752
+ const countBadge = document.getElementById('tabInitiativeCount');
5753
+
5754
+ while (list.firstChild) list.removeChild(list.firstChild);
5755
+ while (digestBox.firstChild) digestBox.removeChild(digestBox.firstChild);
5756
+ digestBox.style.display = 'none';
5757
+ empty.style.display = 'none';
5758
+
5759
+ let itemsRes = null;
5760
+ let digestRes = null;
5761
+ try {
5762
+ const statusQ = filter && filter.value ? `?status=${encodeURIComponent(filter.value)}` : '';
5763
+ const [a, b] = await Promise.all([
5764
+ apiFetch('/initiatives' + statusQ).catch(() => null),
5765
+ apiFetch('/initiatives/digest').catch(() => null),
5766
+ ]);
5767
+ itemsRes = a;
5768
+ digestRes = b;
5769
+ } catch { /* handled by null check below */ }
5770
+
5771
+ if (!itemsRes) {
5772
+ empty.style.display = 'block';
5773
+ empty.textContent = 'Initiative tracker unavailable.';
5774
+ if (countBadge) countBadge.textContent = '0';
5775
+ return;
5776
+ }
5777
+
5778
+ const items = Array.isArray(itemsRes.items) ? itemsRes.items : [];
5779
+ if (countBadge) countBadge.textContent = String(items.length);
5780
+
5781
+ // Digest summary (actionable signals)
5782
+ if (digestRes && Array.isArray(digestRes.items) && digestRes.items.length > 0) {
5783
+ const title = document.createElement('div');
5784
+ title.style.fontWeight = '600';
5785
+ title.style.marginBottom = '6px';
5786
+ title.textContent = `${digestRes.items.length} signal${digestRes.items.length === 1 ? '' : 's'}`;
5787
+ digestBox.appendChild(title);
5788
+ for (const d of digestRes.items) {
5789
+ const row = document.createElement('div');
5790
+ row.style.fontSize = '13px';
5791
+ row.style.padding = '3px 0';
5792
+ const colorMap = {
5793
+ 'needs-user': '#e27d3b',
5794
+ 'next-check-due': '#d65f5f',
5795
+ 'ready-to-advance': '#4a9a4a',
5796
+ 'stale': '#888',
5797
+ };
5798
+ const tag = document.createElement('span');
5799
+ tag.style.color = colorMap[d.reason] || 'var(--text-dim)';
5800
+ tag.style.marginRight = '6px';
5801
+ tag.textContent = `[${d.reason}]`;
5802
+ const label = document.createElement('span');
5803
+ label.textContent = `${d.title} — ${d.detail}`;
5804
+ row.appendChild(tag);
5805
+ row.appendChild(label);
5806
+ digestBox.appendChild(row);
5807
+ }
5808
+ digestBox.style.display = 'block';
5809
+ }
5810
+
5811
+ if (items.length === 0) {
5812
+ empty.style.display = 'block';
5813
+ empty.textContent = 'No initiatives to display. Create one via POST /initiatives.';
5814
+ return;
5815
+ }
5816
+
5817
+ for (const ini of items) {
5818
+ const card = document.createElement('div');
5819
+ card.style.background = 'var(--panel)';
5820
+ card.style.padding = '12px';
5821
+ card.style.borderRadius = '6px';
5822
+ card.style.border = '1px solid var(--border)';
5823
+
5824
+ const header = document.createElement('div');
5825
+ header.style.display = 'flex';
5826
+ header.style.justifyContent = 'space-between';
5827
+ header.style.alignItems = 'center';
5828
+ header.style.marginBottom = '6px';
5829
+
5830
+ const titleEl = document.createElement('div');
5831
+ titleEl.style.fontWeight = '600';
5832
+ titleEl.textContent = ini.title || ini.id || '(untitled)';
5833
+ header.appendChild(titleEl);
5834
+
5835
+ const status = document.createElement('span');
5836
+ status.style.fontSize = '11px';
5837
+ status.style.padding = '2px 8px';
5838
+ status.style.borderRadius = '999px';
5839
+ const statusColors = {
5840
+ active: 'var(--accent)',
5841
+ completed: '#4a9a4a',
5842
+ archived: 'var(--text-dim)',
5843
+ abandoned: '#d65f5f',
5844
+ };
5845
+ status.style.background = statusColors[ini.status] || 'var(--text-dim)';
5846
+ status.style.color = '#fff';
5847
+ status.textContent = ini.status || 'unknown';
5848
+ header.appendChild(status);
5849
+ card.appendChild(header);
5850
+
5851
+ if (ini.description) {
5852
+ const desc = document.createElement('div');
5853
+ desc.style.fontSize = '13px';
5854
+ desc.style.color = 'var(--text-dim)';
5855
+ desc.style.marginBottom = '8px';
5856
+ desc.textContent = ini.description;
5857
+ card.appendChild(desc);
5858
+ }
5859
+
5860
+ // Phase pills
5861
+ if (Array.isArray(ini.phases) && ini.phases.length > 0) {
5862
+ const phaseRow = document.createElement('div');
5863
+ phaseRow.style.display = 'flex';
5864
+ phaseRow.style.gap = '6px';
5865
+ phaseRow.style.flexWrap = 'wrap';
5866
+ phaseRow.style.marginBottom = '8px';
5867
+ const phaseColors = {
5868
+ pending: 'var(--text-dim)',
5869
+ 'in-progress': 'var(--accent)',
5870
+ done: '#4a9a4a',
5871
+ blocked: '#d65f5f',
5872
+ };
5873
+ for (const p of ini.phases) {
5874
+ const pill = document.createElement('span');
5875
+ pill.style.fontSize = '11px';
5876
+ pill.style.padding = '2px 8px';
5877
+ pill.style.borderRadius = '4px';
5878
+ pill.style.border = `1px solid ${phaseColors[p.status] || 'var(--border)'}`;
5879
+ pill.style.color = phaseColors[p.status] || 'var(--text)';
5880
+ pill.textContent = `${p.name} · ${p.status}`;
5881
+ phaseRow.appendChild(pill);
5882
+ }
5883
+ card.appendChild(phaseRow);
5884
+ }
5885
+
5886
+ // Meta row
5887
+ const meta = document.createElement('div');
5888
+ meta.style.fontSize = '11px';
5889
+ meta.style.color = 'var(--text-dim)';
5890
+ const lastTouched = ini.lastTouchedAt ? ini.lastTouchedAt.slice(0, 10) : '';
5891
+ const needsUser = ini.needsUser ? ' · needs you' : '';
5892
+ const blockerCount = Array.isArray(ini.blockers) && ini.blockers.length > 0
5893
+ ? ` · ${ini.blockers.length} blocker${ini.blockers.length === 1 ? '' : 's'}`
5894
+ : '';
5895
+ meta.textContent = `Last touched: ${lastTouched}${needsUser}${blockerCount}`;
5896
+ card.appendChild(meta);
5897
+
5898
+ if (ini.needsUser && ini.needsUserReason) {
5899
+ const reason = document.createElement('div');
5900
+ reason.style.fontSize = '12px';
5901
+ reason.style.marginTop = '4px';
5902
+ reason.style.color = '#e27d3b';
5903
+ reason.textContent = `→ ${ini.needsUserReason}`;
5904
+ card.appendChild(reason);
5905
+ }
5906
+
5907
+ list.appendChild(card);
5908
+ }
5909
+ }
5910
+
5702
5911
  // ── Integrated-Being Tab (v1) ────────────────────────────────
5703
5912
  let integratedBeingPollTimer = null;
5704
5913
 
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA81CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA86ItE;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;AA2PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA81CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAi7ItE;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"}
@@ -5355,7 +5355,9 @@ export async function startServer(options) {
5355
5355
  console.log(pc.green(` Parallel-dev isolation: ${parallelDevConfig.phase} (WorktreeManager wired)`));
5356
5356
  }
5357
5357
  }
5358
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos });
5358
+ const { InitiativeTracker } = await import('../core/InitiativeTracker.js');
5359
+ const initiativeTracker = new InitiativeTracker(config.stateDir);
5360
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker });
5359
5361
  await server.start();
5360
5362
  // Connect DegradationReporter downstream systems now that everything is initialized.
5361
5363
  // Any degradation events queued during startup will drain to feedback + telegram.