instar 1.3.564 → 1.3.565
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +137 -29
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SlackForwardBridge.d.ts +44 -0
- package/dist/core/SlackForwardBridge.d.ts.map +1 -0
- package/dist/core/SlackForwardBridge.js +57 -0
- package/dist/core/SlackForwardBridge.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.565.md +29 -0
- package/upgrades/side-effects/slack-pool-dispatch-to-owner.md +127 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;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;AAg4CD,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,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAshctE;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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -80,6 +80,7 @@ import { SessionMonitor } from '../monitoring/SessionMonitor.js';
|
|
|
80
80
|
import { SessionRecovery } from '../monitoring/SessionRecovery.js';
|
|
81
81
|
import { MultiMachineCoordinator } from '../core/MultiMachineCoordinator.js';
|
|
82
82
|
import { isRemotelyHandled } from '../core/SessionRouter.js';
|
|
83
|
+
import { isSlackSessionKey, reconstructSlackMessage } from '../core/SlackForwardBridge.js';
|
|
83
84
|
import { formatForwardedTopicContext } from '../core/ForwardedTopicContext.js';
|
|
84
85
|
import { resolveAdvertisedMeshUrl, advertiseSelfMeshUrl } from '../core/MeshUrlAdvertiser.js';
|
|
85
86
|
import { relayOutbound } from '../core/TelegramRelay.js';
|
|
@@ -556,6 +557,13 @@ let _projectDir = process.cwd();
|
|
|
556
557
|
let _sharedIntelligence = null;
|
|
557
558
|
let _selfKnowledgeTree = null;
|
|
558
559
|
let _slackAdapter = null;
|
|
560
|
+
// WS1.1 dispatch-to-owner (Slack arm): the owner-side bridge reconstructs a Slack
|
|
561
|
+
// inbound Message from a forwarded mesh deliverMessage and replays it through the
|
|
562
|
+
// SAME local dispatch the live inbound path uses. Set once in startServer's Slack
|
|
563
|
+
// block; null when Slack is not configured (the owner-side Slack branch then
|
|
564
|
+
// no-ops — a forwarded Slack key on a Slack-less owner is a misconfiguration the
|
|
565
|
+
// router's placement should never produce, and falling through is harmless).
|
|
566
|
+
let _slackInboundDispatch = null;
|
|
559
567
|
// SessionRefresh — agent-initiated respawn. Module-scope so onRestartSession
|
|
560
568
|
// (defined outside startServer) can delegate to it once startServer wires it.
|
|
561
569
|
// Null until startServer constructs it; the Telegram /restart handler falls
|
|
@@ -5529,7 +5537,14 @@ export async function startServer(options) {
|
|
|
5529
5537
|
console.warn('[slack] ambient gate wiring skipped:', e.message);
|
|
5530
5538
|
}
|
|
5531
5539
|
// Wire message handler — inject Slack messages into sessions
|
|
5532
|
-
|
|
5540
|
+
// ── WS1.1 dispatch-to-owner (Slack arm) ──────────────────────────────
|
|
5541
|
+
// The actual channel→session dispatch for a Slack inbound message. This is
|
|
5542
|
+
// the body the live inbound `onMessage` handler runs AFTER pool routing has
|
|
5543
|
+
// decided the message belongs to THIS machine — AND the body the owner-side
|
|
5544
|
+
// mesh bridge replays when a Slack message was forwarded to this machine
|
|
5545
|
+
// because it owns the conversation. Sharing one function is "Structure >
|
|
5546
|
+
// Willpower": the forwarded path and the live path can never drift.
|
|
5547
|
+
const slackInboundDispatch = async (message) => {
|
|
5533
5548
|
const channelId = message.channel.identifier;
|
|
5534
5549
|
const isDM = message.metadata?.isDM;
|
|
5535
5550
|
const senderName = message.metadata?.senderName || 'User';
|
|
@@ -5547,33 +5562,6 @@ export async function startServer(options) {
|
|
|
5547
5562
|
const isThreadSession = slackAdapter.isThreadRoutingKey(routingKey);
|
|
5548
5563
|
// The thread_ts to thread replies under (only when this is a thread session).
|
|
5549
5564
|
const replyThreadTs = isThreadSession ? threadTs : undefined;
|
|
5550
|
-
// Sentinel intercept — classify message for emergency stop/pause
|
|
5551
|
-
if (sentinel) {
|
|
5552
|
-
try {
|
|
5553
|
-
const classification = await sentinel.classify(message.content);
|
|
5554
|
-
if (classification.category === 'emergency-stop') {
|
|
5555
|
-
// Kill all sessions
|
|
5556
|
-
const sessions = sessionManager.listRunningSessions();
|
|
5557
|
-
for (const s of sessions) {
|
|
5558
|
-
try {
|
|
5559
|
-
sessionManager.killSession(s.id);
|
|
5560
|
-
}
|
|
5561
|
-
catch { /* ok */ }
|
|
5562
|
-
}
|
|
5563
|
-
slackAdapter.sendToChannel(channelId, '🛑 Emergency stop — all sessions killed.').catch(() => { });
|
|
5564
|
-
return;
|
|
5565
|
-
}
|
|
5566
|
-
else if (classification.category === 'pause') {
|
|
5567
|
-
const existingSession = slackAdapter.getSessionForChannel(routingKey);
|
|
5568
|
-
if (existingSession) {
|
|
5569
|
-
sessionManager.sendKey(existingSession, 'Escape');
|
|
5570
|
-
slackAdapter.sendToChannel(channelId, '⏸️ Session paused.').catch(() => { });
|
|
5571
|
-
}
|
|
5572
|
-
return;
|
|
5573
|
-
}
|
|
5574
|
-
}
|
|
5575
|
-
catch { /* fail-open — if Sentinel errors, process message normally */ }
|
|
5576
|
-
}
|
|
5577
5565
|
// Build injection tag with sender info (matches Telegram's buildInjectionTag pattern)
|
|
5578
5566
|
const slackUserId = message.metadata?.slackUserId;
|
|
5579
5567
|
// Sanitize sender name at injection boundary (prevents injection attacks)
|
|
@@ -5730,6 +5718,88 @@ export async function startServer(options) {
|
|
|
5730
5718
|
catch (err) {
|
|
5731
5719
|
console.error(`[slack] Session spawn failed: ${err instanceof Error ? err.message : err}`);
|
|
5732
5720
|
}
|
|
5721
|
+
};
|
|
5722
|
+
// Expose to the owner-side mesh bridge (a forwarded Slack message replays
|
|
5723
|
+
// through the same dispatch on the machine that owns the conversation).
|
|
5724
|
+
_slackInboundDispatch = slackInboundDispatch;
|
|
5725
|
+
slackAdapter.onMessage(async (message) => {
|
|
5726
|
+
const channelId = message.channel.identifier;
|
|
5727
|
+
const messageTs = message.metadata?.ts;
|
|
5728
|
+
const threadTs = message.metadata?.threadTs;
|
|
5729
|
+
const routingKey = slackAdapter.resolveRoutingKey(channelId, threadTs, messageTs);
|
|
5730
|
+
// Sentinel intercept — classify message for emergency stop/pause. Runs on
|
|
5731
|
+
// the machine the message arrived at (these are local-process actions);
|
|
5732
|
+
// never forwarded.
|
|
5733
|
+
if (sentinel) {
|
|
5734
|
+
try {
|
|
5735
|
+
const classification = await sentinel.classify(message.content);
|
|
5736
|
+
if (classification.category === 'emergency-stop') {
|
|
5737
|
+
// Kill all sessions
|
|
5738
|
+
const sessions = sessionManager.listRunningSessions();
|
|
5739
|
+
for (const s of sessions) {
|
|
5740
|
+
try {
|
|
5741
|
+
sessionManager.killSession(s.id);
|
|
5742
|
+
}
|
|
5743
|
+
catch { /* ok */ }
|
|
5744
|
+
}
|
|
5745
|
+
slackAdapter.sendToChannel(channelId, '🛑 Emergency stop — all sessions killed.').catch(() => { });
|
|
5746
|
+
return;
|
|
5747
|
+
}
|
|
5748
|
+
else if (classification.category === 'pause') {
|
|
5749
|
+
const existingSession = slackAdapter.getSessionForChannel(routingKey);
|
|
5750
|
+
if (existingSession) {
|
|
5751
|
+
sessionManager.sendKey(existingSession, 'Escape');
|
|
5752
|
+
slackAdapter.sendToChannel(channelId, '⏸️ Session paused.').catch(() => { });
|
|
5753
|
+
}
|
|
5754
|
+
return;
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
catch { /* fail-open — if Sentinel errors, process message normally */ }
|
|
5758
|
+
}
|
|
5759
|
+
// ── Multi-Machine Session Pool (§L4 / WS1.1): route through the pool ──
|
|
5760
|
+
// Mirrors the Telegram inbound dispatch. When the rollout stage is past
|
|
5761
|
+
// 'dark', consult the SessionRouter on the Slack routingKey: it may
|
|
5762
|
+
// forward this conversation's message to the machine that OWNS the session
|
|
5763
|
+
// (over the mesh) instead of binding it to whatever local session happens
|
|
5764
|
+
// to be running. DARK (the default) skips this block entirely → the Slack
|
|
5765
|
+
// inbound path is byte-identical to today's local-only dispatch. Any error
|
|
5766
|
+
// falls back to the local dispatch below (fail-safe). This closes the bug
|
|
5767
|
+
// where a Slack channel pinned/transferred to a peer machine still injected
|
|
5768
|
+
// the next message into the already-running LOCAL session (Telegram's
|
|
5769
|
+
// inbound path already followed the transfer; Slack's never did).
|
|
5770
|
+
if (_sessionRouter && _sessionPoolStage() !== 'dark') {
|
|
5771
|
+
try {
|
|
5772
|
+
const outcome = await _sessionRouter.route({
|
|
5773
|
+
sessionKey: routingKey,
|
|
5774
|
+
messageId: String(message.id),
|
|
5775
|
+
payload: message.content,
|
|
5776
|
+
topicMetadata: _topicPinStore?.asTopicMetadata(routingKey),
|
|
5777
|
+
senderEnvelope: {
|
|
5778
|
+
userId: message.metadata?.slackUserId || undefined,
|
|
5779
|
+
firstName: message.metadata?.senderName || undefined,
|
|
5780
|
+
},
|
|
5781
|
+
});
|
|
5782
|
+
console.log(`[session-pool] slack route key=${routingKey} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
5783
|
+
if (isRemotelyHandled(outcome, _meshSelfId)) {
|
|
5784
|
+
console.log(`[session-pool] slack key ${routingKey} handled by owner ${outcome.owner ?? '?'} (${outcome.action}) — not dispatching locally`);
|
|
5785
|
+
return;
|
|
5786
|
+
}
|
|
5787
|
+
// Custody-ack short-circuit (§2.2, mirrors the Telegram path): a
|
|
5788
|
+
// queued/placement-blocked verdict whose enqueue COMMITTED (acked) is
|
|
5789
|
+
// the durable queue's message now — no local fall-through (which would
|
|
5790
|
+
// double-handle). Un-custodied (refused/off/dry-run) falls through.
|
|
5791
|
+
if ((outcome.action === 'queued' || outcome.action === 'placement-blocked') && outcome.acked) {
|
|
5792
|
+
console.log(`[session-pool] slack key ${routingKey} in durable custody (${outcome.detail ?? outcome.action}) — drain will deliver`);
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5795
|
+
// 'handled-locally' / self 'spawned'/'owner-dead-replaced' / un-acked
|
|
5796
|
+
// 'queued'/'placement-blocked' → fall through to local dispatch below.
|
|
5797
|
+
}
|
|
5798
|
+
catch (err) {
|
|
5799
|
+
console.warn(`[session-pool] slack route error for key ${routingKey} — falling back to local dispatch: ${err instanceof Error ? err.message : String(err)}`);
|
|
5800
|
+
}
|
|
5801
|
+
}
|
|
5802
|
+
await slackInboundDispatch(message);
|
|
5733
5803
|
});
|
|
5734
5804
|
await slackAdapter.start();
|
|
5735
5805
|
_slackAdapter = slackAdapter;
|
|
@@ -13697,7 +13767,45 @@ export async function startServer(options) {
|
|
|
13697
13767
|
if (Number.isFinite(wsTopic))
|
|
13698
13768
|
_topicProfileCarrier?.onTopicAcquired(wsTopic);
|
|
13699
13769
|
}
|
|
13700
|
-
if (_sessionPoolStage() === 'dark'
|
|
13770
|
+
if (_sessionPoolStage() === 'dark')
|
|
13771
|
+
return;
|
|
13772
|
+
// ── WS1.1 Slack arm (owner-side bridge) ──────────────────────────
|
|
13773
|
+
// A Slack routing key is a non-numeric string (`C…` channel id, or
|
|
13774
|
+
// `C…:<thread_ts>` for a thread session); a Telegram topic key is a
|
|
13775
|
+
// pure number. When the forwarded session key isn't numeric, this is a
|
|
13776
|
+
// Slack conversation that was forwarded here because THIS machine owns
|
|
13777
|
+
// it: reconstruct the inbound Message and replay it through the SAME
|
|
13778
|
+
// local Slack dispatch the live path uses (which itself handles
|
|
13779
|
+
// inject-into-live-session vs spawn). Fire-and-forget: the durable
|
|
13780
|
+
// receipt is already recorded + ACKed before this runs.
|
|
13781
|
+
const slackKey = cmd.session;
|
|
13782
|
+
if (isSlackSessionKey(slackKey)) {
|
|
13783
|
+
if (!_slackInboundDispatch)
|
|
13784
|
+
return; // Slack not configured here
|
|
13785
|
+
const sText = typeof cmd.payload === 'string'
|
|
13786
|
+
? cmd.payload
|
|
13787
|
+
: (cmd.payload && typeof cmd.payload === 'object' && 'text' in cmd.payload)
|
|
13788
|
+
? String(cmd.payload.text)
|
|
13789
|
+
: '';
|
|
13790
|
+
const envUid = cmd.senderEnvelope?.userId;
|
|
13791
|
+
const sMessage = reconstructSlackMessage({
|
|
13792
|
+
sessionKey: slackKey,
|
|
13793
|
+
messageId: cmd.messageId,
|
|
13794
|
+
text: sText,
|
|
13795
|
+
senderUserId: envUid != null ? String(envUid) : undefined,
|
|
13796
|
+
});
|
|
13797
|
+
void _slackInboundDispatch(sMessage)
|
|
13798
|
+
.then(() => {
|
|
13799
|
+
console.log(pc.green(` [session-pool] owner-side Slack dispatch for forwarded key ${slackKey}`));
|
|
13800
|
+
_inboundQueue?.markRemoteInjected(cmd.session, cmd.messageId);
|
|
13801
|
+
})
|
|
13802
|
+
.catch((err) => {
|
|
13803
|
+
console.warn(` [session-pool] owner-side Slack dispatch failed for key ${slackKey}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13804
|
+
_inboundQueue?.reportPeerInjectError(cmd.session, cmd.messageId, err instanceof Error ? err.message : String(err));
|
|
13805
|
+
});
|
|
13806
|
+
return;
|
|
13807
|
+
}
|
|
13808
|
+
if (!telegram)
|
|
13701
13809
|
return;
|
|
13702
13810
|
const tg = telegram;
|
|
13703
13811
|
const topicId = Number(cmd.session);
|