instar 1.3.731 → 1.3.733

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 (69) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +208 -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 +26 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/ConversationRegistry.d.ts +355 -0
  8. package/dist/core/ConversationRegistry.d.ts.map +1 -0
  9. package/dist/core/ConversationRegistry.js +1438 -0
  10. package/dist/core/ConversationRegistry.js.map +1 -0
  11. package/dist/core/PostUpdateMigrator.d.ts +12 -0
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +80 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/SessionManager.d.ts +15 -0
  16. package/dist/core/SessionManager.d.ts.map +1 -1
  17. package/dist/core/SessionManager.js +28 -0
  18. package/dist/core/SessionManager.js.map +1 -1
  19. package/dist/core/conversationBindToken.d.ts +36 -0
  20. package/dist/core/conversationBindToken.d.ts.map +1 -0
  21. package/dist/core/conversationBindToken.js +144 -0
  22. package/dist/core/conversationBindToken.js.map +1 -0
  23. package/dist/core/conversationIdentity.d.ts +126 -0
  24. package/dist/core/conversationIdentity.d.ts.map +1 -0
  25. package/dist/core/conversationIdentity.js +160 -0
  26. package/dist/core/conversationIdentity.js.map +1 -0
  27. package/dist/core/deliverToConversation.d.ts +105 -0
  28. package/dist/core/deliverToConversation.d.ts.map +1 -0
  29. package/dist/core/deliverToConversation.js +374 -0
  30. package/dist/core/deliverToConversation.js.map +1 -0
  31. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  32. package/dist/core/devGatedFeatures.js +6 -0
  33. package/dist/core/devGatedFeatures.js.map +1 -1
  34. package/dist/core/slackRefreshBinding.d.ts +8 -10
  35. package/dist/core/slackRefreshBinding.d.ts.map +1 -1
  36. package/dist/core/slackRefreshBinding.js +8 -16
  37. package/dist/core/slackRefreshBinding.js.map +1 -1
  38. package/dist/core/types.d.ts +46 -0
  39. package/dist/core/types.d.ts.map +1 -1
  40. package/dist/core/types.js.map +1 -1
  41. package/dist/monitoring/CommitmentTracker.d.ts +64 -0
  42. package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
  43. package/dist/monitoring/CommitmentTracker.js +58 -0
  44. package/dist/monitoring/CommitmentTracker.js.map +1 -1
  45. package/dist/monitoring/PromiseBeacon.d.ts +70 -0
  46. package/dist/monitoring/PromiseBeacon.d.ts.map +1 -1
  47. package/dist/monitoring/PromiseBeacon.js +221 -24
  48. package/dist/monitoring/PromiseBeacon.js.map +1 -1
  49. package/dist/scaffold/templates.d.ts.map +1 -1
  50. package/dist/scaffold/templates.js +10 -0
  51. package/dist/scaffold/templates.js.map +1 -1
  52. package/dist/server/AgentServer.d.ts +12 -0
  53. package/dist/server/AgentServer.d.ts.map +1 -1
  54. package/dist/server/AgentServer.js +20 -0
  55. package/dist/server/AgentServer.js.map +1 -1
  56. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  57. package/dist/server/CapabilityIndex.js +14 -0
  58. package/dist/server/CapabilityIndex.js.map +1 -1
  59. package/dist/server/routes.d.ts +17 -0
  60. package/dist/server/routes.d.ts.map +1 -1
  61. package/dist/server/routes.js +206 -10
  62. package/dist/server/routes.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/data/builtin-manifest.json +65 -65
  65. package/src/scaffold/templates.ts +10 -0
  66. package/upgrades/1.3.732.md +107 -0
  67. package/upgrades/1.3.733.md +108 -0
  68. package/upgrades/side-effects/durable-conversation-identity-increment1.md +89 -0
  69. package/upgrades/side-effects/durable-conversation-identity-increment2.md +88 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAu/CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4ykBtE;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;AAiDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAu/CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAu/kBtE;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"}
@@ -29,6 +29,7 @@ import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGat
29
29
  import { resolveAntiThrashKnobs, readingValidity } from '../core/SwapAntiThrash.js';
30
30
  import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
31
31
  import { PrHandLease } from '../core/PrHandLease.js';
32
+ import { ConversationRegistry } from '../core/ConversationRegistry.js';
32
33
  import { claimSuspensionExcludesPin } from '../core/TopicClaimAnnotationStore.js';
33
34
  import { parseProfileTrigger, platformMessageIdFrom } from '../core/topicProfileIngress.js';
34
35
  import { slugifyChannelName } from '../messaging/slack/sanitize.js';
