total-recall-brain 3.46.2 → 3.47.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/frontend/dist/assets/index-CpsWeV4X.css +1 -0
- package/frontend/dist/assets/{index-D1goIPjC.js → index-poecbHSl.js} +100 -100
- package/frontend/dist/brand/total-recall-lockup-transparent.png +0 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +1 -1
- package/frontend/src/App.tsx +15 -81
- package/frontend/src/components/DashboardOverview.tsx +164 -0
- package/frontend/src/components/brand/BrandMark.tsx +8 -6
- package/frontend/src/components/plugins/PluginWorkspace.tsx +112 -57
- package/frontend/src/components/workspace/PluginHome.tsx +3 -3
- package/frontend/src/index.css +43 -11
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +8 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +5 -33
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-fleet-ops.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-publish.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +119 -10
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +52 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +23 -22
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +20 -33
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +42 -49
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/markdown-links.mjs +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +206 -60
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +18 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +51 -2
- package/src/cli/context.mjs +6 -2
- package/src/core/context-compactor.mjs +2 -0
- package/src/server/routes/vault.mjs +1 -0
- package/frontend/dist/assets/index-BRYE1jmv.css +0 -1
|
@@ -18,7 +18,7 @@ export function PluginHome({ plugin, plugins, brainId, onCommand }: {
|
|
|
18
18
|
const home = (plugin.manifest?.ui as { home?: string } | undefined)?.home || plugin.ui?.home
|
|
19
19
|
const panel = panels.find(element => element.id === home) || panels[0]
|
|
20
20
|
const operations = scopedPluginOperations(plugin)
|
|
21
|
-
const chunks = published?.chunks.length ? published.chunks : defaults
|
|
21
|
+
const chunks = published?.chunks.length ? published.chunks : (panel ? [] : defaults)
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
const controller = new AbortController()
|
|
@@ -62,8 +62,8 @@ export function PluginHome({ plugin, plugins, brainId, onCommand }: {
|
|
|
62
62
|
)}
|
|
63
63
|
{!loading && !published?.chunks.length && panel && !chunks.some(chunk => chunk.panel === panel.id) &&
|
|
64
64
|
<PluginPanel plugin={plugin} elementId={panel.id} designCss={designCss} brainId={brainId} />}
|
|
65
|
-
{!
|
|
66
|
-
<h2>
|
|
65
|
+
{!loading && !panel && !chunks.length && operations.length > 0 && <section aria-label="Plugin operations">
|
|
66
|
+
<h2>Quick actions</h2>
|
|
67
67
|
<ul className="plugin-home-operations">
|
|
68
68
|
{operations.slice(0, 6).map(command => <li key={command.name}>
|
|
69
69
|
<button type="button" className="btn btn-ghost" onClick={() => onCommand(command.name)}>{command.name}</button>
|
package/frontend/src/index.css
CHANGED
|
@@ -132,17 +132,15 @@ code, pre { font-family: var(--font-mono); }
|
|
|
132
132
|
inset: 0 auto 0 0;
|
|
133
133
|
width: var(--sidebar-width);
|
|
134
134
|
min-width: var(--sidebar-width);
|
|
135
|
-
background: var(--
|
|
136
|
-
|
|
137
|
-
-webkit-backdrop-filter: blur(20px) saturate(1.4);
|
|
138
|
-
border-right: 1px solid var(--glass-border);
|
|
135
|
+
background: var(--bg-secondary);
|
|
136
|
+
border-right: 1px solid var(--border);
|
|
139
137
|
display: flex;
|
|
140
138
|
flex-direction: column;
|
|
141
139
|
padding: 14px 0 12px;
|
|
142
140
|
transform: translateX(-100%);
|
|
143
141
|
transition: transform var(--transition-normal);
|
|
144
142
|
z-index: 100;
|
|
145
|
-
box-shadow: 4px 0 40px rgba(0, 0, 0, 0.
|
|
143
|
+
box-shadow: 4px 0 40px rgba(0, 0, 0, 0.45);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
146
|
.sidebar-nav {
|
|
@@ -211,24 +209,56 @@ code, pre { font-family: var(--font-mono); }
|
|
|
211
209
|
flex-direction: column;
|
|
212
210
|
min-width: 0;
|
|
213
211
|
min-height: 0;
|
|
212
|
+
max-width: 100%;
|
|
213
|
+
overflow-x: hidden;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
.workspace-toolbar {
|
|
217
|
-
display:
|
|
217
|
+
display: grid;
|
|
218
|
+
grid-template-columns: 1fr auto 1fr;
|
|
218
219
|
align-items: center;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
gap: 8px 12px;
|
|
221
|
+
width: 100%;
|
|
222
|
+
max-width: 100%;
|
|
223
|
+
box-sizing: border-box;
|
|
224
|
+
padding: 10px clamp(12px, 3vw, 24px);
|
|
222
225
|
border-bottom: 1px solid var(--border);
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
.workspace-logo {
|
|
229
|
+
grid-column: 2;
|
|
230
|
+
justify-self: center;
|
|
226
231
|
border-radius: var(--radius-sm);
|
|
227
232
|
}
|
|
228
233
|
|
|
234
|
+
.workspace-toolbar-actions {
|
|
235
|
+
grid-column: 3;
|
|
236
|
+
justify-self: end;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 8px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@media (max-width: 680px) {
|
|
243
|
+
.workspace-toolbar {
|
|
244
|
+
grid-template-columns: minmax(0, 1fr);
|
|
245
|
+
justify-items: center;
|
|
246
|
+
}
|
|
247
|
+
.workspace-toolbar-spacer {
|
|
248
|
+
display: none;
|
|
249
|
+
}
|
|
250
|
+
.workspace-logo,
|
|
251
|
+
.workspace-toolbar-actions {
|
|
252
|
+
grid-column: 1;
|
|
253
|
+
justify-self: center;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
229
257
|
.workspace-body {
|
|
230
258
|
flex: 1;
|
|
259
|
+
min-width: 0;
|
|
231
260
|
min-height: 0;
|
|
261
|
+
max-width: 100%;
|
|
232
262
|
}
|
|
233
263
|
|
|
234
264
|
.workspace-body[hidden] {
|
|
@@ -399,14 +429,16 @@ code, pre { font-family: var(--font-mono); }
|
|
|
399
429
|
.drawer-shortcut-edit { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto; gap: 8px; }
|
|
400
430
|
.drawer-code, .drawer-context { font-family: var(--font-mono), monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
401
431
|
.drawer-code { width: 100%; resize: vertical; }
|
|
402
|
-
.drawer-html { display: block; width: 100%; height:
|
|
432
|
+
.drawer-html { display: block; width: 100%; height: 110px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
|
|
403
433
|
.help-content { display: grid; gap: 10px; min-width: 0; }
|
|
404
434
|
.help-topic-picker { display: grid; gap: 4px; font-size: 13px; color: var(--text-secondary); }
|
|
405
435
|
.help-topic-description { font-size: 12px; color: var(--text-secondary); }
|
|
406
436
|
.help-article { min-width: 0; overflow-wrap: anywhere; }
|
|
407
437
|
|
|
408
438
|
@media (max-width: 540px) {
|
|
409
|
-
.app-layout { --workspace-right-width: calc(100vw - 48px); --workspace-bottom-height: min(72dvh, 600px); }
|
|
439
|
+
.app-layout { --workspace-right-width: calc(100vw - 48px); --workspace-bottom-height: min(72dvh, 600px); overflow-x: clip; }
|
|
440
|
+
.workspace-toolbar { gap: 6px 8px; padding: 6px 10px; }
|
|
441
|
+
.workspace-toolbar .btn { padding-inline: 10px; font-size: 13px; }
|
|
410
442
|
.workspace-edge-handle--right { font-size: 11px; padding-inline: 5px; }
|
|
411
443
|
.workspace-edge-handle--right[aria-expanded="true"] { font-size: 0; }
|
|
412
444
|
.drawer-shortcut-edit { grid-template-columns: minmax(0, 1fr); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "total-recall-brain",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.47.0",
|
|
4
4
|
"description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-git",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "tr-plugin-git",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.3.0",
|
|
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.46.2",
|
|
16
16
|
"vitest": "^4.1.10"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
@@ -6533,9 +6533,9 @@
|
|
|
6533
6533
|
}
|
|
6534
6534
|
},
|
|
6535
6535
|
"node_modules/total-recall-brain": {
|
|
6536
|
-
"version": "3.
|
|
6537
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.
|
|
6538
|
-
"integrity": "sha512-
|
|
6536
|
+
"version": "3.46.2",
|
|
6537
|
+
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.46.2.tgz",
|
|
6538
|
+
"integrity": "sha512-xEVtIA/lLbMtDJ4fO6WT37N4tDGbDjL/8imvrbFygOA+C17v6Y5M5023A5Wbm8PvwiUE18j8vzBdX5DJl8H3cg==",
|
|
6539
6539
|
"dev": true,
|
|
6540
6540
|
"license": "MIT",
|
|
6541
6541
|
"workspaces": [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
3
3
|
"id": "git",
|
|
4
4
|
"name": "Git",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.3.0",
|
|
6
6
|
"description": "The whole git CLI through Total Recall: sign-in from the auth store, guarded commits and pushes, undo, fleet state, and the intent behind every change.",
|
|
7
7
|
"author": "Total Recall",
|
|
8
8
|
"license": "MIT",
|
|
@@ -147,5 +147,12 @@
|
|
|
147
147
|
"distribution": {
|
|
148
148
|
"tier": "official",
|
|
149
149
|
"license": "open"
|
|
150
|
+
},
|
|
151
|
+
"reuse": {
|
|
152
|
+
"commands": [
|
|
153
|
+
"total-recall auth secret",
|
|
154
|
+
"total-recall ssss status"
|
|
155
|
+
],
|
|
156
|
+
"owned": "Secret-firewalled commits and pushes, worktree management, unmerged branch detection, and fleet repository hygiene."
|
|
150
157
|
}
|
|
151
158
|
}
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { git, tr, ledger, intent, session } from '../lib/context.mjs';
|
|
3
|
-
import { defaultBranch } from './unmerged.mjs';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Waiting here held every commit for 30 s, then killed only the CLI: the
|
|
10
|
-
* generator finished on its own while the commit reported the refresh as failed.
|
|
11
|
-
*/
|
|
12
|
-
export function requestWikiRefresh(ctx, launch = spawn) {
|
|
13
|
-
try {
|
|
14
|
-
const child = launch(ctx.env.GIT_PLUGIN_TR_BIN || 'total-recall', ['openwiki', 'refresh', '--repo', ctx.cwd, '--apply', '--json'], {
|
|
15
|
-
cwd: ctx.cwd, env: ctx.env, detached: true, stdio: 'ignore', shell: false,
|
|
16
|
-
});
|
|
17
|
-
child.on?.('error', () => {});
|
|
18
|
-
child.unref?.();
|
|
19
|
-
return child.pid ? { status: 'requested', command: 'openwiki refresh', outcome: 'openwiki/.refresh-attempt.json' } : { status: 'failed', command: 'openwiki refresh', reason: 'could not start' };
|
|
20
|
-
} catch {
|
|
21
|
-
return { status: 'failed', command: 'openwiki refresh', reason: 'could not start' };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
3
|
+
// A commit never starts an OpenWiki refresh. Requesting one after every commit on the default
|
|
4
|
+
// branch paid a model to re-check wiki pages after each automatic version-pin commit, in about
|
|
5
|
+
// 40 repositories per Total Recall release. OpenWiki refreshes from its own nightly task on one
|
|
6
|
+
// selected node (total-recall openwiki schedule).
|
|
24
7
|
|
|
25
8
|
export const guards = [{
|
|
26
9
|
name: 'commit-memory',
|
|
@@ -37,18 +20,7 @@ export const guards = [{
|
|
|
37
20
|
// Save the commit before any optional provider call.
|
|
38
21
|
store.append(record, { idempotencyKey: `commit-${sha}` });
|
|
39
22
|
const memory = record.decisions.length ? tr(ctx, ['remember', 'decision', `${record.decisions.join('\n')}\nCommit: ${sha}`, '--project', '--slug', `git-decision-${sha}`]) : null;
|
|
40
|
-
|
|
41
|
-
// The wiki documents the default branch. Regenerating it from a feature branch spends model
|
|
42
|
-
// calls on pages that only conflict when the branch merges.
|
|
43
|
-
const branch = ctx.git(['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
44
|
-
const base = defaultBranch(ctx);
|
|
45
|
-
if (!base || branch.status !== 0 || String(branch.stdout).trim() !== base.name) wiki = { status: 'skipped', reason: 'not on the default branch' };
|
|
46
|
-
else {
|
|
47
|
-
const available = tr(ctx, ['openwiki', '--help']);
|
|
48
|
-
wiki = available.status !== 0 || available.error ? { status: 'skipped', reason: 'OpenWiki is unavailable' } : requestWikiRefresh(ctx);
|
|
49
|
-
}
|
|
50
|
-
store.append({ type: 'git.commit_memory', sha, timestamp: new Date().toISOString(), memory: memory === null ? 'skipped: no named decision' : memory.status === 0 && !memory.error ? 'recorded' : 'failed', openwiki: wiki });
|
|
23
|
+
store.append({ type: 'git.commit_memory', sha, timestamp: new Date().toISOString(), memory: memory === null ? 'skipped: no named decision' : memory.status === 0 && !memory.error ? 'recorded' : 'failed' });
|
|
51
24
|
if (memory && (memory.status !== 0 || memory.error)) console.error('Commit saved. Repository decision memory failed.');
|
|
52
|
-
if (wiki.status === 'failed') console.error('Commit saved. The OpenWiki refresh could not be started.');
|
|
53
25
|
},
|
|
54
26
|
}];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: ui-chunk:chunk
|
|
3
|
+
title: Fleet routing & agent coordination
|
|
4
|
+
description: Inspect fleet locations, bisect regressions, resolve conflicts, and coordinate multi-agent git handoffs.
|
|
5
|
+
timestamp: "2026-11-14T08:51:00Z"
|
|
6
|
+
plugin: git
|
|
7
|
+
id: git-fleet-ops
|
|
8
|
+
intents: [inspect fleet repos, locate branch or worktree, bisect or resolve conflicts, hand off git state]
|
|
9
|
+
surfaces: [page]
|
|
10
|
+
operations: [fleet, where, why, group, message, bisect-agent, resolve, handoff]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<section>
|
|
14
|
+
<h2>Fleet & coordination ops</h2>
|
|
15
|
+
<p>Track repository placement across the fleet, diagnose branch divergence, resolve conflicts, and hand off clean git state between agents.</p>
|
|
16
|
+
</section>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: ui-chunk:chunk
|
|
3
|
+
title: Secret-firewalled git publish & auth
|
|
4
|
+
description: Commit, push, configure git credential helper/auth/cli bindings, and safely undo recent changes.
|
|
5
|
+
timestamp: "2026-11-14T08:51:00Z"
|
|
6
|
+
plugin: git
|
|
7
|
+
id: git-publish
|
|
8
|
+
intents: [commit changes, push branch, configure git auth, undo git change]
|
|
9
|
+
surfaces: [page]
|
|
10
|
+
operations: [commit, push, credential-helper, auth, cli, undo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<section>
|
|
14
|
+
<h2>Publish & credential guard</h2>
|
|
15
|
+
<p>Run secret-firewalled commits and pushes, bind Total Recall git credentials, or undo recent working-tree changes.</p>
|
|
16
|
+
</section>
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
8
9
|
import { runBootstrapCommand } from './src/bootstrap.mjs';
|
|
9
|
-
import { describeRetention, hasWiki, isDue, readProjects, refreshProject, refreshProjectAsync, resolveKey, staleness } from './src/refresh.mjs';
|
|
10
|
+
import { CREDIT_REFUSAL, NO_MODEL, describeRetention, hasWiki, isDue, readProjects, refreshProject, refreshProjectAsync, resolveKey, staleness } from './src/refresh.mjs';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_REPO_CONCURRENCY = 2;
|
|
10
13
|
import { watchUpstream, propagateUpdates, readUpstreamState, bundledGeneratorVersion } from './src/upstream-watcher.mjs';
|
|
11
14
|
import { loadOpenWikiConfig, setOpenWikiConfigValue, resetOpenWikiConfig, resolveConfigPath, ALLOWED_CONFIG_KEYS, ALLOWED_PROVIDERS } from './src/config.mjs';
|
|
12
15
|
import { discoverAllHubs } from './src/mesh-vaults.mjs';
|
|
@@ -26,12 +29,14 @@ import { refreshDiagnostics } from './src/refresh-diagnostics.mjs';
|
|
|
26
29
|
* Defaults to a report; --apply performs the refreshes, because regenerating a
|
|
27
30
|
* repository's documentation is a write to that repository.
|
|
28
31
|
*/
|
|
32
|
+
// OpenRouter answers 402 when the balance cannot cover a request.
|
|
33
|
+
|
|
29
34
|
export async function refreshCommand(args, {
|
|
30
35
|
read = readProjects, dueFor = isDue, refresh = refreshProjectAsync,
|
|
31
|
-
getKey = resolveKey, configFor = loadOpenWikiConfig,
|
|
36
|
+
getKey = resolveKey, configFor = loadOpenWikiConfig, clock = Date.now,
|
|
32
37
|
} = {}) {
|
|
33
38
|
if (args.includes('--help') || args.includes('-h')) {
|
|
34
|
-
console.log('Usage: total-recall openwiki refresh [--repo <name>] [--apply] [--force] [--diagnostics [--slot <local-minute>]] [--json]');
|
|
39
|
+
console.log('Usage: total-recall openwiki refresh [--repo <name>] [--apply [--budget-ms <n>]] [--force] [--diagnostics [--slot <local-minute>]] [--json]');
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
const json = args.includes('--json');
|
|
@@ -80,6 +85,8 @@ export async function refreshCommand(args, {
|
|
|
80
85
|
if (!state.due) {
|
|
81
86
|
if (state.skipped) {
|
|
82
87
|
reports.push({ ...project, action: 'skipped', reason: state.reason });
|
|
88
|
+
} else if (state.held) {
|
|
89
|
+
reports.push({ ...project, action: 'held', uninitialized: Boolean(state.uninitialized), reason: state.reason, generationCommit: state.generationCommit, generationTime: state.generationTime, generatedAt: state.generatedAt, commitsSince: state.commitsSince });
|
|
83
90
|
} else {
|
|
84
91
|
reports.push({ ...project, action: 'current', reason: state.reason, generationCommit: state.generationCommit, generationTime: state.generationTime, generatedAt: state.generatedAt, commitsSince: state.commitsSince });
|
|
85
92
|
}
|
|
@@ -102,6 +109,12 @@ export async function refreshCommand(args, {
|
|
|
102
109
|
let payloadBudgetNote = null;
|
|
103
110
|
if (apply && due.length) {
|
|
104
111
|
const config = configFor();
|
|
112
|
+
if (!String(config.model || '').trim()) {
|
|
113
|
+
if (json) console.log(JSON.stringify({ ok: false, error: NO_MODEL, reports }));
|
|
114
|
+
else console.error(`OpenWiki refresh: ${NO_MODEL}`);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
105
118
|
const { key, from } = getKey({ provider: config.provider });
|
|
106
119
|
if (!key) {
|
|
107
120
|
const keyName = config.provider === 'google' ? 'GOOGLE_API_KEY' : 'OPENROUTER_API_KEY';
|
|
@@ -111,21 +124,37 @@ export async function refreshCommand(args, {
|
|
|
111
124
|
process.exitCode = 1;
|
|
112
125
|
return;
|
|
113
126
|
}
|
|
127
|
+
// Repositories refreshed at once. Each one is a separate paid generator run, so
|
|
128
|
+
// the default stays low; OpenWiki's own page concurrency is left at its default.
|
|
114
129
|
const concurrencyArg = args.find((a) => a.startsWith('--concurrency='));
|
|
115
130
|
const concurrencyIndex = args.indexOf('--concurrency');
|
|
116
131
|
const concurrency = Math.max(1, parseInt(
|
|
117
|
-
concurrencyArg ? concurrencyArg.slice('--concurrency='.length) : (concurrencyIndex >= 0 ? args[concurrencyIndex + 1] :
|
|
132
|
+
concurrencyArg ? concurrencyArg.slice('--concurrency='.length) : (concurrencyIndex >= 0 ? args[concurrencyIndex + 1] : String(DEFAULT_REPO_CONCURRENCY)),
|
|
118
133
|
10
|
|
119
|
-
) ||
|
|
134
|
+
) || DEFAULT_REPO_CONCURRENCY);
|
|
135
|
+
|
|
136
|
+
// A scheduled run passes the time it has left before the task runner kills
|
|
137
|
+
// it. A generation started without room to finish is killed part-way, marked
|
|
138
|
+
// interrupted and paid for again on the next attempt, so none is started.
|
|
139
|
+
const budgetArg = args.find((a) => a.startsWith('--budget-ms='));
|
|
140
|
+
const budgetIndex = args.indexOf('--budget-ms');
|
|
141
|
+
const budgetMs = Number(budgetArg ? budgetArg.slice('--budget-ms='.length) : (budgetIndex >= 0 ? args[budgetIndex + 1] : NaN));
|
|
142
|
+
const generationMs = Number.isInteger(config.timeout_ms) ? config.timeout_ms : 300000;
|
|
143
|
+
const dispatchStarted = clock();
|
|
144
|
+
let stopReason = null;
|
|
120
145
|
|
|
121
146
|
if (!json) {
|
|
122
|
-
console.log(`OpenWiki refresh (apply): dispatching ${due.length} projects with concurrency ${concurrency}`);
|
|
147
|
+
console.log(`OpenWiki refresh (apply): dispatching ${due.length} projects with concurrency ${concurrency}${Number.isFinite(budgetMs) ? ` within ${Math.round(budgetMs / 1000)}s` : ''}`);
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
let queueIndex = 0;
|
|
126
151
|
let completedCount = 0;
|
|
127
152
|
const worker = async () => {
|
|
128
153
|
while (queueIndex < due.length) {
|
|
154
|
+
if (!stopReason && Number.isFinite(budgetMs) && clock() - dispatchStarted + generationMs > budgetMs) {
|
|
155
|
+
stopReason = `not started: fewer than ${Math.round(generationMs / 1000)}s left in this run's ${Math.round(budgetMs / 1000)}s budget`;
|
|
156
|
+
}
|
|
157
|
+
if (stopReason) break;
|
|
129
158
|
const itemIndex = queueIndex++;
|
|
130
159
|
const entry = due[itemIndex];
|
|
131
160
|
const mode = entry.mode || (entry.uninitialized ? 'init' : 'update');
|
|
@@ -142,6 +171,9 @@ export async function refreshCommand(args, {
|
|
|
142
171
|
result = { action: 'failed', reason: error.message.split(key).join('[REDACTED]') };
|
|
143
172
|
}
|
|
144
173
|
completedCount++;
|
|
174
|
+
if (!stopReason && CREDIT_REFUSAL.test(String(result.reason || ''))) {
|
|
175
|
+
stopReason = `not started: the provider refused ${entry.name} for credit; add credit, then refresh again`;
|
|
176
|
+
}
|
|
145
177
|
const index = reports.findIndex((r) => r.root === entry.root);
|
|
146
178
|
reports[index] = { ...reports[index], ...result, credential: from };
|
|
147
179
|
if (!json) {
|
|
@@ -154,6 +186,11 @@ export async function refreshCommand(args, {
|
|
|
154
186
|
|
|
155
187
|
const workers = Array.from({ length: Math.min(concurrency, due.length) }, () => worker());
|
|
156
188
|
await Promise.all(workers);
|
|
189
|
+
for (const entry of due.slice(queueIndex)) {
|
|
190
|
+
const index = reports.findIndex((r) => r.root === entry.root);
|
|
191
|
+
reports[index] = { ...reports[index], action: 'deferred', reason: stopReason };
|
|
192
|
+
}
|
|
193
|
+
if (stopReason && queueIndex < due.length) payloadBudgetNote = `${due.length - queueIndex} project(s) ${stopReason}.`;
|
|
157
194
|
}
|
|
158
195
|
|
|
159
196
|
const failed = reports.filter((r) => r.action === 'failed');
|
|
@@ -171,6 +208,8 @@ export async function refreshCommand(args, {
|
|
|
171
208
|
refreshed: reports.filter((r) => r.action === 'refreshed' || r.action === 'initialized').length,
|
|
172
209
|
failed: failed.length,
|
|
173
210
|
partial: partial.length,
|
|
211
|
+
held: reports.filter((r) => r.action === 'held').length,
|
|
212
|
+
deferred: reports.filter((r) => r.action === 'deferred').length,
|
|
174
213
|
skipped: reports.filter((r) => r.action === 'skipped').length,
|
|
175
214
|
current: reports.filter((r) => r.action === 'current').length,
|
|
176
215
|
},
|
|
@@ -438,6 +477,8 @@ async function generateCommand(args) {
|
|
|
438
477
|
|
|
439
478
|
if (json) {
|
|
440
479
|
console.log(JSON.stringify(res, null, 2));
|
|
480
|
+
} else if (res.dryRun) {
|
|
481
|
+
console.log(`\n${res.message}\n`);
|
|
441
482
|
} else {
|
|
442
483
|
console.log(`\n✔ Generated documentation for ${res.repoName} (${Math.round((res.durationMs || 0)/1000)}s) via ${res.generatorVia}`);
|
|
443
484
|
console.log(` Output: ${res.outputDir}\n`);
|
|
@@ -572,8 +613,8 @@ async function configCommand(args) {
|
|
|
572
613
|
else {
|
|
573
614
|
console.log(`\nOpenWiki Configuration (${configPath}):`);
|
|
574
615
|
console.log(` Provider : ${config.provider}`);
|
|
575
|
-
console.log(` Model : ${config.model}`);
|
|
576
|
-
console.log(` Fallback Model : ${config.fallback_model}`);
|
|
616
|
+
console.log(` Model : ${config.model || 'not set (refreshes stop until one is chosen)'}`);
|
|
617
|
+
console.log(` Fallback Model : ${config.fallback_model || 'not set'}`);
|
|
577
618
|
console.log(` Max Tokens : ${config.max_tokens}`);
|
|
578
619
|
console.log(` Timeout (ms) : ${config.timeout_ms}\n`);
|
|
579
620
|
}
|
|
@@ -624,9 +665,76 @@ async function configCommand(args) {
|
|
|
624
665
|
}
|
|
625
666
|
}
|
|
626
667
|
|
|
668
|
+
/**
|
|
669
|
+
* The scheduled run. The Automations runner kills it at the task's declared
|
|
670
|
+
* timeout_ms, so it dispatches only generations that can finish a minute
|
|
671
|
+
* before that.
|
|
672
|
+
*/
|
|
673
|
+
export function autoRefreshBudgetMs(manifestPath = new URL('./plugin.json', import.meta.url)) {
|
|
674
|
+
let timeout = null;
|
|
675
|
+
try {
|
|
676
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
677
|
+
timeout = manifest.tasks?.find((task) => task.command === 'auto-refresh')?.timeout_ms ?? null;
|
|
678
|
+
} catch { /* no readable manifest: use the runner's own limit */ }
|
|
679
|
+
return Math.max(60000, (Number.isInteger(timeout) ? timeout : 600000) - 60000);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async function loadScheduleCore() {
|
|
683
|
+
const root = process.env.TR_PACKAGE_ROOT;
|
|
684
|
+
if (!root) throw new Error('Run through total-recall openwiki schedule');
|
|
685
|
+
const load = (module) => import(pathToFileURL(path.join(root, 'src', 'core', module)).href);
|
|
686
|
+
const [loader, store, mesh] = await Promise.all([load('plugin-loader.mjs'), load('plugin-store.mjs'), load('mesh.mjs')]);
|
|
687
|
+
return { getPlugin: loader.getPlugin, readPluginRecord: store.readPluginRecord, patchPluginRecord: store.patchPluginRecord, getMeshSelf: mesh.getMeshSelf };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Where the nightly refresh runs. Like other selected-node tasks, the placement is
|
|
692
|
+
* task_nodes['auto-refresh'] in this plugin's record; with none, the task stays idle.
|
|
693
|
+
* Pick the node that holds the repository checkouts.
|
|
694
|
+
*/
|
|
695
|
+
export async function scheduleCommand(args = [], { core = null, config = loadOpenWikiConfig } = {}) {
|
|
696
|
+
const json = args.includes('--json');
|
|
697
|
+
const usage = 'Usage: total-recall openwiki schedule [--node <mesh hostname>|--node self|--off] [--json]';
|
|
698
|
+
try {
|
|
699
|
+
if (args.includes('--help') || args.includes('-h')) { console.log(usage); return; }
|
|
700
|
+
const nodeIndex = args.indexOf('--node');
|
|
701
|
+
const nodeArg = nodeIndex >= 0 ? args[nodeIndex + 1] : null;
|
|
702
|
+
if (nodeIndex >= 0 && (!nodeArg || nodeArg.startsWith('--'))) throw new Error(usage);
|
|
703
|
+
const off = args.includes('--off');
|
|
704
|
+
if (off && nodeArg) throw new Error(usage);
|
|
705
|
+
const owner = core || await loadScheduleCore();
|
|
706
|
+
const plugin = owner.getPlugin('openwiki');
|
|
707
|
+
if (!plugin?.valid) throw new Error('Install a valid OpenWiki plugin first');
|
|
708
|
+
const record = owner.readPluginRecord(plugin) || {};
|
|
709
|
+
const task = plugin.manifest?.tasks?.find((entry) => entry.command === 'auto-refresh') || null;
|
|
710
|
+
let node = record.task_nodes?.['auto-refresh'] || null;
|
|
711
|
+
if (off || nodeArg) {
|
|
712
|
+
node = off ? null : nodeArg === 'self' ? owner.getMeshSelf()?.hostname || null : nodeArg;
|
|
713
|
+
if (!off && !node) throw new Error('This machine has no mesh hostname; name the node with --node <hostname>');
|
|
714
|
+
const taskNodes = { ...(record.task_nodes || {}) };
|
|
715
|
+
if (node) taskNodes['auto-refresh'] = node;
|
|
716
|
+
else delete taskNodes['auto-refresh'];
|
|
717
|
+
await owner.patchPluginRecord(plugin, { task_nodes: taskNodes });
|
|
718
|
+
}
|
|
719
|
+
const model = config().model || null;
|
|
720
|
+
const payload = { ok: true, task: 'auto-refresh', schedule: task?.schedule || null, placement: task?.placement || null, node, model, ready: Boolean(node && model) };
|
|
721
|
+
if (json) console.log(JSON.stringify(payload, null, 2));
|
|
722
|
+
else {
|
|
723
|
+
console.log(`OpenWiki nightly refresh: ${node ? `runs on ${node}` : 'idle (no node selected)'} at "${payload.schedule}"`);
|
|
724
|
+
console.log(` Model: ${model || `not set — ${NO_MODEL}`}`);
|
|
725
|
+
}
|
|
726
|
+
} catch (err) {
|
|
727
|
+
if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
728
|
+
else console.error(`OpenWiki schedule: ${err.message}`);
|
|
729
|
+
process.exitCode = 1;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
627
733
|
async function autoRefreshCommand(args = []) {
|
|
628
|
-
const
|
|
629
|
-
|
|
734
|
+
const forwardArgs = args.includes('--apply') ? [...args] : ['--apply', ...args];
|
|
735
|
+
if (!forwardArgs.some((a) => a === '--budget-ms' || a.startsWith('--budget-ms='))) {
|
|
736
|
+
forwardArgs.push(`--budget-ms=${autoRefreshBudgetMs()}`);
|
|
737
|
+
}
|
|
630
738
|
return refreshCommand(forwardArgs);
|
|
631
739
|
}
|
|
632
740
|
|
|
@@ -651,6 +759,7 @@ IMPLEMENTED.autorefresh = autoRefreshCommand;
|
|
|
651
759
|
IMPLEMENTED.watch = watchCommand;
|
|
652
760
|
IMPLEMENTED.propagate = propagateCommand;
|
|
653
761
|
IMPLEMENTED.config = configCommand;
|
|
762
|
+
IMPLEMENTED.schedule = scheduleCommand;
|
|
654
763
|
IMPLEMENTED.search = searchCommand;
|
|
655
764
|
IMPLEMENTED.graph = graphCommand;
|
|
656
765
|
IMPLEMENTED.token = tokenCommand;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-openwiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "tr-plugin-openwiki",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.3.0",
|
|
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.46.2"
|
|
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.46.2",
|
|
5533
|
+
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.46.2.tgz",
|
|
5534
|
+
"integrity": "sha512-xEVtIA/lLbMtDJ4fO6WT37N4tDGbDjL/8imvrbFygOA+C17v6Y5M5023A5Wbm8PvwiUE18j8vzBdX5DJl8H3cg==",
|
|
5535
5535
|
"dev": true,
|
|
5536
5536
|
"license": "MIT",
|
|
5537
5537
|
"workspaces": [
|
|
@@ -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.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"runtime": {
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
"command": "watch"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
"intent": "
|
|
21
|
-
"schedule": "
|
|
20
|
+
"intent": "Refresh OpenWiki documentation nightly for repositories with real code changes, on the selected node",
|
|
21
|
+
"schedule": "20 1-5 * * *",
|
|
22
22
|
"command": "auto-refresh",
|
|
23
|
-
"timeout_ms": 600000
|
|
23
|
+
"timeout_ms": 600000,
|
|
24
|
+
"placement": "selected-node"
|
|
24
25
|
}
|
|
25
26
|
],
|
|
26
27
|
"branding": {
|
|
@@ -291,6 +292,12 @@
|
|
|
291
292
|
"label": "Force regeneration",
|
|
292
293
|
"type": "boolean"
|
|
293
294
|
},
|
|
295
|
+
{
|
|
296
|
+
"name": "budgetMs",
|
|
297
|
+
"flag": "--budget-ms",
|
|
298
|
+
"label": "Start no generation that cannot finish within this many milliseconds",
|
|
299
|
+
"type": "number"
|
|
300
|
+
},
|
|
294
301
|
{
|
|
295
302
|
"name": "json",
|
|
296
303
|
"flag": "--json",
|
|
@@ -422,6 +429,32 @@
|
|
|
422
429
|
"type": "boolean"
|
|
423
430
|
}
|
|
424
431
|
]
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"name": "schedule",
|
|
435
|
+
"description": "Show or choose the node that runs the nightly wiki refresh (task auto-refresh)",
|
|
436
|
+
"effect": "write",
|
|
437
|
+
"output": "json",
|
|
438
|
+
"args": [
|
|
439
|
+
{
|
|
440
|
+
"name": "node",
|
|
441
|
+
"flag": "--node",
|
|
442
|
+
"label": "Mesh hostname, or self for this machine",
|
|
443
|
+
"type": "string"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"name": "off",
|
|
447
|
+
"flag": "--off",
|
|
448
|
+
"label": "Stop the nightly refresh on every node",
|
|
449
|
+
"type": "boolean"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"name": "json",
|
|
453
|
+
"flag": "--json",
|
|
454
|
+
"label": "JSON output",
|
|
455
|
+
"type": "boolean"
|
|
456
|
+
}
|
|
457
|
+
]
|
|
425
458
|
}
|
|
426
459
|
]
|
|
427
460
|
},
|
|
@@ -460,8 +493,23 @@
|
|
|
460
493
|
"formatPropagationReceipt"
|
|
461
494
|
]
|
|
462
495
|
},
|
|
496
|
+
"auth": {
|
|
497
|
+
"plugin": "auth",
|
|
498
|
+
"methods": [
|
|
499
|
+
"passkey",
|
|
500
|
+
"google-oauth2"
|
|
501
|
+
],
|
|
502
|
+
"session_verification": true
|
|
503
|
+
},
|
|
463
504
|
"status": {
|
|
464
505
|
"command": "status",
|
|
465
506
|
"refresh_seconds": 300
|
|
507
|
+
},
|
|
508
|
+
"reuse": {
|
|
509
|
+
"commands": [
|
|
510
|
+
"total-recall auth secret",
|
|
511
|
+
"total-recall ssss status"
|
|
512
|
+
],
|
|
513
|
+
"owned": "Documentation hub discovery, wiki page auditing, staleness tracking, and OpenWiki knowledge ingestion."
|
|
466
514
|
}
|
|
467
515
|
}
|