total-recall-brain 3.41.4 → 3.41.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
  4. package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/package.json +11 -0
  7. package/frontend/src/App.tsx +47 -122
  8. package/frontend/src/components/plugins/plugin-frame.ts +1 -0
  9. package/frontend/src/index.css +152 -86
  10. package/frontend/src/pages/HelpPage.tsx +13 -10
  11. package/frontend/src/pages/MemoryPage.tsx +28 -30
  12. package/frontend/src/pages/NetworkPage.tsx +3 -3
  13. package/frontend/src/pages/PluginsPage.tsx +6 -5
  14. package/frontend/src/pages/RulesPage.tsx +16 -12
  15. package/package.json +4 -3
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
  17. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
  19. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
  83. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
  84. package/src/cli/agent.mjs +12 -1
  85. package/src/cli/compact.mjs +108 -0
  86. package/src/cli/connect.mjs +56 -1
  87. package/src/cli/context.mjs +55 -23
  88. package/src/cli/doctor.mjs +5 -2
  89. package/src/cli/forget.mjs +2 -0
  90. package/src/cli/harness.mjs +62 -50
  91. package/src/cli/memory-entry.mjs +2 -2
  92. package/src/cli/preflight.mjs +152 -16
  93. package/src/cli/release.mjs +386 -0
  94. package/src/cli/remember.mjs +19 -1
  95. package/src/cli/rules.mjs +54 -12
  96. package/src/cli/startup.mjs +1 -1
  97. package/src/cli/status.mjs +12 -2
  98. package/src/core/claude-hooks.mjs +41 -3
  99. package/src/core/cli-commands.mjs +2 -0
  100. package/src/core/cli-semantic-index.mjs +3 -2
  101. package/src/core/conflict-detector.mjs +23 -12
  102. package/src/core/context-compactor.mjs +145 -0
  103. package/src/core/context-policy.mjs +20 -0
  104. package/src/core/duplicate-guard.mjs +350 -0
  105. package/src/core/meta-harness.mjs +31 -11
  106. package/src/core/preflight-gate.mjs +2 -2
  107. package/src/core/preflight-receipt.mjs +29 -1
  108. package/src/core/scaffold-plugins.mjs +7 -0
  109. package/src/core/stdio-flush.mjs +23 -0
  110. package/src/core/surface.mjs +33 -10
  111. package/src/server/invoke-durability.mjs +39 -0
  112. package/src/server/invoke-write-child.mjs +25 -0
  113. package/src/server/routes/invoke.mjs +7 -0
  114. package/src/server/routes/plugins.mjs +2 -2
  115. package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
  116. package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
  117. package/frontend/src/components/DaemonLogsTab.tsx +0 -54
  118. package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
  119. package/frontend/src/components/TaskQueueTab.tsx +0 -193
  120. package/frontend/src/components/VoiceInput.tsx +0 -112
  121. package/frontend/src/pages/ChatPage.tsx +0 -1059
  122. package/frontend/src/pages/CollabPage.tsx +0 -470
  123. package/frontend/src/pages/DesignDocsPage.tsx +0 -621
  124. package/frontend/src/pages/FilesPage.tsx +0 -393
  125. package/frontend/src/pages/HealthPage.tsx +0 -359
  126. package/frontend/src/pages/InboxPage.tsx +0 -83
  127. package/frontend/src/pages/InstructionsPage.tsx +0 -583
  128. package/frontend/src/pages/IntegrationsPage.tsx +0 -761
  129. package/frontend/src/pages/MeshPage.css +0 -58
  130. package/frontend/src/pages/MeshPage.tsx +0 -1219
  131. package/frontend/src/pages/OkfPage.tsx +0 -535
  132. package/frontend/src/pages/SandboxPage.tsx +0 -113
  133. package/frontend/src/pages/SkillsPage.tsx +0 -1994
  134. package/frontend/src/pages/TasksPage.tsx +0 -427
  135. package/frontend/src/pages/UsagePage.tsx +0 -702
  136. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
  137. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
  138. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
  139. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
  140. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
  141. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
  142. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
  143. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
  144. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
  145. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
  146. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
  147. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