@@ -4959,8 +4960,21 @@ export async function startServer(options) {
4959
4960
  // knows whether to reroute a `claude -p` one-shot onto the interactive
4960
4961
  // (subscription) lane. Absent/'off' ⇒ today's behavior, byte-for-byte.
4961
4962
  const subscriptionPathCfg = config.intelligence?.subscriptionPath;
4963
+ // ── durable-conversation-identity §7 bind-time authority (increment 2) ──
4964
+ // Constructed HERE (before SessionManager) so the spawn env can carry the
4965
+ // per-session bind token. One shared secret backs both the spawn-side mint
4966
+ // and the POST /commitments verify (below). Deploy stamp is written once,
4967
+ // first-boot, to anchor the R7-minor-2 straggler grace clock.
4968
+ const { createConversationBindAuth: _createConversationBindAuth, ensureBindDeployStamp: _ensureBindDeployStamp } = await import('../core/conversationBindToken.js');
4969
+ const conversationBindAuth = _createConversationBindAuth(config.stateDir);
4970
+ try {
4971
+ _ensureBindDeployStamp(config.stateDir, startupVersion);
4972
+ }
4973
+ catch { /* @silent-fallback-ok — the stamp only arms the fail-open straggler backstop */ }
4962
4974
  const sessionManagerConfig = {
4963
4975
  ...config.sessions,
4976
+ // §7: mint the per-session bind token into INSTAR_BIND_TOKEN at spawn.
4977
+ mintBindToken: (sessionName, bootstrapConversationIds) => conversationBindAuth.mint(sessionName, bootstrapConversationIds),
4964
4978
  ...(codexThreadlineMcp ? { codexThreadlineMcp } : {}),
4965
4979
  respawnBuildContext: {
4966
4980
  ...(config.sessions.respawnBuildContext ?? {}),
@@ -6659,6 +6673,112 @@ export async function startServer(options) {
6659
6673
  }
6660
6674
  // ── Slack adapter initialization ─────────────────────────────────
6661
6675
  let slackAdapter;
6676
+ // ── Durable conversation identity (durable-conversation-identity §3/§6.3) ──
6677
+ // ALWAYS constructed (the foundation is always-on once shipped — only
6678
+ // DELIVERY is dev-gated), so GET /conversations/health answers 200 even on a
6679
+ // Telegram-only agent (an empty, healthy registry). getLocalWorkspaceId is
6680
+ // late-bound over the adapter declared above; the D1 kill-switch + fsync
6681
+ // escape hatch are read LIVE at the chokepoint (no restart to apply).
6682
+ const conversationRegistry = new ConversationRegistry({
6683
+ stateDir: config.stateDir,
6684
+ machineId: () => _meshSelfId ?? os.hostname(),
6685
+ isRecordingEnabled: () => config.conversationIdentity?.recording?.enabled !== false,
6686
+ isJournalFsyncDisabled: () => config.conversationIdentity?.recording?.disableJournalFsync === true,
6687
+ getLocalWorkspaceId: () => slackAdapter?.getWorkspaceId(),
6688
+ getConfigWorkspacePin: () => config.conversationIdentity?.workspacePin,
6689
+ breaker: config.conversationIdentity?.mintBreaker,
6690
+ onAttention: (dedupeKey, title, body) => {
6691
+ try {
6692
+ _meshAttentionRaise?.({ id: dedupeKey, title, body, priority: 'medium', sourceContext: 'conversation-identity' });
6693
+ }
6694
+ catch { /* @silent-fallback-ok — attention is observability; a failed raise never gates identity or delivery */ }
6695
+ },
6696
+ log: (line) => console.log(pc.dim(` ${line}`)),
6697
+ });
6698
+ try {
6699
+ conversationRegistry.load();
6700
+ }
6701
+ catch (err) {
6702
+ // @silent-fallback-ok — fail toward delivery (§3.6): a broken registry boot
6703
+ // must never take the server down; mints degrade to in-memory candidates
6704
+ // (behavior-identical to legacy hashing) until healed, and the failure is
6705
+ // logged loudly here.
6706
+ console.warn(` [conversation-registry] boot load failed (degraded to in-memory candidates): ${err instanceof Error ? err.message : String(err)}`);
6707
+ }
6708
+ // ── durable-conversation-identity §5 funnel + §6.1 step-2 wiring (increment 2) ──
6709
+ // followThrough gate: dark-on-fleet / live-on-dev via the developmentAgent
6710
+ // gate (config OMITS `enabled` → resolveDevAgentGate resolves it), dryRun
6711
+ // TRUE first (delivery is externally visible — spec §9). The Telegram lane
6712
+ // (`id>0`) is byte-identical regardless.
6713
+ const conversationFollowThrough = () => {
6714
+ const ft = config
6715
+ .conversationIdentity?.followThrough;
6716
+ return {
6717
+ enabled: resolveDevAgentGate(ft?.enabled, config),
6718
+ dryRun: ft?.dryRun !== false,
6719
+ };
6720
+ };
6721
+ const { createConversationDelivery: _createConversationDelivery } = await import('../core/deliverToConversation.js');
6722
+ const deliverToConversation = _createConversationDelivery({
6723
+ registry: conversationRegistry,
6724
+ followThrough: conversationFollowThrough,
6725
+ // id>0 → today's Telegram path via the existing /telegram/reply route
6726
+ // (queue/dedup/tone-gate untouched).
6727
+ sendTelegram: async (topicId, text, opts) => {
6728
+ const url = `http://localhost:${config.port}/telegram/reply/${topicId}`;
6729
+ const response = await fetch(url, {
6730
+ method: 'POST',
6731
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.authToken}` },
6732
+ body: JSON.stringify({
6733
+ text,
6734
+ metadata: { source: opts?.source ?? 'conversation-funnel', isProxy: opts?.isProxy, tier: opts?.tier, allowDuplicate: opts?.allowDuplicate },
6735
+ }),
6736
+ });
6737
+ return response.ok;
6738
+ },
6739
+ // id<0 → the local Slack adapter (channel + thread_ts). Late-bound over
6740
+ // _slackAdapter (declared below); undefined here means no local Slack
6741
+ // adapter → the funnel returns a typed non-delivery (§5.0 ownership).
6742
+ sendSlack: async (channelId, text, threadTs) => {
6743
+ if (!_slackAdapter)
6744
+ throw new Error('no local Slack adapter');
6745
+ await _slackAdapter.sendToChannel(channelId, text, threadTs ? { thread_ts: threadTs } : undefined);
6746
+ },
6747
+ isSystemChannel: (channelId) => _slackAdapter?.isSystemChannel(channelId) ?? false,
6748
+ auditWouldDeliver: (line) => console.log(pc.dim(` ${line}`)),
6749
+ onAttention: (dedupeKey, title, body) => {
6750
+ try {
6751
+ _meshAttentionRaise?.({ id: dedupeKey, title, body, priority: 'medium', sourceContext: 'conversation-identity' });
6752
+ }
6753
+ catch { /* @silent-fallback-ok — attention is observability; never gates delivery */ }
6754
+ },
6755
+ log: (line) => console.log(pc.dim(` ${line}`)),
6756
+ });
6757
+ // §6.1 step 2 — the ConversationBinder the CommitmentTracker uses for a
6758
+ // durable bind on a MINTED id: durable-binding-forced registration (WAL
6759
+ // fsynced) + the §3.5.2 bind-pin, handing back the bind-time tuple.
6760
+ const conversationBinder = {
6761
+ bind: (conversationId) => {
6762
+ const resolved = conversationRegistry.resolve(conversationId);
6763
+ if (!resolved || resolved.platform !== 'slack') {
6764
+ return { ok: false, error: 'conversation-bind-unresolvable' };
6765
+ }
6766
+ const routingKey = resolved.threadTs ? `${resolved.channelId}:${resolved.threadTs}` : resolved.channelId;
6767
+ const mint = conversationRegistry.mintForDurableBinding(routingKey);
6768
+ if (!mint.ok)
6769
+ return { ok: false, error: mint.error };
6770
+ const tuple = conversationRegistry.bindPin(mint.id);
6771
+ if (!tuple)
6772
+ return { ok: false, error: 'conversation-bind-unresolvable' };
6773
+ return { ok: true, boundTuple: { platform: 'slack', channelId: tuple.channelId, threadTs: tuple.threadTs } };
6774
+ },
6775
+ release: (conversationId) => {
6776
+ try {
6777
+ conversationRegistry.bindRelease(conversationId);
6778
+ }
6779
+ catch { /* @silent-fallback-ok — a failed release leaks one refcount (harmless R6-low-4 orphan) */ }
6780
+ },
6781
+ };
6662
6782
  const slackConfig = config.messaging?.find(m => m.type === 'slack' && m.enabled);
6663
6783
  if (slackConfig) {
6664
6784
  try {
@@ -6886,6 +7006,21 @@ export async function startServer(options) {
6886
7006
  const isThreadSession = slackAdapter.isThreadRoutingKey(routingKey);
6887
7007
  // The thread_ts to thread replies under (only when this is a thread session).
6888
7008
  const replyThreadTs = isThreadSession ? threadTs : undefined;
7009
+ // ── §6.3 eager mint (durable-conversation-identity) ────────────────
7010
+ // Get-or-create the durable conversation id for the RESOLVED routing
7011
+ // key on EVERY inbound (one cached registry read after the first).
7012
+ // Carried under the PINNED field key `conversationId` in message
7013
+ // metadata + the session bootstrap context so the session can attach
7014
+ // durable state to it. Fail toward delivery (§3.6): a mint
7015
+ // degradation ("no durable id") never blocks the message.
7016
+ let conversationId = null;
7017
+ try {
7018
+ conversationId = conversationRegistry.mintForInbound(routingKey).id;
7019
+ if (conversationId !== null && message.metadata) {
7020
+ message.metadata.conversationId = conversationId;
7021
+ }
7022
+ }
7023
+ catch { /* @silent-fallback-ok — identity never costs a message (§3.6 fail-toward-delivery): the inbound proceeds without a durable id and re-mints later */ }
6889
7024
  // Build injection tag with sender info (matches Telegram's buildInjectionTag pattern)
6890
7025
  const slackUserId = message.metadata?.slackUserId;
6891
7026
  // Sanitize sender name at injection boundary (prevents injection attacks)
@@ -6946,6 +7081,13 @@ export async function startServer(options) {
6946
7081
  contextLines.push('');
6947
7082
  contextLines.push('Strip the [slack:] prefix before interpreting the message.');
6948
7083
  contextLines.push('Only relay conversational text — not tool output, file contents, or internal reasoning.');
7084
+ if (conversationId !== null) {
7085
+ // §6.3: the durable conversation id rides the session bootstrap
7086
+ // context (pinned metadata key `conversationId`; the prompt phrasing
7087
+ // below is build-time cheap-to-change).
7088
+ contextLines.push('');
7089
+ contextLines.push(`Conversation id: ${conversationId} — the durable identity of THIS Slack conversation. Use it as the topicId when opening durable state (e.g. POST /commitments) for this conversation.`);
7090
+ }
6949
7091
  const contextData = contextLines.join('\n');
6950
7092
  // Also write to file as backup reference
6951
7093
  const ctxPath = path.join(tmpDir, `ctx-${channelId}-${Date.now()}.txt`);
@@ -7029,7 +7171,14 @@ export async function startServer(options) {
7029
7171
  // isolated work session (DMs don't carry thread_ts anyway).
7030
7172
  const targetSession = (isDM && !isThreadSession) ? 'lifeline' : undefined;
7031
7173
  try {
7032
- const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId, slackChannelId: channelId, slackThreadTs: replyThreadTs });
7174
+ const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, {
7175
+ resumeSessionId,
7176
+ slackChannelId: channelId,
7177
+ slackThreadTs: replyThreadTs,
7178
+ // durable-conversation-identity §7: scope this session's bind
7179
+ // token to the minted conversation it may open durable state on.
7180
+ ...(conversationId !== null ? { bootstrapConversationIds: [conversationId] } : {}),
7181
+ });
7033
7182
  if (newSessionName) {
7034
7183
  // Register on the routing key (channelId for a channel session,
7035
7184
  // `<channelId>:<thread_ts>` for a thread session). channelName carries
@@ -7046,6 +7195,32 @@ export async function startServer(options) {
7046
7195
  // Expose to the owner-side mesh bridge (a forwarded Slack message replays
7047
7196
  // through the same dispatch on the machine that owns the conversation).
7048
7197
  _slackInboundDispatch = slackInboundDispatch;
7198
+ // ── §6.2 adoption pass (durable-conversation-identity) ─────────────
7199
+ // Idempotent boot-time ensure: pre-register channel-LEVEL conversations
7200
+ // from slack-channel-registry.json with their legacy-hash ids, inside
7201
+ // ONE batched-save window. Gated to channels with authorized-sender
7202
+ // traffic on record (security-B8): a channel enters the session
7203
+ // registry ONLY via the authorized-sender dispatch above, so presence
7204
+ // there IS the authorized-traffic evidence — an auto-joined channel
7205
+ // with no authorized activity never appears and mints lazily on its
7206
+ // first authorized inbound instead. A pre-population convenience, not a
7207
+ // recovery requirement (§6.2).
7208
+ try {
7209
+ const channelRegistry = slackAdapter.getChannelRegistry();
7210
+ const channelIds = new Map();
7211
+ for (const [routingKey, entry] of Object.entries(channelRegistry)) {
7212
+ const chId = routingKey.includes(':') ? routingKey.slice(0, routingKey.indexOf(':')) : routingKey;
7213
+ if (!channelIds.has(chId))
7214
+ channelIds.set(chId, entry.channelName);
7215
+ }
7216
+ conversationRegistry.runAdoptionPass([...channelIds].map(([channelId, name]) => ({ channelId, ...(name ? { name } : {}) })), () => true);
7217
+ }
7218
+ catch (err) {
7219
+ // @silent-fallback-ok — the adoption pass is a pre-population CONVENIENCE,
7220
+ // not a recovery requirement (§6.2): every channel mints lazily on its
7221
+ // next authorized inbound; the failure is logged loudly here.
7222
+ console.warn(` [conversation-registry] adoption pass failed (channels mint lazily on next inbound): ${err instanceof Error ? err.message : String(err)}`);
7223
+ }
7049
7224
  slackAdapter.onMessage(async (message) => {
7050
7225
  const channelId = message.channel.identifier;
7051
7226
  const messageTs = message.metadata?.ts;
@@ -10959,6 +11134,11 @@ export async function startServer(options) {
10959
11134
  notify('IMMEDIATE', 'commitment', `The change you asked for ("${commitment.userRequest}") keeps reverting — this looks like a bug I need to fix. ${detail}`);
10960
11135
  },
10961
11136
  });
11137
+ // durable-conversation-identity §6.1 step 2: the ConversationBinder is
11138
+ // late-bound (the registry constructs before the tracker). A durable bind
11139
+ // on a minted (negative) conversation id now registers durably + records
11140
+ // the §3.5.2 bind-pin and denormalizes boundTuple onto the commitment.
11141
+ commitmentTracker.setConversationBinder(conversationBinder);
10962
11142
  commitmentTracker.start();
10963
11143
  console.log(pc.green(' Commitment tracker enabled'));
10964
11144
  // SubscriptionPool — multi-account subscription registry (P1.1 of the
@@ -12243,6 +12423,32 @@ export async function startServer(options) {
12243
12423
  if (!response.ok)
12244
12424
  throw new Error(`Beacon reply failed: ${response.status}`);
12245
12425
  },
12426
+ // ── durable-conversation-identity §6.1 step 2: the funnel swap ──
12427
+ // EVERY beacon send routes through deliverToConversation: the id>0
12428
+ // arm is today's Telegram path unchanged; the id<0 arm delivers
12429
+ // into the exact Slack thread with the E1 guard + §5.1 typed
12430
+ // outcomes. The `metadata` shape maps to the funnel opts contract.
12431
+ deliverMessage: (topicId, text, opts) => deliverToConversation(topicId, text, {
12432
+ source: opts.source,
12433
+ isProxy: opts.isProxy,
12434
+ tier: typeof opts.tier === 'number' ? String(opts.tier) : opts.tier,
12435
+ logicalSendId: opts.logicalSendId,
12436
+ ...(opts.boundTuple ? { boundTuple: opts.boundTuple } : {}),
12437
+ }),
12438
+ // §5.0(a): journal send-retire after the sendSeq persist (R5-M3 order).
12439
+ retireSend: (conversationId, logicalSendId) => {
12440
+ try {
12441
+ conversationRegistry.retireSend(conversationId, logicalSendId);
12442
+ }
12443
+ catch { /* guard bookkeeping */ }
12444
+ },
12445
+ // §5.0 ownership predicate (drives the R3-M16 stand-down recheck).
12446
+ ownsConversation: (conversationId) => {
12447
+ if (conversationId > 0)
12448
+ return true; // Telegram is always locally owned
12449
+ const resolved = conversationRegistry.resolve(conversationId);
12450
+ return !!resolved && resolved.platform === 'slack' && resolved.origin !== 'replicated' && !!_slackAdapter;
12451
+ },
12246
12452
  prefix: promiseBeaconCfg.prefix ?? '⏳',
12247
12453
  maxDailyLlmSpendCents: promiseBeaconCfg.maxDailyLlmSpendCents ?? 100,
12248
12454
  sentinelAutoEnable: promiseBeaconCfg.sentinelAutoEnable ?? false,
@@ -20229,7 +20435,7 @@ export async function startServer(options) {
20229
20435
  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, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
20230
20436
  peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
20231
20437
  } if (peers.length === 0)
20232
- return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, reviewCanaryBattery, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
20438
+ return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, conversationRegistry, conversationBindAuth, conversationFollowThrough, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, reviewCanaryBattery, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
20233
20439
  // wired before the server existed; from here on inbound binds use the
20234
20440
  // server's own store instance.
20235
20441
  _agentServerRef = server;