@borgee/agents-host 0.2.35 → 0.2.44
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/agents-host.d.ts +2 -0
- package/dist/agents-host.js +111 -44
- package/dist/chat/sdk-chat-control-plane.js +3 -0
- package/dist/context/injection.d.ts +5 -3
- package/dist/context/injection.js +45 -29
- package/dist/context/prompt.js +23 -35
- package/dist/context/skill-manual.d.ts +13 -0
- package/dist/context/skill-manual.js +18 -0
- package/dist/context/turn-preparation.js +13 -4
- package/dist/gateway/localhost-gateway.js +16 -8
- package/dist/hosted-turn-content.d.ts +15 -0
- package/dist/hosted-turn-content.js +50 -0
- package/dist/managed-daemon.d.ts +3 -2
- package/dist/managed-daemon.js +77 -29
- package/dist/providers/claude/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +10 -0
- package/dist/providers/claude/cli-client.d.ts +11 -2
- package/dist/providers/claude/cli-client.js +98 -27
- package/dist/providers/codex/adapter.d.ts +3 -1
- package/dist/providers/codex/adapter.js +10 -0
- package/dist/providers/codex/cli-client.d.ts +10 -2
- package/dist/providers/codex/cli-client.js +85 -21
- package/dist/providers/codex/project-doc.js +13 -29
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +10 -0
- package/dist/providers/copilot/cli-client.d.ts +9 -1
- package/dist/providers/copilot/cli-client.js +71 -8
- package/dist/providers/create-provider.d.ts +1 -1
- package/dist/providers/create-provider.js +16 -5
- package/dist/providers/provider-adapter.d.ts +35 -0
- package/dist/providers/provider-adapter.js +44 -1
- package/dist/state-paths.d.ts +9 -1
- package/dist/state-paths.js +22 -3
- package/dist/types.d.ts +33 -2
- package/package.json +1 -1
- package/skills/borgee-agent/SKILL.md +119 -38
- package/skills/borgee-agent/references/errors.md +38 -0
- package/skills/borgee-agent/references/task-properties.md +30 -0
- package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
- package/skills/borgee-agent/borgee-agent.mjs +0 -562
- package/skills/borgee-agent/borgee-agent.py +0 -469
package/dist/agents-host.d.ts
CHANGED
|
@@ -93,9 +93,11 @@ export declare class AgentsHost {
|
|
|
93
93
|
private armProtocolFallback;
|
|
94
94
|
private runProtocolFallback;
|
|
95
95
|
private handleProtocolFallbackFailure;
|
|
96
|
+
private ensureProtocolAnchorIncomingParts;
|
|
96
97
|
private finishProtocolState;
|
|
97
98
|
private cancelProtocolState;
|
|
98
99
|
private confirmProtocolMessage;
|
|
100
|
+
private findChannelHistoryMessageById;
|
|
99
101
|
private resolveAttentionStatePath;
|
|
100
102
|
private buildDefaultAttentionSnapshot;
|
|
101
103
|
/**
|
package/dist/agents-host.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createProvider } from './providers/create-provider.js';
|
|
|
14
14
|
import { resolveAttentionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
15
15
|
import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
|
|
16
16
|
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
17
|
+
import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
|
|
17
18
|
const PRIVATE_STATE_ROOT_MODE = 0o700;
|
|
18
19
|
const STREAM_PROGRESS_EDIT_THROTTLE_MS = 150;
|
|
19
20
|
const PROTOCOL_STATE_DIRNAME = 'protocol-state';
|
|
@@ -126,7 +127,9 @@ class DefaultHostRuntime {
|
|
|
126
127
|
deps.provider ??
|
|
127
128
|
createProvider({
|
|
128
129
|
provider: config.agent.provider,
|
|
130
|
+
borgeeBaseUrl: config.borgeeBaseUrl,
|
|
129
131
|
stateRootDir: config.stateRootDir,
|
|
132
|
+
agentApiKey: config.agent.agentApiKey,
|
|
130
133
|
resolveStableAgentId: deps.resolveStableAgentId,
|
|
131
134
|
claudeCommand: config.claudeCommand,
|
|
132
135
|
claudeArgs: config.claudeArgs,
|
|
@@ -559,7 +562,7 @@ export class AgentsHost {
|
|
|
559
562
|
}
|
|
560
563
|
}
|
|
561
564
|
}
|
|
562
|
-
await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot);
|
|
565
|
+
await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot, normalized.incomingParts);
|
|
563
566
|
}
|
|
564
567
|
async handleCollaborationMessage(msg) {
|
|
565
568
|
const normalized = await this.normalizeMessage(msg);
|
|
@@ -608,6 +611,7 @@ export class AgentsHost {
|
|
|
608
611
|
authorId: normalized.authorId,
|
|
609
612
|
authorKind: 'unknown',
|
|
610
613
|
content: normalized.content,
|
|
614
|
+
incomingParts: normalized.incomingParts,
|
|
611
615
|
attentionSnapshot,
|
|
612
616
|
taskThreadContext,
|
|
613
617
|
});
|
|
@@ -626,6 +630,7 @@ export class AgentsHost {
|
|
|
626
630
|
authorId: normalized.authorId,
|
|
627
631
|
authorKind: 'human',
|
|
628
632
|
content: normalized.content,
|
|
633
|
+
incomingParts: normalized.incomingParts,
|
|
629
634
|
attentionSnapshot,
|
|
630
635
|
taskThreadContext,
|
|
631
636
|
wakeBlockedState,
|
|
@@ -658,6 +663,7 @@ export class AgentsHost {
|
|
|
658
663
|
authorId: normalized.authorId,
|
|
659
664
|
authorKind,
|
|
660
665
|
content: normalized.content,
|
|
666
|
+
incomingParts: normalized.incomingParts,
|
|
661
667
|
attentionSnapshot,
|
|
662
668
|
taskThreadContext,
|
|
663
669
|
});
|
|
@@ -752,6 +758,7 @@ export class AgentsHost {
|
|
|
752
758
|
const executed = await this.executeTurn(msg, normalized.authorId, normalized.content, {
|
|
753
759
|
kickoff: protocolCandidate,
|
|
754
760
|
silent: true,
|
|
761
|
+
incomingParts: normalized.incomingParts,
|
|
755
762
|
});
|
|
756
763
|
if (executed.stale) {
|
|
757
764
|
return null;
|
|
@@ -912,6 +919,7 @@ export class AgentsHost {
|
|
|
912
919
|
authorId: normalized.authorId,
|
|
913
920
|
authorKind: 'human',
|
|
914
921
|
content: normalized.content,
|
|
922
|
+
incomingParts: normalized.incomingParts,
|
|
915
923
|
});
|
|
916
924
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
917
925
|
return;
|
|
@@ -924,6 +932,7 @@ export class AgentsHost {
|
|
|
924
932
|
ownerAgentId: selfAgentId,
|
|
925
933
|
anchorAuthorId: normalized.authorId,
|
|
926
934
|
anchorContent: normalized.content,
|
|
935
|
+
anchorIncomingParts: normalized.incomingParts,
|
|
927
936
|
anchorMessageId: msg.message_id ?? randomUUID(),
|
|
928
937
|
participantIds: protocolRequest.participantIds,
|
|
929
938
|
issuerId: protocolRequest.issuerId,
|
|
@@ -946,6 +955,7 @@ export class AgentsHost {
|
|
|
946
955
|
authorId: normalized.authorId,
|
|
947
956
|
authorKind: 'human',
|
|
948
957
|
content: normalized.content,
|
|
958
|
+
incomingParts: normalized.incomingParts,
|
|
949
959
|
});
|
|
950
960
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
951
961
|
return;
|
|
@@ -1014,6 +1024,7 @@ export class AgentsHost {
|
|
|
1014
1024
|
authorId: normalized.authorId,
|
|
1015
1025
|
authorKind: 'agent',
|
|
1016
1026
|
content: normalized.content,
|
|
1027
|
+
incomingParts: normalized.incomingParts,
|
|
1017
1028
|
});
|
|
1018
1029
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
1019
1030
|
}
|
|
@@ -1131,6 +1142,7 @@ export class AgentsHost {
|
|
|
1131
1142
|
channelState.activeTurn = activeTurn;
|
|
1132
1143
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1133
1144
|
activeTurn,
|
|
1145
|
+
incomingParts: message.incomingParts,
|
|
1134
1146
|
incomingAuthorKind: message.authorKind,
|
|
1135
1147
|
attentionSnapshot,
|
|
1136
1148
|
taskThreadContext,
|
|
@@ -1234,6 +1246,7 @@ export class AgentsHost {
|
|
|
1234
1246
|
channelState.activeTurn = activeTurn;
|
|
1235
1247
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1236
1248
|
activeTurn,
|
|
1249
|
+
incomingParts: message.incomingParts,
|
|
1237
1250
|
incomingAuthorKind: message.authorKind,
|
|
1238
1251
|
attentionSnapshot,
|
|
1239
1252
|
taskThreadContext,
|
|
@@ -1541,59 +1554,52 @@ export class AgentsHost {
|
|
|
1541
1554
|
}
|
|
1542
1555
|
async buildResumedProtocolMessage(channelId, protocolState) {
|
|
1543
1556
|
if (protocolState.currentTurnIndex === 0) {
|
|
1557
|
+
const anchorIncomingParts = await this.ensureProtocolAnchorIncomingParts(channelId, protocolState);
|
|
1558
|
+
const anchorIncomingContent = buildHostedIncomingContentText(anchorIncomingParts);
|
|
1544
1559
|
return {
|
|
1545
1560
|
kind: 'protocol-turn',
|
|
1546
1561
|
raw: {
|
|
1547
1562
|
channel_id: channelId,
|
|
1548
1563
|
message_id: protocolState.anchorMessageId,
|
|
1549
1564
|
user_id: protocolState.anchorAuthorId,
|
|
1550
|
-
content:
|
|
1551
|
-
body:
|
|
1565
|
+
content: anchorIncomingContent,
|
|
1566
|
+
body: anchorIncomingContent,
|
|
1567
|
+
attachments: extractHostedMessageAttachments(anchorIncomingParts),
|
|
1552
1568
|
type: 'message',
|
|
1553
1569
|
},
|
|
1554
1570
|
authorId: protocolState.anchorAuthorId,
|
|
1555
1571
|
authorKind: 'human',
|
|
1556
|
-
content:
|
|
1572
|
+
content: anchorIncomingContent,
|
|
1573
|
+
incomingParts: anchorIncomingParts,
|
|
1557
1574
|
};
|
|
1558
1575
|
}
|
|
1559
1576
|
if (!protocolState.lastProtocolMessageId) {
|
|
1560
1577
|
return null;
|
|
1561
1578
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
limit: 200,
|
|
1579
|
+
const priorMessage = await this.findChannelHistoryMessageById(channelId, protocolState.lastProtocolMessageId);
|
|
1580
|
+
if (priorMessage) {
|
|
1581
|
+
const incomingParts = buildHostedTurnContentParts({
|
|
1582
|
+
text: priorMessage.body,
|
|
1583
|
+
attachments: priorMessage.attachments,
|
|
1568
1584
|
});
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
}
|
|
1587
|
-
if (history.length < 200) {
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
const oldestCreatedAt = history.reduce((oldest, entry) => {
|
|
1591
|
-
return oldest == null || entry.createdAt < oldest ? entry.createdAt : oldest;
|
|
1592
|
-
}, null);
|
|
1593
|
-
if (oldestCreatedAt == null || (before != null && oldestCreatedAt >= before)) {
|
|
1594
|
-
break;
|
|
1595
|
-
}
|
|
1596
|
-
before = oldestCreatedAt;
|
|
1585
|
+
const incomingContent = buildHostedIncomingContentText(incomingParts);
|
|
1586
|
+
return {
|
|
1587
|
+
kind: 'protocol-turn',
|
|
1588
|
+
raw: {
|
|
1589
|
+
channel_id: channelId,
|
|
1590
|
+
message_id: priorMessage.id,
|
|
1591
|
+
user_id: priorMessage.authorId,
|
|
1592
|
+
content: incomingContent,
|
|
1593
|
+
body: incomingContent,
|
|
1594
|
+
attachments: priorMessage.attachments,
|
|
1595
|
+
type: 'message',
|
|
1596
|
+
created_at: priorMessage.createdAt,
|
|
1597
|
+
},
|
|
1598
|
+
authorId: priorMessage.authorId,
|
|
1599
|
+
authorKind: 'agent',
|
|
1600
|
+
content: incomingContent,
|
|
1601
|
+
incomingParts,
|
|
1602
|
+
};
|
|
1597
1603
|
}
|
|
1598
1604
|
return null;
|
|
1599
1605
|
}
|
|
@@ -1604,6 +1610,7 @@ export class AgentsHost {
|
|
|
1604
1610
|
ownerAgentId,
|
|
1605
1611
|
anchorAuthorId: protocolState.anchorAuthorId,
|
|
1606
1612
|
anchorContent: protocolState.anchorContent,
|
|
1613
|
+
anchorIncomingParts: protocolState.anchorIncomingParts,
|
|
1607
1614
|
anchorMessageId: protocolState.anchorMessageId,
|
|
1608
1615
|
participantIds: protocolState.participantIds,
|
|
1609
1616
|
issuerId: protocolState.issuerId,
|
|
@@ -1682,18 +1689,36 @@ export class AgentsHost {
|
|
|
1682
1689
|
await this.persistProtocolState(channelId, protocolState);
|
|
1683
1690
|
return;
|
|
1684
1691
|
}
|
|
1692
|
+
const anchorIncomingParts = await this.ensureProtocolAnchorIncomingParts(channelId, protocolState);
|
|
1693
|
+
const anchorIncomingContent = buildHostedIncomingContentText(anchorIncomingParts);
|
|
1685
1694
|
const fallbackMessage = {
|
|
1686
1695
|
channel_id: channelId,
|
|
1687
1696
|
message_id: protocolState.anchorMessageId,
|
|
1688
1697
|
user_id: protocolState.anchorAuthorId,
|
|
1689
|
-
content:
|
|
1690
|
-
body:
|
|
1698
|
+
content: anchorIncomingContent,
|
|
1699
|
+
body: anchorIncomingContent,
|
|
1700
|
+
attachments: extractHostedMessageAttachments(anchorIncomingParts),
|
|
1691
1701
|
type: 'message',
|
|
1692
1702
|
};
|
|
1693
1703
|
this.cancelProtocolState(channelId, channelState);
|
|
1694
1704
|
const taskThreadContext = await this.buildTurnTaskThreadContext(fallbackMessage, channelId);
|
|
1695
1705
|
const attentionSnapshot = this.getAttentionSnapshotForTurn(channelId, taskThreadContext);
|
|
1696
|
-
await this.runUngatedTurn(fallbackMessage, protocolState.anchorAuthorId,
|
|
1706
|
+
await this.runUngatedTurn(fallbackMessage, protocolState.anchorAuthorId, anchorIncomingContent, taskThreadContext, attentionSnapshot, anchorIncomingParts);
|
|
1707
|
+
}
|
|
1708
|
+
async ensureProtocolAnchorIncomingParts(channelId, protocolState) {
|
|
1709
|
+
if (protocolState.anchorIncomingParts) {
|
|
1710
|
+
return protocolState.anchorIncomingParts;
|
|
1711
|
+
}
|
|
1712
|
+
const anchorMessage = await this.findChannelHistoryMessageById(channelId, protocolState.anchorMessageId);
|
|
1713
|
+
if (!anchorMessage) {
|
|
1714
|
+
return buildHostedTurnContentParts({ text: protocolState.anchorContent });
|
|
1715
|
+
}
|
|
1716
|
+
const anchorIncomingParts = buildHostedTurnContentParts({
|
|
1717
|
+
text: protocolState.anchorContent,
|
|
1718
|
+
attachments: anchorMessage.attachments,
|
|
1719
|
+
});
|
|
1720
|
+
protocolState.anchorIncomingParts = anchorIncomingParts;
|
|
1721
|
+
return anchorIncomingParts;
|
|
1697
1722
|
}
|
|
1698
1723
|
finishProtocolState(channelId, channelState, protocolState, lastMessageId) {
|
|
1699
1724
|
protocolState.active = false;
|
|
@@ -1733,6 +1758,36 @@ export class AgentsHost {
|
|
|
1733
1758
|
}
|
|
1734
1759
|
return matched;
|
|
1735
1760
|
}
|
|
1761
|
+
async findChannelHistoryMessageById(channelId, messageId) {
|
|
1762
|
+
let before;
|
|
1763
|
+
for (;;) {
|
|
1764
|
+
const history = await this.borgee.readChannelHistory({
|
|
1765
|
+
channelId,
|
|
1766
|
+
before,
|
|
1767
|
+
limit: 200,
|
|
1768
|
+
});
|
|
1769
|
+
const matched = history.find((entry) => entry.id === messageId);
|
|
1770
|
+
if (matched) {
|
|
1771
|
+
return {
|
|
1772
|
+
...matched,
|
|
1773
|
+
...(matched.attachments
|
|
1774
|
+
? { attachments: matched.attachments.map(normalizeHostedMessageAttachment) }
|
|
1775
|
+
: {}),
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
if (history.length < 200) {
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
const oldestCreatedAt = history.reduce((oldest, entry) => {
|
|
1782
|
+
return oldest == null || entry.createdAt < oldest ? entry.createdAt : oldest;
|
|
1783
|
+
}, null);
|
|
1784
|
+
if (oldestCreatedAt == null || (before != null && oldestCreatedAt >= before)) {
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
before = oldestCreatedAt;
|
|
1788
|
+
}
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1736
1791
|
resolveAttentionStatePath(channelId) {
|
|
1737
1792
|
return resolveAttentionStatePath(this.config.stateRootDir, channelId);
|
|
1738
1793
|
}
|
|
@@ -2037,10 +2092,11 @@ export class AgentsHost {
|
|
|
2037
2092
|
reason: 'wake-suppressed-attention-policy',
|
|
2038
2093
|
});
|
|
2039
2094
|
}
|
|
2040
|
-
async runUngatedTurn(msg, authorId, content, taskThreadContext, attentionSnapshot) {
|
|
2095
|
+
async runUngatedTurn(msg, authorId, content, taskThreadContext, attentionSnapshot, incomingParts) {
|
|
2041
2096
|
const executed = await this.executeTurn(msg, authorId, content, {
|
|
2042
2097
|
attentionSnapshot,
|
|
2043
2098
|
taskThreadContext,
|
|
2099
|
+
incomingParts,
|
|
2044
2100
|
});
|
|
2045
2101
|
if (executed.completed && executed.reply) {
|
|
2046
2102
|
this.updateAwaitingUserState(msg.channel_id, executed.reply.awaitingUser);
|
|
@@ -2133,6 +2189,11 @@ export class AgentsHost {
|
|
|
2133
2189
|
: undefined;
|
|
2134
2190
|
const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
|
|
2135
2191
|
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn();
|
|
2192
|
+
const incomingParts = options?.incomingParts
|
|
2193
|
+
?? buildHostedTurnContentParts({
|
|
2194
|
+
text: String(msg.content ?? msg.body ?? content),
|
|
2195
|
+
attachments: msg.attachments,
|
|
2196
|
+
});
|
|
2136
2197
|
try {
|
|
2137
2198
|
reply = await this.provider.generateReply({
|
|
2138
2199
|
agentName: this.config.agent.agentName,
|
|
@@ -2140,6 +2201,7 @@ export class AgentsHost {
|
|
|
2140
2201
|
channelId,
|
|
2141
2202
|
incomingAuthorId: authorId,
|
|
2142
2203
|
incomingContent: content,
|
|
2204
|
+
incomingParts,
|
|
2143
2205
|
incomingEventKind: msg.type,
|
|
2144
2206
|
incomingMessageType: msg.message_type,
|
|
2145
2207
|
collaboration: turnExecutionId || options?.kickoff
|
|
@@ -2683,17 +2745,22 @@ export class AgentsHost {
|
|
|
2683
2745
|
});
|
|
2684
2746
|
return null;
|
|
2685
2747
|
}
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2748
|
+
const incomingParts = buildHostedTurnContentParts({
|
|
2749
|
+
text: String(msg.content ?? msg.body ?? ''),
|
|
2750
|
+
attachments: msg.attachments,
|
|
2751
|
+
});
|
|
2752
|
+
if (incomingParts.length === 0) {
|
|
2688
2753
|
this.logger.debug('ignored empty message', {
|
|
2689
2754
|
channelId: msg.channel_id,
|
|
2690
2755
|
messageId: msg.message_id,
|
|
2691
2756
|
});
|
|
2692
2757
|
return null;
|
|
2693
2758
|
}
|
|
2759
|
+
const content = buildHostedIncomingContentText(incomingParts);
|
|
2694
2760
|
return {
|
|
2695
2761
|
authorId,
|
|
2696
2762
|
content,
|
|
2763
|
+
incomingParts,
|
|
2697
2764
|
};
|
|
2698
2765
|
}
|
|
2699
2766
|
// Only a task assigned to this agent drives a task-assignment turn: it keeps the message a
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBorgeePlugin, } from '../plugin-sdk.js';
|
|
2
|
+
import { normalizeHostedMessageAttachment } from '../hosted-turn-content.js';
|
|
2
3
|
/**
|
|
3
4
|
* Thin adapter over `@borgee/plugin-sdk` (the same BPP/`/ws/plugin` SDK used by
|
|
4
5
|
* the OpenClaw plugin) that implements the minimal `ChatControlPlane` surface
|
|
@@ -97,6 +98,7 @@ export class SdkChatControlPlane {
|
|
|
97
98
|
authorId: message.authorId,
|
|
98
99
|
body: message.body,
|
|
99
100
|
contentType: message.contentType,
|
|
101
|
+
attachments: message.attachments?.map(normalizeHostedMessageAttachment),
|
|
100
102
|
type: message.type,
|
|
101
103
|
replyToId: message.replyToId,
|
|
102
104
|
createdAt: message.createdAt,
|
|
@@ -142,6 +144,7 @@ export function mapInboundToChannelMessage(event) {
|
|
|
142
144
|
content: event.message?.body,
|
|
143
145
|
body: event.message?.body,
|
|
144
146
|
content_type: event.message?.contentType,
|
|
147
|
+
attachments: event.message?.attachments?.map(normalizeHostedMessageAttachment),
|
|
145
148
|
created_at: event.createdAt,
|
|
146
149
|
};
|
|
147
150
|
}
|
|
@@ -21,7 +21,7 @@ export interface PreparedChannelContext {
|
|
|
21
21
|
directoryPath: string;
|
|
22
22
|
payload: ChannelContextPayload;
|
|
23
23
|
payloadPath?: string;
|
|
24
|
-
|
|
24
|
+
gatewayCredentialPath?: string;
|
|
25
25
|
skillRuntime?: SkillRuntimeBootstrapMetadata;
|
|
26
26
|
localhostGateway?: LocalhostGatewayBootstrapMetadata;
|
|
27
27
|
taskWorkspace?: TaskWorkspaceContext;
|
|
@@ -83,6 +83,7 @@ interface ContextFileSystem {
|
|
|
83
83
|
mode?: number;
|
|
84
84
|
}): Promise<void>;
|
|
85
85
|
unlink(path: string): Promise<void>;
|
|
86
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
86
87
|
access(path: string): Promise<void>;
|
|
87
88
|
}
|
|
88
89
|
interface FileChannelContextStoreOptions {
|
|
@@ -98,8 +99,9 @@ export declare function resolveChannelContextDirectory(stateRootDir: string, cha
|
|
|
98
99
|
export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
|
|
99
100
|
export declare function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir: string): string;
|
|
100
101
|
export declare function resolveTaskWorkspaceDirectory(startupWorkspaceRootDir: string, channelId: string, taskId: string): string;
|
|
101
|
-
export declare function
|
|
102
|
-
export declare function
|
|
102
|
+
export declare function resolveChannelGatewayCredentialPath(stateRootDir: string, channelId: string): string;
|
|
103
|
+
export declare function resolveGatewayCredentialPathFromPayloadPath(payloadPath: string): string;
|
|
104
|
+
export declare function isGatewayCredentialSidecarBasename(basename: string): boolean;
|
|
103
105
|
export declare function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl: string, fileSystem?: Pick<ContextFileSystem, 'access'>): Promise<SkillRuntimeBootstrapMetadata>;
|
|
104
106
|
export declare class FileChannelContextStore implements ChannelContextStore {
|
|
105
107
|
private readonly stateRootDir;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
const CHANNEL_CONTEXT_ROOT_DIRNAME = 'channel-context';
|
|
5
6
|
const CHANNEL_CONTEXT_PAYLOAD_FILENAME = 'context.json';
|
|
6
|
-
const
|
|
7
|
+
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
7
8
|
const TASK_WORKSPACE_ROOT_DIRNAME = '.borgee-task-workspaces';
|
|
8
9
|
export class ChannelContextPreparationError extends Error {
|
|
9
10
|
partialContext;
|
|
@@ -29,6 +30,9 @@ const DEFAULT_FILE_SYSTEM = {
|
|
|
29
30
|
async unlink(path) {
|
|
30
31
|
await fs.unlink(path);
|
|
31
32
|
},
|
|
33
|
+
async rename(oldPath, newPath) {
|
|
34
|
+
await fs.rename(oldPath, newPath);
|
|
35
|
+
},
|
|
32
36
|
async access(path) {
|
|
33
37
|
await fs.access(path);
|
|
34
38
|
},
|
|
@@ -45,7 +49,7 @@ function toSkillRuntimePayload(skillRuntime) {
|
|
|
45
49
|
pythonCliPath: skillRuntime.pythonCliPath,
|
|
46
50
|
};
|
|
47
51
|
}
|
|
48
|
-
function
|
|
52
|
+
function buildBorgeeAgentGatewayCredential(channelId, localhostGateway) {
|
|
49
53
|
if (!localhostGateway) {
|
|
50
54
|
return undefined;
|
|
51
55
|
}
|
|
@@ -53,6 +57,7 @@ function buildLocalhostGatewayAuthPayload(channelId, localhostGateway) {
|
|
|
53
57
|
schemaVersion: 1,
|
|
54
58
|
channelId,
|
|
55
59
|
localhostGateway: {
|
|
60
|
+
baseUrl: localhostGateway.baseUrl,
|
|
56
61
|
token: localhostGateway.token,
|
|
57
62
|
},
|
|
58
63
|
};
|
|
@@ -144,17 +149,28 @@ export function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir) {
|
|
|
144
149
|
export function resolveTaskWorkspaceDirectory(startupWorkspaceRootDir, channelId, taskId) {
|
|
145
150
|
return join(resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir), encodeChannelPathSegment(channelId), encodeChannelPathSegment(taskId));
|
|
146
151
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return join(
|
|
152
|
+
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
153
|
+
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
154
|
+
}
|
|
155
|
+
export function resolveGatewayCredentialPathFromPayloadPath(payloadPath) {
|
|
156
|
+
return join(dirname(payloadPath), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
152
157
|
}
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Every basename a gateway credential can carry in a channel context directory, including the ones
|
|
160
|
+
* this host no longer writes. The directory holds at most one live credential — the token is
|
|
161
|
+
* reissued every turn — and this sweep is the only thing that ever unlinks the others, so a name
|
|
162
|
+
* dropped from this list leaves a 0600 file behind that still looks live.
|
|
163
|
+
*/
|
|
164
|
+
const GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES = [
|
|
165
|
+
'.borgee-agent-gateway.',
|
|
166
|
+
'.borgee-agent-bootstrap.',
|
|
167
|
+
'.localhost-gateway-auth.',
|
|
168
|
+
];
|
|
169
|
+
export function isGatewayCredentialSidecarBasename(basename) {
|
|
170
|
+
return basename.endsWith('.json')
|
|
171
|
+
&& GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES.some((prefix) => basename.startsWith(prefix));
|
|
155
172
|
}
|
|
156
|
-
|
|
157
|
-
async function pruneLegacyGatewayAuthSidecars(fileSystem, directoryPath) {
|
|
173
|
+
async function pruneGatewayCredentialSidecars(fileSystem, directoryPath, keepBasename) {
|
|
158
174
|
let entries;
|
|
159
175
|
try {
|
|
160
176
|
entries = await fileSystem.readdir(directoryPath);
|
|
@@ -167,8 +183,7 @@ async function pruneLegacyGatewayAuthSidecars(fileSystem, directoryPath) {
|
|
|
167
183
|
throw error;
|
|
168
184
|
}
|
|
169
185
|
await Promise.all(entries
|
|
170
|
-
.filter((entry) => entry
|
|
171
|
-
&& entry.endsWith('.json'))
|
|
186
|
+
.filter((entry) => isGatewayCredentialSidecarBasename(entry) && entry !== keepBasename)
|
|
172
187
|
.map(async (entry) => {
|
|
173
188
|
await fileSystem.unlink(join(directoryPath, entry)).catch((error) => {
|
|
174
189
|
if (error.code !== 'ENOENT') {
|
|
@@ -196,8 +211,8 @@ export async function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl, fileSystem
|
|
|
196
211
|
const packageRootPath = await resolvePackageRootFromModuleUrl(moduleUrl, fileSystem);
|
|
197
212
|
const skillDirectoryPath = join(packageRootPath, 'skills', BORGEE_AGENT_SKILL_DIRNAME);
|
|
198
213
|
const skillMarkdownPath = join(skillDirectoryPath, 'SKILL.md');
|
|
199
|
-
const nodeCliPath = join(skillDirectoryPath, 'borgee-agent.mjs');
|
|
200
|
-
const pythonCliPath = join(skillDirectoryPath, 'borgee-agent.py');
|
|
214
|
+
const nodeCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.mjs');
|
|
215
|
+
const pythonCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.py');
|
|
201
216
|
await Promise.all([
|
|
202
217
|
fileSystem.access(skillDirectoryPath),
|
|
203
218
|
fileSystem.access(skillMarkdownPath),
|
|
@@ -249,7 +264,7 @@ export class FileChannelContextStore {
|
|
|
249
264
|
const auxiliaryCollaborationEnabled = collaborationCommandsEnabled && collaborationRoutesAllowedForTurnMode;
|
|
250
265
|
const directoryPath = resolveChannelContextDirectory(this.stateRootDir, input.channelId);
|
|
251
266
|
const payloadPath = resolveChannelContextPayloadPath(this.stateRootDir, input.channelId);
|
|
252
|
-
const
|
|
267
|
+
const gatewayCredentialPath = resolveGatewayCredentialPathFromPayloadPath(payloadPath);
|
|
253
268
|
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(payloadPath, this.fileSystem);
|
|
254
269
|
const skillRuntime = this.skillRuntimeEnabled
|
|
255
270
|
? await this.resolveSkillRuntimeBestEffort()
|
|
@@ -292,15 +307,15 @@ export class FileChannelContextStore {
|
|
|
292
307
|
...(taskWorkspace ? { taskWorkspace } : {}),
|
|
293
308
|
}
|
|
294
309
|
: undefined);
|
|
295
|
-
const
|
|
310
|
+
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
296
311
|
let taskWorkspaceMaterialized = false;
|
|
297
312
|
let payloadWritten = false;
|
|
298
|
-
let
|
|
313
|
+
let gatewayCredentialWritten = false;
|
|
299
314
|
const preparedContext = {
|
|
300
315
|
directoryPath,
|
|
301
316
|
payload,
|
|
302
317
|
payloadPath,
|
|
303
|
-
|
|
318
|
+
gatewayCredentialPath: gatewayCredential ? gatewayCredentialPath : undefined,
|
|
304
319
|
skillRuntime,
|
|
305
320
|
localhostGateway,
|
|
306
321
|
taskWorkspace,
|
|
@@ -311,19 +326,20 @@ export class FileChannelContextStore {
|
|
|
311
326
|
await this.fileSystem.mkdir(taskWorkspace.rootPath, { recursive: true, mode: 0o700 });
|
|
312
327
|
taskWorkspaceMaterialized = true;
|
|
313
328
|
}
|
|
314
|
-
await
|
|
329
|
+
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
315
330
|
await this.fileSystem.writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
316
331
|
payloadWritten = true;
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
if (gatewayCredential) {
|
|
333
|
+
// Replaced atomically: a CLI invocation left over from an earlier turn must read either
|
|
334
|
+
// the old credential or the new one, never a missing or half-written file, because the
|
|
335
|
+
// documented correction for a rotated token is a 401 and not a usage error. The staging
|
|
336
|
+
// name is itself a credential sidecar, so a crashed run's leftover is swept above.
|
|
337
|
+
const stagingCredentialPath = join(directoryPath, `.borgee-agent-gateway.${randomUUID()}.json`);
|
|
338
|
+
await this.fileSystem.writeFile(stagingCredentialPath, `${JSON.stringify(gatewayCredential, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
339
|
+
await this.fileSystem.rename(stagingCredentialPath, gatewayCredentialPath);
|
|
340
|
+
gatewayCredentialWritten = true;
|
|
320
341
|
}
|
|
321
342
|
else {
|
|
322
|
-
await this.fileSystem.unlink(gatewayAuthPath).catch((error) => {
|
|
323
|
-
if (error.code !== 'ENOENT') {
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
343
|
this.localhostGateway?.clearChannel(input.channelId);
|
|
328
344
|
}
|
|
329
345
|
if (issuedLocalhostGateway) {
|
|
@@ -336,7 +352,7 @@ export class FileChannelContextStore {
|
|
|
336
352
|
directoryPath,
|
|
337
353
|
payload,
|
|
338
354
|
payloadPath: payloadWritten ? payloadPath : undefined,
|
|
339
|
-
|
|
355
|
+
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
340
356
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
341
357
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
342
358
|
taskWorkspace: taskWorkspaceMaterialized ? taskWorkspace : undefined,
|