@@ -0,0 +1,173 @@
1
+ /**
2
+ * OpenClaw Messaging Bridge hook adapter.
3
+ * Official doc: https://docs.openclaw.ai/plugins/hooks
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+
9
+ const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
10
+
11
+ export const openclawAdapter = {
12
+ id: 'openclaw',
13
+ level: 'enforced',
14
+ source: {
15
+ url: 'https://docs.openclaw.ai/plugins/hooks',
16
+ retrieved: '2026-11-04',
17
+ note: 'OpenClaw before_tool_call plugin hooks with fail-closed timeout'
18
+ },
19
+ limits: [
20
+ 'OpenClaw before_tool_call fails closed on timeout or exception',
21
+ 'Returns { block: true, blockReason: reason } on deny'
22
+ ],
23
+
24
+ parse(input = {}) {
25
+ const event = input.event || 'before_tool_call';
26
+ const toolName = input.toolName || input.tool_name || input.tool || '';
27
+ const params = input.params || input.args || {};
28
+ const cwd = input.cwd || process.cwd();
29
+
30
+ let tool = 'other';
31
+ let command;
32
+ let filePath;
33
+
34
+ if (toolName === 'execute' || toolName === 'Bash' || toolName === 'shell') {
35
+ tool = 'shell';
36
+ command = params.command || params.cmd || '';
37
+ } else if (toolName === 'write' || toolName === 'edit' || toolName === 'Edit' || toolName === 'Write') {
38
+ tool = 'write';
39
+ filePath = params.path || params.file_path || '';
40
+ } else if (toolName === 'read' || toolName === 'Read') {
41
+ tool = 'read';
42
+ filePath = params.path || params.file_path || '';
43
+ }
44
+
45
+ return {
46
+ event,
47
+ tool,
48
+ command,
49
+ path: filePath,
50
+ cwd,
51
+ raw: input
52
+ };
53
+ },
54
+
55
+ emit(decision, event, cwd = process.cwd()) {
56
+ if (decision?.decision === 'allow') {
57
+ const payload = { block: false };
58
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
59
+ }
60
+
61
+ const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
62
+ const payload = {
63
+ block: true,
64
+ blockReason: reason,
65
+ fix: decision?.fix || ACK_COMMAND
66
+ };
67
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
68
+ },
69
+
70
+ async run({ stdin, decideWith }) {
71
+ const parsed = this.parse(stdin);
72
+ const decision = decideWith({
73
+ tool: parsed.tool,
74
+ command: parsed.command,
75
+ path: parsed.path,
76
+ cwd: parsed.cwd
77
+ });
78
+ return this.emit(decision, parsed.event, parsed.cwd);
79
+ },
80
+
81
+ install({ home = os.homedir(), dryRun = false, remove = false, off = false } = {}) {
82
+ const isRemove = remove || off;
83
+ const openclawDir = path.join(home, '.openclaw');
84
+ const hooksPath = path.join(openclawDir, 'hooks.json');
85
+ const added = [];
86
+ const removed = [];
87
+
88
+ let hooksConfig = { hooks: {} };
89
+ if (fs.existsSync(hooksPath)) {
90
+ try {
91
+ hooksConfig = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
92
+ } catch (err) {
93
+ throw new Error(`${hooksPath} is not valid JSON; fix it before installing hooks: ${err.message}`);
94
+ }
95
+ }
96
+ hooksConfig.hooks = hooksConfig.hooks || {};
97
+ hooksConfig.hooks.before_tool_call = hooksConfig.hooks.before_tool_call || [];
98
+
99
+ if (isRemove) {
100
+ const beforeLen = hooksConfig.hooks.before_tool_call.length;
101
+ hooksConfig.hooks.before_tool_call = hooksConfig.hooks.before_tool_call.filter(e =>
102
+ !String(e.command || '').includes('preflight')
103
+ );
104
+ if (hooksConfig.hooks.before_tool_call.length < beforeLen) {
105
+ removed.push('managed before_tool_call gate');
106
+ }
107
+ if (!dryRun && fs.existsSync(hooksPath)) {
108
+ fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
109
+ }
110
+ return { settings: hooksPath, added: [], removed };
111
+ }
112
+
113
+ const gateCommand = 'total-recall preflight gate --harness openclaw';
114
+ const hasPreTool = hooksConfig.hooks.before_tool_call.some(e =>
115
+ String(e.command || '').includes('preflight gate --harness openclaw')
116
+ );
117
+
118
+ if (!hasPreTool) {
119
+ hooksConfig.hooks.before_tool_call.push({
120
+ matcher: '*',
121
+ command: gateCommand,
122
+ timeout: 10
123
+ });
124
+ added.push('managed before_tool_call gate');
125
+ }
126
+
127
+ if (!dryRun && added.length) {
128
+ fs.mkdirSync(openclawDir, { recursive: true });
129
+ if (fs.existsSync(hooksPath)) {
130
+ fs.copyFileSync(hooksPath, `${hooksPath}.bak-total-recall`);
131
+ }
132
+ fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
133
+ }
134
+
135
+ return { settings: hooksPath, added };
136
+ },
137
+
138
+ verify({ home = os.homedir() } = {}) {
139
+ const hooksPath = path.join(home, '.openclaw', 'hooks.json');
140
+ let installed = false;
141
+ if (fs.existsSync(hooksPath)) {
142
+ try {
143
+ const config = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
144
+ const preHooks = config.hooks?.before_tool_call || [];
145
+ installed = preHooks.some(e => String(e.command || '').includes('preflight gate --harness openclaw'));
146
+ } catch { /* unparseable */ }
147
+ }
148
+
149
+ const syntheticDeniedCall = {
150
+ event: 'before_tool_call',
151
+ toolName: 'execute',
152
+ params: { command: 'rm -rf /tmp/foo' },
153
+ cwd: '/nonexistent-directory-without-receipt'
154
+ };
155
+
156
+ const out = this.emit(
157
+ { decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
158
+ 'before_tool_call',
159
+ syntheticDeniedCall.cwd
160
+ );
161
+
162
+ const exercised = out.exitCode === 2 && out.stdout.includes('"block":true');
163
+
164
+ return {
165
+ installed,
166
+ exercised,
167
+ level: 'enforced',
168
+ native: out.stdout.trim()
169
+ };
170
+ }
171
+ };
172
+
173
+ export default openclawAdapter;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Pi Coding Agent extension hook adapter.
3
+ * Official doc: https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/docs/extensions.md
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+
9
+ const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
10
+
11
+ export const piAdapter = {
12
+ id: 'pi',
13
+ level: 'enforced (unverified live)',
14
+ source: {
15
+ url: 'https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/docs/extensions.md',
16
+ retrieved: '2026-11-04',
17
+ note: 'Pi Coding Agent tool_call extensions in ~/.pi/agent/extensions/'
18
+ },
19
+ limits: [
20
+ 'Pi loaded extensions must fail closed explicitly',
21
+ 'Returns { block: true, reason } on denied calls'
22
+ ],
23
+
24
+ parse(input = {}) {
25
+ const event = input.event || 'tool_call';
26
+ const toolName = input.toolName || input.tool || '';
27
+ const toolInput = input.input || input.params || {};
28
+ const cwd = input.cwd || process.cwd();
29
+
30
+ let tool = 'other';
31
+ let command;
32
+ let filePath;
33
+
34
+ if (toolName === 'execute' || toolName === 'Bash' || toolName === 'shell') {
35
+ tool = 'shell';
36
+ command = toolInput.command || toolInput.cmd || '';
37
+ } else if (toolName === 'edit' || toolName === 'write' || toolName === 'patch') {
38
+ tool = 'write';
39
+ filePath = toolInput.path || toolInput.file || '';
40
+ } else if (toolName === 'read') {
41
+ tool = 'read';
42
+ filePath = toolInput.path || toolInput.file || '';
43
+ }
44
+
45
+ return {
46
+ event,
47
+ tool,
48
+ command,
49
+ path: filePath,
50
+ cwd,
51
+ raw: input
52
+ };
53
+ },
54
+
55
+ emit(decision, event, cwd = process.cwd()) {
56
+ if (decision?.decision === 'allow') {
57
+ const payload = { block: false };
58
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
59
+ }
60
+
61
+ const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
62
+ const payload = {
63
+ block: true,
64
+ reason,
65
+ fix: decision?.fix || ACK_COMMAND
66
+ };
67
+ return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
68
+ },
69
+
70
+ async run({ stdin, decideWith }) {
71
+ const parsed = this.parse(stdin);
72
+ const decision = decideWith({
73
+ tool: parsed.tool,
74
+ command: parsed.command,
75
+ path: parsed.path,
76
+ cwd: parsed.cwd
77
+ });
78
+ return this.emit(decision, parsed.event, parsed.cwd);
79
+ },
80
+
81
+ install({ home = os.homedir(), dryRun = false } = {}) {
82
+ const piDir = path.join(home, '.pi', 'agent', 'extensions');
83
+ const hookPath = path.join(piDir, 'total-recall-preflight.json');
84
+ const added = [];
85
+
86
+ const hookDef = {
87
+ name: 'total-recall-preflight',
88
+ command: 'total-recall preflight gate --harness pi',
89
+ timeout: 5
90
+ };
91
+
92
+ if (!dryRun) {
93
+ fs.mkdirSync(piDir, { recursive: true });
94
+ fs.writeFileSync(hookPath, `${JSON.stringify(hookDef, null, 2)}\n`);
95
+ added.push('managed Pi extension gate');
96
+ }
97
+
98
+ return { settings: hookPath, added };
99
+ },
100
+
101
+ verify({ home = os.homedir() } = {}) {
102
+ const hookPath = path.join(home, '.pi', 'agent', 'extensions', 'total-recall-preflight.json');
103
+ const installed = fs.existsSync(hookPath);
104
+
105
+ const syntheticDeniedCall = {
106
+ event: 'tool_call',
107
+ toolName: 'execute',
108
+ input: { command: 'rm -rf /tmp/foo' },
109
+ cwd: '/nonexistent-directory-without-receipt'
110
+ };
111
+
112
+ const out = this.emit(
113
+ { decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
114
+ 'tool_call',
115
+ syntheticDeniedCall.cwd
116
+ );
117
+
118
+ const exercised = out.exitCode === 2 && out.stdout.includes('"block":true');
119
+
120
+ return {
121
+ installed,
122
+ exercised,
123
+ level: 'enforced (unverified live)',
124
+ native: out.stdout.trim()
125
+ };
126
+ }
127
+ };
128
+
129
+ export default piAdapter;
@@ -0,0 +1,12 @@
1
+ /** Run the repository's own quality gates through the code-quality plugin. No configured gate is not a pass. */
2
+ import { spawnSync } from 'node:child_process';
3
+
4
+ export function runQualityGate(directory, { tier = 'fast', runner = spawnSync, timeoutMs = 600000 } = {}) {
5
+ const result = runner(process.env.TOTAL_RECALL_BIN || 'total-recall', ['code-quality-check', '--tier', tier, '--json'], { cwd: directory, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
6
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim();
7
+ if (result.error || result.status === null) return { status: 'unavailable', passed: false, tier, summary: `The gate did not run: ${result.error?.message || 'terminated'}` };
8
+ let report = null;
9
+ try { report = JSON.parse(result.stdout); } catch { /* a text report */ }
10
+ if (/No code-quality state/.test(report?.error || output)) return { status: 'unconfigured', passed: false, tier, summary: String(report?.error || output).slice(0, 500) };
11
+ return { status: result.status === 0 ? 'passed' : 'failed', passed: result.status === 0, tier, summary: output.slice(-2000), report };
12
+ }
@@ -0,0 +1,22 @@
1
+ /** Agents wrap their answer in prose and code. Find the last well-formed object that carries the expected key. */
2
+ export function lastJsonWith(text, key) {
3
+ const source = String(text || '');
4
+ for (let start = source.lastIndexOf('{'); start >= 0; start = source.lastIndexOf('{', start - 1)) {
5
+ let depth = 0, inString = false;
6
+ for (let i = start; i < source.length; i += 1) {
7
+ const c = source[i];
8
+ if (inString) { if (c === '\\') i += 1; else if (c === '"') inString = false; continue; }
9
+ if (c === '"') inString = true;
10
+ else if (c === '{') depth += 1;
11
+ else if (c === '}') {
12
+ depth -= 1;
13
+ if (depth === 0) {
14
+ try { const value = JSON.parse(source.slice(start, i + 1)); if (value && Object.hasOwn(value, key)) return value; } catch { /* keep looking */ }
15
+ break;
16
+ }
17
+ }
18
+ }
19
+ if (start === 0) break;
20
+ }
21
+ return null;
22
+ }
@@ -1,90 +1,92 @@
1
1
  /**
2
- * Meta Harness Self-Learning Subsystem
3
- * Ingests run execution receipts, updates the empirical scorecard,
4
- * and proposes friction-reducing rule/memory proposals.
2
+ * Execution receipts and the learning loop.
3
+ * Receipts are an append-only SSSS ledger. Scorecards, quota walls and proposals fold from it.
5
4
  */
6
-
7
- import fs from 'node:fs';
8
- import path from 'node:path';
9
- import os from 'node:os';
5
+ import { spawnSync } from 'node:child_process';
10
6
  import { computeEmpiricalScorecard } from '../capability/index.mjs';
7
+ import { receiptsFile } from './paths.mjs';
11
8
 
12
- function getLedgerPath() {
13
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
14
- const baseDir = path.join(home, '.agent', 'skills', 'total-recall');
15
- fs.mkdirSync(baseDir, { recursive: true });
16
- return path.join(baseDir, 'meta-harness-runs.json');
9
+ async function store() {
10
+ const { PluginStateStore } = await import('./ssss-store.mjs');
11
+ return new PluginStateStore(receiptsFile(), 'meta-harness');
17
12
  }
13
+ export const readReceipts = async () => (await store()).read([]);
18
14
 
19
- /**
20
- * Record a run execution receipt
21
- */
22
- export function recordRunReceipt(receipt = {}) {
23
- const ledgerPath = getLedgerPath();
24
- let entries = [];
25
- if (fs.existsSync(ledgerPath)) {
26
- try {
27
- entries = JSON.parse(fs.readFileSync(ledgerPath, 'utf8'));
28
- } catch {
29
- entries = [];
30
- }
31
- }
32
-
15
+ export async function recordRunReceipt(receipt = {}) {
16
+ if (!receipt.harness) throw new Error('A run receipt must name its harness');
33
17
  const entry = {
34
- id: receipt.id || `run-${Date.now()}`,
18
+ id: receipt.id || `run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
35
19
  timestamp: new Date().toISOString(),
36
- harness: receipt.harness || 'agy',
37
- task: receipt.task || '',
38
- complexityScore: receipt.complexityScore || 50,
39
- success: receipt.success !== false,
20
+ harness: receipt.harness,
21
+ role: receipt.role || null,
22
+ node: receipt.node || 'local',
23
+ task: String(receipt.task || '').slice(0, 300),
24
+ complexityScore: receipt.complexityScore ?? null,
25
+ success: receipt.success === true,
40
26
  durationMs: receipt.durationMs || 0,
27
+ failureKind: receipt.failureKind || null,
28
+ error: receipt.error ? String(receipt.error).slice(0, 500) : null,
29
+ resetsAt: receipt.resetsAt || null,
30
+ gate: receipt.gate || null,
41
31
  escalatedFrom: receipt.escalatedFrom || null,
42
- failures: receipt.failures || {}
43
32
  };
44
-
45
- entries.push(entry);
46
- // Keep last 1,000 runs
47
- if (entries.length > 1000) {
48
- entries = entries.slice(-1000);
49
- }
50
-
51
- fs.writeFileSync(ledgerPath, JSON.stringify(entries, null, 2), 'utf8');
52
- return { ok: true, recorded: entry.id, totalRuns: entries.length };
33
+ (await store()).append(entry);
34
+ return entry;
53
35
  }
54
36
 
55
- /**
56
- * Read learning summary and generate memory/friction proposals
57
- */
58
- export function learnFromHistory() {
59
- const ledgerPath = getLedgerPath();
60
- let entries = [];
61
- if (fs.existsSync(ledgerPath)) {
62
- try {
63
- entries = JSON.parse(fs.readFileSync(ledgerPath, 'utf8'));
64
- } catch {
65
- entries = [];
66
- }
37
+ /** A harness is walled from its last quota failure until the reported reset, or until a later success proves it clear. */
38
+ export function activeQuotaWalls(entries = [], now = Date.now(), unknownResetMs = 60 * 60 * 1000) {
39
+ const latest = new Map();
40
+ for (const run of entries) {
41
+ if (run.success || run.failureKind === 'quota') latest.set(run.harness, run);
67
42
  }
43
+ const walls = {};
44
+ for (const [harness, run] of latest) {
45
+ if (run.success) continue;
46
+ const observed = Date.parse(run.timestamp);
47
+ const until = run.resetsAt ? Date.parse(run.resetsAt) : observed + unknownResetMs;
48
+ if (until > now) walls[harness] = { until: new Date(until).toISOString(), resetReported: Boolean(run.resetsAt), observedAt: run.timestamp, message: run.error };
49
+ }
50
+ return walls;
51
+ }
68
52
 
69
- const scorecard = computeEmpiricalScorecard(entries);
53
+ export function proposalsFrom(scorecard, entries = []) {
70
54
  const proposals = [];
71
-
72
- // Identify harnesses with elevated failure rates (>25%)
73
55
  for (const [harness, stats] of Object.entries(scorecard)) {
74
- if (stats.total >= 3 && stats.passRate < 75) {
75
- proposals.push({
76
- id: `proposal-deprecate-or-escalate-${harness}`,
77
- targetHarness: harness,
78
- reason: `Pass rate for ${harness} is ${stats.passRate}% over ${stats.total} runs.`,
79
- recommendation: `Auto-escalate ${harness} to frontier model or apply strict preflight gate.`
80
- });
81
- }
56
+ if (stats.total >= 3 && stats.passRate < 75) proposals.push({
57
+ id: `meta-harness-low-pass-${harness}`, targetHarness: harness,
58
+ reason: `${harness} passed ${stats.passRate}% of ${stats.total} attributable runs.`,
59
+ recommendation: `Route complex work away from ${harness}, or require a gate before accepting its output.`,
60
+ });
82
61
  }
62
+ const gateFailures = new Map();
63
+ for (const run of entries) if (run.failureKind === 'gate') gateFailures.set(run.harness, (gateFailures.get(run.harness) || 0) + 1);
64
+ for (const [harness, count] of gateFailures) if (count >= 3) proposals.push({
65
+ id: `meta-harness-gate-failures-${harness}`, targetHarness: harness,
66
+ reason: `${harness} output failed quality gates ${count} times.`,
67
+ recommendation: `Review recent ${harness} branches for a recurring defect and record the rule that prevents it.`,
68
+ });
69
+ return proposals;
70
+ }
83
71
 
84
- return {
85
- ok: true,
86
- totalHistoryRuns: entries.length,
87
- scorecard,
88
- proposals
89
- };
72
+ /** A harness whose last run failed for a missing login or key stays out of the pool for an hour, then gets another chance. */
73
+ export function activeCredentialBlocks(entries = [], now = Date.now(), holdMs = 60 * 60 * 1000) {
74
+ const latest = new Map();
75
+ for (const run of entries) if (run.success || run.failureKind === 'auth') latest.set(run.harness, run);
76
+ const blocks = {};
77
+ for (const [harness, run] of latest) if (!run.success && Date.parse(run.timestamp) + holdMs > now) blocks[harness] = { observedAt: run.timestamp, message: run.error };
78
+ return blocks;
79
+ }
80
+
81
+ /** With `emit`, proposals are written through the memory owner. A failed write is reported, never hidden. */
82
+ export async function learnFromHistory({ emit = false, runner = spawnSync } = {}) {
83
+ const entries = await readReceipts();
84
+ const scorecard = computeEmpiricalScorecard(entries);
85
+ const proposals = proposalsFrom(scorecard, entries);
86
+ const emitted = [];
87
+ if (emit) for (const proposal of proposals) {
88
+ const result = runner(process.env.TOTAL_RECALL_BIN || 'total-recall', ['remember', 'facts', `${proposal.reason} ${proposal.recommendation}`, '--project', '--tags', `meta-harness,${proposal.id}`], { encoding: 'utf8', timeout: 30000 });
89
+ emitted.push({ id: proposal.id, ok: result.status === 0, error: result.status === 0 ? undefined : String(result.stderr || result.error || 'remember failed').slice(0, 300) });
90
+ }
91
+ return { ok: emitted.every(row => row.ok), totalHistoryRuns: entries.length, scorecard, walls: activeQuotaWalls(entries), credentialBlocks: activeCredentialBlocks(entries), proposals, ...(emit ? { emitted } : {}) };
90
92
  }