vault-go 0.20.0 → 0.21.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/README.md +20 -1
- package/dist/capabilities.js +2 -2
- package/dist/hook-install.d.ts +7 -2
- package/dist/hook-install.js +71 -15
- package/dist/hooks.js +69 -13
- package/dist/installer.js +11 -1
- package/dist/opencode-plugin.d.ts +16 -0
- package/dist/opencode-plugin.js +73 -0
- package/dist/update-control.d.ts +2 -0
- package/dist/update-control.js +19 -2
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -181,7 +181,7 @@ limitados.
|
|
|
181
181
|
|
|
182
182
|
O seletor oferece Codex (Terra, Luna ou Sol pela assinatura ChatGPT), Claude (CLI da assinatura), OpenRouter (chave), Gemini (chave) ou o Vault AI Resume da conta, quando disponível no servidor. Grave chaves com `vault-go engine key openrouter|gemini` ou no painel local; as ferramentas MCP não aceitam segredos.
|
|
183
183
|
|
|
184
|
-
Na instalação, o Vault Go registra hooks no Claude Code, no Codex, no Grok e no
|
|
184
|
+
Na instalação, o Vault Go registra hooks no Claude Code, no Codex, no Grok, no Agy e no Gemini CLI. Eles carregam contexto e capturam os eventos oferecidos pelo cliente; o serviço local (`http://localhost:38850`) gera memórias com o motor escolhido, persistindo no Vault. No OpenCode 1.x, um plugin acrescenta contexto ao sistema antes das chamadas do modelo; esse adaptador não adiciona captura automática.
|
|
185
185
|
|
|
186
186
|
O recurso `vault-go://status` fornece o resumo sanitizado em JSON.
|
|
187
187
|
|
|
@@ -278,6 +278,10 @@ O runtime fica em `~/.memvault/local-runtime`, independente do cache bunx.
|
|
|
278
278
|
`local stop` para o processo; `local uninstall` remove o início automático e
|
|
279
279
|
preserva autenticação, cache e atividade. A atualização automática mantém o
|
|
280
280
|
serviço e os hooks atualizados; clientes MCP abertos precisam reconectar.
|
|
281
|
+
Após validar a nova versão do serviço, a atualização também repara os hooks do
|
|
282
|
+
Gemini e o plugin do OpenCode quando esses clientes já têm o MCP Vault configurado.
|
|
283
|
+
Plugins de terceiros são preservados. Reinicie clientes abertos para carregar
|
|
284
|
+
novos hooks ou o módulo atualizado do plugin.
|
|
281
285
|
|
|
282
286
|
A atividade registra método, rota, duração e resultado das chamadas feitas
|
|
283
287
|
pelo MCP Vault, sem argumentos, consultas ou credenciais. Não captura toda a
|
|
@@ -322,6 +326,21 @@ conforme os eventos oferecidos pelo cliente. O hook anterior à leitura de arqui
|
|
|
322
326
|
é síncrono para conseguir inserir o contexto. `vault_go_session_start_context`
|
|
323
327
|
executa o mesmo leitor, respeitando `contextItems` e `contextMaxChars` da conta.
|
|
324
328
|
|
|
329
|
+
Claude, Codex e Gemini mostram uma confirmação quando o contexto é carregado,
|
|
330
|
+
sem exibir conteúdo da memória nessa confirmação. Worktrees Git reutilizam o
|
|
331
|
+
projeto do repositório principal; se ele ainda não estiver cadastrado, um projeto
|
|
332
|
+
existente do próprio worktree continua legível. Submódulos mantêm seu projeto.
|
|
333
|
+
|
|
334
|
+
O Gemini usa `SessionStart`, `BeforeAgent`, `AfterTool` e `AfterAgent`, conforme
|
|
335
|
+
seu [contrato de hooks](https://geminicli.com/docs/hooks/reference/). Não há
|
|
336
|
+
injeção de histórico de arquivo em `BeforeTool`, pois esse evento não oferece
|
|
337
|
+
esse contrato. O plugin OpenCode usa `experimental.chat.system.transform`
|
|
338
|
+
do [contrato 1.x](https://github.com/anomalyco/opencode/blob/v1.18.31/packages/plugin/src/index.ts),
|
|
339
|
+
com prazo de três segundos e continuação normal em caso de falha. Consultas
|
|
340
|
+
simultâneas da mesma sessão são compartilhadas; respostas de uma credencial
|
|
341
|
+
substituída não são injetadas. Clientes sem esses adaptadores acessam memória
|
|
342
|
+
pelas ferramentas MCP, sem promessa de contexto automático.
|
|
343
|
+
|
|
325
344
|
A captura respeita `automaticCapture`, `captureMode` e a opção de incluir resultados
|
|
326
345
|
de ferramentas. Uma falha ao consultar preferências desativa aquela captura.
|
|
327
346
|
Eventos privados não são enviados; campos sensíveis e marcadores privados são
|
package/dist/capabilities.js
CHANGED
|
@@ -6,7 +6,7 @@ export const CAPABILITIES = {
|
|
|
6
6
|
{ upstream: ['important_workflow'], status: 'available', tools: ['vault_go_workflow'] },
|
|
7
7
|
{ upstream: ['search', 'get_observations', 'timeline'], status: 'partial', tools: ['vault_go_search_index', 'vault_go_observations', 'vault_go_timeline', 'vault_go_feed'], difference: 'UUID identifiers; search requires query; feed provides cursor pagination. No offset/orderBy or automatic timeline anchor.' },
|
|
8
8
|
{ upstream: ['get_tool_uses'], status: 'available', tools: ['vault_go_tool_uses'], requires: 'Engine with POST /v1/events/batch. Historical output is unavailable unless it was captured. Private data is redacted.' },
|
|
9
|
-
{ upstream: ['session_start_context'], status: 'partial', tools: ['vault_go_session_start_context', 'vault_go_context', 'vault_go_file_context'], difference: '
|
|
9
|
+
{ upstream: ['session_start_context'], status: 'partial', tools: ['vault_go_session_start_context', 'vault_go_context', 'vault_go_file_context'], difference: 'Vault SessionStart output and configured budget; canonical Git worktree projects. Native hooks for Claude/Codex/Grok/Agy/Gemini; context-only OpenCode 1.x system plugin. No upstream multi-project chain renderer.' },
|
|
10
10
|
{ upstream: ['smart_search', 'smart_outline', 'smart_unfold'], status: 'partial', tools: ['vault_go_smart_search', 'vault_go_smart_outline', 'vault_go_smart_unfold'], difference: 'Local AST with bounded workspace reads; supported languages are returned by the tools, not all upstream grammars.' },
|
|
11
11
|
{ upstream: ['observation_add', 'observation_record_event', 'observation_search', 'observation_context'], status: 'available', tools: ['vault_go_remember', 'vault_go_event', 'vault_go_search', 'vault_go_context'], requires: 'generate:false requires updated engine and prevents duplicate server processing. summaryMode=off preserves raw memory without invoking a model.' },
|
|
12
12
|
{ upstream: ['automatic updates'], status: 'available', tools: ['vault_go_update_status', 'vault_go_update_check', 'vault_go_update_install', 'vault_go_update_configure'], requires: 'Local worker running, npm installed and official registry reachable. Daily stable-release check; idle activation and runtime rollback. Reconnect MCP clients to use new tool definitions.' },
|
|
@@ -16,7 +16,7 @@ export const CAPABILITIES = {
|
|
|
16
16
|
{ upstream: ['provider selection', 'automatic observation processing'], status: 'available', tools: ['vault_go_engines', 'vault_go_engine_select', 'vault_go_generate_context', 'vault_go_processing_status'], requires: 'Local worker running and selected provider authenticated. Codex Terra/Luna/Sol use ChatGPT subscription CLI. Retried persistence is idempotent.' },
|
|
17
17
|
{ upstream: ['privacy', 'capture modes', 'context settings'], status: 'partial', tools: ['vault_go_preferences', 'vault_go_preferences_update'], difference: 'Built-in capture modes and private-tag redaction; no custom mode inheritance/taxonomy. Preference update requires session authentication.' },
|
|
18
18
|
{ upstream: ['import', 'export', 'cloud sync'], status: 'partial', tools: ['vault_go_import', 'vault_go_export'], difference: 'Vault PostgreSQL persistence; upstream SQLite exports are a different format. Export is a bounded batch, not a complete backup. No offline replicated memory store.' },
|
|
19
|
-
{ upstream: ['Telegram', 'Grok awareness push', 'CCS Align', 'transcript watchers'], status: 'unsupported', tools: [], difference: 'Not ported. Native capture hooks
|
|
19
|
+
{ upstream: ['Telegram', 'Grok awareness push', 'CCS Align', 'transcript watchers'], status: 'unsupported', tools: [], difference: 'Not ported. Native capture hooks target Claude, Codex, Grok, Agy and Gemini. OpenCode has context-only integration; remaining clients use MCP.' },
|
|
20
20
|
{ upstream: ['development/report/presentation skills', 'babysit'], status: 'partial', tools: ['vault_go_modes', 'vault_go_mode'], difference: '20 original Vault workflow prompts, also exposed as tools for clients without prompt support. Requirements and missing runtimes are explicit. Loading a mode does not start background agents or execute external actions.' },
|
|
21
21
|
],
|
|
22
22
|
};
|
package/dist/hook-install.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare const HOOK_EVENTS: {
|
|
|
9
9
|
export declare function quoteShell(value: string): string;
|
|
10
10
|
export declare function hookEntryPath(configHome?: string): string;
|
|
11
11
|
export declare function hookCommand(adapter: string, event: string, configHome?: string): string;
|
|
12
|
-
export type HookClient = "claude" | "codex" | "grok" | "agy";
|
|
12
|
+
export type HookClient = "claude" | "codex" | "grok" | "agy" | "gemini";
|
|
13
13
|
export declare function installClientHooks(client: HookClient, userHome?: string, configHome?: string): void;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function installOpenCodePlugin(userHome?: string, configHome?: string): void;
|
|
15
|
+
/** Repairs only clients explicitly connected to Vault; never installs a CLI. */
|
|
16
|
+
export declare function repairAdditionalClientHooks(userHome?: string, configHome?: string, report?: typeof reportRepairWarning): void;
|
|
17
|
+
declare function reportRepairWarning(message: string): void;
|
|
18
|
+
export declare function repairClientHooks(userHome?: string, configHome?: string, report?: typeof reportRepairWarning): void;
|
|
19
|
+
export {};
|
package/dist/hook-install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { vaultHome } from "./config.js";
|
|
6
6
|
function readJsonObject(path) {
|
|
7
7
|
if (!existsSync(path))
|
|
@@ -21,9 +21,12 @@ function readJsonObject(path) {
|
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
23
|
function atomicWriteJson(path, value) {
|
|
24
|
+
atomicWriteText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
function atomicWriteText(path, text) {
|
|
24
27
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
25
28
|
const temporary = join(dirname(path), `.${process.pid}.${Date.now()}.tmp`);
|
|
26
|
-
writeFileSync(temporary,
|
|
29
|
+
writeFileSync(temporary, text, {
|
|
27
30
|
mode: 0o600,
|
|
28
31
|
});
|
|
29
32
|
renameSync(temporary, path);
|
|
@@ -37,6 +40,12 @@ export const HOOK_EVENTS = {
|
|
|
37
40
|
PostToolUse: "observation",
|
|
38
41
|
Stop: "summarize",
|
|
39
42
|
};
|
|
43
|
+
const GEMINI_EVENTS = {
|
|
44
|
+
SessionStart: "context",
|
|
45
|
+
BeforeAgent: "session-init",
|
|
46
|
+
AfterTool: "observation",
|
|
47
|
+
AfterAgent: "summarize",
|
|
48
|
+
};
|
|
40
49
|
export function quoteShell(value) {
|
|
41
50
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
42
51
|
}
|
|
@@ -51,7 +60,7 @@ export function hookCommand(adapter, event, configHome = vaultHome()) {
|
|
|
51
60
|
}
|
|
52
61
|
function hookManifest(adapter, configHome = vaultHome()) {
|
|
53
62
|
return {
|
|
54
|
-
hooks: Object.fromEntries(Object.entries(HOOK_EVENTS).map(([name, event]) => [
|
|
63
|
+
hooks: Object.fromEntries(Object.entries(adapter === "gemini" ? GEMINI_EVENTS : HOOK_EVENTS).map(([name, event]) => [
|
|
55
64
|
name,
|
|
56
65
|
[
|
|
57
66
|
{
|
|
@@ -59,10 +68,15 @@ function hookManifest(adapter, configHome = vaultHome()) {
|
|
|
59
68
|
{
|
|
60
69
|
type: "command",
|
|
61
70
|
command: hookCommand(adapter, event, configHome),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
...(adapter === "gemini" ? {
|
|
72
|
+
timeout: event === "context" ? 60000 : 20000,
|
|
73
|
+
name: `vault-go:${event}`,
|
|
74
|
+
description: HOOK_OWNER,
|
|
75
|
+
} : {
|
|
76
|
+
timeout: event === "context" ? 60 : 20,
|
|
77
|
+
statusMessage: HOOK_OWNER,
|
|
78
|
+
}),
|
|
79
|
+
...(adapter !== "gemini" && (event === "observation" || event === "summarize")
|
|
66
80
|
? { async: true }
|
|
67
81
|
: {}),
|
|
68
82
|
},
|
|
@@ -100,7 +114,7 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
100
114
|
atomicWriteJson(path, { ...config, "vault-go": agyHookManifest(configHome) });
|
|
101
115
|
return;
|
|
102
116
|
}
|
|
103
|
-
const path = join(userHome, client === "claude" ? ".claude/settings.json" : ".codex/hooks.json");
|
|
117
|
+
const path = join(userHome, client === "claude" ? ".claude/settings.json" : client === "gemini" ? ".gemini/settings.json" : ".codex/hooks.json");
|
|
104
118
|
const config = readJsonObject(path);
|
|
105
119
|
const hooks = {
|
|
106
120
|
...(config.hooks &&
|
|
@@ -110,7 +124,7 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
110
124
|
: {}),
|
|
111
125
|
};
|
|
112
126
|
const desired = hookManifest(client, configHome).hooks;
|
|
113
|
-
for (const event of Object.keys(
|
|
127
|
+
for (const event of Object.keys(desired)) {
|
|
114
128
|
const current = hooks[event];
|
|
115
129
|
const retained = Array.isArray(current)
|
|
116
130
|
? current
|
|
@@ -121,7 +135,8 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
121
135
|
return group;
|
|
122
136
|
const filtered = group.hooks.filter((hook) => !hook ||
|
|
123
137
|
typeof hook !== "object" ||
|
|
124
|
-
hook.statusMessage !== HOOK_OWNER
|
|
138
|
+
(hook.statusMessage !== HOOK_OWNER &&
|
|
139
|
+
!(client === "gemini" && hook.name === `vault-go:${GEMINI_EVENTS[event]}`)));
|
|
125
140
|
return { ...group, hooks: filtered };
|
|
126
141
|
})
|
|
127
142
|
.filter((group) => !group ||
|
|
@@ -133,15 +148,56 @@ export function installClientHooks(client, userHome = homedir(), configHome = va
|
|
|
133
148
|
}
|
|
134
149
|
atomicWriteJson(path, { ...config, hooks });
|
|
135
150
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
const OPENCODE_OWNER = "// Managed by vault-go: automatic memory context";
|
|
152
|
+
export function installOpenCodePlugin(userHome = homedir(), configHome = vaultHome()) {
|
|
153
|
+
const path = join(userHome, ".config", "opencode", "plugins", "vault-go-memory.js");
|
|
154
|
+
if (existsSync(path) && !readFileSync(path, "utf8").startsWith(OPENCODE_OWNER + "\n")) {
|
|
155
|
+
throw new Error(`Refusing to replace an unowned plugin: ${path}`);
|
|
156
|
+
}
|
|
157
|
+
const runtime = pathToFileURL(join(configHome, "local-runtime", "dist", "opencode-plugin.js")).href;
|
|
158
|
+
atomicWriteText(path, `${OPENCODE_OWNER}\nexport default async function(input) {\n try {\n const runtime = await import(${JSON.stringify(runtime)});\n return runtime.createOpenCodePlugin(input, { home: ${JSON.stringify(configHome)} });\n } catch { return {}; }\n}\n`);
|
|
159
|
+
}
|
|
160
|
+
/** Repairs only clients explicitly connected to Vault; never installs a CLI. */
|
|
161
|
+
export function repairAdditionalClientHooks(userHome = homedir(), configHome = vaultHome(), report = reportRepairWarning) {
|
|
162
|
+
repairClient("gemini", () => {
|
|
163
|
+
const gemini = readJsonObject(join(userHome, ".gemini", "settings.json"));
|
|
164
|
+
const server = gemini.mcpServers?.["vault-go"];
|
|
165
|
+
if (server && server.enabled !== false && server.disabled !== true) {
|
|
166
|
+
installClientHooks("gemini", userHome, configHome);
|
|
167
|
+
}
|
|
168
|
+
}, report);
|
|
169
|
+
repairClient("opencode", () => {
|
|
170
|
+
const opencode = readJsonObject(join(userHome, ".config", "opencode", "opencode.json"));
|
|
171
|
+
const server = opencode.mcp?.["vault-go"];
|
|
172
|
+
if (server && server.enabled !== false)
|
|
173
|
+
installOpenCodePlugin(userHome, configHome);
|
|
174
|
+
}, report);
|
|
175
|
+
}
|
|
176
|
+
function reportRepairWarning(message) {
|
|
177
|
+
process.stderr.write(message + "\n");
|
|
178
|
+
}
|
|
179
|
+
function repairClient(client, repair, report) {
|
|
180
|
+
try {
|
|
181
|
+
repair();
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Client configuration failures must not stop another client's repair or reveal its contents.
|
|
185
|
+
try {
|
|
186
|
+
report(`Vault Go: reparo de ${client} pendente; configuração existente preservada.`);
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export function repairClientHooks(userHome = homedir(), configHome = vaultHome(), report = reportRepairWarning) {
|
|
192
|
+
repairAdditionalClientHooks(userHome, configHome, report);
|
|
193
|
+
repairClient("claude", () => installClientHooks("claude", userHome, configHome), report);
|
|
194
|
+
repairClient("codex", () => installClientHooks("codex", userHome, configHome), report);
|
|
139
195
|
if (existsSync(join(userHome, ".grok"))) {
|
|
140
|
-
installClientHooks("grok", userHome, configHome);
|
|
196
|
+
repairClient("grok", () => installClientHooks("grok", userHome, configHome), report);
|
|
141
197
|
}
|
|
142
198
|
if (existsSync(join(userHome, ".gemini", "config")) ||
|
|
143
199
|
existsSync(join(userHome, ".gemini", "antigravity")) ||
|
|
144
200
|
existsSync(join(userHome, ".gemini", "antigravity-cli"))) {
|
|
145
|
-
installClientHooks("agy", userHome, configHome);
|
|
201
|
+
repairClient("agy", () => installClientHooks("agy", userHome, configHome), report);
|
|
146
202
|
}
|
|
147
203
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { basename, dirname, resolve } from "node:path";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { VaultCloudClient } from "./cloud.js";
|
|
5
5
|
import { vaultHome } from "./config.js";
|
|
6
6
|
import { enqueueGeneration } from "./hook-queue.js";
|
|
@@ -16,6 +16,14 @@ const record = (value) => value && typeof value === "object" && !Array.isArray(v
|
|
|
16
16
|
: {};
|
|
17
17
|
const string = (...values) => values.find((value) => typeof value === "string" && value.trim()) ||
|
|
18
18
|
"";
|
|
19
|
+
function resolvedPath(path) {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(resolve(path));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return resolve(path);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
function root(cwd) {
|
|
20
28
|
const initial = resolve(cwd);
|
|
21
29
|
let dir = initial;
|
|
@@ -26,6 +34,43 @@ function root(cwd) {
|
|
|
26
34
|
}
|
|
27
35
|
return initial;
|
|
28
36
|
}
|
|
37
|
+
function gitMetadata(path) {
|
|
38
|
+
const file = statSync(path);
|
|
39
|
+
if (!file.isFile() || file.size > 4096)
|
|
40
|
+
return "";
|
|
41
|
+
return readFileSync(path, "utf8").trim();
|
|
42
|
+
}
|
|
43
|
+
function projectRoots(workspace) {
|
|
44
|
+
const fallback = [resolvedPath(workspace)];
|
|
45
|
+
try {
|
|
46
|
+
const marker = /^gitdir:\s*(.+)$/.exec(gitMetadata(resolve(workspace, ".git")));
|
|
47
|
+
if (!marker)
|
|
48
|
+
return fallback;
|
|
49
|
+
const gitdir = resolve(workspace, marker[1]);
|
|
50
|
+
const common = gitMetadata(resolve(gitdir, "commondir"));
|
|
51
|
+
// A submodule or separate git directory has no shared worktree metadata.
|
|
52
|
+
if (!common)
|
|
53
|
+
return fallback;
|
|
54
|
+
const shared = resolvedPath(resolve(gitdir, common));
|
|
55
|
+
if (basename(shared) !== ".git" || !statSync(shared).isDirectory())
|
|
56
|
+
return fallback;
|
|
57
|
+
return [...new Set([dirname(shared), ...fallback])];
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Keep existing projects usable if Git metadata is absent or unreadable.
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function canonicalFile(file, workspace, project) {
|
|
65
|
+
const absolute = resolve(workspace, file);
|
|
66
|
+
for (const directory of [workspace, resolvedPath(workspace)]) {
|
|
67
|
+
const path = relative(directory, absolute);
|
|
68
|
+
if (path !== ".." && !path.startsWith("../") && !path.startsWith("..\\") && !isAbsolute(path)) {
|
|
69
|
+
return resolve(project, path);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return absolute;
|
|
73
|
+
}
|
|
29
74
|
export function redact(value) {
|
|
30
75
|
if (typeof value === "string") {
|
|
31
76
|
return value
|
|
@@ -58,7 +103,7 @@ export function normalizeHookInput(adapter, value) {
|
|
|
58
103
|
cwd: string(input.cwd, input.workspace, input.workspaceRoot, workspace),
|
|
59
104
|
tool_name: string(input.tool_name, input.toolName, toolCall.name),
|
|
60
105
|
tool_input: record(input.tool_input ?? input.toolInput ?? toolCall.args),
|
|
61
|
-
last_assistant_message: string(input.last_assistant_message, input.lastAssistantMessage, input.response, input.summary),
|
|
106
|
+
last_assistant_message: string(input.last_assistant_message, input.lastAssistantMessage, input.prompt_response, input.response, input.summary),
|
|
62
107
|
prompt: string(input.prompt, input.user_prompt, input.userPrompt),
|
|
63
108
|
};
|
|
64
109
|
}
|
|
@@ -73,19 +118,24 @@ export function hookOutput(event, context = "", adapter = "claude") {
|
|
|
73
118
|
return { decision: "allow" };
|
|
74
119
|
return {};
|
|
75
120
|
}
|
|
76
|
-
const result =
|
|
121
|
+
const result = adapter === "codex"
|
|
122
|
+
? {}
|
|
123
|
+
: { continue: true, suppressOutput: true };
|
|
77
124
|
const name = event === "context"
|
|
78
125
|
? "SessionStart"
|
|
79
126
|
: event === "file-context"
|
|
80
127
|
? "PreToolUse"
|
|
81
128
|
: event === "session-init"
|
|
82
|
-
? "UserPromptSubmit"
|
|
129
|
+
? adapter === "gemini" ? "BeforeAgent" : "UserPromptSubmit"
|
|
83
130
|
: "";
|
|
84
131
|
if (name && context) {
|
|
85
132
|
result.hookSpecificOutput = {
|
|
86
133
|
hookEventName: name,
|
|
87
134
|
additionalContext: context,
|
|
88
135
|
};
|
|
136
|
+
if (event === "context" && ["claude", "codex", "gemini"].includes(adapter)) {
|
|
137
|
+
result.systemMessage = "Vault Memory: contexto do projeto carregado.";
|
|
138
|
+
}
|
|
89
139
|
}
|
|
90
140
|
return result;
|
|
91
141
|
}
|
|
@@ -130,15 +180,17 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
130
180
|
const sessionId = string(input.session_id);
|
|
131
181
|
if (!reading && !sessionId)
|
|
132
182
|
return hookOutput(event, "", adapter);
|
|
133
|
-
const
|
|
183
|
+
const workspace = root(string(input.cwd, input.workspace) || process.cwd());
|
|
184
|
+
const roots = projectRoots(workspace);
|
|
134
185
|
const projects = await cloud.projects();
|
|
135
186
|
if (!Array.isArray(projects))
|
|
136
187
|
throw new Error("Resposta de projetos inválida");
|
|
137
|
-
let project = projects.find((item) => item &&
|
|
188
|
+
let project = roots.map((cwd) => projects.find((item) => item &&
|
|
138
189
|
typeof item === "object" &&
|
|
139
190
|
typeof item.rootPath === "string" &&
|
|
140
191
|
Boolean(String(item.rootPath).trim()) &&
|
|
141
|
-
|
|
192
|
+
resolvedPath(String(item.rootPath)) === cwd)).find(Boolean);
|
|
193
|
+
const cwd = project ? resolvedPath(String(project.rootPath)) : roots[0];
|
|
142
194
|
if (!project && reading)
|
|
143
195
|
return hookOutput(event, "", adapter);
|
|
144
196
|
if (!project) {
|
|
@@ -154,11 +206,15 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
154
206
|
limit: boundedPreference(prefs.contextItems, 20, 1, 100),
|
|
155
207
|
maxChars: boundedPreference(prefs.contextMaxChars, 24_000, 1_000, 100_000),
|
|
156
208
|
};
|
|
209
|
+
const projectFile = file ? canonicalFile(file, workspace, cwd) : "";
|
|
210
|
+
const files = file
|
|
211
|
+
? [...new Set([file, ...(resolvedPath(workspace) !== cwd ? [projectFile] : [])])]
|
|
212
|
+
: [];
|
|
157
213
|
if (reading) {
|
|
158
214
|
const result = record(event === "file-context"
|
|
159
215
|
? await cloud.fileContext({
|
|
160
216
|
...limits,
|
|
161
|
-
paths: [...new Set([file, resolve(
|
|
217
|
+
paths: [...new Set([file, resolve(workspace, file), projectFile])],
|
|
162
218
|
})
|
|
163
219
|
: await cloud.context(limits));
|
|
164
220
|
return hookOutput(event, string(result.context).slice(0, limits.maxChars), adapter);
|
|
@@ -218,8 +274,8 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
218
274
|
title: toolName ||
|
|
219
275
|
(event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
|
|
220
276
|
content,
|
|
221
|
-
filesRead: !modifies
|
|
222
|
-
filesModified: modifies
|
|
277
|
+
filesRead: !modifies ? files : [],
|
|
278
|
+
filesModified: modifies ? files : [],
|
|
223
279
|
},
|
|
224
280
|
occurredAtEpoch: Date.now(),
|
|
225
281
|
});
|
|
@@ -233,8 +289,8 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
|
|
|
233
289
|
(event === "summarize" ? "Resumo da sessão" : "Solicitação do usuário"),
|
|
234
290
|
kind: event === "summarize" ? "summary" : event === "session-init" ? "prompt" : "observation",
|
|
235
291
|
maxChars: boundedPreference(prefs.summaryMaxChars, 4_000, 256, 16_000),
|
|
236
|
-
filesRead: !modifies
|
|
237
|
-
filesModified: modifies
|
|
292
|
+
filesRead: !modifies ? files : [],
|
|
293
|
+
filesModified: modifies ? files : [],
|
|
238
294
|
});
|
|
239
295
|
}
|
|
240
296
|
if (event === "summarize")
|
package/dist/installer.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { vaultHome } from './config.js';
|
|
6
6
|
import { stageLocalRuntime } from './local-install.js';
|
|
7
|
-
import { installClientHooks } from './hook-install.js';
|
|
7
|
+
import { installClientHooks, installOpenCodePlugin } from './hook-install.js';
|
|
8
8
|
export const MCP_SERVER_NAME = 'vault-go';
|
|
9
9
|
export const MCP_SERVER_COMMAND = ['bunx', '--bun', 'vault-go@latest', 'serve'];
|
|
10
10
|
export const MCP_CLIENTS = [
|
|
@@ -199,6 +199,11 @@ export function installMcpClient(client, options = {}) {
|
|
|
199
199
|
if (client === 'gemini') {
|
|
200
200
|
const path = join(home, '.gemini', 'settings.json');
|
|
201
201
|
mergeServer(path);
|
|
202
|
+
try {
|
|
203
|
+
stageLocalRuntime(vaultHome());
|
|
204
|
+
}
|
|
205
|
+
catch { /* incomplete installs still write hooks */ }
|
|
206
|
+
installClientHooks('gemini', home);
|
|
202
207
|
return { client, status: 'installed', destination: path };
|
|
203
208
|
}
|
|
204
209
|
if (client === 'vault-desktop') {
|
|
@@ -221,6 +226,11 @@ export function installMcpClient(client, options = {}) {
|
|
|
221
226
|
else
|
|
222
227
|
path = join(home, '.config', 'opencode', 'opencode.json');
|
|
223
228
|
if (client === 'opencode') {
|
|
229
|
+
try {
|
|
230
|
+
stageLocalRuntime(vaultHome());
|
|
231
|
+
}
|
|
232
|
+
catch { /* plugin becomes active when runtime is available */ }
|
|
233
|
+
installOpenCodePlugin(home);
|
|
224
234
|
mergeServer(path, 'mcp', {
|
|
225
235
|
type: 'local',
|
|
226
236
|
command: [...MCP_SERVER_COMMAND],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Options = {
|
|
2
|
+
home?: string;
|
|
3
|
+
fetch?: typeof fetch;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
};
|
|
6
|
+
/** OpenCode 1.x plugin contract: context only; capture stays with the user's existing integrations. */
|
|
7
|
+
export declare function createOpenCodePlugin(input: {
|
|
8
|
+
directory: string;
|
|
9
|
+
}, options?: Options): {
|
|
10
|
+
'experimental.chat.system.transform': (request: {
|
|
11
|
+
sessionID?: string;
|
|
12
|
+
}, output: {
|
|
13
|
+
system: string[];
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { loadConfig, loadTokens, vaultHome } from './config.js';
|
|
3
|
+
import { VaultCloudClient } from './cloud.js';
|
|
4
|
+
import { executeHook } from './hooks.js';
|
|
5
|
+
function owner(home) {
|
|
6
|
+
return createHash('sha256').update(JSON.stringify([
|
|
7
|
+
loadConfig(home).apiUrl,
|
|
8
|
+
process.env.VAULT_GO_TOKEN || loadTokens(home)?.accessToken || '',
|
|
9
|
+
])).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
/** OpenCode 1.x plugin contract: context only; capture stays with the user's existing integrations. */
|
|
12
|
+
export function createOpenCodePlugin(input, options = {}) {
|
|
13
|
+
const home = options.home ?? vaultHome();
|
|
14
|
+
const pending = new Map();
|
|
15
|
+
const empty = () => ({ context: '', owner: '' });
|
|
16
|
+
async function readContext(sessionID) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const boundedFetch = (async (url, init) => {
|
|
20
|
+
controller.signal.throwIfAborted();
|
|
21
|
+
return fetcher(url, {
|
|
22
|
+
...init,
|
|
23
|
+
signal: init?.signal ? AbortSignal.any([controller.signal, init.signal]) : controller.signal,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const cloud = new VaultCloudClient(home, boundedFetch, false);
|
|
27
|
+
let timer;
|
|
28
|
+
const load = async () => {
|
|
29
|
+
// Authenticate/refresh before taking the ownership snapshot. No model or capture writes run here.
|
|
30
|
+
const preferences = await cloud.preferences();
|
|
31
|
+
controller.signal.throwIfAborted();
|
|
32
|
+
const requestOwner = owner(home);
|
|
33
|
+
cloud.preferences = async () => preferences;
|
|
34
|
+
const output = await executeHook('opencode', 'context', { cwd: input.directory, session_id: sessionID }, cloud, home);
|
|
35
|
+
const context = output.hookSpecificOutput?.additionalContext;
|
|
36
|
+
if (controller.signal.aborted || requestOwner !== owner(home) || typeof context !== 'string')
|
|
37
|
+
return empty();
|
|
38
|
+
return { context, owner: requestOwner };
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
return await Promise.race([
|
|
42
|
+
load(),
|
|
43
|
+
new Promise(resolve => {
|
|
44
|
+
timer = setTimeout(() => { controller.abort(); resolve(empty()); }, options.timeoutMs ?? 3000);
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return empty();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (timer)
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
controller.abort();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
'experimental.chat.system.transform': async (request, output) => {
|
|
59
|
+
if (!request.sessionID || !input.directory)
|
|
60
|
+
return;
|
|
61
|
+
const key = JSON.stringify([request.sessionID, owner(home)]);
|
|
62
|
+
let loading = pending.get(key);
|
|
63
|
+
if (!loading) {
|
|
64
|
+
loading = readContext(request.sessionID).finally(() => pending.delete(key));
|
|
65
|
+
pending.set(key, loading);
|
|
66
|
+
}
|
|
67
|
+
const result = await loading;
|
|
68
|
+
if (result.context && result.owner === owner(home) && !output.system.includes(result.context)) {
|
|
69
|
+
output.system.push(result.context);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
package/dist/update-control.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { type UpdateState } from './updater.js';
|
|
|
3
3
|
export declare function activatePreparedUpdate(home: string, state: UpdateState, operations?: {
|
|
4
4
|
stop: (home: string) => Promise<boolean>;
|
|
5
5
|
start: (home: string) => Promise<LocalHealth>;
|
|
6
|
+
repair?: (home: string) => void;
|
|
7
|
+
warn?: (message: string) => void;
|
|
6
8
|
}, options?: {
|
|
7
9
|
automatic?: boolean;
|
|
8
10
|
}): Promise<LocalHealth>;
|
package/dist/update-control.js
CHANGED
|
@@ -2,10 +2,12 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { existsSync, openSync, closeSync, readFileSync, realpathSync, renameSync, rmSync, lstatSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, relative, isAbsolute } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { stageLocalRuntime, startLocal, stopLocal } from './local-install.js';
|
|
6
7
|
import { installUpdate, updateStatus } from './updater.js';
|
|
7
8
|
import { vaultHome } from './config.js';
|
|
8
9
|
import { generationQueueStatus } from './hook-queue.js';
|
|
10
|
+
import { repairAdditionalClientHooks } from './hook-install.js';
|
|
9
11
|
function preparedPackage(home, state) {
|
|
10
12
|
if (state.status !== 'ready' || !state.packageRoot || !state.latest)
|
|
11
13
|
throw new Error('No validated update is ready.');
|
|
@@ -18,8 +20,20 @@ function preparedPackage(home, state) {
|
|
|
18
20
|
throw new Error('Invalid prepared update.');
|
|
19
21
|
return root;
|
|
20
22
|
}
|
|
21
|
-
export async function activatePreparedUpdate(home, state, operations = { stop: stopLocal, start: startLocal }, options = {}) {
|
|
23
|
+
export async function activatePreparedUpdate(home, state, operations = { stop: stopLocal, start: startLocal, repair: home => repairAdditionalClientHooks(homedir(), home) }, options = {}) {
|
|
22
24
|
const release = await acquireActivationLock(home);
|
|
25
|
+
const repair = () => {
|
|
26
|
+
try {
|
|
27
|
+
operations.repair?.(home);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
const message = 'Vault Go: runtime atualizado; reparo dos hooks dos clientes pendente.';
|
|
31
|
+
try {
|
|
32
|
+
(operations.warn ?? (warning => { process.stderr.write(warning + '\n'); }))(message);
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
}
|
|
36
|
+
};
|
|
23
37
|
try {
|
|
24
38
|
if (options.automatic && !updateStatus(home).enabled)
|
|
25
39
|
throw new Error('Automatic updates are disabled.');
|
|
@@ -29,11 +43,14 @@ export async function activatePreparedUpdate(home, state, operations = { stop: s
|
|
|
29
43
|
const health = await operations.start(home);
|
|
30
44
|
if (health.version !== state.latest)
|
|
31
45
|
throw new Error('Active worker version mismatch.');
|
|
46
|
+
repair();
|
|
32
47
|
return health;
|
|
33
48
|
}
|
|
34
49
|
if (active && compareVersions(active, state.latest) > 0)
|
|
35
50
|
throw new Error('Refusing to activate an older runtime.');
|
|
36
|
-
|
|
51
|
+
const health = await replaceRuntime(home, state, source, operations);
|
|
52
|
+
repair();
|
|
53
|
+
return health;
|
|
37
54
|
}
|
|
38
55
|
finally {
|
|
39
56
|
release();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vault-go",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Servidor MCP universal com autenticação e instalação multi-cliente para a plataforma Vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
+
"dist/opencode-plugin.js",
|
|
24
|
+
"dist/opencode-plugin.d.ts",
|
|
23
25
|
"dist/activity.js",
|
|
24
26
|
"dist/activity.d.ts",
|
|
25
27
|
"dist/auth.js",
|