@vellumai/assistant 0.3.16 → 0.3.18
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/ARCHITECTURE.md +70 -13
- package/README.md +6 -0
- package/docs/architecture/http-token-refresh.md +23 -1
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +4 -7
- package/src/__tests__/channel-guardian.test.ts +3 -1
- package/src/__tests__/checker.test.ts +79 -48
- package/src/__tests__/config-watcher.test.ts +11 -13
- package/src/__tests__/conversation-pairing.test.ts +103 -3
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -1
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -1
- package/src/__tests__/guardian-action-late-reply.test.ts +131 -0
- package/src/__tests__/guardian-action-store.test.ts +182 -0
- package/src/__tests__/guardian-dispatch.test.ts +120 -0
- package/src/__tests__/ipc-snapshot.test.ts +21 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -2
- package/src/__tests__/notification-broadcaster.test.ts +115 -4
- package/src/__tests__/notification-decision-strategy.test.ts +2 -1
- package/src/__tests__/notification-deep-link.test.ts +44 -1
- package/src/__tests__/notification-guardian-path.test.ts +157 -0
- package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +21 -21
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +5 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +9 -9
- package/src/__tests__/update-bulletin-state.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +66 -3
- package/src/__tests__/update-template-contract.test.ts +6 -11
- package/src/__tests__/voice-session-bridge.test.ts +109 -9
- package/src/calls/call-controller.ts +129 -8
- package/src/calls/guardian-action-sweep.ts +1 -1
- package/src/calls/guardian-dispatch.ts +8 -0
- package/src/calls/voice-session-bridge.ts +4 -2
- package/src/cli/core-commands.ts +41 -1
- package/src/config/templates/UPDATES.md +5 -6
- package/src/config/update-bulletin-format.ts +2 -0
- package/src/config/update-bulletin-state.ts +1 -1
- package/src/config/update-bulletin-template-path.ts +6 -0
- package/src/config/update-bulletin.ts +21 -6
- package/src/daemon/config-watcher.ts +3 -2
- package/src/daemon/daemon-control.ts +64 -10
- package/src/daemon/handlers/config-slack-channel.ts +1 -1
- package/src/daemon/handlers/identity.ts +45 -25
- package/src/daemon/handlers/sessions.ts +1 -1
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/workspace.ts +12 -1
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +8 -0
- package/src/daemon/server.ts +25 -3
- package/src/daemon/session-process.ts +438 -184
- package/src/daemon/tls-certs.ts +17 -12
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/memory/channel-delivery-store.ts +18 -20
- package/src/memory/channel-guardian-store.ts +39 -42
- package/src/memory/conversation-crud.ts +2 -2
- package/src/memory/conversation-queries.ts +2 -2
- package/src/memory/conversation-store.ts +24 -25
- package/src/memory/db-init.ts +9 -1
- package/src/memory/fts-reconciler.ts +41 -26
- package/src/memory/guardian-action-store.ts +57 -7
- package/src/memory/guardian-verification.ts +1 -0
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
- package/src/memory/migrations/index.ts +4 -2
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +6 -1
- package/src/memory/search/semantic.ts +3 -3
- package/src/notifications/README.md +158 -17
- package/src/notifications/broadcaster.ts +68 -50
- package/src/notifications/conversation-pairing.ts +96 -18
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/deliveries-store.ts +12 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/thread-candidates.ts +60 -25
- package/src/notifications/types.ts +2 -1
- package/src/permissions/checker.ts +1 -16
- package/src/permissions/defaults.ts +14 -4
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/http-server.ts +11 -11
- package/src/runtime/routes/access-request-decision.ts +1 -1
- package/src/runtime/routes/debug-routes.ts +4 -4
- package/src/runtime/routes/guardian-approval-interception.ts +4 -4
- package/src/runtime/routes/inbound-message-handler.ts +6 -6
- package/src/runtime/routes/integration-routes.ts +2 -2
- package/src/tools/permission-checker.ts +1 -2
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/system/voice-config.ts +1 -1
- package/src/version.ts +29 -2
|
@@ -19,7 +19,7 @@ import { getLogger } from '../util/logger.js';
|
|
|
19
19
|
import { createDecision } from './decisions-store.js';
|
|
20
20
|
import { getPreferenceSummary } from './preference-summary.js';
|
|
21
21
|
import type { NotificationSignal, RoutingIntent } from './signal.js';
|
|
22
|
-
import {
|
|
22
|
+
import { buildThreadCandidates, serializeCandidatesForPrompt,type ThreadCandidateSet } from './thread-candidates.js';
|
|
23
23
|
import type { NotificationChannel, NotificationDecision, RenderedChannelCopy, ThreadAction } from './types.js';
|
|
24
24
|
|
|
25
25
|
const log = getLogger('notification-decision-engine');
|
|
@@ -385,13 +385,16 @@ export function validateThreadActions(
|
|
|
385
385
|
if (action.action === 'start_new') {
|
|
386
386
|
result[channel] = { action: 'start_new' };
|
|
387
387
|
} else if (action.action === 'reuse_existing') {
|
|
388
|
-
const
|
|
389
|
-
if (typeof
|
|
388
|
+
const rawConversationId = action.conversationId;
|
|
389
|
+
if (typeof rawConversationId !== 'string' || !rawConversationId.trim()) {
|
|
390
390
|
log.warn({ channel }, 'LLM returned reuse_existing without conversationId — downgrading to start_new');
|
|
391
391
|
result[channel] = { action: 'start_new' };
|
|
392
392
|
continue;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// Normalize: the LLM may return a valid ID with leading/trailing whitespace
|
|
396
|
+
const conversationId = rawConversationId.trim();
|
|
397
|
+
|
|
395
398
|
// Strict validation: the conversationId must exist in the candidate set
|
|
396
399
|
const candidateIds = validCandidateIds.get(channel);
|
|
397
400
|
if (!candidateIds || !candidateIds.has(conversationId)) {
|
|
@@ -28,6 +28,9 @@ export interface NotificationDeliveryRow {
|
|
|
28
28
|
conversationId: string | null;
|
|
29
29
|
messageId: string | null;
|
|
30
30
|
conversationStrategy: string | null;
|
|
31
|
+
threadAction: string | null;
|
|
32
|
+
threadTargetConversationId: string | null;
|
|
33
|
+
threadDecisionFallbackUsed: number | null;
|
|
31
34
|
clientDeliveryStatus: string | null;
|
|
32
35
|
clientDeliveryError: string | null;
|
|
33
36
|
clientDeliveryAt: number | null;
|
|
@@ -52,6 +55,9 @@ function rowToDelivery(row: typeof notificationDeliveries.$inferSelect): Notific
|
|
|
52
55
|
conversationId: row.conversationId,
|
|
53
56
|
messageId: row.messageId,
|
|
54
57
|
conversationStrategy: row.conversationStrategy,
|
|
58
|
+
threadAction: row.threadAction,
|
|
59
|
+
threadTargetConversationId: row.threadTargetConversationId,
|
|
60
|
+
threadDecisionFallbackUsed: row.threadDecisionFallbackUsed,
|
|
55
61
|
clientDeliveryStatus: row.clientDeliveryStatus,
|
|
56
62
|
clientDeliveryError: row.clientDeliveryError,
|
|
57
63
|
clientDeliveryAt: row.clientDeliveryAt,
|
|
@@ -76,6 +82,9 @@ export interface CreateDeliveryParams {
|
|
|
76
82
|
conversationId?: string;
|
|
77
83
|
messageId?: string;
|
|
78
84
|
conversationStrategy?: string;
|
|
85
|
+
threadAction?: string;
|
|
86
|
+
threadTargetConversationId?: string;
|
|
87
|
+
threadDecisionFallbackUsed?: boolean;
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
/** Create a new delivery audit record. */
|
|
@@ -99,6 +108,9 @@ export function createDelivery(params: CreateDeliveryParams): NotificationDelive
|
|
|
99
108
|
conversationId: params.conversationId ?? null,
|
|
100
109
|
messageId: params.messageId ?? null,
|
|
101
110
|
conversationStrategy: params.conversationStrategy ?? null,
|
|
111
|
+
threadAction: params.threadAction ?? null,
|
|
112
|
+
threadTargetConversationId: params.threadTargetConversationId ?? null,
|
|
113
|
+
threadDecisionFallbackUsed: params.threadDecisionFallbackUsed != null ? (params.threadDecisionFallbackUsed ? 1 : 0) : null,
|
|
102
114
|
clientDeliveryStatus: null,
|
|
103
115
|
clientDeliveryError: null,
|
|
104
116
|
clientDeliveryAt: null,
|
|
@@ -205,6 +205,7 @@ export async function emitNotificationSignal(params: EmitSignalParams): Promise<
|
|
|
205
205
|
|
|
206
206
|
// Step 2: Evaluate the signal through the decision engine
|
|
207
207
|
const connectedChannels = getConnectedChannels(assistantId);
|
|
208
|
+
|
|
208
209
|
let decision = await evaluateSignal(signal, connectedChannels);
|
|
209
210
|
|
|
210
211
|
// Step 2.5: Enforce routing intent policy (fire-time guard)
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
* needs for a routing decision, not full conversation contents.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { and, desc, eq, isNotNull } from 'drizzle-orm';
|
|
13
|
+
import { and, count, desc, eq, inArray, isNotNull } from 'drizzle-orm';
|
|
14
14
|
|
|
15
15
|
import { getDb } from '../memory/db.js';
|
|
16
|
-
import {
|
|
17
|
-
import { conversations, notificationDeliveries, notificationDecisions, notificationEvents } from '../memory/schema.js';
|
|
16
|
+
import { channelGuardianApprovalRequests, conversations, notificationDecisions, notificationDeliveries, notificationEvents } from '../memory/schema.js';
|
|
18
17
|
import { getLogger } from '../util/logger.js';
|
|
19
18
|
import type { NotificationChannel } from './types.js';
|
|
20
19
|
|
|
@@ -148,49 +147,78 @@ function buildCandidatesForChannel(
|
|
|
148
147
|
|
|
149
148
|
seen.add(row.conversationId);
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
candidates.push({
|
|
152
151
|
conversationId: row.conversationId,
|
|
153
152
|
title: row.convTitle,
|
|
154
153
|
updatedAt: row.convUpdatedAt,
|
|
155
154
|
latestSourceEventName: row.sourceEventName ?? null,
|
|
156
155
|
channel: channel,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Enrich with guardian context
|
|
160
|
-
const guardianContext = buildGuardianContext(row.conversationId, assistantId);
|
|
161
|
-
if (guardianContext) {
|
|
162
|
-
candidate.guardianContext = guardianContext;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
candidates.push(candidate);
|
|
156
|
+
});
|
|
166
157
|
|
|
167
158
|
if (candidates.length >= MAX_CANDIDATES_PER_CHANNEL) break;
|
|
168
159
|
}
|
|
169
160
|
|
|
161
|
+
// Batch-enrich all candidates with guardian context in a single query
|
|
162
|
+
if (candidates.length > 0) {
|
|
163
|
+
const pendingCounts = batchCountPendingByConversation(
|
|
164
|
+
candidates.map((c) => c.conversationId),
|
|
165
|
+
assistantId,
|
|
166
|
+
);
|
|
167
|
+
for (const candidate of candidates) {
|
|
168
|
+
const pendingCount = pendingCounts.get(candidate.conversationId) ?? 0;
|
|
169
|
+
if (pendingCount > 0) {
|
|
170
|
+
candidate.guardianContext = { pendingUnresolvedRequestCount: pendingCount };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
170
175
|
return candidates;
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
// -- Guardian context enrichment ----------------------------------------------
|
|
174
179
|
|
|
175
180
|
/**
|
|
176
|
-
*
|
|
177
|
-
* Returns
|
|
181
|
+
* Batch-count pending guardian approval requests for multiple conversations
|
|
182
|
+
* in a single query. Returns a map from conversationId to pending count
|
|
183
|
+
* (only entries with count > 0 are included).
|
|
178
184
|
*/
|
|
179
|
-
function
|
|
180
|
-
|
|
185
|
+
function batchCountPendingByConversation(
|
|
186
|
+
conversationIds: string[],
|
|
181
187
|
assistantId: string,
|
|
182
|
-
):
|
|
188
|
+
): Map<string, number> {
|
|
189
|
+
const result = new Map<string, number>();
|
|
190
|
+
if (conversationIds.length === 0) return result;
|
|
191
|
+
|
|
183
192
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
const db = getDb();
|
|
194
|
+
|
|
195
|
+
const rows = db
|
|
196
|
+
.select({
|
|
197
|
+
conversationId: channelGuardianApprovalRequests.conversationId,
|
|
198
|
+
count: count(),
|
|
199
|
+
})
|
|
200
|
+
.from(channelGuardianApprovalRequests)
|
|
201
|
+
.where(
|
|
202
|
+
and(
|
|
203
|
+
inArray(channelGuardianApprovalRequests.conversationId, conversationIds),
|
|
204
|
+
eq(channelGuardianApprovalRequests.status, 'pending'),
|
|
205
|
+
eq(channelGuardianApprovalRequests.assistantId, assistantId),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
.groupBy(channelGuardianApprovalRequests.conversationId)
|
|
209
|
+
.all();
|
|
210
|
+
|
|
211
|
+
for (const row of rows) {
|
|
212
|
+
if (row.count > 0) {
|
|
213
|
+
result.set(row.conversationId, row.count);
|
|
214
|
+
}
|
|
187
215
|
}
|
|
188
216
|
} catch (err) {
|
|
189
217
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
190
|
-
log.warn({ err: errMsg
|
|
218
|
+
log.warn({ err: errMsg }, 'Failed to batch-query guardian context for candidates');
|
|
191
219
|
}
|
|
192
220
|
|
|
193
|
-
return
|
|
221
|
+
return result;
|
|
194
222
|
}
|
|
195
223
|
|
|
196
224
|
// -- Prompt serialization -----------------------------------------------------
|
|
@@ -213,13 +241,20 @@ export function serializeCandidatesForPrompt(candidateSet: ThreadCandidateSet):
|
|
|
213
241
|
|
|
214
242
|
const lines: string[] = [`Channel: ${channel}`];
|
|
215
243
|
for (const c of candidates) {
|
|
244
|
+
// Escape title to prevent format corruption from quotes or newlines in
|
|
245
|
+
// user/model-provided text. JSON.stringify produces a safe single-line
|
|
246
|
+
// quoted string; we strip the outer quotes since we wrap in our own.
|
|
247
|
+
const safeTitle = c.title
|
|
248
|
+
? JSON.stringify(c.title).slice(1, -1)
|
|
249
|
+
: '(untitled)';
|
|
216
250
|
const parts: string[] = [
|
|
217
251
|
` - id=${c.conversationId}`,
|
|
218
|
-
`title="${
|
|
252
|
+
`title="${safeTitle}"`,
|
|
219
253
|
`updated=${new Date(c.updatedAt).toISOString()}`,
|
|
220
254
|
];
|
|
221
255
|
if (c.latestSourceEventName) {
|
|
222
|
-
|
|
256
|
+
const safeEventName = JSON.stringify(c.latestSourceEventName).slice(1, -1);
|
|
257
|
+
parts.push(`lastEvent="${safeEventName}"`);
|
|
223
258
|
}
|
|
224
259
|
if (c.guardianContext) {
|
|
225
260
|
parts.push(`pendingRequests=${c.guardianContext.pendingUnresolvedRequestCount}`);
|
|
@@ -95,13 +95,14 @@ export interface ThreadActionReuseExisting {
|
|
|
95
95
|
/** Per-channel thread action — either start a new thread or reuse an existing one. */
|
|
96
96
|
export type ThreadAction = ThreadActionStartNew | ThreadActionReuseExisting;
|
|
97
97
|
|
|
98
|
+
|
|
98
99
|
/** Output produced by the notification decision engine for a given signal. */
|
|
99
100
|
export interface NotificationDecision {
|
|
100
101
|
shouldNotify: boolean;
|
|
101
102
|
selectedChannels: NotificationChannel[];
|
|
102
103
|
reasoningSummary: string;
|
|
103
104
|
renderedCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>>;
|
|
104
|
-
/** Per-channel thread
|
|
105
|
+
/** Per-channel thread actions decided by the model. Absent channels default to start_new. */
|
|
105
106
|
threadActions?: Partial<Record<NotificationChannel, ThreadAction>>;
|
|
106
107
|
deepLinkTarget?: Record<string, unknown>;
|
|
107
108
|
dedupeKey: string;
|
|
@@ -123,19 +123,6 @@ function getWrappedProgram(seg: { program: string; args: string[] }): string | u
|
|
|
123
123
|
return undefined;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
function isHighRiskRm(args: string[]): boolean {
|
|
127
|
-
// rm with -r, -rf, -fr, or targeting root/home
|
|
128
|
-
for (const arg of args) {
|
|
129
|
-
if (arg.startsWith('-') && (arg.includes('r') || arg.includes('f'))) {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
if (arg === '/' || arg === '~' || arg === '$HOME') {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
126
|
function getStringField(input: Record<string, unknown>, ...keys: string[]): string {
|
|
140
127
|
for (const key of keys) {
|
|
141
128
|
const value = input[key];
|
|
@@ -398,9 +385,7 @@ async function classifyRiskUncached(toolName: string, input: Record<string, unkn
|
|
|
398
385
|
if (HIGH_RISK_PROGRAMS.has(prog)) return RiskLevel.High;
|
|
399
386
|
|
|
400
387
|
if (prog === 'rm') {
|
|
401
|
-
|
|
402
|
-
maxRisk = RiskLevel.Medium;
|
|
403
|
-
continue;
|
|
388
|
+
return RiskLevel.High;
|
|
404
389
|
}
|
|
405
390
|
|
|
406
391
|
if (prog === 'chmod' || prog === 'chown' || prog === 'chgrp'
|
|
@@ -37,6 +37,13 @@ const COMPUTER_USE_TOOLS = [
|
|
|
37
37
|
* Computed at runtime so paths reflect the configured root directory.
|
|
38
38
|
*/
|
|
39
39
|
export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
40
|
+
// Some test suites mock getConfig() with partial objects; treat missing
|
|
41
|
+
// branches as defaults so rule generation remains deterministic.
|
|
42
|
+
const config = getConfig() as {
|
|
43
|
+
sandbox?: { enabled?: boolean };
|
|
44
|
+
skills?: { load?: { extraDirs?: unknown } };
|
|
45
|
+
};
|
|
46
|
+
|
|
40
47
|
const hostFileRules = HOST_FILE_TOOLS.map((tool) => ({
|
|
41
48
|
id: `default:ask-${tool}-global`,
|
|
42
49
|
tool,
|
|
@@ -50,11 +57,11 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
50
57
|
// global default ask rule uses "**" (globstar) instead of a "tool:*" prefix
|
|
51
58
|
// because commands often contain "/" (e.g. "cat /etc/hosts").
|
|
52
59
|
const hostShellRule: DefaultRuleTemplate = {
|
|
53
|
-
id: 'default:
|
|
60
|
+
id: 'default:allow-host_bash-global',
|
|
54
61
|
tool: 'host_bash',
|
|
55
62
|
pattern: '**',
|
|
56
63
|
scope: 'everywhere',
|
|
57
|
-
decision: '
|
|
64
|
+
decision: 'allow',
|
|
58
65
|
priority: 50,
|
|
59
66
|
};
|
|
60
67
|
|
|
@@ -62,7 +69,7 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
62
69
|
// them (including high-risk) so the user is never prompted for sandbox work.
|
|
63
70
|
// Only emit this rule when the sandbox is actually enabled; otherwise bash
|
|
64
71
|
// commands execute on the host and must go through normal permission checks.
|
|
65
|
-
const sandboxEnabled =
|
|
72
|
+
const sandboxEnabled = config.sandbox?.enabled !== false;
|
|
66
73
|
const sandboxShellRule: DefaultRuleTemplate | null = sandboxEnabled
|
|
67
74
|
? {
|
|
68
75
|
id: 'default:allow-bash-global',
|
|
@@ -149,7 +156,10 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
149
156
|
|
|
150
157
|
// Append any user-configured extra skill directories so they get the
|
|
151
158
|
// same default ask rules as managed and bundled dirs.
|
|
152
|
-
const
|
|
159
|
+
const rawExtraDirs = config.skills?.load?.extraDirs;
|
|
160
|
+
const extraDirs = Array.isArray(rawExtraDirs)
|
|
161
|
+
? rawExtraDirs.filter((dir): dir is string => typeof dir === 'string')
|
|
162
|
+
: [];
|
|
153
163
|
for (let i = 0; i < extraDirs.length; i++) {
|
|
154
164
|
skillDirs.push({ dir: extraDirs[i].replaceAll('\\', '/'), label: `extra-${i}` });
|
|
155
165
|
}
|
|
@@ -24,8 +24,8 @@ import { getGatewayInternalBaseUrl } from '../config/env.js';
|
|
|
24
24
|
import { getOrCreateConversation } from '../memory/conversation-key-store.js';
|
|
25
25
|
import {
|
|
26
26
|
finalizeFollowup,
|
|
27
|
-
getGuardianActionRequest,
|
|
28
27
|
type FollowupAction,
|
|
28
|
+
getGuardianActionRequest,
|
|
29
29
|
type GuardianActionRequest,
|
|
30
30
|
} from '../memory/guardian-action-store.js';
|
|
31
31
|
import { getLogger } from '../util/logger.js';
|
|
@@ -85,7 +85,6 @@ import {
|
|
|
85
85
|
handleGetAttachmentContent,
|
|
86
86
|
handleUploadAttachment,
|
|
87
87
|
} from './routes/attachment-routes.js';
|
|
88
|
-
import { handleDebug } from './routes/debug-routes.js';
|
|
89
88
|
import {
|
|
90
89
|
handleAnswerCall,
|
|
91
90
|
handleCancelCall,
|
|
@@ -116,8 +115,19 @@ import {
|
|
|
116
115
|
handleSearchConversations,
|
|
117
116
|
handleSendMessage,
|
|
118
117
|
} from './routes/conversation-routes.js';
|
|
118
|
+
import { handleDebug } from './routes/debug-routes.js';
|
|
119
119
|
import { handleSubscribeAssistantEvents } from './routes/events-routes.js';
|
|
120
120
|
import { handleGetIdentity,handleHealth } from './routes/identity-routes.js';
|
|
121
|
+
import {
|
|
122
|
+
handleBlockMember,
|
|
123
|
+
handleCreateInvite,
|
|
124
|
+
handleListInvites,
|
|
125
|
+
handleListMembers,
|
|
126
|
+
handleRedeemInvite,
|
|
127
|
+
handleRevokeInvite,
|
|
128
|
+
handleRevokeMember,
|
|
129
|
+
handleUpsertMember,
|
|
130
|
+
} from './routes/ingress-routes.js';
|
|
121
131
|
import {
|
|
122
132
|
handleCancelOutbound,
|
|
123
133
|
handleClearSlackChannelConfig,
|
|
@@ -140,16 +150,6 @@ import {
|
|
|
140
150
|
handlePairingRequest,
|
|
141
151
|
handlePairingStatus,
|
|
142
152
|
} from './routes/pairing-routes.js';
|
|
143
|
-
import {
|
|
144
|
-
handleBlockMember,
|
|
145
|
-
handleCreateInvite,
|
|
146
|
-
handleListInvites,
|
|
147
|
-
handleListMembers,
|
|
148
|
-
handleRedeemInvite,
|
|
149
|
-
handleRevokeInvite,
|
|
150
|
-
handleRevokeMember,
|
|
151
|
-
handleUpsertMember,
|
|
152
|
-
} from './routes/ingress-routes.js';
|
|
153
153
|
import { handleAddSecret } from './routes/secret-routes.js';
|
|
154
154
|
|
|
155
155
|
// Re-export for consumers
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* instead of resuming an agent loop.
|
|
7
7
|
*/
|
|
8
8
|
import {
|
|
9
|
-
resolveApprovalRequest,
|
|
10
9
|
type GuardianApprovalRequest,
|
|
10
|
+
resolveApprovalRequest,
|
|
11
11
|
} from '../../memory/channel-guardian-store.js';
|
|
12
12
|
import { getLogger } from '../../util/logger.js';
|
|
13
13
|
import { createOutboundSession } from '../channel-guardian-service.js';
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { statSync } from 'node:fs';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getConfig } from '../../config/loader.js';
|
|
8
8
|
import { countConversations } from '../../memory/conversation-store.js';
|
|
9
|
-
import { getMemoryJobCounts } from '../../memory/jobs-store.js';
|
|
10
|
-
import { countSchedules } from '../../schedule/schedule-store.js';
|
|
11
9
|
import { rawAll } from '../../memory/db.js';
|
|
12
|
-
import {
|
|
10
|
+
import { getMemoryJobCounts } from '../../memory/jobs-store.js';
|
|
13
11
|
import { getProviderDebugStatus } from '../../providers/registry.js';
|
|
12
|
+
import { countSchedules } from '../../schedule/schedule-store.js';
|
|
13
|
+
import { getDbPath } from '../../util/platform.js';
|
|
14
14
|
|
|
15
15
|
/** Process start time — used to calculate uptime. */
|
|
16
16
|
const startedAt = Date.now();
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
getPendingApprovalByRequestAndGuardianChat,
|
|
9
9
|
getPendingApprovalForRequest,
|
|
10
10
|
getUnresolvedApprovalForRequest,
|
|
11
|
-
updateApprovalDecision,
|
|
12
11
|
type GuardianApprovalRequest,
|
|
12
|
+
updateApprovalDecision,
|
|
13
13
|
} from '../../memory/channel-guardian-store.js';
|
|
14
14
|
import { emitNotificationSignal } from '../../notifications/emit-signal.js';
|
|
15
15
|
import { getLogger } from '../../util/logger.js';
|
|
@@ -31,12 +31,12 @@ import type {
|
|
|
31
31
|
ApprovalCopyGenerator,
|
|
32
32
|
} from '../http-types.js';
|
|
33
33
|
import {
|
|
34
|
-
handleAccessRequestDecision,
|
|
35
34
|
deliverVerificationCodeToGuardian,
|
|
35
|
+
type DeliveryResult,
|
|
36
|
+
handleAccessRequestDecision,
|
|
36
37
|
notifyRequesterOfApproval,
|
|
37
|
-
notifyRequesterOfDenial,
|
|
38
38
|
notifyRequesterOfDeliveryFailure,
|
|
39
|
-
|
|
39
|
+
notifyRequesterOfDenial,
|
|
40
40
|
} from './access-request-decision.js';
|
|
41
41
|
import {
|
|
42
42
|
buildGuardianDenyContext,
|
|
@@ -50,12 +50,16 @@ import {
|
|
|
50
50
|
validateAndConsumeChallenge,
|
|
51
51
|
} from '../channel-guardian-service.js';
|
|
52
52
|
import { deliverChannelReply } from '../gateway-client.js';
|
|
53
|
+
import { processGuardianFollowUpTurn } from '../guardian-action-conversation-turn.js';
|
|
54
|
+
import { executeFollowupAction } from '../guardian-action-followup-executor.js';
|
|
55
|
+
import { composeGuardianActionMessageGenerative } from '../guardian-action-message-composer.js';
|
|
53
56
|
import { resolveGuardianContext } from '../guardian-context-resolver.js';
|
|
54
57
|
import {
|
|
55
58
|
composeChannelVerifyReply,
|
|
56
59
|
composeVerificationTelegram,
|
|
57
60
|
GUARDIAN_VERIFY_TEMPLATE_KEYS,
|
|
58
61
|
} from '../guardian-verification-templates.js';
|
|
62
|
+
import { httpError } from '../http-errors.js';
|
|
59
63
|
import type {
|
|
60
64
|
ApprovalConversationGenerator,
|
|
61
65
|
ApprovalCopyGenerator,
|
|
@@ -63,10 +67,6 @@ import type {
|
|
|
63
67
|
GuardianFollowUpConversationGenerator,
|
|
64
68
|
MessageProcessor,
|
|
65
69
|
} from '../http-types.js';
|
|
66
|
-
import { processGuardianFollowUpTurn } from '../guardian-action-conversation-turn.js';
|
|
67
|
-
import { composeGuardianActionMessageGenerative } from '../guardian-action-message-composer.js';
|
|
68
|
-
import { executeFollowupAction } from '../guardian-action-followup-executor.js';
|
|
69
|
-
import { httpError } from '../http-errors.js';
|
|
70
70
|
import { deliverReplyViaCallback } from './channel-delivery-routes.js';
|
|
71
71
|
import {
|
|
72
72
|
canonicalChannelAssistantId,
|
|
@@ -1120,9 +1120,9 @@ export async function handleChannelInbound(
|
|
|
1120
1120
|
// that the request was already resolved and skip action execution.
|
|
1121
1121
|
let stateApplied = true;
|
|
1122
1122
|
if (turnResult.disposition === 'call_back' || turnResult.disposition === 'message_back') {
|
|
1123
|
-
stateApplied = progressFollowupState(followupRequest.id, 'dispatching', turnResult.disposition) !==
|
|
1123
|
+
stateApplied = progressFollowupState(followupRequest.id, 'dispatching', turnResult.disposition) !== undefined;
|
|
1124
1124
|
} else if (turnResult.disposition === 'decline') {
|
|
1125
|
-
stateApplied = finalizeFollowup(followupRequest.id, 'declined') !==
|
|
1125
|
+
stateApplied = finalizeFollowup(followupRequest.id, 'declined') !== undefined;
|
|
1126
1126
|
}
|
|
1127
1127
|
// keep_pending: no state change — guardian can reply again
|
|
1128
1128
|
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
createGuardianChallenge,
|
|
27
27
|
getGuardianStatus,
|
|
28
28
|
} from '../../daemon/handlers/config-channels.js';
|
|
29
|
-
import { httpError } from '../http-errors.js';
|
|
30
29
|
import {
|
|
31
30
|
clearSlackChannelConfig,
|
|
32
31
|
getSlackChannelConfig,
|
|
@@ -39,14 +38,15 @@ import {
|
|
|
39
38
|
setTelegramConfig,
|
|
40
39
|
setupTelegram,
|
|
41
40
|
} from '../../daemon/handlers/config-telegram.js';
|
|
41
|
+
import { normalizePhoneNumber } from '../../util/phone.js';
|
|
42
42
|
import {
|
|
43
43
|
cancelOutbound,
|
|
44
44
|
normalizeTelegramDestination,
|
|
45
45
|
resendOutbound,
|
|
46
46
|
startOutbound,
|
|
47
47
|
} from '../guardian-outbound-actions.js';
|
|
48
|
+
import { httpError } from '../http-errors.js';
|
|
48
49
|
import { guardianVerificationLimiter } from '../verification-rate-limiter.js';
|
|
49
|
-
import { normalizePhoneNumber } from '../../util/phone.js';
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* GET /v1/integrations/telegram/config
|
|
@@ -3,12 +3,11 @@ import { getHookManager } from '../hooks/manager.js';
|
|
|
3
3
|
import { check, classifyRisk, generateAllowlistOptions, generateScopeOptions } from '../permissions/checker.js';
|
|
4
4
|
import type { PermissionPrompter } from '../permissions/prompter.js';
|
|
5
5
|
import { addRule } from '../permissions/trust-store.js';
|
|
6
|
-
import { RiskLevel } from '../permissions/types.js';
|
|
7
6
|
import { getLogger } from '../util/logger.js';
|
|
8
|
-
import type { ExecutionTarget } from './types.js';
|
|
9
7
|
import { buildPolicyContext } from './policy-context.js';
|
|
10
8
|
import { isSideEffectTool } from './side-effects.js';
|
|
11
9
|
import { wrapCommand } from './terminal/sandbox.js';
|
|
10
|
+
import type { ExecutionTarget } from './types.js';
|
|
12
11
|
import type { Tool, ToolContext, ToolLifecycleEvent } from './types.js';
|
|
13
12
|
|
|
14
13
|
const log = getLogger('permission-checker');
|
|
@@ -2,8 +2,8 @@ import { getConfig } from '../config/loader.js';
|
|
|
2
2
|
import { getHookManager } from '../hooks/manager.js';
|
|
3
3
|
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
4
4
|
import { RiskLevel } from '../permissions/types.js';
|
|
5
|
-
import { compileCustomPatterns, redactSecrets, scanText } from '../security/secret-scanner.js';
|
|
6
5
|
import type { SecretPattern } from '../security/secret-scanner.js';
|
|
6
|
+
import { compileCustomPatterns, redactSecrets, scanText } from '../security/secret-scanner.js';
|
|
7
7
|
import type { ExecutionTarget, ToolContext, ToolExecutionResult, ToolLifecycleEvent } from './types.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -32,7 +32,7 @@ export const voiceConfigUpdateTool: Tool = {
|
|
|
32
32
|
|
|
33
33
|
async execute(
|
|
34
34
|
input: Record<string, unknown>,
|
|
35
|
-
|
|
35
|
+
_context: ToolContext,
|
|
36
36
|
): Promise<ToolExecutionResult> {
|
|
37
37
|
const rawKey = input.activation_key;
|
|
38
38
|
if (typeof rawKey !== 'string' || rawKey.trim() === '') {
|
package/src/version.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
function resolveVersion(): string {
|
|
5
|
+
const envVersion = process.env.APP_VERSION;
|
|
6
|
+
|
|
7
|
+
// When APP_VERSION is not set, we're in local development — return the dev
|
|
8
|
+
// sentinel so Sentry (and similar) classify the session as "development".
|
|
9
|
+
if (!envVersion) return '0.0.0-dev';
|
|
10
|
+
|
|
11
|
+
// CI sets APP_VERSION to the dev placeholder during builds; resolve it to
|
|
12
|
+
// the package.json release version so Sentry gets a meaningful release tag.
|
|
13
|
+
if (envVersion === '0.0.0-dev') {
|
|
14
|
+
try {
|
|
15
|
+
const pkgPath = join(import.meta.dirname ?? __dirname, '..', 'package.json');
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
if (pkg.version && typeof pkg.version === 'string') return pkg.version;
|
|
18
|
+
} catch {
|
|
19
|
+
// package.json missing or unreadable
|
|
20
|
+
}
|
|
21
|
+
return '0.0.0-dev';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return envVersion;
|
|
25
|
+
}
|
|
26
|
+
|
|
1
27
|
// Version is embedded at compile time via --define in CI.
|
|
2
|
-
// Falls back to "0.0.0-dev" for local development
|
|
3
|
-
|
|
28
|
+
// Falls back to "0.0.0-dev" for local development, or resolves the dev
|
|
29
|
+
// placeholder to package.json version when explicitly set in CI.
|
|
30
|
+
export const APP_VERSION: string = resolveVersion();
|