gnosys 5.14.0 → 5.15.2
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 +2 -2
- package/dist/cli.js +6 -0
- package/dist/index.js +4 -3
- package/dist/lib/ask.js +5 -1
- package/dist/lib/askCommand.js +4 -1
- package/dist/lib/chat/llmTurn.d.ts +9 -0
- package/dist/lib/chat/llmTurn.js +23 -6
- package/dist/lib/config.d.ts +0 -2
- package/dist/lib/config.js +25 -8
- package/dist/lib/dreamLaunchd.d.ts +65 -0
- package/dist/lib/dreamLaunchd.js +207 -0
- package/dist/lib/embeddings.js +15 -4
- package/dist/lib/hybridSearchCommand.js +4 -1
- package/dist/lib/importCommand.js +5 -1
- package/dist/lib/interactiveGuard.d.ts +17 -0
- package/dist/lib/interactiveGuard.js +26 -0
- package/dist/lib/rulesGen.js +8 -3
- package/dist/lib/setup.js +12 -1
- package/dist/lib/setupKeys.js +3 -0
- package/dist/lib/statusCommand.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,8 +71,8 @@ This package installs two binaries:
|
|
|
71
71
|
| `gnosys_read` | Read a specific memory. |
|
|
72
72
|
| `gnosys_search` | Search memories by keyword across all stores. |
|
|
73
73
|
| `gnosys_list` | List memories across all stores, optionally filtered by category, tag, or store layer. |
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
74
|
+
| `gnosys_add_structured` | **Preferred for LLM agents.** Add a memory with structured fields you supply (title, category, tags, content) — makes no server-side LLM call. |
|
|
75
|
+
| `gnosys_add` | Add a memory from raw text; the server's LLM structures it. For non-agent callers (scripts, cron) — agents should use `gnosys_add_structured`. |
|
|
76
76
|
| `gnosys_tags` | List all tags in the registry, grouped by category. |
|
|
77
77
|
| `gnosys_tags_add` | Add a new tag to the registry. |
|
|
78
78
|
| `gnosys_reinforce` | Signal whether a memory was useful. |
|
package/dist/cli.js
CHANGED
|
@@ -1097,6 +1097,12 @@ program
|
|
|
1097
1097
|
console.error(`\nCould not write upgrade marker: ${err instanceof Error ? err.message : err}`);
|
|
1098
1098
|
console.error(`Running MCP servers will need to be restarted manually.`);
|
|
1099
1099
|
}
|
|
1100
|
+
// v5.15: a Node upgrade moves the node path hardcoded in the dream
|
|
1101
|
+
// LaunchAgent plist, silently killing the scheduler. Repair it here.
|
|
1102
|
+
const { repairDreamLaunchAgentAfterUpgrade } = await import("./lib/dreamLaunchd.js");
|
|
1103
|
+
const dreamRepairLine = await repairDreamLaunchAgentAfterUpgrade();
|
|
1104
|
+
if (dreamRepairLine)
|
|
1105
|
+
console.log(`\n${dreamRepairLine}`);
|
|
1100
1106
|
if (opts.sync === false || opts.yes) {
|
|
1101
1107
|
console.log(`\nDone. Run 'gnosys setup sync-projects' when you're ready to refresh registered projects.`);
|
|
1102
1108
|
return;
|
package/dist/index.js
CHANGED
|
@@ -624,7 +624,7 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
|
|
|
624
624
|
}
|
|
625
625
|
});
|
|
626
626
|
// ─── Tool: gnosys_add ────────────────────────────────────────────────────
|
|
627
|
-
regTool("gnosys_add", "Add a new memory
|
|
627
|
+
regTool("gnosys_add", "Add a new memory from raw text — the server's LLM structures it into an atomic memory. For non-agent callers (scripts, cron); if you are an LLM agent, use gnosys_add_structured instead to avoid a redundant server-side LLM call. Writes to the project store by default. Use store='personal' for cross-project knowledge, or store='global' to explicitly write to shared org knowledge.", {
|
|
628
628
|
input: z
|
|
629
629
|
.string()
|
|
630
630
|
.describe("Raw text input. Can be a decision, concept, fact, observation, or any knowledge."),
|
|
@@ -738,7 +738,7 @@ regTool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structures
|
|
|
738
738
|
}
|
|
739
739
|
});
|
|
740
740
|
// ─── Tool: gnosys_add_structured ─────────────────────────────────────────
|
|
741
|
-
regTool("gnosys_add_structured", "
|
|
741
|
+
regTool("gnosys_add_structured", "Preferred for LLM agents: add a memory with structured fields you supply (title, category, tags, content) — makes no server-side LLM call. Writes to the project store by default. Use store='global' to explicitly write to shared org knowledge.", {
|
|
742
742
|
title: z.string().describe("Memory title"),
|
|
743
743
|
category: z.string().describe("Category directory name"),
|
|
744
744
|
tags: z
|
|
@@ -1703,7 +1703,8 @@ regTool("gnosys_import", "Bulk import structured data (CSV, JSON, JSONL) into Gn
|
|
|
1703
1703
|
skipExisting,
|
|
1704
1704
|
limit,
|
|
1705
1705
|
offset,
|
|
1706
|
-
|
|
1706
|
+
// v5.15: explicit param wins; otherwise config importConcurrency.
|
|
1707
|
+
concurrency: concurrency ?? ctx.config?.importConcurrency,
|
|
1707
1708
|
batchCommit: true,
|
|
1708
1709
|
});
|
|
1709
1710
|
// Reindex after import
|
package/dist/lib/ask.js
CHANGED
|
@@ -289,7 +289,11 @@ export class GnosysAsk {
|
|
|
289
289
|
}
|
|
290
290
|
return usedArchiveIds;
|
|
291
291
|
}
|
|
292
|
-
catch {
|
|
292
|
+
catch (err) {
|
|
293
|
+
// Degrade gracefully, but leave a trace on stderr — a hard dearchive
|
|
294
|
+
// failure previously looked identical to "no archive hits"
|
|
295
|
+
// (sprint 2026-07-02).
|
|
296
|
+
console.error(`gnosys: dearchive failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
293
297
|
return [];
|
|
294
298
|
}
|
|
295
299
|
}
|
package/dist/lib/askCommand.js
CHANGED
|
@@ -132,7 +132,10 @@ export async function runAskCommand(getResolver, question, opts) {
|
|
|
132
132
|
const writeTarget = resolver.getWriteTarget();
|
|
133
133
|
if (writeTarget) {
|
|
134
134
|
const { GnosysMaintenanceEngine } = await import("./maintenance.js");
|
|
135
|
-
await GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, result.sources.map((s) => s.relativePath)).catch(() => {
|
|
135
|
+
await GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, result.sources.map((s) => s.relativePath)).catch((err) => {
|
|
136
|
+
// Best-effort, but don't be fully silent (sprint 2026-07-02).
|
|
137
|
+
console.error(`gnosys: reinforcement skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
138
|
+
});
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
}
|
|
@@ -42,6 +42,15 @@ export interface LLMTurnResult {
|
|
|
42
42
|
result: string;
|
|
43
43
|
}>;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the system prompt for a chat turn.
|
|
47
|
+
*
|
|
48
|
+
* v5.15: wires two previously-unread config knobs —
|
|
49
|
+
* `chat.systemPromptPrefix` (prepended before the base prompt) and
|
|
50
|
+
* `chat.toolsEnabled` (false omits the gnosys-tool addendum entirely).
|
|
51
|
+
* Exported for tests.
|
|
52
|
+
*/
|
|
53
|
+
export declare function composeSystemPrompt(config: GnosysConfig, recalled: RecalledMemory[] | undefined): string;
|
|
45
54
|
/**
|
|
46
55
|
* Run a single turn against the LLM. Loops if the assistant emits
|
|
47
56
|
* gnosys-tool fences: each tool is executed in-process, the result is
|
package/dist/lib/chat/llmTurn.js
CHANGED
|
@@ -12,9 +12,23 @@ import { CHOOSE_SYSTEM_PROMPT_ADDENDUM } from "./choose.js";
|
|
|
12
12
|
import { buildToolsSystemPrompt, findTool } from "./tools.js";
|
|
13
13
|
import { extractToolFences } from "./toolFence.js";
|
|
14
14
|
const BASE_SYSTEM_PROMPT = `You are an assistant inside the Gnosys terminal chat — a memory-aware REPL. The user has persistent memory across sessions; relevant memories are injected as <memory id="..."> blocks before their question. Cite memory IDs in square brackets like [deci-037] when you use them. Be concise and direct. Markdown renders.${CHOOSE_SYSTEM_PROMPT_ADDENDUM}`;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Build the system prompt for a chat turn.
|
|
17
|
+
*
|
|
18
|
+
* v5.15: wires two previously-unread config knobs —
|
|
19
|
+
* `chat.systemPromptPrefix` (prepended before the base prompt) and
|
|
20
|
+
* `chat.toolsEnabled` (false omits the gnosys-tool addendum entirely).
|
|
21
|
+
* Exported for tests.
|
|
22
|
+
*/
|
|
23
|
+
export function composeSystemPrompt(config, recalled) {
|
|
24
|
+
const toolsEnabled = config.chat?.toolsEnabled !== false;
|
|
25
|
+
let base = toolsEnabled
|
|
26
|
+
? `${BASE_SYSTEM_PROMPT}\n${buildToolsSystemPrompt()}`
|
|
27
|
+
: BASE_SYSTEM_PROMPT;
|
|
28
|
+
const prefix = config.chat?.systemPromptPrefix;
|
|
29
|
+
if (typeof prefix === "string" && prefix.trim().length > 0) {
|
|
30
|
+
base = `${prefix.trim()}\n\n${base}`;
|
|
31
|
+
}
|
|
18
32
|
if (!recalled || recalled.length === 0)
|
|
19
33
|
return base;
|
|
20
34
|
return `${base}\n\n${formatRecallForPrompt(recalled)}`;
|
|
@@ -47,8 +61,11 @@ export async function runTurn(config, opts) {
|
|
|
47
61
|
// falls back to defaultProvider when no chat override is set, so existing
|
|
48
62
|
// installs keep working without a chat-specific config.
|
|
49
63
|
const provider = getLLMProvider(config, "chat");
|
|
50
|
-
const system = composeSystemPrompt(opts.recalled);
|
|
51
|
-
|
|
64
|
+
const system = composeSystemPrompt(config, opts.recalled);
|
|
65
|
+
// v5.15: when chat.toolsEnabled === false the first response is final —
|
|
66
|
+
// no tool-fence execution loop.
|
|
67
|
+
const toolsEnabled = config.chat?.toolsEnabled !== false;
|
|
68
|
+
const maxIterations = toolsEnabled ? (opts.maxToolIterations ?? 4) : 1;
|
|
52
69
|
const toolCalls = [];
|
|
53
70
|
let toolPreamble;
|
|
54
71
|
let combinedText = "";
|
|
@@ -64,7 +81,7 @@ export async function runTurn(config, opts) {
|
|
|
64
81
|
combinedText += (combinedText ? "\n\n" : "") + chunk;
|
|
65
82
|
// Look for tool fences; if none, this iteration's chunk IS the answer.
|
|
66
83
|
const extraction = extractToolFences(chunk);
|
|
67
|
-
if (!extraction || extraction.calls.length === 0) {
|
|
84
|
+
if (!toolsEnabled || !extraction || extraction.calls.length === 0) {
|
|
68
85
|
break;
|
|
69
86
|
}
|
|
70
87
|
// Run each tool, accumulate results into the next prompt as a system block
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -183,7 +183,6 @@ export declare const GnosysConfigSchema: z.ZodObject<{
|
|
|
183
183
|
openai: "openai";
|
|
184
184
|
}>>;
|
|
185
185
|
defaultModel: z.ZodOptional<z.ZodString>;
|
|
186
|
-
bulkIngestionBatchSize: z.ZodDefault<z.ZodNumber>;
|
|
187
186
|
importConcurrency: z.ZodDefault<z.ZodNumber>;
|
|
188
187
|
autoCommit: z.ZodDefault<z.ZodBoolean>;
|
|
189
188
|
llmRetryAttempts: z.ZodDefault<z.ZodNumber>;
|
|
@@ -241,7 +240,6 @@ export declare const GnosysConfigSchema: z.ZodObject<{
|
|
|
241
240
|
}, z.core.$strip>>;
|
|
242
241
|
chat: z.ZodDefault<z.ZodObject<{
|
|
243
242
|
toolsEnabled: z.ZodDefault<z.ZodBoolean>;
|
|
244
|
-
autoSummarizeAfterTurns: z.ZodDefault<z.ZodNumber>;
|
|
245
243
|
systemPromptPrefix: z.ZodDefault<z.ZodString>;
|
|
246
244
|
}, z.core.$strip>>;
|
|
247
245
|
multimodal: z.ZodDefault<z.ZodObject<{
|
package/dist/lib/config.js
CHANGED
|
@@ -137,9 +137,6 @@ const RecallConfigSchema = z.object({
|
|
|
137
137
|
const ChatConfigSchema = z.object({
|
|
138
138
|
/** Allow LLM to call gnosys tools via gnosys-tool fences (default: true). */
|
|
139
139
|
toolsEnabled: z.boolean().default(true),
|
|
140
|
-
/** Suggest /save-turn after N consecutive turns where no memory was written.
|
|
141
|
-
* 0 disables the nudge. Heuristic only — never writes without consent. */
|
|
142
|
-
autoSummarizeAfterTurns: z.number().int().min(0).default(0),
|
|
143
140
|
/** Prepended to the chat system prompt. Use for persona / style / domain hints. */
|
|
144
141
|
systemPromptPrefix: z.string().default(""),
|
|
145
142
|
});
|
|
@@ -210,8 +207,6 @@ export const GnosysConfigSchema = z.object({
|
|
|
210
207
|
.optional(),
|
|
211
208
|
/** @deprecated Use llm.anthropic.model or llm.ollama.model instead */
|
|
212
209
|
defaultModel: z.string().optional(),
|
|
213
|
-
/** Max records per batch commit during bulk import */
|
|
214
|
-
bulkIngestionBatchSize: z.number().int().min(1).max(10000).default(500),
|
|
215
210
|
/** Parallel LLM calls during import */
|
|
216
211
|
importConcurrency: z.number().int().min(1).max(20).default(5),
|
|
217
212
|
/** Enable auto-commit on every write (disable for manual git) */
|
|
@@ -259,7 +254,6 @@ export const GnosysConfigSchema = z.object({
|
|
|
259
254
|
/** Chat TUI — interactive chat configuration (v5.8.0) */
|
|
260
255
|
chat: ChatConfigSchema.default({
|
|
261
256
|
toolsEnabled: true,
|
|
262
|
-
autoSummarizeAfterTurns: 0,
|
|
263
257
|
systemPromptPrefix: "",
|
|
264
258
|
}),
|
|
265
259
|
/** Multimodal ingestion — PDF, audio, image, video processing */
|
|
@@ -483,6 +477,30 @@ function deepMergeConfig(base, override) {
|
|
|
483
477
|
}
|
|
484
478
|
return result;
|
|
485
479
|
}
|
|
480
|
+
// ─── Removed-knob deprecation warnings (v5.15) ──────────────────────────
|
|
481
|
+
//
|
|
482
|
+
// These knobs were documented but never read by any code ("settings that
|
|
483
|
+
// lie"), so v5.15 removed them from the schema. Zod strips unknown keys on
|
|
484
|
+
// parse, so old config files keep loading — but warn once per process (on
|
|
485
|
+
// stderr only; MCP serve stdout must stay clean for JSON-RPC).
|
|
486
|
+
const warnedRemovedKeys = new Set();
|
|
487
|
+
function warnRemovedConfigKeys(raw) {
|
|
488
|
+
const found = [];
|
|
489
|
+
if ("bulkIngestionBatchSize" in raw)
|
|
490
|
+
found.push("bulkIngestionBatchSize");
|
|
491
|
+
const chat = raw.chat;
|
|
492
|
+
if (typeof chat === "object" &&
|
|
493
|
+
chat !== null &&
|
|
494
|
+
"autoSummarizeAfterTurns" in chat) {
|
|
495
|
+
found.push("chat.autoSummarizeAfterTurns");
|
|
496
|
+
}
|
|
497
|
+
for (const key of found) {
|
|
498
|
+
if (warnedRemovedKeys.has(key))
|
|
499
|
+
continue;
|
|
500
|
+
warnedRemovedKeys.add(key);
|
|
501
|
+
console.error(`gnosys: config option "${key}" was removed in v5.15 and is ignored`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
486
504
|
export async function loadConfig(storePath) {
|
|
487
505
|
const configPath = path.join(storePath, "gnosys.json");
|
|
488
506
|
const globalPath = path.join(getGnosysHome(), "gnosys.json");
|
|
@@ -510,6 +528,7 @@ export async function loadConfig(storePath) {
|
|
|
510
528
|
if (raw === null) {
|
|
511
529
|
return DEFAULT_CONFIG;
|
|
512
530
|
}
|
|
531
|
+
warnRemovedConfigKeys(raw);
|
|
513
532
|
const migrated = migrateConfig(raw);
|
|
514
533
|
const parsed = GnosysConfigSchema.parse(migrated);
|
|
515
534
|
return applyDreamProviderInheritance(parsed, migrated);
|
|
@@ -579,7 +598,6 @@ export function generateConfigTemplate() {
|
|
|
579
598
|
},
|
|
580
599
|
},
|
|
581
600
|
taskModels: {},
|
|
582
|
-
bulkIngestionBatchSize: 500,
|
|
583
601
|
importConcurrency: 5,
|
|
584
602
|
autoCommit: true,
|
|
585
603
|
llmRetryAttempts: 3,
|
|
@@ -608,7 +626,6 @@ export function generateConfigTemplate() {
|
|
|
608
626
|
},
|
|
609
627
|
chat: {
|
|
610
628
|
toolsEnabled: true,
|
|
611
|
-
autoSummarizeAfterTurns: 0,
|
|
612
629
|
systemPromptPrefix: "",
|
|
613
630
|
},
|
|
614
631
|
}, null, 2);
|
|
@@ -1,2 +1,67 @@
|
|
|
1
|
+
export interface LaunchctlResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
/** Pure helper: argv for loading a LaunchAgent plist. Exported for tests. */
|
|
6
|
+
export declare function launchctlLoadArgs(plistFile: string): string[];
|
|
7
|
+
/** Pure helper: argv for unloading a LaunchAgent plist. Exported for tests. */
|
|
8
|
+
export declare function launchctlUnloadArgs(plistFile: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Pure helper: interpret a launchctl load failure. "Already loaded" is a
|
|
11
|
+
* success for our purposes (the agent is active). Exported for tests.
|
|
12
|
+
*/
|
|
13
|
+
export declare function interpretLaunchctlLoadError(stderr: string): LaunchctlResult;
|
|
14
|
+
/**
|
|
15
|
+
* Best-effort `launchctl load` of the dream LaunchAgent so scheduled runs
|
|
16
|
+
* start immediately instead of waiting for the next login. (v5.14.x sprint,
|
|
17
|
+
* pre-approved.) Never throws.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadDreamLaunchAgent(plistFile: string): LaunchctlResult;
|
|
20
|
+
/**
|
|
21
|
+
* Best-effort `launchctl unload` before removing the plist, so disabling
|
|
22
|
+
* Dream Mode deactivates the schedule immediately. Never throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unloadDreamLaunchAgent(plistFile: string): LaunchctlResult;
|
|
1
25
|
export declare function installDreamLaunchAgent(): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Pure helper: extract the node and cli paths from a dream LaunchAgent
|
|
28
|
+
* plist body. Template shape (installDreamLaunchAgent): the first
|
|
29
|
+
* `<string>` is the Label, the second is the node binary path, the third
|
|
30
|
+
* is the cli path. Exported for tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseDreamPlistPaths(body: string): {
|
|
33
|
+
nodePath?: string;
|
|
34
|
+
cliPath?: string;
|
|
35
|
+
};
|
|
36
|
+
export interface DreamLaunchAgentHealth {
|
|
37
|
+
installed: boolean;
|
|
38
|
+
loaded: boolean;
|
|
39
|
+
nodeExists: boolean;
|
|
40
|
+
cliExists: boolean;
|
|
41
|
+
healthy: boolean;
|
|
42
|
+
problems: string[];
|
|
43
|
+
plistFile: string | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Health-check the dream LaunchAgent. The plist hardcodes absolute node +
|
|
47
|
+
* cli paths (e.g. ~/.nvm/versions/node/vX.Y.Z/bin/node), so a Node upgrade
|
|
48
|
+
* silently kills the scheduler — this detects that. Never throws.
|
|
49
|
+
*/
|
|
50
|
+
export declare function checkDreamLaunchAgent(): DreamLaunchAgentHealth;
|
|
51
|
+
/**
|
|
52
|
+
* Repair the dream LaunchAgent: rewrite the plist with the current
|
|
53
|
+
* process's node + cli paths, then reload it. Best-effort — never throws.
|
|
54
|
+
*/
|
|
55
|
+
export declare function repairDreamLaunchAgent(): {
|
|
56
|
+
ok: boolean;
|
|
57
|
+
message: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Post-upgrade hook: when Dream Mode is enabled and the LaunchAgent is
|
|
61
|
+
* installed but unhealthy (typically because a Node upgrade moved the
|
|
62
|
+
* hardcoded node path), repair it. Returns a printable status line, or
|
|
63
|
+
* null when nothing applies (non-darwin, dream disabled, agent healthy
|
|
64
|
+
* or not installed). Never throws.
|
|
65
|
+
*/
|
|
66
|
+
export declare function repairDreamLaunchAgentAfterUpgrade(): Promise<string | null>;
|
|
2
67
|
export declare function uninstallDreamLaunchAgent(): string | null;
|
package/dist/lib/dreamLaunchd.js
CHANGED
|
@@ -1,7 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
1
2
|
import fs from "fs";
|
|
2
3
|
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
const LABEL = "com.gnosys.dream";
|
|
6
|
+
/** Pure helper: argv for loading a LaunchAgent plist. Exported for tests. */
|
|
7
|
+
export function launchctlLoadArgs(plistFile) {
|
|
8
|
+
return ["load", "-w", plistFile];
|
|
9
|
+
}
|
|
10
|
+
/** Pure helper: argv for unloading a LaunchAgent plist. Exported for tests. */
|
|
11
|
+
export function launchctlUnloadArgs(plistFile) {
|
|
12
|
+
return ["unload", plistFile];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Pure helper: interpret a launchctl load failure. "Already loaded" is a
|
|
16
|
+
* success for our purposes (the agent is active). Exported for tests.
|
|
17
|
+
*/
|
|
18
|
+
export function interpretLaunchctlLoadError(stderr) {
|
|
19
|
+
const text = stderr.trim();
|
|
20
|
+
if (/already loaded|service already loaded|Load failed: 5/i.test(text)) {
|
|
21
|
+
return { ok: true, message: "launchd agent already loaded" };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
message: `launchctl load failed${text ? `: ${text}` : ""} — the agent will load at next login`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort `launchctl load` of the dream LaunchAgent so scheduled runs
|
|
30
|
+
* start immediately instead of waiting for the next login. (v5.14.x sprint,
|
|
31
|
+
* pre-approved.) Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export function loadDreamLaunchAgent(plistFile) {
|
|
34
|
+
if (process.platform !== "darwin")
|
|
35
|
+
return { ok: false, message: "launchctl unavailable (not macOS)" };
|
|
36
|
+
try {
|
|
37
|
+
execFileSync("launchctl", launchctlLoadArgs(plistFile), {
|
|
38
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
39
|
+
timeout: 10_000,
|
|
40
|
+
});
|
|
41
|
+
return { ok: true, message: "launchd agent loaded — scheduled dream runs are active now" };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const stderr = err && typeof err === "object" && "stderr" in err && err.stderr
|
|
45
|
+
? String(err.stderr)
|
|
46
|
+
: err instanceof Error
|
|
47
|
+
? err.message
|
|
48
|
+
: String(err);
|
|
49
|
+
return interpretLaunchctlLoadError(stderr);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Best-effort `launchctl unload` before removing the plist, so disabling
|
|
54
|
+
* Dream Mode deactivates the schedule immediately. Never throws.
|
|
55
|
+
*/
|
|
56
|
+
export function unloadDreamLaunchAgent(plistFile) {
|
|
57
|
+
if (process.platform !== "darwin")
|
|
58
|
+
return { ok: false, message: "launchctl unavailable (not macOS)" };
|
|
59
|
+
try {
|
|
60
|
+
execFileSync("launchctl", launchctlUnloadArgs(plistFile), {
|
|
61
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
62
|
+
timeout: 10_000,
|
|
63
|
+
});
|
|
64
|
+
return { ok: true, message: "launchd agent unloaded" };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Not loaded (or launchctl unavailable) — nothing to deactivate.
|
|
68
|
+
return { ok: true, message: "launchd agent was not loaded" };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
5
71
|
function plistPath() {
|
|
6
72
|
return path.join(os.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
7
73
|
}
|
|
@@ -58,10 +124,151 @@ export function installDreamLaunchAgent() {
|
|
|
58
124
|
fs.writeFileSync(file, body, "utf8");
|
|
59
125
|
return file;
|
|
60
126
|
}
|
|
127
|
+
function xmlUnescape(value) {
|
|
128
|
+
return value
|
|
129
|
+
.replace(/"/g, '"')
|
|
130
|
+
.replace(/>/g, ">")
|
|
131
|
+
.replace(/</g, "<")
|
|
132
|
+
.replace(/&/g, "&");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Pure helper: extract the node and cli paths from a dream LaunchAgent
|
|
136
|
+
* plist body. Template shape (installDreamLaunchAgent): the first
|
|
137
|
+
* `<string>` is the Label, the second is the node binary path, the third
|
|
138
|
+
* is the cli path. Exported for tests.
|
|
139
|
+
*/
|
|
140
|
+
export function parseDreamPlistPaths(body) {
|
|
141
|
+
const strings = [...body.matchAll(/<string>([^<]*)<\/string>/g)].map((m) => xmlUnescape(m[1]));
|
|
142
|
+
return {
|
|
143
|
+
nodePath: strings.length > 1 ? strings[1] : undefined,
|
|
144
|
+
cliPath: strings.length > 2 ? strings[2] : undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Health-check the dream LaunchAgent. The plist hardcodes absolute node +
|
|
149
|
+
* cli paths (e.g. ~/.nvm/versions/node/vX.Y.Z/bin/node), so a Node upgrade
|
|
150
|
+
* silently kills the scheduler — this detects that. Never throws.
|
|
151
|
+
*/
|
|
152
|
+
export function checkDreamLaunchAgent() {
|
|
153
|
+
if (process.platform !== "darwin") {
|
|
154
|
+
return {
|
|
155
|
+
installed: false,
|
|
156
|
+
loaded: false,
|
|
157
|
+
nodeExists: false,
|
|
158
|
+
cliExists: false,
|
|
159
|
+
healthy: false,
|
|
160
|
+
problems: ["launchd unavailable (not macOS)"],
|
|
161
|
+
plistFile: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const file = plistPath();
|
|
165
|
+
if (!fs.existsSync(file)) {
|
|
166
|
+
return {
|
|
167
|
+
installed: false,
|
|
168
|
+
loaded: false,
|
|
169
|
+
nodeExists: false,
|
|
170
|
+
cliExists: false,
|
|
171
|
+
healthy: false,
|
|
172
|
+
problems: ["launchd agent not installed"],
|
|
173
|
+
plistFile: file,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const problems = [];
|
|
177
|
+
let nodeExists = false;
|
|
178
|
+
let cliExists = false;
|
|
179
|
+
try {
|
|
180
|
+
const body = fs.readFileSync(file, "utf8");
|
|
181
|
+
const { nodePath, cliPath } = parseDreamPlistPaths(body);
|
|
182
|
+
nodeExists = nodePath !== undefined && fs.existsSync(nodePath);
|
|
183
|
+
if (!nodeExists) {
|
|
184
|
+
problems.push(`node binary missing at ${nodePath ?? "(unknown)"} — a Node upgrade likely moved it; run repair to rewrite the agent`);
|
|
185
|
+
}
|
|
186
|
+
// "gnosys" (bare command) counts as existing — resolved via PATH.
|
|
187
|
+
cliExists =
|
|
188
|
+
cliPath !== undefined && (cliPath === "gnosys" || fs.existsSync(cliPath));
|
|
189
|
+
if (!cliExists) {
|
|
190
|
+
problems.push(`gnosys cli missing at ${cliPath ?? "(unknown)"}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
problems.push(`could not read plist: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
let loaded = false;
|
|
197
|
+
try {
|
|
198
|
+
execFileSync("launchctl", ["list", LABEL], {
|
|
199
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
200
|
+
timeout: 10_000,
|
|
201
|
+
});
|
|
202
|
+
loaded = true;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
problems.push("launchd agent not loaded");
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
installed: true,
|
|
209
|
+
loaded,
|
|
210
|
+
nodeExists,
|
|
211
|
+
cliExists,
|
|
212
|
+
healthy: loaded && nodeExists && cliExists,
|
|
213
|
+
problems,
|
|
214
|
+
plistFile: file,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Repair the dream LaunchAgent: rewrite the plist with the current
|
|
219
|
+
* process's node + cli paths, then reload it. Best-effort — never throws.
|
|
220
|
+
*/
|
|
221
|
+
export function repairDreamLaunchAgent() {
|
|
222
|
+
const file = installDreamLaunchAgent();
|
|
223
|
+
if (!file) {
|
|
224
|
+
return { ok: false, message: "launchd unavailable (not macOS)" };
|
|
225
|
+
}
|
|
226
|
+
unloadDreamLaunchAgent(file);
|
|
227
|
+
const load = loadDreamLaunchAgent(file);
|
|
228
|
+
if (!load.ok) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
message: `agent reinstalled but reload failed: ${load.message}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { ok: true, message: "dream launchd agent repaired and reloaded" };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Post-upgrade hook: when Dream Mode is enabled and the LaunchAgent is
|
|
238
|
+
* installed but unhealthy (typically because a Node upgrade moved the
|
|
239
|
+
* hardcoded node path), repair it. Returns a printable status line, or
|
|
240
|
+
* null when nothing applies (non-darwin, dream disabled, agent healthy
|
|
241
|
+
* or not installed). Never throws.
|
|
242
|
+
*/
|
|
243
|
+
export async function repairDreamLaunchAgentAfterUpgrade() {
|
|
244
|
+
if (process.platform !== "darwin")
|
|
245
|
+
return null;
|
|
246
|
+
try {
|
|
247
|
+
const { loadConfig } = await import("./config.js");
|
|
248
|
+
const { getGnosysHome } = await import("./paths.js");
|
|
249
|
+
const config = await loadConfig(getGnosysHome());
|
|
250
|
+
if (!config.dream?.enabled)
|
|
251
|
+
return null;
|
|
252
|
+
const health = checkDreamLaunchAgent();
|
|
253
|
+
if (!health.installed || health.healthy)
|
|
254
|
+
return null;
|
|
255
|
+
const repair = repairDreamLaunchAgent();
|
|
256
|
+
return repair.ok
|
|
257
|
+
? "✓ dream launchd agent repaired (node path had changed)"
|
|
258
|
+
: `⚠ dream launchd agent unhealthy and repair failed: ${repair.message}`;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
61
264
|
export function uninstallDreamLaunchAgent() {
|
|
62
265
|
if (process.platform !== "darwin")
|
|
63
266
|
return null;
|
|
64
267
|
const file = plistPath();
|
|
268
|
+
// Unload before unlinking — launchctl needs the plist on disk to resolve
|
|
269
|
+
// the label, and this deactivates the schedule immediately. Best-effort.
|
|
270
|
+
if (fs.existsSync(file))
|
|
271
|
+
unloadDreamLaunchAgent(file);
|
|
65
272
|
try {
|
|
66
273
|
fs.unlinkSync(file);
|
|
67
274
|
}
|
package/dist/lib/embeddings.js
CHANGED
|
@@ -35,8 +35,10 @@ export class GnosysEmbeddings {
|
|
|
35
35
|
const cacheDir = process.env.GNOSYS_CACHE_DIR ||
|
|
36
36
|
path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".cache", "gnosys");
|
|
37
37
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
38
|
-
//
|
|
39
|
-
//
|
|
38
|
+
// NOTE: @huggingface/transformers does NOT honor HF_HOME/TRANSFORMERS_CACHE
|
|
39
|
+
// for its Node file cache (that's the Python huggingface_hub convention).
|
|
40
|
+
// It caches under its own `env.cacheDir`, which we set below after import.
|
|
41
|
+
// We still export the env vars for any adjacent tooling that reads them.
|
|
40
42
|
process.env.HF_HOME = cacheDir;
|
|
41
43
|
process.env.TRANSFORMERS_CACHE = cacheDir;
|
|
42
44
|
// Dynamic import — keeps @huggingface/transformers out of the main bundle.
|
|
@@ -45,13 +47,22 @@ export class GnosysEmbeddings {
|
|
|
45
47
|
// Use `any` here so `tsc` succeeds even when the optional dep is not installed
|
|
46
48
|
// (CI network-share-simulation job, fresh checkouts, etc.). The real type
|
|
47
49
|
// is only needed at runtime when the package is present.
|
|
48
|
-
|
|
50
|
+
// `any`: the optional dep may be absent at type-check time (see note above).
|
|
51
|
+
let transformers;
|
|
49
52
|
try {
|
|
50
|
-
|
|
53
|
+
transformers = await import("@huggingface/transformers");
|
|
51
54
|
}
|
|
52
55
|
catch {
|
|
53
56
|
throw new Error("Local embeddings require @huggingface/transformers. Install it with: npm install @huggingface/transformers");
|
|
54
57
|
}
|
|
58
|
+
// `env.cacheDir` is the actual knob transformers.js uses for its on-disk
|
|
59
|
+
// model cache. Without it the model lands in the package's own
|
|
60
|
+
// node_modules/.cache (wiped on reinstall) and GNOSYS_CACHE_DIR is silently
|
|
61
|
+
// ignored — it does NOT honor HF_HOME/TRANSFORMERS_CACHE. Access by property
|
|
62
|
+
// (not destructuring) so a mocked module without `env` doesn't throw.
|
|
63
|
+
if (transformers.env)
|
|
64
|
+
transformers.env.cacheDir = cacheDir;
|
|
65
|
+
const pipeline = transformers.pipeline;
|
|
55
66
|
this.pipeline = (await pipeline("feature-extraction", MODEL_NAME, {
|
|
56
67
|
dtype: "q8",
|
|
57
68
|
}));
|
|
@@ -92,7 +92,10 @@ export async function runHybridSearchCommand(getResolver, query, opts) {
|
|
|
92
92
|
const writeTarget = resolver.getWriteTarget();
|
|
93
93
|
if (writeTarget) {
|
|
94
94
|
const { GnosysMaintenanceEngine } = await import("./maintenance.js");
|
|
95
|
-
await GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, results.map((r) => r.relativePath)).catch(() => {
|
|
95
|
+
await GnosysMaintenanceEngine.reinforceBatch(writeTarget.store, results.map((r) => r.relativePath)).catch((err) => {
|
|
96
|
+
// Best-effort, but don't be fully silent (sprint 2026-07-02).
|
|
97
|
+
console.error(`gnosys: reinforcement skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
+
});
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
search.close();
|
|
@@ -33,7 +33,11 @@ export async function runImportCommand(getResolver, fileOrUrl, opts) {
|
|
|
33
33
|
const ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
|
|
34
34
|
const format = opts.format;
|
|
35
35
|
const mode = opts.mode;
|
|
36
|
-
|
|
36
|
+
// v5.15: --concurrency CLI flag wins; otherwise the config's
|
|
37
|
+
// importConcurrency (schema default 5) applies.
|
|
38
|
+
const { loadConfig } = await import("./config.js");
|
|
39
|
+
const config = await loadConfig(writeTarget.store.getStorePath());
|
|
40
|
+
const concurrency = opts.concurrency || config.importConcurrency;
|
|
37
41
|
// Show estimate for LLM mode
|
|
38
42
|
if (mode === "llm") {
|
|
39
43
|
console.error(`Mode: LLM (concurrency: ${concurrency})`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard for interactive prompt flows (v5.14.x sprint, pre-approved).
|
|
3
|
+
*
|
|
4
|
+
* Interactive setup flows crash with ERR_USE_AFTER_CLOSE when stdin is
|
|
5
|
+
* closed (e.g. `gnosys setup dream` run non-interactively from a script
|
|
6
|
+
* or CI). Instead of a stack trace, print a friendly one-line error and
|
|
7
|
+
* exit 1.
|
|
8
|
+
*/
|
|
9
|
+
/** Returns true when stdin can host an interactive prompt session. */
|
|
10
|
+
export declare function stdinIsInteractive(): boolean;
|
|
11
|
+
/** Human-readable message for a blocked interactive flow. */
|
|
12
|
+
export declare function nonInteractiveMessage(flow: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Exit with a friendly one-liner when stdin is not a TTY.
|
|
15
|
+
* Call at the entry of interactive prompt flows, before any readline use.
|
|
16
|
+
*/
|
|
17
|
+
export declare function guardInteractiveStdin(flow: string): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard for interactive prompt flows (v5.14.x sprint, pre-approved).
|
|
3
|
+
*
|
|
4
|
+
* Interactive setup flows crash with ERR_USE_AFTER_CLOSE when stdin is
|
|
5
|
+
* closed (e.g. `gnosys setup dream` run non-interactively from a script
|
|
6
|
+
* or CI). Instead of a stack trace, print a friendly one-line error and
|
|
7
|
+
* exit 1.
|
|
8
|
+
*/
|
|
9
|
+
/** Returns true when stdin can host an interactive prompt session. */
|
|
10
|
+
export function stdinIsInteractive() {
|
|
11
|
+
return Boolean(process.stdin.isTTY);
|
|
12
|
+
}
|
|
13
|
+
/** Human-readable message for a blocked interactive flow. */
|
|
14
|
+
export function nonInteractiveMessage(flow) {
|
|
15
|
+
return `gnosys ${flow} is interactive and requires a terminal (stdin is not a TTY). Re-run from an interactive shell.`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Exit with a friendly one-liner when stdin is not a TTY.
|
|
19
|
+
* Call at the entry of interactive prompt flows, before any readline use.
|
|
20
|
+
*/
|
|
21
|
+
export function guardInteractiveStdin(flow) {
|
|
22
|
+
if (stdinIsInteractive())
|
|
23
|
+
return;
|
|
24
|
+
console.error(nonInteractiveMessage(flow));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
package/dist/lib/rulesGen.js
CHANGED
|
@@ -35,7 +35,8 @@ This project uses **Gnosys** for persistent memory via MCP. Gnosys uses a centra
|
|
|
35
35
|
|
|
36
36
|
### Write automatically
|
|
37
37
|
|
|
38
|
-
-
|
|
38
|
+
- **Always write with \`gnosys_add_structured\`** — you are an LLM, so structure the memory yourself (title, category, tags, content, relevance) and pass the explicit fields. Do NOT use the freeform \`gnosys_add\`: it makes the Gnosys server run a *second, redundant* LLM call to structure your text and adds an external provider-key dependency that can fail silently. \`gnosys_add_structured\` makes no server-side model call. (Freeform \`gnosys_add\` exists only for non-agent callers — cron jobs, scripts — that cannot structure text themselves.)
|
|
39
|
+
- When user says "remember", "memorize", "save this", "note this down", "don't forget" — call \`gnosys_add_structured\`
|
|
39
40
|
- When user states a decision or preference (even casually) — commit to \`decisions\` category
|
|
40
41
|
- When user provides a spec or plan — commit BEFORE starting work
|
|
41
42
|
- After significant implementation — commit findings and gotchas
|
|
@@ -47,7 +48,7 @@ This project uses **Gnosys** for persistent memory via MCP. Gnosys uses a centra
|
|
|
47
48
|
|--------|------|
|
|
48
49
|
| Find memories | \`gnosys_discover\` (metadata) → \`gnosys_read\` (content) |
|
|
49
50
|
| Search | \`gnosys_hybrid_search\` (best), \`gnosys_federated_search\` (cross-project), \`gnosys_search\` (keyword), \`gnosys_ask\` (Q&A) |
|
|
50
|
-
| Write | \`
|
|
51
|
+
| Write | \`gnosys_add_structured\` (explicit fields — **always use this as an agent**; no server-side LLM call). \`gnosys_add\` (freeform) is for non-agent callers only. |
|
|
51
52
|
| Update | \`gnosys_update\`, \`gnosys_reinforce\` (useful/not_relevant/outdated) |
|
|
52
53
|
| Browse | \`gnosys_list\`, \`gnosys_lens\` (filtered), \`gnosys_tags\`, \`gnosys_graph\` |
|
|
53
54
|
| Maintain | \`gnosys_maintain\`, \`gnosys_stale\`, \`gnosys_history\`, \`gnosys_dashboard\` |
|
|
@@ -63,7 +64,11 @@ This project uses **Gnosys** for persistent memory via MCP. Gnosys uses a centra
|
|
|
63
64
|
|
|
64
65
|
### Categories
|
|
65
66
|
|
|
66
|
-
\`architecture\` · \`decisions\` · \`requirements\` · \`concepts\` · \`roadmap\` · \`landscape\` · \`open-questions
|
|
67
|
+
\`architecture\` · \`decisions\` · \`requirements\` · \`concepts\` · \`roadmap\` · \`landscape\` · \`open-questions\`
|
|
68
|
+
|
|
69
|
+
### Keeping these instructions current
|
|
70
|
+
|
|
71
|
+
This block is generated by Gnosys and can drift from the installed version. After upgrading Gnosys (or if guidance here looks stale), run \`gnosys sync\` to regenerate this section in every detected IDE rules file (CLAUDE.md, .cursor/rules, .codex). Your user preferences and project conventions are re-injected at the same time.`;
|
|
67
72
|
}
|
|
68
73
|
// ─── Content generation ─────────────────────────────────────────────────
|
|
69
74
|
/**
|
package/dist/lib/setup.js
CHANGED
|
@@ -14,6 +14,7 @@ import fsSync from "fs";
|
|
|
14
14
|
import path from "path";
|
|
15
15
|
import os from "os";
|
|
16
16
|
import { execSync } from "child_process";
|
|
17
|
+
import { guardInteractiveStdin } from "./interactiveGuard.js";
|
|
17
18
|
import { loadConfig, updateConfig, resolveTaskModel, getProviderModel, } from "./config.js";
|
|
18
19
|
import { isApiKeyValidationError, validateModel } from "./modelValidation.js";
|
|
19
20
|
import { buildOpenRouterTiers, OPENROUTER_STATIC_TIERS, } from "./openrouterTiers.js";
|
|
@@ -1162,6 +1163,7 @@ export async function runSetup(opts) {
|
|
|
1162
1163
|
};
|
|
1163
1164
|
}
|
|
1164
1165
|
// ─── Interactive mode ─────────────────────────────────────────────────
|
|
1166
|
+
guardInteractiveStdin("setup");
|
|
1165
1167
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
1166
1168
|
let setupCompleted = false;
|
|
1167
1169
|
// Handle Ctrl+C gracefully — only show "cancelled" if setup didn't finish
|
|
@@ -2130,6 +2132,8 @@ export function buildTaskModelsPatchFromAccepted(accepted, currentByTask, select
|
|
|
2130
2132
|
export async function runModelsSetup(opts = {}) {
|
|
2131
2133
|
const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
|
|
2132
2134
|
const ownsRl = !opts.rl;
|
|
2135
|
+
if (ownsRl)
|
|
2136
|
+
guardInteractiveStdin("setup models");
|
|
2133
2137
|
const rl = opts.rl ?? createInterface({ input: stdin, output: stdout });
|
|
2134
2138
|
try {
|
|
2135
2139
|
const { Header } = await import("./setup/ui/header.js");
|
|
@@ -2519,6 +2523,8 @@ async function runModelsTaskRoutingSetup(ctx) {
|
|
|
2519
2523
|
export async function runDreamSetup(opts = {}) {
|
|
2520
2524
|
const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
|
|
2521
2525
|
const ownsRl = !opts.rl;
|
|
2526
|
+
if (ownsRl)
|
|
2527
|
+
guardInteractiveStdin("setup dream");
|
|
2522
2528
|
const rl = opts.rl ?? createInterface({ input: stdin, output: stdout });
|
|
2523
2529
|
try {
|
|
2524
2530
|
// v5.9.3 Screen 7 — three grouped sub-screens (7.0 enable, 7.1
|
|
@@ -2757,8 +2763,11 @@ export async function runDreamSetup(opts = {}) {
|
|
|
2757
2763
|
// Reset consecutive failure counter on a fresh setup so Layer 4
|
|
2758
2764
|
// doesn't fire immediately based on stale history.
|
|
2759
2765
|
localDb.resetDreamConsecutiveFailures();
|
|
2760
|
-
const { installDreamLaunchAgent } = await import("./dreamLaunchd.js");
|
|
2766
|
+
const { installDreamLaunchAgent, loadDreamLaunchAgent } = await import("./dreamLaunchd.js");
|
|
2761
2767
|
const launchdPath = installDreamLaunchAgent();
|
|
2768
|
+
// Best-effort immediate activation so the schedule doesn't wait for the
|
|
2769
|
+
// next login (v5.14.x sprint, pre-approved).
|
|
2770
|
+
const launchdLoad = launchdPath ? loadDreamLaunchAgent(launchdPath) : null;
|
|
2762
2771
|
localDb.close();
|
|
2763
2772
|
remoteDb?.close();
|
|
2764
2773
|
// Final Diff block per the design — provider/machine + the two
|
|
@@ -2789,6 +2798,8 @@ export async function runDreamSetup(opts = {}) {
|
|
|
2789
2798
|
printStatus("progress", `scheduled dream checks run nightly (${scheduleStartHour}:00-${scheduleEndHour}:00) on ${dreamerName}`);
|
|
2790
2799
|
if (launchdPath)
|
|
2791
2800
|
printStatus("ok", "launchd agent installed", launchdPath);
|
|
2801
|
+
if (launchdLoad)
|
|
2802
|
+
printStatus(launchdLoad.ok ? "ok" : "warn", launchdLoad.message);
|
|
2792
2803
|
printStatus("progress", "check status anytime with `gnosys status --system`");
|
|
2793
2804
|
}
|
|
2794
2805
|
finally {
|
package/dist/lib/setupKeys.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "readline/promises";
|
|
2
2
|
import { stdin, stdout } from "process";
|
|
3
|
+
import { guardInteractiveStdin } from "./interactiveGuard.js";
|
|
3
4
|
import fs from "fs/promises";
|
|
4
5
|
import fsSync from "fs";
|
|
5
6
|
import os from "os";
|
|
@@ -522,6 +523,8 @@ export function renderProviderTable(providers) {
|
|
|
522
523
|
}
|
|
523
524
|
export async function runKeysSetup(opts) {
|
|
524
525
|
const ownReadline = !opts?.rl;
|
|
526
|
+
if (ownReadline)
|
|
527
|
+
guardInteractiveStdin("setup keys");
|
|
525
528
|
const rl = opts?.rl ?? createInterface({ input: stdin, output: stdout });
|
|
526
529
|
try {
|
|
527
530
|
while (true) {
|
|
@@ -86,6 +86,22 @@ export async function runStatusCommand(opts, deps) {
|
|
|
86
86
|
}
|
|
87
87
|
const data = await collectDashboardData(resolver, cfg, deps.pkgVersion, dashDb ?? undefined);
|
|
88
88
|
console.log(opts.json ? formatDashboardJSON(data) : formatDashboard(data));
|
|
89
|
+
// v5.15: dream launchd agent health (read-only — repair happens in
|
|
90
|
+
// `gnosys upgrade`). macOS + dream enabled only; skip in JSON mode
|
|
91
|
+
// to keep the JSON payload shape stable.
|
|
92
|
+
if (!opts.json && process.platform === "darwin" && cfg.dream?.enabled) {
|
|
93
|
+
const { checkDreamLaunchAgent } = await import("./dreamLaunchd.js");
|
|
94
|
+
const health = checkDreamLaunchAgent();
|
|
95
|
+
console.log("\nDream launchd agent:");
|
|
96
|
+
if (health.healthy) {
|
|
97
|
+
console.log(" ✓ healthy (installed, loaded, node + cli paths valid)");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
for (const problem of health.problems) {
|
|
101
|
+
console.log(` ⚠ ${problem}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
89
105
|
}
|
|
90
106
|
catch (err) {
|
|
91
107
|
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnosys",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.2",
|
|
4
4
|
"description": "Gnosys — Persistent Memory for AI Agents. Sandbox-first runtime, central SQLite brain, federated search, Dream Mode, Web Knowledge Base, Obsidian export.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|