vault-go 0.20.0 → 0.22.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.
Files changed (66) hide show
  1. package/README.md +58 -9
  2. package/dist/awareness-tools.d.ts +4 -0
  3. package/dist/awareness-tools.js +38 -0
  4. package/dist/backup.d.ts +41 -0
  5. package/dist/backup.js +117 -0
  6. package/dist/capabilities.d.ts +218 -25
  7. package/dist/capabilities.js +46 -16
  8. package/dist/capture-privacy.d.ts +1 -0
  9. package/dist/capture-privacy.js +19 -0
  10. package/dist/ccs-align.d.ts +122 -0
  11. package/dist/ccs-align.js +376 -0
  12. package/dist/cli.d.ts +3 -1
  13. package/dist/cli.js +52 -0
  14. package/dist/cloud.d.ts +15 -2
  15. package/dist/cloud.js +62 -4
  16. package/dist/context-chain.d.ts +37 -0
  17. package/dist/context-chain.js +98 -0
  18. package/dist/context-engines.d.ts +6 -0
  19. package/dist/context-engines.js +28 -8
  20. package/dist/crystal-tools.d.ts +9 -0
  21. package/dist/crystal-tools.js +106 -0
  22. package/dist/custom-modes.d.ts +59 -0
  23. package/dist/custom-modes.js +249 -0
  24. package/dist/deployment-verification.d.ts +19 -0
  25. package/dist/deployment-verification.js +171 -0
  26. package/dist/grok-awareness.d.ts +51 -0
  27. package/dist/grok-awareness.js +244 -0
  28. package/dist/hook-install.d.ts +7 -2
  29. package/dist/hook-install.js +71 -15
  30. package/dist/hook-queue.d.ts +6 -2
  31. package/dist/hook-queue.js +23 -4
  32. package/dist/hooks.d.ts +4 -3
  33. package/dist/hooks.js +83 -32
  34. package/dist/index.js +9 -1
  35. package/dist/installer.js +11 -1
  36. package/dist/knowledge-agent.d.ts +39 -0
  37. package/dist/knowledge-agent.js +143 -0
  38. package/dist/knowledge-tools.d.ts +3 -0
  39. package/dist/knowledge-tools.js +19 -0
  40. package/dist/local-service.js +6 -1
  41. package/dist/mode-integrations.d.ts +9 -0
  42. package/dist/mode-integrations.js +149 -0
  43. package/dist/modes.js +2 -2
  44. package/dist/opencode-plugin.d.ts +16 -0
  45. package/dist/opencode-plugin.js +73 -0
  46. package/dist/parity-tools.js +4 -4
  47. package/dist/server.js +56 -8
  48. package/dist/smart-code.d.ts +12 -16
  49. package/dist/smart-code.js +139 -28
  50. package/dist/telegram.d.ts +39 -0
  51. package/dist/telegram.js +138 -0
  52. package/dist/transcript-config.d.ts +41 -0
  53. package/dist/transcript-config.js +97 -0
  54. package/dist/transcript-parser.d.ts +24 -0
  55. package/dist/transcript-parser.js +118 -0
  56. package/dist/transcript-tools.d.ts +3 -0
  57. package/dist/transcript-tools.js +21 -0
  58. package/dist/transcript-watchers.d.ts +13 -0
  59. package/dist/transcript-watchers.js +311 -0
  60. package/dist/update-control.d.ts +2 -0
  61. package/dist/update-control.js +19 -2
  62. package/docs/ast-and-awareness.md +72 -0
  63. package/docs/mode-integrations.md +74 -0
  64. package/docs/parity-deployment-review.md +60 -0
  65. package/docs/transcripts-and-context.md +75 -0
  66. package/package.json +62 -2
