total-recall-brain 3.44.0 → 3.45.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/bin/total-recall.mjs +6 -4
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +13 -8
- package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +2 -2
- package/frontend/src/pages/PluginsPage.tsx +1 -1
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/command.mjs +41 -2
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +6 -7
- package/src/cli/plugin/index.mjs +11 -7
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/startup.mjs +1 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +6 -2
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +31 -11
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compiler.mjs +20 -0
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +148 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-contracts/card.mjs +3 -0
- package/src/core/plugin-loader.mjs +12 -3
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-store.mjs +35 -23
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +26 -4
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +1 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +32 -4
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +65 -54
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +1 -0
- package/src/core/vector-field.mjs +134 -192
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/route-manifest.json +0 -56
- package/src/server/routes/brains.mjs +0 -18
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
|
@@ -16,6 +16,7 @@ import { getTokenStatus, setTokenValue } from './src/token.mjs';
|
|
|
16
16
|
import { lintWikiHubs } from './src/lint.mjs';
|
|
17
17
|
import { generateWiki } from './src/generate.mjs';
|
|
18
18
|
import { retrieveContext } from './src/context-provider.mjs';
|
|
19
|
+
import { refreshDiagnostics } from './src/refresh-diagnostics.mjs';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Regenerate the generated pages of every registered project that carries them.
|
|
@@ -25,7 +26,14 @@ import { retrieveContext } from './src/context-provider.mjs';
|
|
|
25
26
|
* Defaults to a report; --apply performs the refreshes, because regenerating a
|
|
26
27
|
* repository's documentation is a write to that repository.
|
|
27
28
|
*/
|
|
28
|
-
async function refreshCommand(args
|
|
29
|
+
export async function refreshCommand(args, {
|
|
30
|
+
read = readProjects, dueFor = isDue, refresh = refreshProjectAsync,
|
|
31
|
+
getKey = resolveKey, configFor = loadOpenWikiConfig,
|
|
32
|
+
} = {}) {
|
|
33
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
34
|
+
console.log('Usage: total-recall openwiki refresh [--repo <name>] [--apply] [--force] [--diagnostics [--slot <local-minute>]] [--json]');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
29
37
|
const json = args.includes('--json');
|
|
30
38
|
const apply = args.includes('--apply');
|
|
31
39
|
const force = args.includes('--force');
|
|
@@ -37,7 +45,7 @@ async function refreshCommand(args) {
|
|
|
37
45
|
|
|
38
46
|
let projects;
|
|
39
47
|
try {
|
|
40
|
-
projects =
|
|
48
|
+
projects = read();
|
|
41
49
|
} catch (err) {
|
|
42
50
|
if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
43
51
|
else console.error(`OpenWiki refresh: ${err.message}`);
|
|
@@ -52,20 +60,28 @@ async function refreshCommand(args) {
|
|
|
52
60
|
return;
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
if (args.includes('--diagnostics')) {
|
|
64
|
+
const slotIndex = args.indexOf('--slot');
|
|
65
|
+
const result = await refreshDiagnostics({ projects, slot: slotIndex >= 0 ? args[slotIndex + 1] : null });
|
|
66
|
+
console.log(JSON.stringify(result, null, 2));
|
|
67
|
+
if (!result.ok) process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
const reports = [];
|
|
56
72
|
for (const project of projects) {
|
|
57
73
|
let state;
|
|
58
74
|
try {
|
|
59
|
-
state =
|
|
75
|
+
state = dueFor(project, { force });
|
|
60
76
|
} catch (err) {
|
|
61
|
-
reports.push({ ...project, action: '
|
|
77
|
+
reports.push({ ...project, action: 'failed', reason: err.message });
|
|
62
78
|
continue;
|
|
63
79
|
}
|
|
64
80
|
if (!state.due) {
|
|
65
81
|
if (state.skipped) {
|
|
66
82
|
reports.push({ ...project, action: 'skipped', reason: state.reason });
|
|
67
83
|
} else {
|
|
68
|
-
reports.push({ ...project, action: 'current', generatedAt: state.generatedAt, commitsSince: state.commitsSince });
|
|
84
|
+
reports.push({ ...project, action: 'current', reason: state.reason, generationCommit: state.generationCommit, generationTime: state.generationTime, generatedAt: state.generatedAt, commitsSince: state.commitsSince });
|
|
69
85
|
}
|
|
70
86
|
continue;
|
|
71
87
|
}
|
|
@@ -73,7 +89,10 @@ async function refreshCommand(args) {
|
|
|
73
89
|
...project,
|
|
74
90
|
action: 'due',
|
|
75
91
|
uninitialized: Boolean(state.uninitialized),
|
|
92
|
+
mode: state.mode,
|
|
76
93
|
generatedAt: state.generatedAt,
|
|
94
|
+
generationCommit: state.generationCommit,
|
|
95
|
+
generationTime: state.generationTime,
|
|
77
96
|
commitsSince: state.commitsSince,
|
|
78
97
|
reason: state.reason
|
|
79
98
|
});
|
|
@@ -82,8 +101,8 @@ async function refreshCommand(args) {
|
|
|
82
101
|
const due = reports.filter((r) => r.action === 'due');
|
|
83
102
|
let payloadBudgetNote = null;
|
|
84
103
|
if (apply && due.length) {
|
|
85
|
-
const config =
|
|
86
|
-
const { key, from } =
|
|
104
|
+
const config = configFor();
|
|
105
|
+
const { key, from } = getKey({ provider: config.provider });
|
|
87
106
|
if (!key) {
|
|
88
107
|
const keyName = config.provider === 'google' ? 'GOOGLE_API_KEY' : 'OPENROUTER_API_KEY';
|
|
89
108
|
const error = `No model credential for provider '${config.provider}'. Save via: total-recall auth secret set ${keyName} --stdin`;
|
|
@@ -109,20 +128,25 @@ async function refreshCommand(args) {
|
|
|
109
128
|
while (queueIndex < due.length) {
|
|
110
129
|
const itemIndex = queueIndex++;
|
|
111
130
|
const entry = due[itemIndex];
|
|
112
|
-
const mode = entry.uninitialized ? 'init' : 'update';
|
|
131
|
+
const mode = entry.mode || (entry.uninitialized ? 'init' : 'update');
|
|
113
132
|
if (!json) {
|
|
114
133
|
console.log(`[${itemIndex + 1}/${due.length}] ▶ ${entry.name.padEnd(28)} starting ${mode}...`);
|
|
115
134
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
135
|
+
let result;
|
|
136
|
+
try {
|
|
137
|
+
result = await refresh(
|
|
138
|
+
{ name: entry.name, root: entry.root },
|
|
139
|
+
{ key, model: config.model, provider: config.provider, timeoutMs: config.timeout_ms }
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
result = { action: 'failed', reason: error.message.split(key).join('[REDACTED]') };
|
|
143
|
+
}
|
|
120
144
|
completedCount++;
|
|
121
145
|
const index = reports.findIndex((r) => r.root === entry.root);
|
|
122
146
|
reports[index] = { ...reports[index], ...result, credential: from };
|
|
123
147
|
if (!json) {
|
|
124
|
-
const duration = Math.round((
|
|
125
|
-
const icon = result.action
|
|
148
|
+
const duration = Math.round((result.durationMs || 0) / 1000);
|
|
149
|
+
const icon = ['failed', 'partial'].includes(result.action) ? '✖' : '✔';
|
|
126
150
|
console.log(`[${completedCount}/${due.length}] ${icon} ${entry.name.padEnd(28)} ${result.action} (${duration}s)${result.reason ? ` — ${result.reason}` : ''}`);
|
|
127
151
|
}
|
|
128
152
|
}
|
|
@@ -133,8 +157,9 @@ async function refreshCommand(args) {
|
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
const failed = reports.filter((r) => r.action === 'failed');
|
|
160
|
+
const partial = reports.filter((r) => r.action === 'partial');
|
|
136
161
|
const payload = {
|
|
137
|
-
ok: failed.length === 0,
|
|
162
|
+
ok: failed.length === 0 && partial.length === 0,
|
|
138
163
|
applied: apply,
|
|
139
164
|
retention: describeRetention(),
|
|
140
165
|
note: payloadBudgetNote,
|
|
@@ -145,6 +170,8 @@ async function refreshCommand(args) {
|
|
|
145
170
|
uninitialized: reports.filter((r) => r.uninitialized).length,
|
|
146
171
|
refreshed: reports.filter((r) => r.action === 'refreshed' || r.action === 'initialized').length,
|
|
147
172
|
failed: failed.length,
|
|
173
|
+
partial: partial.length,
|
|
174
|
+
skipped: reports.filter((r) => r.action === 'skipped').length,
|
|
148
175
|
current: reports.filter((r) => r.action === 'current').length,
|
|
149
176
|
},
|
|
150
177
|
reports,
|
|
@@ -152,15 +179,17 @@ async function refreshCommand(args) {
|
|
|
152
179
|
if (json) console.log(JSON.stringify(payload, null, 2));
|
|
153
180
|
else {
|
|
154
181
|
console.log(`OpenWiki refresh (${apply ? 'apply' : 'report'}): ${payload.counts.registered} registered, ${payload.counts.due} due (${payload.counts.uninitialized} uninitialized)`);
|
|
155
|
-
|
|
182
|
+
// Put failures last so bounded scheduler tails retain their causes.
|
|
183
|
+
for (const r of [...reports].sort((a, b) => Number(['failed', 'partial'].includes(a.action)) - Number(['failed', 'partial'].includes(b.action)))) {
|
|
156
184
|
if (r.action === 'current') continue;
|
|
157
|
-
const age = r.uninitialized ? 'uninitialized' : (r.generatedAt ? `
|
|
185
|
+
const age = r.action === 'skipped' ? 'outside repository generation' : r.generationTime ? `generated ${r.generationTime} at ${r.generationCommit}` : r.uninitialized ? 'uninitialized' : (r.generatedAt ? `last attempt ${r.generatedAt}; generation commit unknown` : 'never generated');
|
|
158
186
|
console.log(` ${r.action.padEnd(9)} ${r.name.padEnd(28)} ${age}${Number.isFinite(r.commitsSince) && r.commitsSince !== Infinity ? `, ${r.commitsSince} commits since` : ''}${r.reason ? ` — ${r.reason}` : ''}`);
|
|
159
187
|
}
|
|
160
188
|
if (payloadBudgetNote) console.log(payloadBudgetNote);
|
|
161
189
|
if (!apply && due.length) console.log('Re-run with --apply to regenerate. Pages are a projection; vault content is never touched.');
|
|
162
190
|
}
|
|
163
|
-
if (
|
|
191
|
+
if (!payload.ok) process.exitCode = 1;
|
|
192
|
+
return payload;
|
|
164
193
|
}
|
|
165
194
|
|
|
166
195
|
async function probeCommand(args) {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-openwiki",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "tr-plugin-openwiki",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.11",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@gregiteen/ssss-cli": "0.14.5"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"total-recall-brain": "3.
|
|
15
|
+
"total-recall-brain": "3.44.0"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"node_modules/@alcalzone/ansi-tokenize": {
|
|
@@ -5529,9 +5529,9 @@
|
|
|
5529
5529
|
}
|
|
5530
5530
|
},
|
|
5531
5531
|
"node_modules/total-recall-brain": {
|
|
5532
|
-
"version": "3.
|
|
5533
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.
|
|
5534
|
-
"integrity": "sha512-
|
|
5532
|
+
"version": "3.44.0",
|
|
5533
|
+
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.44.0.tgz",
|
|
5534
|
+
"integrity": "sha512-VNPaIO9zsSwoxAsJ5ByIQBP9zPpLq4RiQO7Vrex4rBOHFQJT50P7utcZiLMSZTyZJU4C/m5uB8qTCSBMp6+l6Q==",
|
|
5535
5535
|
"dev": true,
|
|
5536
5536
|
"license": "MIT",
|
|
5537
5537
|
"workspaces": [
|
|
@@ -5539,7 +5539,7 @@
|
|
|
5539
5539
|
],
|
|
5540
5540
|
"dependencies": {
|
|
5541
5541
|
"@simplewebauthn/server": "^13.3.2",
|
|
5542
|
-
"@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.
|
|
5542
|
+
"@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.5",
|
|
5543
5543
|
"bcrypt": "^6.0.0",
|
|
5544
5544
|
"better-sqlite3": "^12.11.1",
|
|
5545
5545
|
"cookie-parser": "^1.4.7",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
3
3
|
"id": "openwiki",
|
|
4
4
|
"name": "OpenWiki",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.11",
|
|
6
6
|
"description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"runtime": {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { readProjects } from './refresh.mjs';
|
|
6
|
+
|
|
7
|
+
/** Read through Total Recall's event service. Never open vault files here. */
|
|
8
|
+
async function readTaskEvents(options) {
|
|
9
|
+
const service = process.env.TR_PACKAGE_ROOT
|
|
10
|
+
? pathToFileURL(path.join(process.env.TR_PACKAGE_ROOT, 'src', 'core', 'ssss-operation-service.mjs')).href
|
|
11
|
+
: 'total-recall-brain/src/core/ssss-operation-service.mjs';
|
|
12
|
+
const { listVfsEvents } = await import(service);
|
|
13
|
+
return listVfsEvents(options);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function refreshDiagnostics({
|
|
17
|
+
projects = readProjects(),
|
|
18
|
+
vaultRoot = path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'memory-vault'),
|
|
19
|
+
listEvents = readTaskEvents,
|
|
20
|
+
slot = null,
|
|
21
|
+
} = {}) {
|
|
22
|
+
let taskEvents = [], eventError = null;
|
|
23
|
+
try {
|
|
24
|
+
taskEvents = (await listEvents({ vaultRoot })).flatMap(event => {
|
|
25
|
+
let payload = event.payload || event.content;
|
|
26
|
+
if (typeof payload === 'string') {
|
|
27
|
+
try { payload = JSON.parse(payload); } catch { return []; }
|
|
28
|
+
}
|
|
29
|
+
return payload?.kind === 'plugin.task_run' && payload.plugin_id === 'openwiki' && payload.command === 'auto-refresh'
|
|
30
|
+
? [{ at: event.timestamp || event.created_at || null, ...payload }] : [];
|
|
31
|
+
});
|
|
32
|
+
} catch (error) { eventError = error.message; }
|
|
33
|
+
taskEvents.sort((a, b) => String(a.at).localeCompare(String(b.at)));
|
|
34
|
+
taskEvents = slot ? taskEvents.filter(event => event.slot === slot) : taskEvents.slice(-1);
|
|
35
|
+
const reports = projects.map(project => {
|
|
36
|
+
const logPath = path.join(project.root, 'openwiki', '.refresh-attempt.json');
|
|
37
|
+
try { return { ...project, logPath, attempt: JSON.parse(fs.readFileSync(logPath, 'utf8')) }; }
|
|
38
|
+
catch (error) { return { ...project, logPath, attempt: null, logError: error.code === 'ENOENT' ? 'No retained generator output' : error.message }; }
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
ok: !eventError, eventError, taskEvents, reports,
|
|
42
|
+
note: 'Task events retain only an output tail. Full legacy generator output was not saved; it cannot be reconstructed from that tail.',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -30,6 +30,19 @@ const PLUGIN_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
|
30
30
|
const RETENTION_NOTE = 'pages are a projection, not canonical memory';
|
|
31
31
|
const DEFAULT_LOCK_MS = 6 * 60 * 60 * 1000;
|
|
32
32
|
|
|
33
|
+
const gitRun = (_command, args, options) => spawnSync('total-recall', ['git', ...args], options);
|
|
34
|
+
|
|
35
|
+
function readJson(file) {
|
|
36
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** An unfinished init must resume as init, even after it has written pages. */
|
|
40
|
+
export function generationMode(root) {
|
|
41
|
+
const state = readJson(path.join(root, 'openwiki', '.run.json'));
|
|
42
|
+
if (state?.mode === 'init' && state.phase && !['complete', 'idle'].includes(state.phase)) return 'init';
|
|
43
|
+
return hasWiki(root) ? 'update' : 'init';
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
export function registryPath(home = os.homedir()) {
|
|
34
47
|
return path.join(home, '.agent', 'skills', 'total-recall', 'config', 'project-registry.json');
|
|
35
48
|
}
|
|
@@ -69,7 +82,7 @@ export function readProjects({ registry = registryPath() } = {}) {
|
|
|
69
82
|
});
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
/** Does this project carry generated pages
|
|
85
|
+
/** Does this project carry generated Markdown pages? */
|
|
73
86
|
export function hasWiki(root) {
|
|
74
87
|
const dir = path.join(root, 'openwiki');
|
|
75
88
|
const stat = fs.lstatSync(dir, { throwIfNoEntry: false });
|
|
@@ -82,15 +95,18 @@ export function hasWiki(root) {
|
|
|
82
95
|
* since the recorded generation, which is the same measure the per-repo driver
|
|
83
96
|
* used. Uncommitted work is not counted, so an open edit does not read as drift.
|
|
84
97
|
*/
|
|
85
|
-
export function isGitRepo(root, { git =
|
|
98
|
+
export function isGitRepo(root, { git = gitRun } = {}) {
|
|
86
99
|
if (fs.existsSync(path.join(root, '.git'))) return true;
|
|
87
100
|
const res = git('git', ['-C', root, 'rev-parse', '--is-inside-work-tree'], { encoding: 'utf8' });
|
|
88
|
-
|
|
101
|
+
if (res.error || res.status !== 0 || String(res.stdout).trim() !== 'true') return false;
|
|
102
|
+
const top = git('git', ['-C', root, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });
|
|
103
|
+
if (top.error || top.status !== 0) return false;
|
|
104
|
+
return fs.realpathSync(root) === fs.realpathSync(String(top.stdout).trim());
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
export function staleness(root, { git =
|
|
107
|
+
export function staleness(root, { git = gitRun } = {}) {
|
|
92
108
|
if (!isGitRepo(root, { git })) {
|
|
93
|
-
return { root, head: '', generatedAt: null, generatedStatus: null, files: null, lastCodeChange: null, commitsSince: 0, stale: false };
|
|
109
|
+
return { root, head: '', generationCommit: null, generationTime: null, generatedAt: null, generatedStatus: null, files: null, lastCodeChange: null, commitsSince: 0, stale: false };
|
|
94
110
|
}
|
|
95
111
|
const marker = path.join(root, 'openwiki', '.last-update.json');
|
|
96
112
|
let head = '';
|
|
@@ -104,6 +120,11 @@ export function staleness(root, { git = spawnSync } = {}) {
|
|
|
104
120
|
generatedAt = typeof parsed.updatedAt === 'string' ? parsed.updatedAt : null;
|
|
105
121
|
generatedStatus = typeof parsed.status === 'string' ? parsed.status : null;
|
|
106
122
|
} catch { /* no marker: never generated, or unreadable */ }
|
|
123
|
+
const receipt = readJson(path.join(root, 'openwiki', '.generation.json'));
|
|
124
|
+
const validReceipt = typeof receipt?.commit === 'string' && /^[a-f0-9]{7,64}$/i.test(receipt.commit)
|
|
125
|
+
&& typeof receipt.completedAt === 'string' && Number.isFinite(Date.parse(receipt.completedAt));
|
|
126
|
+
const generationCommit = validReceipt ? receipt.commit : null;
|
|
127
|
+
const generationTime = validReceipt ? receipt.completedAt : null;
|
|
107
128
|
|
|
108
129
|
// The page contents, so a refresh can be judged on what it actually rewrote.
|
|
109
130
|
// openwiki writes its marker when generation begins, so the marker alone
|
|
@@ -122,12 +143,15 @@ export function staleness(root, { git = spawnSync } = {}) {
|
|
|
122
143
|
const lastCodeChange = !log.error && log.status === 0 ? String(log.stdout || '').trim() || null : null;
|
|
123
144
|
|
|
124
145
|
let commitsSince = null;
|
|
125
|
-
if (generatedAt) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
if (generationCommit || generatedAt) {
|
|
147
|
+
const range = generationCommit ? [`${generationCommit}..HEAD`] : [`--since=${generatedAt}`, 'HEAD'];
|
|
148
|
+
const count = git('git', ['-C', root, 'rev-list', '--count', ...range, '--', '.', ':!openwiki'], { encoding: 'utf8' });
|
|
149
|
+
const value = String(count.stdout || '').trim();
|
|
150
|
+
if (!count.error && count.status === 0 && /^\d+$/.test(value)) commitsSince = Number(value);
|
|
151
|
+
}
|
|
152
|
+
const incomplete = generatedStatus === 'interrupted' || stillGenerating(root);
|
|
153
|
+
const stale = incomplete || generatedAt === null || commitsSince === null || commitsSince > 0;
|
|
154
|
+
return { root, head, generationCommit, generationTime, generatedAt, generatedStatus, files, lastCodeChange, commitsSince, stale, mode: generationMode(root) };
|
|
131
155
|
}
|
|
132
156
|
|
|
133
157
|
/**
|
|
@@ -250,14 +274,81 @@ export function stillGenerating(root) {
|
|
|
250
274
|
}
|
|
251
275
|
}
|
|
252
276
|
|
|
277
|
+
function writeReceipt(root, name, value) {
|
|
278
|
+
const dir = path.join(root, 'openwiki');
|
|
279
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
280
|
+
const target = path.join(dir, name);
|
|
281
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
282
|
+
try {
|
|
283
|
+
fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
284
|
+
fs.renameSync(tmp, target);
|
|
285
|
+
} finally {
|
|
286
|
+
fs.rmSync(tmp, { force: true });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function redact(value, key) {
|
|
291
|
+
let text = String(value || '');
|
|
292
|
+
if (key) text = text.split(key).join('[REDACTED]');
|
|
293
|
+
return text.replace(/\bBearer\s+\S+/gi, 'Bearer [REDACTED]').replace(/\bsk-[\w-]+/g, '[REDACTED]');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function finishRefresh(project, { before, result, git, key, mode, startedAt, via, clock }) {
|
|
297
|
+
const after = staleness(project.root, { git });
|
|
298
|
+
const exitCode = result.error ? (result.error.code || 1) : result.signal ? 1 : result.status;
|
|
299
|
+
const completedAt = new Date(clock()).toISOString();
|
|
300
|
+
const stdout = redact(result.stdout, key), stderr = redact(result.stderr, key);
|
|
301
|
+
const error = redact(result.error?.message, key);
|
|
302
|
+
const lines = `${stderr}\n${stdout}`.replace(/\x1b\[[0-9;]*m/g, '').split('\n').map(line => line.trim()).filter(Boolean);
|
|
303
|
+
const diagnostic = lines.find(line => /context_length_exceeded|Provider returned error|exited without|Could not restore|interrupted OpenWiki|fetch failed/.test(line));
|
|
304
|
+
const reason = error || (result.signal ? `terminated by ${result.signal}` : diagnostic || lines.filter(line => !/^(\[ERR\]\s*)?Error Diagnostics$/.test(line)).at(-1) || `exit ${exitCode}`);
|
|
305
|
+
const wrote = pagesWritten(before, after) || (!before.files && hasWiki(project.root) ? 1 : 0);
|
|
306
|
+
const advanced = after.generatedAt !== before.generatedAt;
|
|
307
|
+
let action, failureReason;
|
|
308
|
+
if (after.generatedStatus === 'interrupted' || stillGenerating(project.root)) {
|
|
309
|
+
action = 'partial';
|
|
310
|
+
failureReason = `${wrote} page(s) rewritten, but the run did not finish; it resumes on the next refresh (${reason})`;
|
|
311
|
+
} else if (exitCode !== 0) {
|
|
312
|
+
action = 'failed'; failureReason = reason;
|
|
313
|
+
} else if (!advanced && !wrote) {
|
|
314
|
+
action = 'failed'; failureReason = 'command exited 0 and no page changed';
|
|
315
|
+
} else if (!before.head) {
|
|
316
|
+
action = 'failed'; failureReason = 'Cannot record generation without a source commit';
|
|
317
|
+
} else {
|
|
318
|
+
action = mode === 'init' ? 'initialized' : 'refreshed';
|
|
319
|
+
}
|
|
320
|
+
const logPath = path.join(project.root, 'openwiki', '.refresh-attempt.json');
|
|
321
|
+
const report = { ...project, ...after, action, reason: failureReason, exitCode, signal: result.signal || null, from: before.generatedAt, to: after.generatedAt, durationMs: clock() - startedAt, via, mode, logPath };
|
|
322
|
+
try {
|
|
323
|
+
writeReceipt(project.root, '.refresh-attempt.json', { startedAt: new Date(startedAt).toISOString(), completedAt, sourceCommit: before.head || null, action, mode, exitCode, signal: result.signal || null, error, stdout, stderr });
|
|
324
|
+
if (action === 'initialized' || action === 'refreshed') {
|
|
325
|
+
writeReceipt(project.root, '.generation.json', { commit: before.head, completedAt });
|
|
326
|
+
report.generationCommit = before.head;
|
|
327
|
+
report.generationTime = completedAt;
|
|
328
|
+
report.commitsSince = after.head === before.head ? 0 : null;
|
|
329
|
+
report.stale = after.head !== before.head;
|
|
330
|
+
} else if (before.generationCommit && before.generationTime) {
|
|
331
|
+
// Init can replace openwiki/ before it fails. Retain the last success.
|
|
332
|
+
writeReceipt(project.root, '.generation.json', { commit: before.generationCommit, completedAt: before.generationTime });
|
|
333
|
+
report.generationCommit = before.generationCommit;
|
|
334
|
+
report.generationTime = before.generationTime;
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
report.action = 'failed'; report.reason = `Could not save refresh receipt: ${error.message}`;
|
|
338
|
+
}
|
|
339
|
+
return report;
|
|
340
|
+
}
|
|
341
|
+
|
|
253
342
|
export function refreshProject(project, {
|
|
254
|
-
key, model = null, provider = null, binary = null, binaryArgs = [], timeoutMs = 30 * 60 * 1000, run = spawnSync, now = Date.now(), targetVersion = null, pluginRoot = PLUGIN_ROOT,
|
|
343
|
+
key, model = null, provider = null, binary = null, binaryArgs = [], timeoutMs = 30 * 60 * 1000, run = spawnSync, now = Date.now(), clock = Date.now, targetVersion = null, pluginRoot = PLUGIN_ROOT,
|
|
255
344
|
} = {}) {
|
|
256
|
-
|
|
345
|
+
const git = run === spawnSync ? gitRun : run;
|
|
346
|
+
if (!isGitRepo(project.root, { git })) {
|
|
257
347
|
return { ...project, action: 'skipped', reason: 'not a git repository' };
|
|
258
348
|
}
|
|
259
|
-
const before = staleness(project.root, { git
|
|
349
|
+
const before = staleness(project.root, { git });
|
|
260
350
|
const uninitialized = !hasWiki(project.root);
|
|
351
|
+
const mode = generationMode(project.root);
|
|
261
352
|
|
|
262
353
|
let command = binary, args = binaryArgs, via = binary ? 'explicit' : null;
|
|
263
354
|
if (!command) {
|
|
@@ -273,19 +364,12 @@ export function refreshProject(project, {
|
|
|
273
364
|
const selectedModel = model || config.model;
|
|
274
365
|
const selectedProvider = provider || config.provider;
|
|
275
366
|
|
|
276
|
-
const modeFlag =
|
|
367
|
+
const modeFlag = `--${mode}`;
|
|
277
368
|
const cmdArgs = [...args, modeFlag, '-p'];
|
|
278
369
|
if (selectedModel) {
|
|
279
370
|
cmdArgs.push('--modelId', selectedModel);
|
|
280
371
|
}
|
|
281
372
|
|
|
282
|
-
if (uninitialized) {
|
|
283
|
-
const runFile = path.join(project.root, 'openwiki', '.run.json');
|
|
284
|
-
if (fs.existsSync(runFile)) {
|
|
285
|
-
try { fs.unlinkSync(runFile); } catch {}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
373
|
const childEnv = { ...process.env };
|
|
290
374
|
if (selectedProvider === 'google') {
|
|
291
375
|
childEnv.GOOGLE_API_KEY = key;
|
|
@@ -312,47 +396,18 @@ export function refreshProject(project, {
|
|
|
312
396
|
timeout: timeoutMs,
|
|
313
397
|
});
|
|
314
398
|
|
|
315
|
-
|
|
316
|
-
const exitCode = result.error ? (result.error.code || 1) : result.status;
|
|
317
|
-
const advanced = after.generatedAt !== before.generatedAt || (uninitialized && hasWiki(project.root));
|
|
318
|
-
const wrote = pagesWritten(before, after) || (uninitialized && hasWiki(project.root) ? 1 : 0);
|
|
319
|
-
|
|
320
|
-
const outcome = after.generatedStatus === 'interrupted' || stillGenerating(project.root)
|
|
321
|
-
? 'partial'
|
|
322
|
-
: (advanced ? (uninitialized ? 'initialized' : 'refreshed') : 'unchanged');
|
|
323
|
-
|
|
324
|
-
if (exitCode !== 0 && !wrote) {
|
|
325
|
-
return { ...project, action: 'failed', reason: result.error?.message || `exit ${exitCode}`, exitCode, ...after };
|
|
326
|
-
}
|
|
327
|
-
if (!advanced && !wrote) {
|
|
328
|
-
return { ...project, action: 'failed', reason: `command exited ${exitCode} and no page changed`, exitCode, ...after };
|
|
329
|
-
}
|
|
330
|
-
if (outcome === 'partial') {
|
|
331
|
-
return {
|
|
332
|
-
...project,
|
|
333
|
-
action: 'partial',
|
|
334
|
-
reason: `${wrote} page(s) rewritten, but the run did not finish (the generator was cut off); it resumes on the next refresh`,
|
|
335
|
-
exitCode,
|
|
336
|
-
from: before.generatedAt,
|
|
337
|
-
to: after.generatedAt,
|
|
338
|
-
via,
|
|
339
|
-
...after,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
if (exitCode !== 0) {
|
|
343
|
-
return { ...project, action: 'failed', reason: result.error?.message || `exit ${exitCode}`, exitCode, ...after };
|
|
344
|
-
}
|
|
345
|
-
return { ...project, action: uninitialized ? 'initialized' : 'refreshed', from: before.generatedAt, to: after.generatedAt, durationMs: Date.now() - startedAt, via, ...after };
|
|
399
|
+
return finishRefresh(project, { before, result, git, key, mode, startedAt, via, clock });
|
|
346
400
|
}
|
|
347
401
|
|
|
348
402
|
export async function refreshProjectAsync(project, {
|
|
349
|
-
key, model = null, provider = null, binary = null, binaryArgs = [], timeoutMs = 30 * 60 * 1000, now = Date.now(), targetVersion = null, pluginRoot = PLUGIN_ROOT,
|
|
403
|
+
key, model = null, provider = null, binary = null, binaryArgs = [], timeoutMs = 30 * 60 * 1000, now = Date.now(), clock = Date.now, targetVersion = null, pluginRoot = PLUGIN_ROOT, git = gitRun, spawnProcess = spawn,
|
|
350
404
|
} = {}) {
|
|
351
|
-
if (!isGitRepo(project.root, { git
|
|
405
|
+
if (!isGitRepo(project.root, { git })) {
|
|
352
406
|
return { ...project, action: 'skipped', reason: 'not a git repository' };
|
|
353
407
|
}
|
|
354
|
-
const before = staleness(project.root, { git
|
|
408
|
+
const before = staleness(project.root, { git });
|
|
355
409
|
const uninitialized = !hasWiki(project.root);
|
|
410
|
+
const mode = generationMode(project.root);
|
|
356
411
|
let command = binary, args = binaryArgs, via = binary ? 'explicit' : null;
|
|
357
412
|
if (!command) {
|
|
358
413
|
const resolved = resolveGenerator({ run: spawnSync, targetVersion, pluginRoot, requireBundled: uninitialized });
|
|
@@ -367,19 +422,12 @@ export async function refreshProjectAsync(project, {
|
|
|
367
422
|
const selectedModel = model || config.model;
|
|
368
423
|
const selectedProvider = provider || config.provider;
|
|
369
424
|
|
|
370
|
-
const modeFlag =
|
|
425
|
+
const modeFlag = `--${mode}`;
|
|
371
426
|
const cmdArgs = [...args, modeFlag, '-p'];
|
|
372
427
|
if (selectedModel) {
|
|
373
428
|
cmdArgs.push('--modelId', selectedModel);
|
|
374
429
|
}
|
|
375
430
|
|
|
376
|
-
if (uninitialized) {
|
|
377
|
-
const runFile = path.join(project.root, 'openwiki', '.run.json');
|
|
378
|
-
if (fs.existsSync(runFile)) {
|
|
379
|
-
try { fs.unlinkSync(runFile); } catch {}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
431
|
const childEnv = { ...process.env };
|
|
384
432
|
if (selectedProvider === 'google') {
|
|
385
433
|
childEnv.GOOGLE_API_KEY = key;
|
|
@@ -399,74 +447,33 @@ export async function refreshProjectAsync(project, {
|
|
|
399
447
|
|
|
400
448
|
const startedAt = now;
|
|
401
449
|
const result = await new Promise((resolve) => {
|
|
402
|
-
const proc =
|
|
450
|
+
const proc = spawnProcess(command, cmdArgs, {
|
|
403
451
|
cwd: project.root,
|
|
404
452
|
env: childEnv,
|
|
405
453
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
406
454
|
timeout: timeoutMs,
|
|
407
455
|
});
|
|
408
|
-
let stdout = '', stderr = '';
|
|
456
|
+
let stdout = '', stderr = '', error = null;
|
|
409
457
|
proc.stdout?.on('data', (d) => { stdout += d; });
|
|
410
458
|
proc.stderr?.on('data', (d) => { stderr += d; });
|
|
411
|
-
proc.on('error', (err) =>
|
|
412
|
-
proc.on('close', (status) => resolve({
|
|
459
|
+
proc.on('error', (err) => { error = err; });
|
|
460
|
+
proc.on('close', (status, signal) => resolve({ error, status, signal, stdout, stderr }));
|
|
413
461
|
});
|
|
414
|
-
|
|
415
|
-
const after = staleness(project.root, { git: spawnSync });
|
|
416
|
-
const exitCode = result.error ? (result.error.code || 1) : result.status;
|
|
417
|
-
const advanced = after.generatedAt !== before.generatedAt || (uninitialized && hasWiki(project.root));
|
|
418
|
-
const wrote = pagesWritten(before, after) || (uninitialized && hasWiki(project.root) ? 1 : 0);
|
|
419
|
-
|
|
420
|
-
const outcome = after.generatedStatus === 'interrupted' || stillGenerating(project.root)
|
|
421
|
-
? 'partial'
|
|
422
|
-
: (advanced ? (uninitialized ? 'initialized' : 'refreshed') : 'unchanged');
|
|
423
|
-
|
|
424
|
-
const extractReason = (rawErr, rawOut) => {
|
|
425
|
-
if (result.error?.message) return result.error.message;
|
|
426
|
-
const combined = `${rawErr || ''}\n${rawOut || ''}`.trim();
|
|
427
|
-
if (!combined) return `exit ${exitCode}`;
|
|
428
|
-
const lines = combined.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
429
|
-
const diag = lines.find((l) => l.includes('Error Diagnostics') || l.includes('exited without') || l.includes('Could not restore') || l.includes('Provider returned error') || l.includes('fetch failed'));
|
|
430
|
-
if (diag) return diag.replace(/^\[ERR\]\s*/, '');
|
|
431
|
-
const last = lines[lines.length - 1];
|
|
432
|
-
return last ? last.replace(/^\[ERR\]\s*/, '') : `exit ${exitCode}`;
|
|
433
|
-
};
|
|
434
|
-
const failReason = extractReason(result.stderr, result.stdout);
|
|
435
|
-
|
|
436
|
-
if (exitCode !== 0 && !wrote) {
|
|
437
|
-
return { ...project, action: 'failed', reason: failReason, exitCode, ...after };
|
|
438
|
-
}
|
|
439
|
-
if (!advanced && !wrote) {
|
|
440
|
-
return { ...project, action: 'failed', reason: failReason || `command exited ${exitCode} and no page changed`, exitCode, ...after };
|
|
441
|
-
}
|
|
442
|
-
if (outcome === 'partial') {
|
|
443
|
-
return {
|
|
444
|
-
...project,
|
|
445
|
-
action: 'partial',
|
|
446
|
-
reason: `${wrote} page(s) rewritten, but the run did not finish (the generator was cut off); it resumes on the next refresh`,
|
|
447
|
-
exitCode,
|
|
448
|
-
from: before.generatedAt,
|
|
449
|
-
to: after.generatedAt,
|
|
450
|
-
via,
|
|
451
|
-
...after,
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
if (exitCode !== 0) {
|
|
455
|
-
return { ...project, action: 'failed', reason: failReason, exitCode, ...after };
|
|
456
|
-
}
|
|
457
|
-
return { ...project, action: uninitialized ? 'initialized' : 'refreshed', from: before.generatedAt, to: after.generatedAt, durationMs: Date.now() - startedAt, via, ...after };
|
|
462
|
+
return finishRefresh(project, { before, result, git, key, mode, startedAt, via, clock });
|
|
458
463
|
}
|
|
459
464
|
|
|
460
465
|
/** Is a refresh due for this project under the configured interval? */
|
|
461
|
-
export function isDue(project, { lockMs = DEFAULT_LOCK_MS, now = Date.now(), git =
|
|
466
|
+
export function isDue(project, { lockMs = DEFAULT_LOCK_MS, now = Date.now(), git = gitRun, force = false } = {}) {
|
|
462
467
|
if (!isGitRepo(project.root, { git })) {
|
|
463
468
|
return { due: false, skipped: true, reason: 'not a git repository' };
|
|
464
469
|
}
|
|
465
470
|
const state = staleness(project.root, { git });
|
|
471
|
+
if (force) return { due: true, uninitialized: !hasWiki(project.root), ...state };
|
|
466
472
|
if (!hasWiki(project.root)) {
|
|
467
473
|
return { due: true, uninitialized: true, reason: 'uninitialized (missing openwiki/ pages)', ...state };
|
|
468
474
|
}
|
|
469
475
|
if (!state.stale) return { due: false, ...state };
|
|
476
|
+
if (state.generatedStatus === 'interrupted' || stillGenerating(project.root)) return { due: true, reason: 'resume incomplete generation', ...state };
|
|
470
477
|
if (state.generatedAt) {
|
|
471
478
|
const at = Date.parse(state.generatedAt);
|
|
472
479
|
if (Number.isFinite(at) && now - at < lockMs) return { due: false, reason: 'refreshed recently', ...state };
|