@vellumai/assistant 0.7.3 → 0.8.0
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 +29 -28
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/knip.json +1 -0
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/openapi.yaml +22 -4
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +12 -25
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -26
- package/src/__tests__/context-search-pkb-source.test.ts +12 -6
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -6
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +3 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -6
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +15 -6
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +73 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +59 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -7
- package/src/__tests__/filing-service.test.ts +2 -19
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/injector-chain.test.ts +24 -16
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/relay-server.test.ts +46 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +15 -27
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-decision-primitive.ts +0 -13
- package/src/approvals/guardian-request-resolvers.ts +4 -32
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/memory-v2.ts +7 -7
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -254
- package/src/cli/commands/oauth/connect.ts +10 -52
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/loader.ts +72 -19
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +32 -0
- package/src/daemon/conversation-agent-loop.ts +13 -10
- package/src/daemon/conversation-lifecycle.ts +22 -8
- package/src/daemon/conversation-surfaces.ts +16 -14
- package/src/daemon/conversation-tool-setup.ts +9 -5
- package/src/daemon/conversation.ts +1 -1
- package/src/daemon/handlers/shared.ts +26 -0
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +88 -73
- package/src/daemon/memory-v2-startup.ts +55 -14
- package/src/daemon/message-types/messages.ts +19 -1
- package/src/documents/document-store.ts +35 -1
- package/src/filing/filing-service.ts +2 -3
- package/src/heartbeat/heartbeat-service.ts +1 -1
- package/src/ipc/assistant-server.ts +93 -36
- package/src/ipc/skill-server.ts +99 -42
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +10 -57
- package/src/memory/context-search/sources/memory-v2.ts +1 -17
- package/src/memory/context-search/sources/memory.ts +2 -2
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +104 -61
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +32 -9
- package/src/memory/graph/graph-search.test.ts +6 -5
- package/src/memory/graph/graph-search.ts +3 -4
- package/src/memory/graph/retriever.test.ts +12 -7
- package/src/memory/graph/retriever.ts +4 -5
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +1 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-worker.ts +8 -4
- package/src/memory/pkb/pkb-search.test.ts +6 -5
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -0
- package/src/memory/search/semantic.ts +4 -5
- package/src/memory/v2/__tests__/activation.test.ts +35 -5
- package/src/memory/v2/__tests__/consolidation-job.test.ts +21 -32
- package/src/memory/v2/__tests__/injection.test.ts +140 -23
- package/src/memory/v2/__tests__/qdrant.test.ts +310 -9
- package/src/memory/v2/__tests__/sim.test.ts +118 -7
- package/src/memory/v2/__tests__/static-context.test.ts +1 -13
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/consolidation-job.ts +7 -8
- package/src/memory/v2/injection.ts +32 -12
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +5 -0
- package/src/memory/v2/qdrant.ts +209 -48
- package/src/memory/v2/sim.ts +67 -26
- package/src/memory/v2/static-context.ts +4 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +7 -0
- package/src/notifications/copy-composer.ts +46 -12
- package/src/notifications/decision-engine.ts +46 -0
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +1 -2
- package/src/proactive-artifact/job.test.ts +51 -4
- package/src/proactive-artifact/job.ts +16 -2
- package/src/proactive-artifact/message-copy.ts +18 -1
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/guardian-reply-router.ts +0 -10
- package/src/runtime/pending-interactions.ts +19 -15
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/debug-bash-routes.ts +2 -0
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +0 -3
- package/src/runtime/routes/memory-item-routes.test.ts +3 -9
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +103 -17
- package/src/skills/include-graph.ts +35 -13
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/memory/register.test.ts +7 -5
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +19 -1
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +4 -62
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +6 -0
|
@@ -61,14 +61,10 @@ type IncludeValidationResult =
|
|
|
61
61
|
| IncludeValidationError
|
|
62
62
|
| IncludeValidationCycleError;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
* Validate the include graph starting from the given root skill ID.
|
|
66
|
-
* Uses three-state DFS (unseen/visiting/done) to detect both missing children
|
|
67
|
-
* and cycles. Returns the first error encountered in DFS order.
|
|
68
|
-
*/
|
|
69
|
-
export function validateIncludes(
|
|
64
|
+
function validateIncludeGraph(
|
|
70
65
|
rootId: string,
|
|
71
66
|
catalogIndex: Map<string, SkillSummary>,
|
|
67
|
+
options: { failOnMissing: boolean },
|
|
72
68
|
): IncludeValidationResult {
|
|
73
69
|
const visited: string[] = [];
|
|
74
70
|
type State = "unseen" | "visiting" | "done";
|
|
@@ -97,13 +93,16 @@ export function validateIncludes(
|
|
|
97
93
|
if (skill?.includes) {
|
|
98
94
|
for (const childId of skill.includes) {
|
|
99
95
|
if (!catalogIndex.has(childId)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
if (options.failOnMissing) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: "missing",
|
|
100
|
+
missingChildId: childId,
|
|
101
|
+
parentId: id,
|
|
102
|
+
path: [...ancestry],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
107
106
|
}
|
|
108
107
|
const childError = dfs(childId);
|
|
109
108
|
if (childError) return childError;
|
|
@@ -120,6 +119,29 @@ export function validateIncludes(
|
|
|
120
119
|
return { ok: true, visited };
|
|
121
120
|
}
|
|
122
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Validate the include graph starting from the given root skill ID.
|
|
124
|
+
* Uses three-state DFS (unseen/visiting/done) to detect both missing children
|
|
125
|
+
* and cycles. Returns the first error encountered in DFS order.
|
|
126
|
+
*/
|
|
127
|
+
export function validateIncludes(
|
|
128
|
+
rootId: string,
|
|
129
|
+
catalogIndex: Map<string, SkillSummary>,
|
|
130
|
+
): IncludeValidationResult {
|
|
131
|
+
return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate only cycle safety for a graph that may intentionally have missing
|
|
136
|
+
* advisory includes. Missing child IDs are skipped during traversal.
|
|
137
|
+
*/
|
|
138
|
+
export function validateIncludeCycles(
|
|
139
|
+
rootId: string,
|
|
140
|
+
catalogIndex: Map<string, SkillSummary>,
|
|
141
|
+
): IncludeValidationResult {
|
|
142
|
+
return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: false });
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
/**
|
|
124
146
|
* Recursively traverse the include graph starting from the given root skill ID.
|
|
125
147
|
* Returns all visited skill IDs in DFS pre-order.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
saveDocument,
|
|
5
|
+
updateDocumentContent,
|
|
6
|
+
} from "../../documents/document-store.js";
|
|
3
7
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
4
8
|
|
|
5
9
|
// ── Exported execute functions ──────────────────────────────────────
|
|
@@ -12,6 +16,20 @@ export function executeDocumentCreate(
|
|
|
12
16
|
const initialContent = (input.initial_content as string | undefined) || "";
|
|
13
17
|
const surfaceId = `doc-${randomUUID()}`;
|
|
14
18
|
|
|
19
|
+
// Persist the document so any client (web or macOS) can fetch it via
|
|
20
|
+
// GET /v1/documents/:id. The macOS client may later update the row
|
|
21
|
+
// via document_save; ON CONFLICT DO UPDATE handles that.
|
|
22
|
+
const wordCount = initialContent
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.filter((w) => w.length > 0).length;
|
|
25
|
+
saveDocument({
|
|
26
|
+
surfaceId,
|
|
27
|
+
conversationId: context.conversationId,
|
|
28
|
+
title,
|
|
29
|
+
content: initialContent,
|
|
30
|
+
wordCount,
|
|
31
|
+
});
|
|
32
|
+
|
|
15
33
|
// Send document_editor_show message to open the built-in RTE
|
|
16
34
|
if (context.sendToClient) {
|
|
17
35
|
context.sendToClient({
|
|
@@ -67,6 +85,8 @@ export function executeDocumentUpdate(
|
|
|
67
85
|
const content = input.content as string;
|
|
68
86
|
const mode = (input.mode as string | undefined) || "append";
|
|
69
87
|
|
|
88
|
+
updateDocumentContent(surfaceId, content, mode);
|
|
89
|
+
|
|
70
90
|
// Send document_editor_update message to update the built-in RTE
|
|
71
91
|
if (context.sendToClient) {
|
|
72
92
|
context.sendToClient({
|
package/src/tools/executor.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { applyEdit } from "./shared/filesystem/edit-engine.js";
|
|
|
27
27
|
import { sandboxPolicy } from "./shared/filesystem/path-policy.js";
|
|
28
28
|
import { MAX_FILE_SIZE_BYTES } from "./shared/filesystem/size-guard.js";
|
|
29
29
|
import { ToolApprovalHandler } from "./tool-approval-handler.js";
|
|
30
|
+
import { resolveToolNameAlias } from "./tool-name-aliases.js";
|
|
30
31
|
import type {
|
|
31
32
|
ToolContext,
|
|
32
33
|
ToolExecutionResult,
|
|
@@ -76,7 +77,12 @@ export class ToolExecutor {
|
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
const middlewares = getMiddlewaresFor("toolExecute");
|
|
79
|
-
const
|
|
80
|
+
const executionName = resolveToolNameAlias(name, context.allowedToolNames);
|
|
81
|
+
const pipelineArgs: ToolExecuteArgs = {
|
|
82
|
+
name: executionName,
|
|
83
|
+
input,
|
|
84
|
+
context,
|
|
85
|
+
};
|
|
80
86
|
|
|
81
87
|
// No pipeline-level timeout: `executeInternal` already wraps the real
|
|
82
88
|
// tool invocation in `executeWithTimeout`, which is the sole enforcer
|
|
@@ -107,6 +113,11 @@ export class ToolExecutor {
|
|
|
107
113
|
riskLevel: string;
|
|
108
114
|
riskReason: string;
|
|
109
115
|
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
116
|
+
riskAllowlistOptions?: Array<{
|
|
117
|
+
label: string;
|
|
118
|
+
description: string;
|
|
119
|
+
pattern: string;
|
|
120
|
+
}>;
|
|
110
121
|
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
111
122
|
isContainerized?: boolean;
|
|
112
123
|
}
|
|
@@ -205,6 +216,7 @@ export class ToolExecutor {
|
|
|
205
216
|
riskLevel: permRiskMeta?.riskLevel,
|
|
206
217
|
riskReason: permRiskMeta?.riskReason,
|
|
207
218
|
riskScopeOptions: permRiskMeta?.riskScopeOptions,
|
|
219
|
+
riskAllowlistOptions: permRiskMeta?.riskAllowlistOptions,
|
|
208
220
|
riskDirectoryScopeOptions: permRiskMeta?.riskDirectoryScopeOptions,
|
|
209
221
|
isContainerized: permRiskMeta?.isContainerized,
|
|
210
222
|
matchedTrustRuleId: permMatchedTrustRuleId,
|
|
@@ -422,12 +434,16 @@ export class ToolExecutor {
|
|
|
422
434
|
riskLevel: permRiskMeta.riskLevel,
|
|
423
435
|
riskReason: permRiskMeta.riskReason,
|
|
424
436
|
riskScopeOptions: permRiskMeta.riskScopeOptions,
|
|
437
|
+
riskAllowlistOptions: permRiskMeta.riskAllowlistOptions,
|
|
425
438
|
riskDirectoryScopeOptions: permRiskMeta.riskDirectoryScopeOptions,
|
|
426
439
|
isContainerized: permRiskMeta.isContainerized,
|
|
427
440
|
};
|
|
428
441
|
}
|
|
429
442
|
if (permMatchedTrustRuleId) {
|
|
430
|
-
execResult = {
|
|
443
|
+
execResult = {
|
|
444
|
+
...execResult,
|
|
445
|
+
matchedTrustRuleId: permMatchedTrustRuleId,
|
|
446
|
+
};
|
|
431
447
|
}
|
|
432
448
|
if (permApprovalMode) {
|
|
433
449
|
execResult = { ...execResult, approvalMode: permApprovalMode };
|
|
@@ -11,14 +11,16 @@ import {
|
|
|
11
11
|
test,
|
|
12
12
|
} from "bun:test";
|
|
13
13
|
|
|
14
|
-
import { _setOverridesForTesting } from "../../config/assistant-feature-flags.js";
|
|
15
14
|
import { PKB_WORKSPACE_SCOPE } from "../../memory/pkb/types.js";
|
|
16
15
|
import type { ToolContext } from "../types.js";
|
|
17
16
|
|
|
18
|
-
// This test exercises v1 PKB re-index enqueue.
|
|
19
|
-
// (
|
|
20
|
-
//
|
|
21
|
-
|
|
17
|
+
// This test exercises v1 PKB re-index enqueue. `config.memory.v2.enabled`
|
|
18
|
+
// (default `true`) makes the enqueue path skipped — force it off so the
|
|
19
|
+
// v1 PKB index path stays under test.
|
|
20
|
+
mock.module("../../config/loader.js", () => ({
|
|
21
|
+
getConfig: () => ({ memory: { v2: { enabled: false } } }),
|
|
22
|
+
loadConfig: () => ({ memory: { v2: { enabled: false } } }),
|
|
23
|
+
}));
|
|
22
24
|
|
|
23
25
|
let tmpWorkspace: string;
|
|
24
26
|
let previousWorkspaceEnv: string | undefined;
|
|
@@ -32,6 +32,11 @@ export type PermissionDecision =
|
|
|
32
32
|
riskLevel: string;
|
|
33
33
|
riskReason: string;
|
|
34
34
|
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
35
|
+
riskAllowlistOptions?: Array<{
|
|
36
|
+
label: string;
|
|
37
|
+
description: string;
|
|
38
|
+
pattern: string;
|
|
39
|
+
}>;
|
|
35
40
|
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
36
41
|
isContainerized?: boolean;
|
|
37
42
|
};
|
|
@@ -51,6 +56,11 @@ export type PermissionDecision =
|
|
|
51
56
|
riskLevel: string;
|
|
52
57
|
riskReason: string;
|
|
53
58
|
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
59
|
+
riskAllowlistOptions?: Array<{
|
|
60
|
+
label: string;
|
|
61
|
+
description: string;
|
|
62
|
+
pattern: string;
|
|
63
|
+
}>;
|
|
54
64
|
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
55
65
|
isContainerized?: boolean;
|
|
56
66
|
};
|
|
@@ -111,7 +121,12 @@ export class PermissionChecker {
|
|
|
111
121
|
? {
|
|
112
122
|
riskLevel: cachedAssessment.riskLevel,
|
|
113
123
|
riskReason: cachedAssessment.reason,
|
|
124
|
+
// Display ladder (regex patterns — internal only, not for save).
|
|
114
125
|
riskScopeOptions: cachedAssessment.scopeOptions,
|
|
126
|
+
// Save ladder (Minimatch globs — what the gateway matches against).
|
|
127
|
+
// Populated for classifiers that produce allowlist options
|
|
128
|
+
// (bash, file, skill); undefined otherwise.
|
|
129
|
+
riskAllowlistOptions: cachedAssessment.allowlistOptions,
|
|
115
130
|
riskDirectoryScopeOptions: cachedAssessment.directoryScopeOptions,
|
|
116
131
|
isContainerized: getIsContainerized(),
|
|
117
132
|
}
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
collectAllMissing,
|
|
21
21
|
indexCatalogById,
|
|
22
|
-
|
|
22
|
+
validateIncludeCycles,
|
|
23
23
|
} from "../../skills/include-graph.js";
|
|
24
24
|
import { renderInlineCommands } from "../../skills/inline-command-render.js";
|
|
25
25
|
import { parseToolManifestFile } from "../../skills/tool-manifest.js";
|
|
@@ -204,6 +204,7 @@ export class SkillLoadTool implements Tool {
|
|
|
204
204
|
|
|
205
205
|
// Load catalog for include validation and child metadata output
|
|
206
206
|
let catalogIndex: Map<string, SkillSummary> | undefined;
|
|
207
|
+
let missingIncludedSkillIds: string[] = [];
|
|
207
208
|
if (skill.includes && skill.includes.length > 0) {
|
|
208
209
|
let catalog = loadSkillCatalog();
|
|
209
210
|
catalogIndex = indexCatalogById(catalog);
|
|
@@ -260,19 +261,13 @@ export class SkillLoadTool implements Tool {
|
|
|
260
261
|
catalogIndex = indexCatalogById(catalog);
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
missingIncludedSkillIds = [...collectAllMissing(skill.id, catalogIndex)];
|
|
265
|
+
|
|
266
|
+
// Validate cycles fail closed. Missing includes are advisory: the parent
|
|
267
|
+
// skill should still load so the assistant can decide whether to search
|
|
268
|
+
// for and install the suggested dependency.
|
|
269
|
+
const validation = validateIncludeCycles(skill.id, catalogIndex);
|
|
265
270
|
if (!validation.ok) {
|
|
266
|
-
if (validation.error === "missing") {
|
|
267
|
-
return {
|
|
268
|
-
content: `Error: skill "${skill.id}" includes "${
|
|
269
|
-
validation.missingChildId
|
|
270
|
-
}" which was not found (referenced by "${
|
|
271
|
-
validation.parentId
|
|
272
|
-
}" via path: ${validation.path.join(" → ")})`,
|
|
273
|
-
isError: true,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
271
|
if (validation.error === "cycle") {
|
|
277
272
|
return {
|
|
278
273
|
content: `Error: skill "${
|
|
@@ -283,10 +278,6 @@ export class SkillLoadTool implements Tool {
|
|
|
283
278
|
isError: true,
|
|
284
279
|
};
|
|
285
280
|
}
|
|
286
|
-
return {
|
|
287
|
-
content: `Error: skill "${skill.id}" has an invalid include graph`,
|
|
288
|
-
isError: true,
|
|
289
|
-
};
|
|
290
281
|
}
|
|
291
282
|
}
|
|
292
283
|
|
|
@@ -444,13 +435,25 @@ export class SkillLoadTool implements Tool {
|
|
|
444
435
|
}
|
|
445
436
|
}
|
|
446
437
|
}
|
|
447
|
-
immediateChildrenSection =
|
|
448
|
-
|
|
449
|
-
|
|
438
|
+
immediateChildrenSection =
|
|
439
|
+
childLines.length > 0
|
|
440
|
+
? `Included Skills (immediate):\n${childLines.join("\n")}`
|
|
441
|
+
: "Included Skills (immediate): none";
|
|
450
442
|
} else {
|
|
451
443
|
immediateChildrenSection = "Included Skills (immediate): none";
|
|
452
444
|
}
|
|
453
445
|
|
|
446
|
+
const missingIncludesSection =
|
|
447
|
+
missingIncludedSkillIds.length > 0
|
|
448
|
+
? [
|
|
449
|
+
"Suggested Included Skills (not loaded):",
|
|
450
|
+
...missingIncludedSkillIds.map(
|
|
451
|
+
(id) =>
|
|
452
|
+
` - ${id}: not installed or unavailable. If this task needs it, search for and install this skill, then load it.`,
|
|
453
|
+
),
|
|
454
|
+
].join("\n")
|
|
455
|
+
: undefined;
|
|
456
|
+
|
|
454
457
|
let versionHash: string | undefined;
|
|
455
458
|
try {
|
|
456
459
|
versionHash = computeSkillVersionHash(skill.directoryPath);
|
|
@@ -512,6 +515,7 @@ export class SkillLoadTool implements Tool {
|
|
|
512
515
|
: []),
|
|
513
516
|
...includedBodies.flatMap((b) => [b, ""]),
|
|
514
517
|
immediateChildrenSection,
|
|
518
|
+
...(missingIncludesSection ? [missingIncludesSection] : []),
|
|
515
519
|
"",
|
|
516
520
|
`<loaded_skill id="${skill.id}"${versionAttr} />`,
|
|
517
521
|
...includeMarkers,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const TOOL_NAME_ALIASES = new Map<string, string>([
|
|
2
|
+
["create_app", "app_create"],
|
|
3
|
+
]);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve high-confidence compatibility aliases before active-tool gating.
|
|
7
|
+
* Keep this list narrow: aliases should only cover observed model drift where
|
|
8
|
+
* the canonical target is active for the turn.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveToolNameAlias(
|
|
11
|
+
name: string,
|
|
12
|
+
allowedToolNames?: ReadonlySet<string>,
|
|
13
|
+
): string {
|
|
14
|
+
if (allowedToolNames?.has(name)) return name;
|
|
15
|
+
const canonical = TOOL_NAME_ALIASES.get(name);
|
|
16
|
+
if (!canonical) return name;
|
|
17
|
+
if (allowedToolNames && !allowedToolNames.has(canonical)) return name;
|
|
18
|
+
return canonical;
|
|
19
|
+
}
|
package/src/tools/types.ts
CHANGED
|
@@ -115,8 +115,26 @@ export interface ToolExecutionResult {
|
|
|
115
115
|
riskThreshold?: string;
|
|
116
116
|
/** Whether the daemon is running in a containerized (Docker) environment. */
|
|
117
117
|
isContainerized?: boolean;
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Display-only ladder of scope option labels for the rule editor
|
|
120
|
+
* (narrowest to broadest). The `pattern` field here is a regex-style
|
|
121
|
+
* descriptor used internally by the daemon and is NOT a valid trust
|
|
122
|
+
* rule pattern. Use `riskAllowlistOptions` for the pattern that gets
|
|
123
|
+
* saved as a trust rule.
|
|
124
|
+
*/
|
|
119
125
|
riskScopeOptions?: Array<{ pattern: string; label: string }>;
|
|
126
|
+
/**
|
|
127
|
+
* Allowlist options for the rule editor save path (narrowest to
|
|
128
|
+
* broadest). Each `pattern` is a Minimatch-glob compatible string
|
|
129
|
+
* (e.g. raw command for exact match, `action:<program>` for command
|
|
130
|
+
* wildcards) — what the gateway actually matches against. Mirrors
|
|
131
|
+
* the `allowlistOptions` field on `ConfirmationRequest` SSE events.
|
|
132
|
+
*/
|
|
133
|
+
riskAllowlistOptions?: Array<{
|
|
134
|
+
label: string;
|
|
135
|
+
description: string;
|
|
136
|
+
pattern: string;
|
|
137
|
+
}>;
|
|
120
138
|
/** Directory scope ladder for the rule editor (narrowest to broadest). */
|
|
121
139
|
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
122
140
|
/**
|
|
@@ -1,72 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
appendFileSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
writeFileSync,
|
|
6
|
-
} from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
|
|
9
|
-
import { getLogger } from "../../util/logger.js";
|
|
10
1
|
import type { WorkspaceMigration } from "./types.js";
|
|
11
2
|
|
|
12
|
-
const log = getLogger(
|
|
13
|
-
"workspace-migration-067-release-notes-safe-storage-limits",
|
|
14
|
-
);
|
|
15
|
-
|
|
16
3
|
const MIGRATION_ID = "067-release-notes-safe-storage-limits";
|
|
17
|
-
const MARKER = `<!-- release-note-id:${MIGRATION_ID} -->`;
|
|
18
|
-
|
|
19
|
-
const RELEASE_NOTE = `${MARKER}
|
|
20
|
-
## Safe storage limits
|
|
21
|
-
|
|
22
|
-
A new storage protection mode is available behind the safe-storage-limits
|
|
23
|
-
rollout flag. When enabled, the assistant watches workspace disk usage and
|
|
24
|
-
enters cleanup mode if the volume reaches the critical 95% threshold.
|
|
25
|
-
|
|
26
|
-
In cleanup mode, background processes pause and remote messages, including
|
|
27
|
-
trusted-contact messages, are blocked until the guardian frees enough space or
|
|
28
|
-
explicitly overrides the lock. The macOS app now shows a storage cleanup banner
|
|
29
|
-
that must be acknowledged before cleanup chat continues, then keeps a status
|
|
30
|
-
banner visible while cleanup mode is active.
|
|
31
|
-
`;
|
|
32
4
|
|
|
33
5
|
export const releaseNotesSafeStorageLimitsMigration: WorkspaceMigration = {
|
|
34
6
|
id: MIGRATION_ID,
|
|
35
|
-
description: "
|
|
36
|
-
|
|
37
|
-
run(workspaceDir: string): void {
|
|
38
|
-
const updatesPath = join(workspaceDir, "UPDATES.md");
|
|
7
|
+
description: "Reserved migration slot for safe storage limits release notes",
|
|
39
8
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const existing = readFileSync(updatesPath, "utf-8");
|
|
43
|
-
if (existing.includes(MARKER)) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const needsLeadingNewline = !existing.endsWith("\n\n");
|
|
47
|
-
const prefix = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
48
|
-
appendFileSync(
|
|
49
|
-
updatesPath,
|
|
50
|
-
needsLeadingNewline ? `${prefix}${RELEASE_NOTE}` : RELEASE_NOTE,
|
|
51
|
-
"utf-8",
|
|
52
|
-
);
|
|
53
|
-
} else {
|
|
54
|
-
writeFileSync(updatesPath, RELEASE_NOTE, "utf-8");
|
|
55
|
-
}
|
|
56
|
-
log.info(
|
|
57
|
-
{ path: updatesPath },
|
|
58
|
-
"Appended safe storage limits release note",
|
|
59
|
-
);
|
|
60
|
-
} catch (err) {
|
|
61
|
-
log.warn(
|
|
62
|
-
{ err, path: updatesPath },
|
|
63
|
-
"Failed to append safe storage limits release note to UPDATES.md",
|
|
64
|
-
);
|
|
65
|
-
}
|
|
9
|
+
run(_workspaceDir: string): void {
|
|
10
|
+
// Registered no-op slot retained for workspace migration checkpoint compatibility.
|
|
66
11
|
},
|
|
67
12
|
|
|
68
|
-
down(_workspaceDir: string): void {
|
|
69
|
-
// Forward-only: UPDATES.md is a user-facing bulletin the assistant
|
|
70
|
-
// processes and deletes on its own.
|
|
71
|
-
},
|
|
13
|
+
down(_workspaceDir: string): void {},
|
|
72
14
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const ONBOARDING_THREADS = `- Figure out what kind of personality would best mesh with your user. Figure out who you are and what your voice should be. Your choice should be DISTINCT and have CHARACTER. Once you've figured this out, rewrite SOUL.md and IDENTITY.md in your own voice to define who you are.
|
|
7
|
+
- Work with your user to set a custom avatar for yourself. This task is done once data/avatar/avatar-image.png exists.
|
|
8
|
+
- Ask your user if they use ChatGPT, Claude or another AI tool and offer to help them import memories from there.
|
|
9
|
+
- Suggest connecting to available messaging services like Slack or Telegram
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const seedOnboardingThreadsMigration: WorkspaceMigration = {
|
|
13
|
+
id: "069-seed-onboarding-threads",
|
|
14
|
+
description:
|
|
15
|
+
"Seed memory/threads.md with onboarding tasks for brand new assistants",
|
|
16
|
+
|
|
17
|
+
run(workspaceDir: string): void {
|
|
18
|
+
const filePath = join(workspaceDir, "memory", "threads.md");
|
|
19
|
+
if (!existsSync(filePath)) return;
|
|
20
|
+
const current = readFileSync(filePath, "utf-8");
|
|
21
|
+
if (current.trim().length > 0) return;
|
|
22
|
+
writeFileSync(filePath, ONBOARDING_THREADS, "utf-8");
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
down(_workspaceDir: string): void {
|
|
26
|
+
// Forward-only: never delete user-visible memory content on rollback.
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Audit-only entry for the v2 concept-page schema upgrade introduced in PR
|
|
5
|
+
* #29823 (summary_dense / summary_sparse named vectors). The destructive
|
|
6
|
+
* collection rebuild and reembed enqueue both run inside the daemon at
|
|
7
|
+
* Qdrant init time — see `maybeRebuildMemoryV2Concepts` in
|
|
8
|
+
* `assistant/src/daemon/memory-v2-startup.ts`. The "exactly-once" fence is
|
|
9
|
+
* per-collection schema introspection, not per-workspace checkpoint, so
|
|
10
|
+
* users who wipe Qdrant separately still get re-rebuilt without resetting
|
|
11
|
+
* any workspace flag.
|
|
12
|
+
*
|
|
13
|
+
* This migration exists so the registry chronology records the schema
|
|
14
|
+
* upgrade alongside the release that introduced it.
|
|
15
|
+
*/
|
|
16
|
+
export const memoryV2SummarySchemaRebuildMigration: WorkspaceMigration = {
|
|
17
|
+
id: "070-memory-v2-summary-schema-rebuild",
|
|
18
|
+
description:
|
|
19
|
+
"Audit entry: v2 concept-page Qdrant collection now carries summary_dense / summary_sparse named vectors. Rebuild + reembed handled by the daemon's startup hook.",
|
|
20
|
+
|
|
21
|
+
run(_workspaceDir: string): void {
|
|
22
|
+
// No-op: the actual rebuild lives in the Qdrant client layer where the
|
|
23
|
+
// collection schema is owned. Workspace migrations cannot touch Qdrant
|
|
24
|
+
// (they run before it starts and must be self-contained per
|
|
25
|
+
// assistant/src/workspace/migrations/AGENTS.md).
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
down(_workspaceDir: string): void {
|
|
29
|
+
// Forward-only: schema rollbacks happen outside the migration system.
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const MIGRATION_ID = "071-remove-safe-storage-release-note";
|
|
7
|
+
const SAFE_STORAGE_RELEASE_NOTE_ID = "067-release-notes-safe-storage-limits";
|
|
8
|
+
const SAFE_STORAGE_MARKER = `<!-- release-note-id:${SAFE_STORAGE_RELEASE_NOTE_ID} -->`;
|
|
9
|
+
const RELEASE_NOTE_MARKER_PREFIX = "<!-- release-note-id:";
|
|
10
|
+
|
|
11
|
+
const SAFE_STORAGE_RELEASE_NOTE = `${SAFE_STORAGE_MARKER}
|
|
12
|
+
## Safe storage limits
|
|
13
|
+
|
|
14
|
+
A new storage protection mode is available behind the safe-storage-limits
|
|
15
|
+
rollout flag. When enabled, the assistant watches workspace disk usage and
|
|
16
|
+
enters cleanup mode if the volume reaches the critical 95% threshold.
|
|
17
|
+
|
|
18
|
+
In cleanup mode, background processes pause and remote messages, including
|
|
19
|
+
trusted-contact messages, are blocked until the guardian frees enough space or
|
|
20
|
+
explicitly overrides the lock. The macOS app now shows a storage cleanup banner
|
|
21
|
+
that must be acknowledged before cleanup chat continues, then keeps a status
|
|
22
|
+
banner visible while cleanup mode is active.
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const LEADING_NEWLINES = /^(?:(?:\r\n)|\n|\r)+/;
|
|
26
|
+
const TRAILING_NEWLINES = /(?:(?:\r\n)|\n|\r)+$/;
|
|
27
|
+
|
|
28
|
+
function findNewlineStyle(...samples: string[]): string {
|
|
29
|
+
for (const sample of samples) {
|
|
30
|
+
const match = sample.match(/\r\n|\n|\r/);
|
|
31
|
+
if (match) {
|
|
32
|
+
return match[0];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return "\n";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stitchAroundRemovedBlock(before: string, after: string): string {
|
|
40
|
+
if (before.trim() === "") {
|
|
41
|
+
return after.replace(LEADING_NEWLINES, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (after.trim() === "") {
|
|
45
|
+
return `${before.replace(TRAILING_NEWLINES, "")}${findNewlineStyle(before)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const newline = findNewlineStyle(before, after);
|
|
49
|
+
return `${before.replace(TRAILING_NEWLINES, "")}${newline}${newline}${after.replace(LEADING_NEWLINES, "")}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function removeRange(content: string, start: number, end: number): string {
|
|
53
|
+
return stitchAroundRemovedBlock(content.slice(0, start), content.slice(end));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function removeSafeStorageReleaseNote(content: string): string {
|
|
57
|
+
const exactStart = content.indexOf(SAFE_STORAGE_RELEASE_NOTE);
|
|
58
|
+
if (exactStart >= 0) {
|
|
59
|
+
return removeRange(
|
|
60
|
+
content,
|
|
61
|
+
exactStart,
|
|
62
|
+
exactStart + SAFE_STORAGE_RELEASE_NOTE.length,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const markerStart = content.indexOf(SAFE_STORAGE_MARKER);
|
|
67
|
+
if (markerStart < 0) {
|
|
68
|
+
return content;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const nextMarkerStart = content.indexOf(
|
|
72
|
+
RELEASE_NOTE_MARKER_PREFIX,
|
|
73
|
+
markerStart + SAFE_STORAGE_MARKER.length,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return removeRange(
|
|
77
|
+
content,
|
|
78
|
+
markerStart,
|
|
79
|
+
nextMarkerStart >= 0 ? nextMarkerStart : content.length,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const removeSafeStorageReleaseNoteMigration: WorkspaceMigration = {
|
|
84
|
+
id: MIGRATION_ID,
|
|
85
|
+
description: "Remove safe storage release note from UPDATES.md",
|
|
86
|
+
|
|
87
|
+
run(workspaceDir: string): void {
|
|
88
|
+
const updatesPath = join(workspaceDir, "UPDATES.md");
|
|
89
|
+
if (!existsSync(updatesPath)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const existing = readFileSync(updatesPath, "utf-8");
|
|
94
|
+
if (!existing.includes(SAFE_STORAGE_MARKER)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const cleaned = removeSafeStorageReleaseNote(existing);
|
|
99
|
+
if (cleaned.trim() === "") {
|
|
100
|
+
rmSync(updatesPath, { force: true });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
writeFileSync(updatesPath, cleaned, "utf-8");
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
down(_workspaceDir: string): void {
|
|
108
|
+
// Forward-only: this removes a pending bulletin that should no longer be
|
|
109
|
+
// shown to users.
|
|
110
|
+
},
|
|
111
|
+
};
|
|
@@ -66,6 +66,9 @@ import { bumpStaleHeartbeatIntervalMigration } from "./065-bump-stale-heartbeat-
|
|
|
66
66
|
import { seedHeartbeatCallsiteCostDefaultMigration } from "./066-seed-heartbeat-callsite-cost-default.js";
|
|
67
67
|
import { releaseNotesSafeStorageLimitsMigration } from "./067-release-notes-safe-storage-limits.js";
|
|
68
68
|
import { releaseNotesLocalTimezoneMigration } from "./068-release-notes-local-timezone.js";
|
|
69
|
+
import { seedOnboardingThreadsMigration } from "./069-seed-onboarding-threads.js";
|
|
70
|
+
import { memoryV2SummarySchemaRebuildMigration } from "./070-memory-v2-summary-schema-rebuild.js";
|
|
71
|
+
import { removeSafeStorageReleaseNoteMigration } from "./071-remove-safe-storage-release-note.js";
|
|
69
72
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
70
73
|
import type { WorkspaceMigration } from "./types.js";
|
|
71
74
|
|
|
@@ -143,4 +146,7 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
143
146
|
seedHeartbeatCallsiteCostDefaultMigration,
|
|
144
147
|
releaseNotesSafeStorageLimitsMigration,
|
|
145
148
|
releaseNotesLocalTimezoneMigration,
|
|
149
|
+
seedOnboardingThreadsMigration,
|
|
150
|
+
memoryV2SummarySchemaRebuildMigration,
|
|
151
|
+
removeSafeStorageReleaseNoteMigration,
|
|
146
152
|
];
|