@canonmsg/claude-code-plugin 0.12.1 → 0.12.3
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/host.js +59 -112
- package/package.json +3 -3
- package/dist/host-runtime.d.ts +0 -134
- package/dist/host-runtime.js +0 -264
package/dist/host.js
CHANGED
|
@@ -26,8 +26,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
26
26
|
import { existsSync } from 'node:fs';
|
|
27
27
|
import { query, } from '@anthropic-ai/claude-agent-sdk';
|
|
28
28
|
import { isAnthropicImageAttachment, materializeMessageMedia, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
|
|
29
|
-
import { CLAUDE_PERMISSION_MODE_OPTIONS, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream,
|
|
30
|
-
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
29
|
+
import { CLAUDE_PERMISSION_MODE_OPTIONS, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, rtdbRead, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
31
30
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
32
31
|
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
33
32
|
import { runCli } from './cli-entry.js';
|
|
@@ -47,76 +46,16 @@ let runtimeMetadata = {
|
|
|
47
46
|
};
|
|
48
47
|
let runtimeMetadataRefresh = null;
|
|
49
48
|
function buildClaudeRuntimeDescriptor(input) {
|
|
50
|
-
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: 'workspace',
|
|
63
|
-
label: 'Project',
|
|
64
|
-
options: input.workspaces.map((workspace) => ({
|
|
65
|
-
value: workspace.id,
|
|
66
|
-
label: workspace.label,
|
|
67
|
-
...(workspace.description ? { description: workspace.description } : {}),
|
|
68
|
-
...(workspace.workspaceRootId ? { workspaceRootId: workspace.workspaceRootId } : {}),
|
|
69
|
-
...(workspace.workspaceRelativePath ? { workspaceRelativePath: workspace.workspaceRelativePath } : {}),
|
|
70
|
-
...(workspace.source ? { source: workspace.source } : {}),
|
|
71
|
-
})),
|
|
72
|
-
defaultValue: input.workspaces[0]?.id ?? null,
|
|
73
|
-
availability: 'setup',
|
|
74
|
-
liveBehavior: 'none',
|
|
75
|
-
selectionPolicy: 'inherit',
|
|
76
|
-
description: input.workspaceRoots?.length
|
|
77
|
-
? 'Choose one of the projects discovered inside the approved local roots for this host.'
|
|
78
|
-
: 'Choose one of the local projects advertised by this host.',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: 'executionMode',
|
|
82
|
-
label: 'Execution mode',
|
|
83
|
-
options: input.executionModes.map((mode) => ({
|
|
84
|
-
value: mode,
|
|
85
|
-
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared project',
|
|
86
|
-
description: mode === 'worktree'
|
|
87
|
-
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected project is a git repo.'
|
|
88
|
-
: 'Runs directly in the selected project folder. Changes happen there.',
|
|
89
|
-
})),
|
|
90
|
-
defaultValue: null,
|
|
91
|
-
availability: 'setup',
|
|
92
|
-
liveBehavior: 'none',
|
|
93
|
-
selectionPolicy: 'required_explicit',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
runtimeControls: [
|
|
97
|
-
{
|
|
98
|
-
id: 'permissionMode',
|
|
99
|
-
label: 'Permission mode',
|
|
100
|
-
options: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
101
|
-
defaultValue: 'default',
|
|
102
|
-
availability: 'setup_and_live',
|
|
103
|
-
liveBehavior: 'immediate',
|
|
104
|
-
selectionPolicy: 'inherit',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'effort',
|
|
108
|
-
label: 'Thinking level',
|
|
109
|
-
options: [
|
|
110
|
-
{ value: 'low', label: 'Low' },
|
|
111
|
-
{ value: 'medium', label: 'Medium' },
|
|
112
|
-
{ value: 'high', label: 'High' },
|
|
113
|
-
],
|
|
114
|
-
defaultValue: 'medium',
|
|
115
|
-
availability: 'setup_and_live',
|
|
116
|
-
liveBehavior: 'immediate',
|
|
117
|
-
selectionPolicy: 'inherit',
|
|
118
|
-
},
|
|
119
|
-
],
|
|
49
|
+
return buildFirstPartyCodingRuntimeDescriptor({
|
|
50
|
+
clientType: 'claude-code',
|
|
51
|
+
models: input.models,
|
|
52
|
+
workspaces: input.workspaces,
|
|
53
|
+
workspaceRoots: input.workspaceRoots,
|
|
54
|
+
executionModes: input.executionModes,
|
|
55
|
+
permissionModes: CLAUDE_PERMISSION_MODE_OPTIONS,
|
|
56
|
+
defaultPermissionMode: 'default',
|
|
57
|
+
streamingTextMode: 'delta',
|
|
58
|
+
admissionActions: HOST_ADMISSION_ACTIONS_DISABLED,
|
|
120
59
|
actions: [
|
|
121
60
|
{
|
|
122
61
|
id: 'plan',
|
|
@@ -204,11 +143,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
204
143
|
dispatch: { kind: 'signal', signal: 'interrupt' },
|
|
205
144
|
},
|
|
206
145
|
],
|
|
207
|
-
|
|
208
|
-
supportsInterrupt: true,
|
|
209
|
-
streamingTextMode: 'delta',
|
|
210
|
-
admissionActions: HOST_ADMISSION_ACTIONS_DISABLED,
|
|
211
|
-
};
|
|
146
|
+
});
|
|
212
147
|
}
|
|
213
148
|
function normalizeClaudeEffortLevel(value) {
|
|
214
149
|
return value === 'low' || value === 'medium' || value === 'high'
|
|
@@ -550,7 +485,7 @@ async function buildCanonUserContent(input) {
|
|
|
550
485
|
];
|
|
551
486
|
}
|
|
552
487
|
// ── Session factory ─────────────────────────────────────────────────
|
|
553
|
-
function createSession(conversationId, environment, agentId, client, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
488
|
+
function createSession(conversationId, environment, agentId, client, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
554
489
|
const { cwd } = environment;
|
|
555
490
|
// Per-session input stream with message queue
|
|
556
491
|
let resolveInput = null;
|
|
@@ -757,10 +692,9 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
757
692
|
session.streamingTimer = timer;
|
|
758
693
|
},
|
|
759
694
|
onWrite: (text) => {
|
|
760
|
-
|
|
695
|
+
runtimeState.writeStreaming(conversationId, {
|
|
761
696
|
text,
|
|
762
697
|
status: 'streaming',
|
|
763
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
764
698
|
}).catch(() => { });
|
|
765
699
|
},
|
|
766
700
|
});
|
|
@@ -773,7 +707,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
773
707
|
}
|
|
774
708
|
function clearStreaming() {
|
|
775
709
|
streamingBuffer.clear();
|
|
776
|
-
|
|
710
|
+
runtimeState.clearStreaming(conversationId).catch(() => { });
|
|
777
711
|
}
|
|
778
712
|
function clearIdleResetTimer() {
|
|
779
713
|
if (!session.idleResetTimer)
|
|
@@ -862,8 +796,11 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
862
796
|
}, FINAL_DELIVERY_RETRY_MS);
|
|
863
797
|
}
|
|
864
798
|
function writeState() {
|
|
865
|
-
writeSessionState(conversationId,
|
|
866
|
-
|
|
799
|
+
runtimeState.writeSessionState(conversationId, {
|
|
800
|
+
model: session.state.model,
|
|
801
|
+
permissionMode: session.state.permissionMode,
|
|
802
|
+
effort: session.state.effort,
|
|
803
|
+
contextUsage: session.state.contextUsage,
|
|
867
804
|
...(session.availableModels.length ? { availableModels: session.availableModels } : {}),
|
|
868
805
|
cwd,
|
|
869
806
|
executionMode: session.environment.mode,
|
|
@@ -877,7 +814,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
877
814
|
}).catch(() => { });
|
|
878
815
|
}
|
|
879
816
|
function writeTurn() {
|
|
880
|
-
writeTurnState(conversationId,
|
|
817
|
+
runtimeState.writeTurnState(conversationId, {
|
|
881
818
|
turnId: session.currentTurnId,
|
|
882
819
|
state: session.turnState,
|
|
883
820
|
queueDepth: session.pendingInputs.length,
|
|
@@ -1118,7 +1055,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
1118
1055
|
parent_tool_use_id: null,
|
|
1119
1056
|
});
|
|
1120
1057
|
}
|
|
1121
|
-
//
|
|
1058
|
+
// Refresh the canonical runtime descriptor after Claude reports supported models.
|
|
1122
1059
|
try {
|
|
1123
1060
|
const models = await q.supportedModels();
|
|
1124
1061
|
const modelList = toModelOptions(models);
|
|
@@ -1138,9 +1075,11 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
1138
1075
|
executionModes: config.availableExecutionModes,
|
|
1139
1076
|
}),
|
|
1140
1077
|
});
|
|
1141
|
-
writeSessionState(conversationId,
|
|
1142
|
-
|
|
1143
|
-
|
|
1078
|
+
runtimeState.writeSessionState(conversationId, {
|
|
1079
|
+
model: session.state.model,
|
|
1080
|
+
permissionMode: session.state.permissionMode,
|
|
1081
|
+
effort: session.state.effort,
|
|
1082
|
+
contextUsage: session.state.contextUsage,
|
|
1144
1083
|
cwd,
|
|
1145
1084
|
executionMode: session.environment.mode,
|
|
1146
1085
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
@@ -1244,6 +1183,11 @@ export async function main() {
|
|
|
1244
1183
|
lastStartedAt: new Date().toISOString(),
|
|
1245
1184
|
lastHeartbeatAt: new Date().toISOString(),
|
|
1246
1185
|
});
|
|
1186
|
+
const runtimeState = createRuntimeStatePublisher({
|
|
1187
|
+
agentId,
|
|
1188
|
+
clientType: 'claude-code',
|
|
1189
|
+
hostMode: true,
|
|
1190
|
+
});
|
|
1247
1191
|
let streamConnected = false;
|
|
1248
1192
|
const hostAvailableExecutionModes = [
|
|
1249
1193
|
...EXECUTION_ENVIRONMENT_MODES,
|
|
@@ -1294,6 +1238,7 @@ export async function main() {
|
|
|
1294
1238
|
runtime: runtimeDescriptor,
|
|
1295
1239
|
workspaceOptions,
|
|
1296
1240
|
defaultCwd: workingDir,
|
|
1241
|
+
extraSessionConfigFields: ['permissionMode', 'effort'],
|
|
1297
1242
|
liveSessionConfigByConversation: new Map(Array.from(sessions.values()).map((session) => {
|
|
1298
1243
|
const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
|
|
1299
1244
|
return [
|
|
@@ -1317,13 +1262,11 @@ export async function main() {
|
|
|
1317
1262
|
? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
|
|
1318
1263
|
: runtimeDescriptor.defaultWorkspaceId;
|
|
1319
1264
|
const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
|
|
1265
|
+
const descriptor = runtimeDescriptor.runtimeDescriptor;
|
|
1266
|
+
if (!descriptor)
|
|
1267
|
+
return;
|
|
1320
1268
|
const payload = buildClaudeRuntimeInfo({
|
|
1321
|
-
descriptor
|
|
1322
|
-
models: runtimeModels,
|
|
1323
|
-
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1324
|
-
workspaceRoots: workspaceRootMetadata,
|
|
1325
|
-
executionModes: hostAvailableExecutionModes,
|
|
1326
|
-
}),
|
|
1269
|
+
descriptor,
|
|
1327
1270
|
metadata,
|
|
1328
1271
|
workspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
1329
1272
|
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
@@ -1334,7 +1277,7 @@ export async function main() {
|
|
|
1334
1277
|
worktreePath: session?.environment.worktreePath ?? null,
|
|
1335
1278
|
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
1336
1279
|
});
|
|
1337
|
-
await writeRuntimeInfo(conversationId,
|
|
1280
|
+
await runtimeState.writeRuntimeInfo(conversationId, payload);
|
|
1338
1281
|
})).catch((error) => {
|
|
1339
1282
|
console.error('[canon-host] Failed to publish runtime info:', error);
|
|
1340
1283
|
});
|
|
@@ -1446,7 +1389,7 @@ export async function main() {
|
|
|
1446
1389
|
if (session.streamingTimer)
|
|
1447
1390
|
clearTimeout(session.streamingTimer);
|
|
1448
1391
|
session.streamingText = '';
|
|
1449
|
-
|
|
1392
|
+
runtimeState.clearStreaming(conversationId).catch(() => { });
|
|
1450
1393
|
if (session.idleResetTimer) {
|
|
1451
1394
|
clearTimeout(session.idleResetTimer);
|
|
1452
1395
|
session.idleResetTimer = null;
|
|
@@ -1455,8 +1398,8 @@ export async function main() {
|
|
|
1455
1398
|
clearTimeout(session.finalDeliveryTimer);
|
|
1456
1399
|
session.finalDeliveryTimer = null;
|
|
1457
1400
|
}
|
|
1458
|
-
clearSessionState(conversationId
|
|
1459
|
-
clearTurnState(conversationId
|
|
1401
|
+
runtimeState.clearSessionState(conversationId).catch(() => { });
|
|
1402
|
+
runtimeState.clearTurnState(conversationId).catch(() => { });
|
|
1460
1403
|
client.setTyping(conversationId, false).catch(() => { });
|
|
1461
1404
|
try {
|
|
1462
1405
|
session.query.close();
|
|
@@ -1510,7 +1453,7 @@ export async function main() {
|
|
|
1510
1453
|
executionMode: environment.mode,
|
|
1511
1454
|
});
|
|
1512
1455
|
const resumeId = savedSessionIds.get(resumeKey) ?? persisted?.claudeSessionId;
|
|
1513
|
-
const session = createSession(conversationId, environment, agentId, client, (convoId, sdkSessionId) => {
|
|
1456
|
+
const session = createSession(conversationId, environment, agentId, client, runtimeState, (convoId, sdkSessionId) => {
|
|
1514
1457
|
// Called when session processing loop ends — save session ID for resume
|
|
1515
1458
|
const existing = sessions.get(convoId);
|
|
1516
1459
|
if (existing) {
|
|
@@ -1533,8 +1476,8 @@ export async function main() {
|
|
|
1533
1476
|
}
|
|
1534
1477
|
}
|
|
1535
1478
|
sessions.delete(convoId);
|
|
1536
|
-
clearSessionState(convoId
|
|
1537
|
-
clearTurnState(convoId
|
|
1479
|
+
runtimeState.clearSessionState(convoId).catch(() => { });
|
|
1480
|
+
runtimeState.clearTurnState(convoId).catch(() => { });
|
|
1538
1481
|
}, (descriptor) => {
|
|
1539
1482
|
runtimeDescriptor = descriptor;
|
|
1540
1483
|
void publishRuntimeHeartbeat();
|
|
@@ -1794,7 +1737,7 @@ export async function main() {
|
|
|
1794
1737
|
},
|
|
1795
1738
|
onDisconnected: () => {
|
|
1796
1739
|
streamConnected = false;
|
|
1797
|
-
|
|
1740
|
+
runtimeState.clearAgentRuntime().catch(() => { });
|
|
1798
1741
|
console.error('[canon-host] SSE disconnected');
|
|
1799
1742
|
},
|
|
1800
1743
|
onError: (err) => console.error(`[canon-host] SSE error: ${err.message}`),
|
|
@@ -1852,9 +1795,11 @@ export async function main() {
|
|
|
1852
1795
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
|
|
1853
1796
|
}
|
|
1854
1797
|
}
|
|
1855
|
-
writeSessionState(convoId,
|
|
1856
|
-
|
|
1857
|
-
|
|
1798
|
+
runtimeState.writeSessionState(convoId, {
|
|
1799
|
+
model: session.state.model,
|
|
1800
|
+
permissionMode: session.state.permissionMode,
|
|
1801
|
+
effort: session.state.effort,
|
|
1802
|
+
contextUsage: session.state.contextUsage,
|
|
1858
1803
|
cwd: session.cwd,
|
|
1859
1804
|
executionMode: session.environment.mode,
|
|
1860
1805
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
@@ -1880,7 +1825,7 @@ export async function main() {
|
|
|
1880
1825
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Interrupt signal`);
|
|
1881
1826
|
try {
|
|
1882
1827
|
session.turnState = 'interrupted';
|
|
1883
|
-
writeTurnState(convoId,
|
|
1828
|
+
runtimeState.writeTurnState(convoId, {
|
|
1884
1829
|
turnId: session.currentTurnId,
|
|
1885
1830
|
state: 'interrupted',
|
|
1886
1831
|
queueDepth: session.pendingInputs.length,
|
|
@@ -1896,7 +1841,7 @@ export async function main() {
|
|
|
1896
1841
|
session.streamingTimer = null;
|
|
1897
1842
|
}
|
|
1898
1843
|
session.streamingText = '';
|
|
1899
|
-
|
|
1844
|
+
runtimeState.clearStreaming(convoId).catch(() => { });
|
|
1900
1845
|
client.setTyping(convoId, false).catch(() => { });
|
|
1901
1846
|
}
|
|
1902
1847
|
catch (err) {
|
|
@@ -1917,9 +1862,11 @@ export async function main() {
|
|
|
1917
1862
|
// ── Heartbeat (per-session) ──
|
|
1918
1863
|
const heartbeat = setInterval(() => {
|
|
1919
1864
|
for (const session of sessions.values()) {
|
|
1920
|
-
writeSessionState(session.conversationId,
|
|
1921
|
-
|
|
1922
|
-
|
|
1865
|
+
runtimeState.writeSessionState(session.conversationId, {
|
|
1866
|
+
model: session.state.model,
|
|
1867
|
+
permissionMode: session.state.permissionMode,
|
|
1868
|
+
effort: session.state.effort,
|
|
1869
|
+
contextUsage: session.state.contextUsage,
|
|
1923
1870
|
cwd: session.cwd,
|
|
1924
1871
|
executionMode: session.environment.mode,
|
|
1925
1872
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
@@ -1927,7 +1874,7 @@ export async function main() {
|
|
|
1927
1874
|
hostMode: true,
|
|
1928
1875
|
isActive: true,
|
|
1929
1876
|
}).catch(() => { });
|
|
1930
|
-
writeTurnState(session.conversationId,
|
|
1877
|
+
runtimeState.writeTurnState(session.conversationId, {
|
|
1931
1878
|
turnId: session.currentTurnId,
|
|
1932
1879
|
state: session.turnState,
|
|
1933
1880
|
queueDepth: session.pendingInputs.length,
|
|
@@ -1962,7 +1909,7 @@ export async function main() {
|
|
|
1962
1909
|
clearInterval(heartbeat);
|
|
1963
1910
|
clearInterval(idleCheck);
|
|
1964
1911
|
stream.stop();
|
|
1965
|
-
await
|
|
1912
|
+
await runtimeState.clearAgentRuntime().catch(() => { });
|
|
1966
1913
|
for (const convoId of [...sessions.keys()]) {
|
|
1967
1914
|
closeSession(convoId);
|
|
1968
1915
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
34
|
-
"@canonmsg/agent-sdk": "^0.
|
|
35
|
-
"@canonmsg/core": "^0.
|
|
34
|
+
"@canonmsg/agent-sdk": "^1.0.0",
|
|
35
|
+
"@canonmsg/core": "^0.15.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
package/dist/host-runtime.d.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-runtime helpers, inlined from @canonmsg/core.
|
|
3
|
-
*
|
|
4
|
-
* These helpers glue Canon host wrappers (Codex, Claude Code) to the Canon
|
|
5
|
-
* RTDB session/runtime surface. They used to live in
|
|
6
|
-
* `@canonmsg/core/src/host-runtime/` but were moved into each plugin in
|
|
7
|
-
* media-parity PR C so core does not have to carry host-specific concerns.
|
|
8
|
-
*
|
|
9
|
-
* Keep this file in lockstep with the equivalent file in
|
|
10
|
-
* `packages/codex-plugin/src/host-runtime.ts`. If you change the
|
|
11
|
-
* behavior here, update that copy too and adjust the shared golden
|
|
12
|
-
* fixture test (`packages/claude-code-plugin/src/host-runtime.test.ts`).
|
|
13
|
-
*/
|
|
14
|
-
import { type AgentClientType, type AgentRuntime, type CanonClient, type CanonConversation, type CanonMessage, type CanonMessagesPage, type MessageCreatedPayload, type ResolvedAgentBehaviorPolicy, type SessionWorkspaceConfig } from '@canonmsg/core';
|
|
15
|
-
export interface HostInboundParticipantContext {
|
|
16
|
-
conversationType: CanonConversation['type'] | 'unknown';
|
|
17
|
-
memberCount: number | null;
|
|
18
|
-
senderType: 'human' | 'ai_agent';
|
|
19
|
-
senderName: string;
|
|
20
|
-
isOwner: boolean;
|
|
21
|
-
mentionedAgent: boolean;
|
|
22
|
-
recentSenderTypes: Array<'human' | 'ai_agent'>;
|
|
23
|
-
recentHumanCount: number;
|
|
24
|
-
recentAgentCount: number;
|
|
25
|
-
consecutiveAgentTurns: number;
|
|
26
|
-
currentAgentStreakStartedByHuman: boolean;
|
|
27
|
-
}
|
|
28
|
-
type HostInboundMessage = {
|
|
29
|
-
text?: string | null;
|
|
30
|
-
contentType?: CanonMessage['contentType'] | null;
|
|
31
|
-
attachments?: CanonMessage['attachments'];
|
|
32
|
-
senderType?: CanonMessage['senderType'];
|
|
33
|
-
mentions?: string[] | null;
|
|
34
|
-
contactCard?: CanonMessage['contactCard'];
|
|
35
|
-
};
|
|
36
|
-
interface HostWorkspaceResolverOption {
|
|
37
|
-
id: string;
|
|
38
|
-
cwd: string;
|
|
39
|
-
}
|
|
40
|
-
export declare const HOST_ADMISSION_ACTION_CAPABILITIES: Readonly<{
|
|
41
|
-
canStartDirectConversation: false;
|
|
42
|
-
canSendContactRequest: false;
|
|
43
|
-
canApprovePendingContactRequests: false;
|
|
44
|
-
canRejectPendingContactRequests: false;
|
|
45
|
-
}>;
|
|
46
|
-
export declare function buildCanonHostPrompt(input: {
|
|
47
|
-
hostLabel: string;
|
|
48
|
-
content: string;
|
|
49
|
-
conversationId: string;
|
|
50
|
-
participantContext: HostInboundParticipantContext;
|
|
51
|
-
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
52
|
-
workSession?: MessageCreatedPayload['message']['workSession'];
|
|
53
|
-
workSessions?: MessageCreatedPayload['workSessions'];
|
|
54
|
-
buildInboundContextLines: (context: HostInboundParticipantContext) => string[];
|
|
55
|
-
}): string;
|
|
56
|
-
/**
|
|
57
|
-
* Render the **text portion** of an inbound Canon message. Images and other
|
|
58
|
-
* binary attachments are referenced by short placeholders — their actual
|
|
59
|
-
* bytes are delivered to the host as native vision/media inputs (Anthropic
|
|
60
|
-
* image blocks, Codex `-i` file args). URLs are intentionally *not* inlined,
|
|
61
|
-
* since the harness never needs to refetch and earlier `[Image: <url>]`
|
|
62
|
-
* inlining caused vision models to see a string about an image instead of
|
|
63
|
-
* the image itself.
|
|
64
|
-
*
|
|
65
|
-
* `materialized` may be passed so non-image attachments can reference a
|
|
66
|
-
* local path the agent can Read. Without it we fall back to an unadorned
|
|
67
|
-
* placeholder; the vision path still works because image blocks carry the
|
|
68
|
-
* bytes directly.
|
|
69
|
-
*/
|
|
70
|
-
export declare function renderCanonHostInboundContent(message: HostInboundMessage, materialized?: ReadonlyArray<{
|
|
71
|
-
kind: 'image' | 'audio' | 'file';
|
|
72
|
-
path: string;
|
|
73
|
-
fileName?: string;
|
|
74
|
-
durationMs?: number;
|
|
75
|
-
index: number;
|
|
76
|
-
}>): string;
|
|
77
|
-
export declare function buildHydratedInboundContext(input: {
|
|
78
|
-
agentId: string;
|
|
79
|
-
conversation: CanonConversation | null;
|
|
80
|
-
page?: CanonMessagesPage | null;
|
|
81
|
-
message: HostInboundMessage;
|
|
82
|
-
senderName: string;
|
|
83
|
-
isOwner: boolean;
|
|
84
|
-
}): {
|
|
85
|
-
participantContext: HostInboundParticipantContext;
|
|
86
|
-
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
87
|
-
workSessions: NonNullable<MessageCreatedPayload['workSessions']>;
|
|
88
|
-
hydratedFromPage: boolean;
|
|
89
|
-
};
|
|
90
|
-
export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
|
|
91
|
-
export declare function publishHostSessionSnapshots(input: {
|
|
92
|
-
conversationIds: string[];
|
|
93
|
-
agentId: string;
|
|
94
|
-
clientType: AgentClientType;
|
|
95
|
-
runtime: AgentRuntime;
|
|
96
|
-
workspaceOptions: HostWorkspaceResolverOption[];
|
|
97
|
-
defaultCwd: string;
|
|
98
|
-
liveSessionConfigByConversation?: ReadonlyMap<string, {
|
|
99
|
-
model?: string;
|
|
100
|
-
permissionMode?: string;
|
|
101
|
-
effort?: string;
|
|
102
|
-
runtimeControlValues?: Record<string, string>;
|
|
103
|
-
workspaceId?: string;
|
|
104
|
-
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
105
|
-
executionBranch?: string | null;
|
|
106
|
-
}>;
|
|
107
|
-
}): Promise<void>;
|
|
108
|
-
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
109
|
-
runtimeControlValues?: Record<string, string>;
|
|
110
|
-
}) | null;
|
|
111
|
-
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
112
|
-
conversationId: string;
|
|
113
|
-
agentId: string;
|
|
114
|
-
extraStringFields?: readonly TExtra[];
|
|
115
|
-
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
116
|
-
runtimeControlValues?: Record<string, string>;
|
|
117
|
-
}) | null>;
|
|
118
|
-
export declare function resolveHostWorkspaceCwd(input: {
|
|
119
|
-
workspaceOptions: HostWorkspaceResolverOption[];
|
|
120
|
-
config: {
|
|
121
|
-
workspaceId?: string;
|
|
122
|
-
retiredWorkspaceConfig?: boolean;
|
|
123
|
-
} | null;
|
|
124
|
-
defaultCwd: string;
|
|
125
|
-
}): string;
|
|
126
|
-
export declare function createConversationMetadataLoader(input: {
|
|
127
|
-
client: CanonClient;
|
|
128
|
-
conversationCache: Map<string, CanonConversation>;
|
|
129
|
-
cacheTtlMs?: number;
|
|
130
|
-
}): {
|
|
131
|
-
refreshConversationCache(force?: boolean): Promise<void>;
|
|
132
|
-
getConversationMeta(conversationId: string): Promise<CanonConversation | null>;
|
|
133
|
-
};
|
|
134
|
-
export {};
|
package/dist/host-runtime.js
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-runtime helpers, inlined from @canonmsg/core.
|
|
3
|
-
*
|
|
4
|
-
* These helpers glue Canon host wrappers (Codex, Claude Code) to the Canon
|
|
5
|
-
* RTDB session/runtime surface. They used to live in
|
|
6
|
-
* `@canonmsg/core/src/host-runtime/` but were moved into each plugin in
|
|
7
|
-
* media-parity PR C so core does not have to carry host-specific concerns.
|
|
8
|
-
*
|
|
9
|
-
* Keep this file in lockstep with the equivalent file in
|
|
10
|
-
* `packages/codex-plugin/src/host-runtime.ts`. If you change the
|
|
11
|
-
* behavior here, update that copy too and adjust the shared golden
|
|
12
|
-
* fixture test (`packages/claude-code-plugin/src/host-runtime.test.ts`).
|
|
13
|
-
*/
|
|
14
|
-
import { buildAgentSessionSnapshot, buildConversationWorktreeSpec, buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, patchAgentSessionSnapshot, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
|
|
15
|
-
export const HOST_ADMISSION_ACTION_CAPABILITIES = Object.freeze({
|
|
16
|
-
canStartDirectConversation: false,
|
|
17
|
-
canSendContactRequest: false,
|
|
18
|
-
canApprovePendingContactRequests: false,
|
|
19
|
-
canRejectPendingContactRequests: false,
|
|
20
|
-
});
|
|
21
|
-
export function buildCanonHostPrompt(input) {
|
|
22
|
-
const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
|
|
23
|
-
return [
|
|
24
|
-
`You are connected to Canon messaging through a ${input.hostLabel} host wrapper.`,
|
|
25
|
-
'Only the last assistant message from this turn will be delivered as the permanent Canon reply.',
|
|
26
|
-
'Short intermediate assistant messages may be shown as ephemeral status while you work.',
|
|
27
|
-
...input.buildInboundContextLines(input.participantContext),
|
|
28
|
-
...buildBehaviorPolicyLines(input.behavior),
|
|
29
|
-
...buildWorkSessionsPromptLines(resolvedWorkSessions),
|
|
30
|
-
'Canon participants may be humans or AI agents.',
|
|
31
|
-
'Honor the Canon behavior policy above when deciding how proactively to participate.',
|
|
32
|
-
...(resolvedWorkSessions.length > 0
|
|
33
|
-
? ['Honor the Canon work-session context above within its stated disclosure limits.']
|
|
34
|
-
: []),
|
|
35
|
-
`Conversation ID: ${input.conversationId}`,
|
|
36
|
-
'',
|
|
37
|
-
'New Canon message:',
|
|
38
|
-
input.content,
|
|
39
|
-
].join('\n');
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Render the **text portion** of an inbound Canon message. Images and other
|
|
43
|
-
* binary attachments are referenced by short placeholders — their actual
|
|
44
|
-
* bytes are delivered to the host as native vision/media inputs (Anthropic
|
|
45
|
-
* image blocks, Codex `-i` file args). URLs are intentionally *not* inlined,
|
|
46
|
-
* since the harness never needs to refetch and earlier `[Image: <url>]`
|
|
47
|
-
* inlining caused vision models to see a string about an image instead of
|
|
48
|
-
* the image itself.
|
|
49
|
-
*
|
|
50
|
-
* `materialized` may be passed so non-image attachments can reference a
|
|
51
|
-
* local path the agent can Read. Without it we fall back to an unadorned
|
|
52
|
-
* placeholder; the vision path still works because image blocks carry the
|
|
53
|
-
* bytes directly.
|
|
54
|
-
*/
|
|
55
|
-
export function renderCanonHostInboundContent(message, materialized) {
|
|
56
|
-
const body = message.text || '';
|
|
57
|
-
const placeholders = [];
|
|
58
|
-
const attachments = message.attachments ?? [];
|
|
59
|
-
for (let i = 0; i < attachments.length; i += 1) {
|
|
60
|
-
const att = attachments[i];
|
|
61
|
-
const mat = materialized?.find((m) => m.index === i) ?? null;
|
|
62
|
-
placeholders.push(describeAttachment(att, mat));
|
|
63
|
-
}
|
|
64
|
-
if (message.contentType === 'contact_card' && message.contactCard) {
|
|
65
|
-
placeholders.push(describeContactCard(message.contactCard));
|
|
66
|
-
}
|
|
67
|
-
const rendered = [...placeholders, body].filter(Boolean).join('\n');
|
|
68
|
-
return rendered || '[Empty message]';
|
|
69
|
-
}
|
|
70
|
-
function describeContactCard(card) {
|
|
71
|
-
const parts = [`${card.userType} · userId: ${card.userId}`];
|
|
72
|
-
if (card.ownerName)
|
|
73
|
-
parts.push(`owner: ${card.ownerName}`);
|
|
74
|
-
if (card.about)
|
|
75
|
-
parts.push(`about: ${card.about}`);
|
|
76
|
-
const identity = `📇 Contact card: "${card.displayName}" (${parts.join(' · ')}).`;
|
|
77
|
-
const missingCapabilities = [
|
|
78
|
-
!HOST_ADMISSION_ACTION_CAPABILITIES.canStartDirectConversation
|
|
79
|
-
? 'start a direct conversation'
|
|
80
|
-
: null,
|
|
81
|
-
!HOST_ADMISSION_ACTION_CAPABILITIES.canSendContactRequest
|
|
82
|
-
? 'send a contact request'
|
|
83
|
-
: null,
|
|
84
|
-
!HOST_ADMISSION_ACTION_CAPABILITIES.canApprovePendingContactRequests
|
|
85
|
-
? 'approve pending requests'
|
|
86
|
-
: null,
|
|
87
|
-
!HOST_ADMISSION_ACTION_CAPABILITIES.canRejectPendingContactRequests
|
|
88
|
-
? 'reject pending requests'
|
|
89
|
-
: null,
|
|
90
|
-
].filter(Boolean).join(', ');
|
|
91
|
-
const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
|
|
92
|
-
return `${identity}\n${hint}`;
|
|
93
|
-
}
|
|
94
|
-
function describeAttachment(attachment, materialized) {
|
|
95
|
-
if (attachment.kind === 'image') {
|
|
96
|
-
return '[Image attached]';
|
|
97
|
-
}
|
|
98
|
-
if (attachment.kind === 'audio') {
|
|
99
|
-
const durationMs = materialized?.durationMs ?? attachment.durationMs;
|
|
100
|
-
const duration = durationMs ? ` (${Math.round(durationMs / 1000)}s)` : '';
|
|
101
|
-
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
102
|
-
return `[Voice message${duration}${ref}]`;
|
|
103
|
-
}
|
|
104
|
-
// file
|
|
105
|
-
const label = materialized?.fileName ?? attachment.fileName ?? 'File';
|
|
106
|
-
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
107
|
-
return `[File: ${label}${ref}]`;
|
|
108
|
-
}
|
|
109
|
-
export function buildHydratedInboundContext(input) {
|
|
110
|
-
const history = buildParticipationHistorySnapshot(input.page?.messages ?? [], input.agentId);
|
|
111
|
-
return {
|
|
112
|
-
participantContext: {
|
|
113
|
-
conversationType: input.conversation?.type ?? 'unknown',
|
|
114
|
-
memberCount: input.conversation?.memberIds?.length ?? null,
|
|
115
|
-
senderType: input.message.senderType ?? 'human',
|
|
116
|
-
senderName: input.senderName,
|
|
117
|
-
isOwner: input.isOwner,
|
|
118
|
-
mentionedAgent: Array.isArray(input.message.mentions) && input.message.mentions.includes(input.agentId),
|
|
119
|
-
recentSenderTypes: history.recentSenderTypes,
|
|
120
|
-
recentHumanCount: history.recentHumanCount,
|
|
121
|
-
recentAgentCount: history.recentAgentCount,
|
|
122
|
-
consecutiveAgentTurns: history.consecutiveAgentTurns,
|
|
123
|
-
currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
|
|
124
|
-
},
|
|
125
|
-
behavior: input.page?.behavior ?? input.conversation?.behavior,
|
|
126
|
-
workSessions: input.page?.workSessions ?? [],
|
|
127
|
-
hydratedFromPage: input.page != null,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
export async function publishHostAgentRuntime(agentId, clientType, runtime) {
|
|
131
|
-
await rtdbWrite(`/agent-runtime/${agentId}`, {
|
|
132
|
-
clientType,
|
|
133
|
-
hostMode: true,
|
|
134
|
-
...runtime,
|
|
135
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
export async function publishHostSessionSnapshots(input) {
|
|
139
|
-
if (input.conversationIds.length === 0) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
await Promise.all(input.conversationIds.map(async (conversationId) => {
|
|
143
|
-
const persistedConfig = await loadHostSessionConfig({
|
|
144
|
-
conversationId,
|
|
145
|
-
agentId: input.agentId,
|
|
146
|
-
extraStringFields: ['permissionMode', 'effort'],
|
|
147
|
-
});
|
|
148
|
-
const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
|
|
149
|
-
const mergedConfig = {
|
|
150
|
-
...(persistedConfig ?? {}),
|
|
151
|
-
...(liveConfig ?? {}),
|
|
152
|
-
};
|
|
153
|
-
const snapshot = buildAgentSessionSnapshot({
|
|
154
|
-
conversationId,
|
|
155
|
-
agentId: input.agentId,
|
|
156
|
-
runtime: {
|
|
157
|
-
...input.runtime,
|
|
158
|
-
clientType: input.clientType,
|
|
159
|
-
hostMode: true,
|
|
160
|
-
},
|
|
161
|
-
sessionConfig: {
|
|
162
|
-
...(mergedConfig.model ? { model: mergedConfig.model } : {}),
|
|
163
|
-
...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
|
|
164
|
-
...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
|
|
165
|
-
...(mergedConfig.runtimeControlValues
|
|
166
|
-
? { runtimeControlValues: mergedConfig.runtimeControlValues }
|
|
167
|
-
: {}),
|
|
168
|
-
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
169
|
-
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
170
|
-
},
|
|
171
|
-
lastHeartbeatAt: undefined,
|
|
172
|
-
});
|
|
173
|
-
let executionBranch = liveConfig?.executionBranch ?? null;
|
|
174
|
-
if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
|
|
175
|
-
const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
|
|
176
|
-
if (workspace) {
|
|
177
|
-
executionBranch = buildConversationWorktreeSpec({
|
|
178
|
-
agentId: input.agentId,
|
|
179
|
-
conversationId,
|
|
180
|
-
workspaceCwd: workspace.cwd,
|
|
181
|
-
}).branch;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return patchAgentSessionSnapshot(conversationId, input.agentId, {
|
|
185
|
-
clientType: input.clientType,
|
|
186
|
-
hostMode: true,
|
|
187
|
-
model: snapshot.model ?? null,
|
|
188
|
-
permissionMode: snapshot.permissionMode ?? null,
|
|
189
|
-
effort: snapshot.effort ?? null,
|
|
190
|
-
runtimeControlValues: snapshot.runtimeControlValues ?? null,
|
|
191
|
-
workspaceId: snapshot.workspaceId ?? null,
|
|
192
|
-
executionMode: snapshot.executionMode ?? null,
|
|
193
|
-
executionBranch,
|
|
194
|
-
modelOptions: snapshot.modelOptions,
|
|
195
|
-
permissionModeOptions: snapshot.permissionModeOptions,
|
|
196
|
-
workspaceOptions: snapshot.workspaceOptions,
|
|
197
|
-
availableExecutionModes: snapshot.availableExecutionModes,
|
|
198
|
-
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
199
|
-
});
|
|
200
|
-
}));
|
|
201
|
-
}
|
|
202
|
-
export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
203
|
-
const baseConfig = readSessionWorkspaceConfig(raw);
|
|
204
|
-
if (!raw || typeof raw !== 'object') {
|
|
205
|
-
return baseConfig;
|
|
206
|
-
}
|
|
207
|
-
const data = raw;
|
|
208
|
-
const extraConfig = Object.fromEntries(extraStringFields.flatMap((field) => {
|
|
209
|
-
const value = normalizeOptionalString(data[field]);
|
|
210
|
-
return value ? [[field, value]] : [];
|
|
211
|
-
}));
|
|
212
|
-
const runtimeControlValues = Object.fromEntries(Object.entries(data.runtimeControlValues && typeof data.runtimeControlValues === 'object'
|
|
213
|
-
? data.runtimeControlValues
|
|
214
|
-
: {}).flatMap(([key, value]) => {
|
|
215
|
-
const normalizedValue = normalizeOptionalString(value);
|
|
216
|
-
return normalizedValue ? [[key, normalizedValue]] : [];
|
|
217
|
-
}));
|
|
218
|
-
return {
|
|
219
|
-
...(baseConfig ?? {}),
|
|
220
|
-
...extraConfig,
|
|
221
|
-
...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
export async function loadHostSessionConfig(input) {
|
|
225
|
-
const raw = await rtdbRead(`/session-config/${input.conversationId}/${input.agentId}`);
|
|
226
|
-
return readHostSessionConfig(raw, input.extraStringFields);
|
|
227
|
-
}
|
|
228
|
-
export function resolveHostWorkspaceCwd(input) {
|
|
229
|
-
return resolveConfiguredWorkspaceCwd(input);
|
|
230
|
-
}
|
|
231
|
-
export function createConversationMetadataLoader(input) {
|
|
232
|
-
const cacheTtlMs = input.cacheTtlMs ?? 10_000;
|
|
233
|
-
let conversationCacheLoadedAt = 0;
|
|
234
|
-
async function refreshConversationCache(force = false) {
|
|
235
|
-
if (!force
|
|
236
|
-
&& input.conversationCache.size > 0
|
|
237
|
-
&& Date.now() - conversationCacheLoadedAt < cacheTtlMs) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const conversations = await input.client.getConversations();
|
|
241
|
-
input.conversationCache.clear();
|
|
242
|
-
for (const conversation of conversations) {
|
|
243
|
-
input.conversationCache.set(conversation.id, conversation);
|
|
244
|
-
}
|
|
245
|
-
conversationCacheLoadedAt = Date.now();
|
|
246
|
-
}
|
|
247
|
-
async function getConversationMeta(conversationId) {
|
|
248
|
-
try {
|
|
249
|
-
await refreshConversationCache();
|
|
250
|
-
const cached = input.conversationCache.get(conversationId);
|
|
251
|
-
if (cached)
|
|
252
|
-
return cached;
|
|
253
|
-
await refreshConversationCache(true);
|
|
254
|
-
return input.conversationCache.get(conversationId) ?? null;
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
return input.conversationCache.get(conversationId) ?? null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
refreshConversationCache,
|
|
262
|
-
getConversationMeta,
|
|
263
|
-
};
|
|
264
|
-
}
|