@@ -0,0 +1,244 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { loadConfig, loadTokens } from './config.js';
6
+ import { redact } from './capture-privacy.js';
7
+ const AGENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+ const MAX_LOG_BYTES = 1_048_576;
9
+ function credential(home) {
10
+ return process.env['VAULT_GO_TOKEN'] || loadTokens(home)?.accessToken || '';
11
+ }
12
+ function owner(home) {
13
+ const token = credential(home);
14
+ if (!token)
15
+ return '';
16
+ const config = loadConfig(home);
17
+ return createHash('sha256').update(JSON.stringify([config.apiUrl, config.email ?? '', token])).digest('hex');
18
+ }
19
+ export function awarenessAccountBinding(home) {
20
+ return createHash('sha256').update(JSON.stringify([owner(home), credential(home)])).digest('hex');
21
+ }
22
+ function defaults(home) { return { owner: owner(home), enabled: false, agentDataRoot: '', agentIds: [], triggerTypes: [], triggerConcepts: [] }; }
23
+ function load(home) {
24
+ try {
25
+ const file = join(home, 'grok-awareness.json');
26
+ if (statSync(file).size > 64_000)
27
+ return defaults(home);
28
+ const value = JSON.parse(readFileSync(file, 'utf8'));
29
+ if (value.owner !== owner(home) || typeof value.agentDataRoot !== 'string' || ![value.agentIds, value.triggerTypes, value.triggerConcepts].every(v => Array.isArray(v) && v.length <= 50 && v.every(s => typeof s === 'string' && s.length <= 100)))
30
+ return defaults(home);
31
+ return value;
32
+ }
33
+ catch {
34
+ return defaults(home);
35
+ }
36
+ }
37
+ function save(home, config) {
38
+ mkdirSync(home, { recursive: true, mode: 0o700 });
39
+ const file = join(home, 'grok-awareness.json'), temp = `${file}.${randomUUID()}.tmp`;
40
+ writeFileSync(temp, JSON.stringify(config) + '\n', { mode: 0o600 });
41
+ renameSync(temp, file);
42
+ chmodSync(file, 0o600);
43
+ }
44
+ export function awarenessStatus(home) {
45
+ const { owner: _owner, ...config } = load(home);
46
+ return { ...config, maxLineChars: 500, maxLogBytes: MAX_LOG_BYTES, transport: 'local-monthly-agent-log', requiresAgentId: true };
47
+ }
48
+ export function configureAwareness(home, input) {
49
+ const config = load(home);
50
+ if (input.enabled) {
51
+ if (input.consent !== true || !owner(home))
52
+ throw new Error('Ativar awareness exige conta autenticada e consent=true.');
53
+ const root = input.agentDataRoot ?? config.agentDataRoot;
54
+ if (!isAbsolute(root) || !statSync(root).isDirectory())
55
+ throw new Error('Informe uma raiz absoluta existente para os dados dos agentes Grok.');
56
+ const agentIds = [...new Set(input.agentIds ?? config.agentIds)];
57
+ const triggerTypes = [...new Set(input.triggerTypes ?? config.triggerTypes)], triggerConcepts = [...new Set(input.triggerConcepts ?? config.triggerConcepts)];
58
+ if (!agentIds.length || agentIds.length > 50 || agentIds.some(id => !AGENT_ID.test(id)))
59
+ throw new Error('Informe de 1 a 50 UUIDs de agentes permitidos.');
60
+ if (![triggerTypes, triggerConcepts].every(v => v.length <= 50 && v.every(s => s.length > 0 && s.length <= 100)) || !triggerTypes.length && !triggerConcepts.length)
61
+ throw new Error('Defina ao menos um filtro de tipo/conceito, com até 50 valores por lista.');
62
+ Object.assign(config, { owner: owner(home), enabled: true, agentDataRoot: realpathSync(root), agentIds, triggerTypes, triggerConcepts });
63
+ }
64
+ else
65
+ config.enabled = false;
66
+ delete config.lastError;
67
+ save(home, config);
68
+ return awarenessStatus(home);
69
+ }
70
+ function lineFor(observation, now) {
71
+ if (observation.private || /<(private|vault-private)\b/i.test(JSON.stringify(observation)))
72
+ return null;
73
+ const clean = (value) => String(redact(value)).replace(/\s+/g, ' ').trim();
74
+ const title = [observation.title, observation.subtitle].filter((v) => typeof v === 'string').map(clean).filter(Boolean).join(': ');
75
+ const detail = [title, clean(observation.facts?.[0] ?? '')].filter(Boolean).join('. ');
76
+ const line = `- ${now.toISOString().slice(0, 10)} [awareness] ${clean(observation.type)}${detail ? ` — ${detail}` : ''}`;
77
+ if (line.length <= 500)
78
+ return line;
79
+ let prefix = line.slice(0, 499);
80
+ if (/[\uD800-\uDBFF]$/.test(prefix))
81
+ prefix = prefix.slice(0, -1);
82
+ return `${prefix}…`;
83
+ }
84
+ function ensureDirectory(root, parts) {
85
+ if (realpathSync(root) !== root || !lstatSync(root).isDirectory())
86
+ throw new Error('Unsafe agent data root');
87
+ let path = root;
88
+ for (const part of parts) {
89
+ path = join(path, part);
90
+ if (!existsSync(path))
91
+ mkdirSync(path, { mode: 0o700 });
92
+ const stat = lstatSync(path);
93
+ if (stat.isSymbolicLink() || !stat.isDirectory() || realpathSync(path) !== path)
94
+ throw new Error('Unsafe agent directory');
95
+ }
96
+ return path;
97
+ }
98
+ async function lock(home) {
99
+ const file = join(home, 'grok-awareness.lock'), token = randomUUID();
100
+ for (let attempt = 0; attempt < 100; attempt++) {
101
+ try {
102
+ writeFileSync(file, JSON.stringify({ pid: process.pid, token }), { flag: 'wx', mode: 0o600 });
103
+ return () => { try {
104
+ if (JSON.parse(readFileSync(file, 'utf8')).token === token)
105
+ rmSync(file);
106
+ }
107
+ catch { /* another owner */ } };
108
+ }
109
+ catch (error) {
110
+ if (error.code !== 'EEXIST')
111
+ throw error;
112
+ try {
113
+ const before = statSync(file);
114
+ if (before.size > 1024)
115
+ throw new Error('Invalid lock');
116
+ const pid = Number(JSON.parse(readFileSync(file, 'utf8')).pid);
117
+ if (Number.isInteger(pid) && pid > 0) {
118
+ try {
119
+ process.kill(pid, 0);
120
+ }
121
+ catch (probe) {
122
+ if (probe.code === 'ESRCH') {
123
+ const after = statSync(file);
124
+ if (before.ino === after.ino && before.mtimeMs === after.mtimeMs)
125
+ rmSync(file);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ catch { /* changed or invalid lock: do not remove another writer */ }
131
+ await new Promise(r => setTimeout(r, 10));
132
+ }
133
+ }
134
+ throw new Error('Awareness writer is busy');
135
+ }
136
+ export async function pushAwareness(home, input, now = new Date(), io = fs) {
137
+ const initial = load(home), identity = owner(home);
138
+ let release;
139
+ const result = { written: 0, skipped: input.observations.length };
140
+ if (!identity || !initial.enabled || !input.agentId || !AGENT_ID.test(input.agentId) || !initial.agentIds.includes(input.agentId))
141
+ return result;
142
+ try {
143
+ if (input.observations.length > 100)
144
+ throw new Error('Batch exceeds 100 observations');
145
+ release = await lock(home);
146
+ const config = load(home);
147
+ if (!config.enabled || owner(home) !== identity || config.agentDataRoot !== initial.agentDataRoot || !config.agentIds.includes(input.agentId))
148
+ return result;
149
+ const lines = input.observations.filter(obs => config.triggerTypes.includes(obs.type) || obs.concepts?.some(concept => config.triggerConcepts.includes(concept))).map(obs => lineFor(obs, now)).filter((line) => Boolean(line));
150
+ if (!lines.length)
151
+ return result;
152
+ const parts = ['agents', input.agentId, 'memory', 'log'], directory = ensureDirectory(config.agentDataRoot, parts), file = join(directory, `${now.toISOString().slice(0, 7)}.md`);
153
+ const body = (line) => { const index = line.indexOf('[awareness]'); return (index < 0 ? line : line.slice(index)).trim(); };
154
+ const unique = (text, candidates) => {
155
+ const seen = new Set(text.split('\n').map(body)), additions = [];
156
+ for (const line of candidates)
157
+ if (!seen.has(body(line))) {
158
+ seen.add(body(line));
159
+ additions.push(line);
160
+ }
161
+ return additions;
162
+ };
163
+ const compose = (text, additions) => text + (text && !text.endsWith('\n') ? '\n' : '') + additions.join('\n') + '\n';
164
+ if (!existsSync(file)) {
165
+ const additions = unique('', lines);
166
+ if (additions.length) {
167
+ const next = compose('', additions);
168
+ if (Buffer.byteLength(next) > MAX_LOG_BYTES)
169
+ throw new Error('Monthly log too large');
170
+ const temp = join(directory, `.awareness-${randomUUID()}.tmp`);
171
+ try {
172
+ writeFileSync(temp, next, { mode: 0o600, flag: 'wx' });
173
+ ensureDirectory(config.agentDataRoot, parts);
174
+ renameSync(temp, file);
175
+ chmodSync(file, 0o600);
176
+ }
177
+ finally {
178
+ rmSync(temp, { force: true });
179
+ }
180
+ result.written = additions.length;
181
+ result.skipped -= additions.length;
182
+ }
183
+ }
184
+ else {
185
+ const stat = lstatSync(file);
186
+ if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1 || stat.size > MAX_LOG_BYTES)
187
+ throw new Error('Unsafe monthly log');
188
+ let existing = io.readFileSync(file, 'utf8');
189
+ if (Buffer.byteLength(existing) > MAX_LOG_BYTES)
190
+ throw new Error('Monthly log too large');
191
+ const fd = fs.openSync(file, 'r+');
192
+ try {
193
+ const current = fs.fstatSync(fd), pathStat = lstatSync(file);
194
+ if (current.ino !== stat.ino || pathStat.ino !== stat.ino || current.nlink !== 1 || !current.isFile() || pathStat.isSymbolicLink())
195
+ throw new Error('Monthly log rotated');
196
+ if (current.size > MAX_LOG_BYTES)
197
+ throw new Error('Monthly log too large');
198
+ if (current.size !== Buffer.byteLength(existing)) {
199
+ const buf = Buffer.alloc(current.size);
200
+ existing = buf.subarray(0, io.readSync(fd, buf, 0, current.size, 0)).toString('utf8');
201
+ const after = fs.fstatSync(fd);
202
+ if (after.ino !== stat.ino || lstatSync(file).ino !== stat.ino)
203
+ throw new Error('Monthly log rotated');
204
+ if (after.size > MAX_LOG_BYTES || Buffer.byteLength(existing) > MAX_LOG_BYTES)
205
+ throw new Error('Monthly log too large');
206
+ }
207
+ const additions = unique(existing, lines);
208
+ if (additions.length) {
209
+ const next = compose(existing, additions);
210
+ if (Buffer.byteLength(next) > MAX_LOG_BYTES)
211
+ throw new Error('Monthly log too large');
212
+ const encoded = Buffer.from(next, 'utf8');
213
+ fs.writeSync(fd, encoded, 0, encoded.length, 0);
214
+ fs.ftruncateSync(fd, encoded.length);
215
+ fs.fsyncSync(fd);
216
+ result.written = additions.length;
217
+ result.skipped -= additions.length;
218
+ }
219
+ }
220
+ finally {
221
+ fs.closeSync(fd);
222
+ }
223
+ chmodSync(file, 0o600);
224
+ }
225
+ config.lastPush = Date.now();
226
+ delete config.lastError;
227
+ save(home, config);
228
+ }
229
+ catch {
230
+ result.error = 'awareness_write_failed';
231
+ const config = load(home);
232
+ if (config.owner === identity) {
233
+ config.lastError = result.error;
234
+ try {
235
+ save(home, config);
236
+ }
237
+ catch { /* status cannot be persisted when storage is unavailable */ }
238
+ }
239
+ }
240
+ finally {
241
+ release?.();
242
+ }
243
+ return result;
244
+ }
@@ -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 repairClientHooks(userHome?: string, configHome?: string): void;
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 {};
@@ -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, `${JSON.stringify(value, null, 2)}\n`, {
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
- timeout: event === "context" ? 60 : 20,
63
- statusMessage: HOOK_OWNER,
64
- ...(event === "observation" ||
65
- event === "summarize"
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(HOOK_EVENTS)) {
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
- export function repairClientHooks(userHome = homedir(), configHome = vaultHome()) {
137
- installClientHooks("claude", userHome, configHome);
138
- installClientHooks("codex", userHome, configHome);
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
  }
@@ -1,9 +1,11 @@
1
1
  import { VaultCloudClient } from './cloud.js';
2
2
  import { generateContext, type ContextResult } from './context-engines.js';
3
+ import { type TelegramObservation } from './telegram.js';
3
4
  export interface GenerationJob {
4
5
  id: string;
5
6
  projectId: string;
6
7
  sessionId?: string;
8
+ agentId?: string;
7
9
  text: string;
8
10
  title?: string;
9
11
  kind: 'observation' | 'summary' | 'prompt';
@@ -15,7 +17,7 @@ export interface GenerationJob {
15
17
  attempts?: number;
16
18
  nextAttemptAt?: number;
17
19
  lastError?: string;
18
- result?: Pick<ContextResult, 'title' | 'content' | 'facts' | 'concepts'>;
20
+ result?: Pick<ContextResult, 'title' | 'content' | 'facts' | 'concepts' | 'type'>;
19
21
  }
20
22
  export declare function enqueueGeneration(home: string, job: GenerationJob): Promise<void>;
21
23
  export declare function generationQueueStatus(home?: string): {
@@ -24,4 +26,6 @@ export declare function generationQueueStatus(home?: string): {
24
26
  legacyAwaitingOwnership: number;
25
27
  heldForOtherAccount: number;
26
28
  };
27
- export declare function processGenerationQueue(home?: string, cloud?: VaultCloudClient, generate?: typeof generateContext): Promise<boolean>;
29
+ export declare function processGenerationQueue(home?: string, cloud?: VaultCloudClient, generate?: typeof generateContext, options?: {
30
+ notifyObservation?: (observation: TelegramObservation) => Promise<unknown>;
31
+ }): Promise<boolean>;
@@ -4,10 +4,12 @@ import { join } from 'node:path';
4
4
  import { VaultCloudClient } from './cloud.js';
5
5
  import { loadConfig, loadTokens, vaultHome } from './config.js';
6
6
  import { generateContext, selectedContextEngine } from './context-engines.js';
7
+ import { notifyTelegramObservation } from './telegram.js';
8
+ import { pushAwareness } from './grok-awareness.js';
7
9
  const queuePath = (home) => join(home, 'generation-queue.json');
8
10
  function ownerKey(home) {
9
11
  const config = loadConfig(home);
10
- return createHash('sha256').update(JSON.stringify([config.apiUrl, process.env['VAULT_GO_TOKEN'] ?? config.email ?? loadTokens(home)?.accessToken ?? 'anonymous'])).digest('hex');
12
+ return createHash('sha256').update(JSON.stringify([config.apiUrl, process.env['VAULT_GO_TOKEN'] || config.email || loadTokens(home)?.accessToken || 'anonymous'])).digest('hex');
11
13
  }
12
14
  function readQueue(home) {
13
15
  if (!existsSync(queuePath(home)))
@@ -93,7 +95,7 @@ export function generationQueueStatus(home = vaultHome()) {
93
95
  const jobs = readQueue(home), owner = ownerKey(home);
94
96
  return { pending: jobs.filter(j => j.owner === owner || !j.owner).length, failed: jobs.filter(j => j.owner === owner && j.lastError).length, legacyAwaitingOwnership: jobs.filter(j => !j.owner).length, heldForOtherAccount: jobs.filter(j => j.owner && j.owner !== owner).length };
95
97
  }
96
- export async function processGenerationQueue(home = vaultHome(), cloud = new VaultCloudClient(home), generate = generateContext) {
98
+ export async function processGenerationQueue(home = vaultHome(), cloud = new VaultCloudClient(home), generate = generateContext, options = {}) {
97
99
  const release = await lock(home, 'generation-worker', false);
98
100
  if (!release)
99
101
  return false;
@@ -113,23 +115,40 @@ export async function processGenerationQueue(home = vaultHome(), cloud = new Vau
113
115
  return false;
114
116
  try {
115
117
  const result = job.result ?? (job.generate === false ? { title: job.title ?? 'Memória', content: job.text, facts: [], concepts: [] } : await generate(home, selectedContextEngine(home), job.text, {
116
- ...(cloud.observerGenerate ? { managedGenerate: (text, options) => cloud.observerGenerate(text, options.signal) } : {}),
118
+ ...(cloud.observerGenerate ? { managedGenerate: (text, options) => cloud.observerGenerate(text, options.signal, options.modeGuidance ? { modeGuidance: options.modeGuidance } : undefined) } : {}),
117
119
  }));
118
120
  if (owner !== ownerKey(home))
119
121
  throw new Error('Account changed.');
120
122
  // Cache successful generation before persistence, so a network retry does not pay for the model twice.
121
123
  await mutate(home, jobs => jobs.map(item => item.id === job.id ? { ...item, result } : item));
124
+ const memoryType = result.type ?? 'discovery';
122
125
  await cloud.importMemories({ projectId: job.projectId, records: [{
123
126
  sourceId: `vault-go:event:${job.id}`,
124
127
  ...(job.sessionId ? { sessionId: job.sessionId } : {}),
125
128
  kind: job.kind,
126
- memoryType: 'discovery',
129
+ memoryType,
127
130
  title: result.title || job.title,
128
131
  content: result.content.slice(0, job.maxChars ?? 4000),
129
132
  facts: result.facts, concepts: result.concepts,
130
133
  filesRead: job.filesRead ?? [], filesModified: job.filesModified ?? [],
131
134
  }] });
135
+ if (owner !== ownerKey(home))
136
+ throw new Error('Account changed.');
137
+ const awareness = await pushAwareness(home, { ...(job.agentId ? { agentId: job.agentId } : {}), observations: [{ type: memoryType, title: result.title || job.title || 'Memória', facts: result.facts, concepts: result.concepts }] });
138
+ if (awareness.error)
139
+ throw new Error('Awareness delivery failed; retry with cached generation.');
132
140
  await mutate(home, jobs => jobs.filter(item => item.id !== job.id));
141
+ if (job.kind === 'observation' && owner === ownerKey(home)) {
142
+ try {
143
+ await (options.notifyObservation ?? ((observation) => notifyTelegramObservation(home, observation)))({
144
+ id: job.id,
145
+ type: memoryType,
146
+ title: result.title,
147
+ concepts: result.concepts,
148
+ });
149
+ }
150
+ catch { /* notification delivery never retries an already persisted memory */ }
151
+ }
133
152
  }
134
153
  catch {
135
154
  await mutate(home, jobs => jobs.map(item => item.id !== job.id ? item : {
package/dist/hooks.d.ts CHANGED
@@ -1,11 +1,12 @@
1
+ export { redact } from './capture-privacy.js';
1
2
  import { type VaultMemoryApi } from "./cloud.js";
2
3
  export declare const HOOK_EVENTS: readonly ["context", "session-init", "observation", "file-context", "summarize"];
3
4
  export type HookEvent = (typeof HOOK_EVENTS)[number];
4
5
  type RecordValue = Record<string, unknown>;
5
- export declare function redact(value: unknown): unknown;
6
6
  export declare function normalizeHookInput(adapter: string, value: unknown): RecordValue;
7
7
  export declare function hookOutput(event: HookEvent, context?: string, adapter?: string): RecordValue;
8
- export declare function executeHook(adapter: string, event: HookEvent, value: unknown, cloud?: VaultMemoryApi, home?: string): Promise<RecordValue>;
8
+ export declare function executeHook(adapter: string, event: HookEvent, value: unknown, cloud?: VaultMemoryApi, home?: string, options?: {
9
+ transcript?: boolean;
10
+ }): Promise<RecordValue>;
9
11
  export declare function readHookInput(stream?: NodeJS.ReadableStream): Promise<unknown>;
10
12
  export declare function runHook(adapter: string, event: string): Promise<void>;
11
- export {};