total-recall-brain 3.43.2 → 3.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/bin/total-recall-server-release.mjs +4 -0
  2. package/docs/reference/cli-reference.md +14 -0
  3. package/package.json +5 -4
  4. package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
  5. package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
  6. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
  7. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
  8. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
  9. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
  10. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
  11. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
  12. package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
  13. package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
  14. package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
  15. package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
  16. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
  17. package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
  18. package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +14 -13
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +18 -1
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
  27. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +2 -2
  28. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +39 -6
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +2 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +14 -13
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  33. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +14 -13
  34. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
  35. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +14 -13
  37. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
  38. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +14 -13
  40. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +3 -3
  41. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +14 -13
  43. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
  44. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +38 -5
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +38 -7
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +14 -13
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -3
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +85 -3
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +2 -1
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +12 -1
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +15 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +38 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +42 -11
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +40 -13
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +1 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +5 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +1 -1
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +27 -11
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +322 -40
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +14 -13
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +14 -13
  71. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
  72. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
  74. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
  75. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
  76. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
  77. package/src/cli/connect.mjs +2 -29
  78. package/src/cli/context.mjs +1 -0
  79. package/src/cli/ingest-okf.mjs +4 -4
  80. package/src/cli/release.mjs +27 -2
  81. package/src/cli/startup.mjs +1 -0
  82. package/src/cli/update.mjs +14 -3
  83. package/src/core/automatic-brain-updates.mjs +13 -3
  84. package/src/core/autopull-install.mjs +14 -0
  85. package/src/core/brain-updates.mjs +12 -2
  86. package/src/core/context-compactor.mjs +7 -2
  87. package/src/core/context-compiler.mjs +6 -1
  88. package/src/core/context-policy.mjs +7 -3
  89. package/src/core/daemon-control.mjs +22 -0
  90. package/src/core/global-brain-updates.mjs +3 -3
  91. package/src/core/mesh-brain-updates.mjs +20 -5
  92. package/src/core/running-server.mjs +76 -0
  93. package/src/core/server-release.mjs +487 -0
  94. package/src/core/server-restart.mjs +128 -7
  95. package/src/core/source-brain-updates.mjs +35 -6
  96. package/src/core/ssss-kernel-bridge.mjs +1 -2
  97. package/src/core/startup-health.mjs +13 -2
  98. package/src/core/surface.mjs +3 -3
  99. package/src/core/task-context.mjs +9 -8
  100. package/src/core/validated-write.mjs +4 -0
  101. package/src/core/vault-files.mjs +8 -2
  102. package/src/core/vault.mjs +1 -1
  103. package/src/server/index.mjs +17 -1
@@ -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.9",
5
+ "version": "0.1.10",
6
6
  "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall.",
7
7
  "license": "MIT",
8
8
  "runtime": {
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "tr-plugin-security",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-security",
9
- "version": "0.3.5",
9
+ "version": "0.3.6",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@gregiteen/ssss-cli": "0.14.4"
12
+ "@gregiteen/ssss-cli": "0.14.5"
13
13
  },
14
14
  "devDependencies": {
15
15
  "jsdom": "^26.1.0",
16
- "total-recall-brain": "3.42.1"
16
+ "total-recall-brain": "3.43.3"
17
17
  }
18
18
  },
19
19
  "node_modules/@alcalzone/ansi-tokenize": {
@@ -845,9 +845,9 @@
845
845
  }
846
846
  },
847
847
  "node_modules/@gregiteen/ssss-cli": {
848
- "version": "0.14.4",
849
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.4.tgz",
850
- "integrity": "sha512-R6JIgPRvS9xXp0xb6BYO3rauv+72r35GIiDrJcvbjPdTSHFZXr8/jRxFOgoF8yTed/5xyGFF3ey3DI9bOjmHVA==",
848
+ "version": "0.14.5",
849
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.5.tgz",
850
+ "integrity": "sha512-qVEYOocnwn97uJ0eDzmuwN06CYsYWr2t3+iKylpisBHX4wlLWk7P8EmFFdBlx7NyKVIZDDeYT164Gd7/T8UGgw==",
851
851
  "license": "ISC",
852
852
  "bin": {
853
853
  "ssss": "scripts/ssss.mjs"
@@ -1686,9 +1686,9 @@
1686
1686
  },
1687
1687
  "node_modules/@ssss/cli": {
1688
1688
  "name": "@gregiteen/ssss-cli",
1689
- "version": "0.14.4",
1690
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.4.tgz",
1691
- "integrity": "sha512-R6JIgPRvS9xXp0xb6BYO3rauv+72r35GIiDrJcvbjPdTSHFZXr8/jRxFOgoF8yTed/5xyGFF3ey3DI9bOjmHVA==",
1689
+ "version": "0.14.5",
1690
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.5.tgz",
1691
+ "integrity": "sha512-qVEYOocnwn97uJ0eDzmuwN06CYsYWr2t3+iKylpisBHX4wlLWk7P8EmFFdBlx7NyKVIZDDeYT164Gd7/T8UGgw==",
1692
1692
  "dev": true,
1693
1693
  "license": "ISC",
1694
1694
  "bin": {
@@ -5698,9 +5698,9 @@
5698
5698
  }
5699
5699
  },
5700
5700
  "node_modules/total-recall-brain": {
5701
- "version": "3.42.1",
5702
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.42.1.tgz",
5703
- "integrity": "sha512-woH7mdkGwPFYlKPZurhE8L3BPe+cSeIruNGaPOjGZJm/O8C92Bbr1gsdOY0tTLg5QHlgPHcXG1GMtsm9lPDz8w==",
5701
+ "version": "3.43.3",
5702
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.43.3.tgz",
5703
+ "integrity": "sha512-yjfPN/BQxEEa+iodyFXQov/6fRt1pZg/7RcmgsbpALwBt6Ki3JJjT3tzArBC7NxHQmXgGYyKnCYId6xe/W2rPg==",
5704
5704
  "dev": true,
5705
5705
  "license": "MIT",
5706
5706
  "workspaces": [
@@ -5731,6 +5731,7 @@
5731
5731
  "antigravity": "bin/antigravity.mjs",
5732
5732
  "total-recall": "bin/total-recall.mjs",
5733
5733
  "total-recall-memory": "bin/total-recall-memory.mjs",
5734
+ "total-recall-server-release": "bin/total-recall-server-release.mjs",
5734
5735
  "vibebrain": "bin/total-recall.mjs"
5735
5736
  },
5736
5737
  "optionalDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tr-plugin-security",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "description": "Standalone security suite for Total Recall: static analysis, file and permission hardening schemes, AES-256-GCM encryption tools, and egress audit controls",
6
6
  "scripts": {
@@ -24,9 +24,9 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@gregiteen/ssss-cli": "0.14.4"
27
+ "@gregiteen/ssss-cli": "0.14.5"
28
28
  },
29
29
  "overrides": {
30
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.4"
30
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.5"
31
31
  }
32
32
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "security",
4
4
  "name": "Security Assessments",
5
- "version": "0.3.5",
5
+ "version": "0.3.6",
6
6
  "description": "Machine and device security assessments for macOS, Linux, Windows, Android, Chromebook, iPhone and iPad, with explicit measured and manual coverage.",
7
7
  "license": "MIT",
8
8
  "requires": [{ "id": "auth", "range": ">=1.2.1" }],
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.14.4",
2
+ "version": "0.14.5",
3
3
  "files": {
4
4
  ".gitignore": {
5
5
  "sha256": "0d6c69a5cbd2270c9163d8e1563976b6d15131d0ef599e6fbc413ee287a86da0",
@@ -18,8 +18,8 @@
18
18
  "content": "# ssss-project — System Architecture & Design\n\n> Technical design and architecture specification for ssss-project.\n\n## Overview\n\n<High-level summary of the system and its primary goals.>\n\n## Component Architecture\n\n- **Vault Structure**: Typed Markdown documents under `vault/` serving as the source of truth.\n- **Workflows & Execution**: Automated procedures defined in `vault/workflows/` triggered by operational events.\n- **Capabilities & Resources**: Tools and external service integrations required for execution.\n\n## Data & Schema Contracts\n\n<Core entities, external dependencies, and integration interfaces.>\n\n## Open Decisions & Invariants\n\n- SSSS VFS is the single source of truth for application state.\n- Structural business logic is strictly decoupled from private tenant operational data.\n- App-specific primitives reside in `registry/extensions/` within this repository and extend `core`.\n- Core SSSS library (`@gregiteen/ssss-cli`) remains vendor-neutral and dependency-free.\n"
19
19
  },
20
20
  "package.json": {
21
- "sha256": "18389bfc91857c6d5917a3ac1af9da8a992c99b013f42f2843921cdc7b41bdfe",
22
- "content": "{\n \"name\": \"ssss-project\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"description\": \"An SSSS-native project — Markdown vault as source of truth.\",\n \"scripts\": {\n \"ssss\": \"ssss\",\n \"test\": \"node --test\",\n \"export\": \"ssss export vault --profile sale --out dist/bundle.ucw.json\",\n \"validate\": \"ssss conformance --engine\"\n },\n \"dependencies\": {\n \"@gregiteen/ssss-cli\": \"github:gregiteen/ssss#v0.14.4\"\n }\n}\n"
21
+ "sha256": "ea77c2944b853999741a8e603de257604048c63b5db8754a0e8b92f383ff973b",
22
+ "content": "{\n \"name\": \"ssss-project\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"description\": \"An SSSS-native project — Markdown vault as source of truth.\",\n \"scripts\": {\n \"ssss\": \"ssss\",\n \"test\": \"node --test\",\n \"export\": \"ssss export vault --profile sale --out dist/bundle.ucw.json\",\n \"validate\": \"ssss conformance --engine\"\n },\n \"dependencies\": {\n \"@gregiteen/ssss-cli\": \"github:gregiteen/ssss#v0.14.5\"\n }\n}\n"
23
23
  },
24
24
  "test/ssss-conformance.test.mjs": {
25
25
  "sha256": "f626d2f56799fcfda74375d3ddf546cc181a10caf2df08b72b7f2e36782c9bc1",
@@ -43,8 +43,8 @@
43
43
  }
44
44
  },
45
45
  "skill": {
46
- "SKILL.md": "4976c9615482f257fd023761dd3ef36b5aa278305ff22ae110295fe4945f5d4c",
47
- "core/SKILL.md": "96c6479ad06b84c5f7ffd8bb05bff414dbb8c22a040ec1f1c05d8fa4fdbd976e",
46
+ "SKILL.md": "e5c2c0aa2c0fb5480c1f7bdb37928f935e1160bff18fd6b63366a04f3e281938",
47
+ "core/SKILL.md": "12543720135f13650efcd7afd751e3253996692c6ab805fd7da99788b6fc4af9",
48
48
  "core/references/core-registry.json": "16831aa3b6be3f561a9ca7abdc36065a4843ff595aa3acf5c3fb8af2d733b7e3",
49
49
  "core/references/ssss-spec.md": "cc319e78b1b568651d5717695348363349d502c0e20663da9858369022ddfc3b",
50
50
  "core/scripts/maintain.mjs": "eebcae898f2ce0cf3f0059c6f6737bee8877409de2d370d54a4015a9a82414e9",
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "tr-plugin-ssss",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-ssss",
9
- "version": "0.1.2",
9
+ "version": "0.1.3",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@gregiteen/ssss-cli": "0.14.4"
12
+ "@gregiteen/ssss-cli": "0.14.5"
13
13
  }
14
14
  },
15
15
  "node_modules/@gregiteen/ssss-cli": {
16
- "version": "0.14.4",
17
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.4.tgz",
18
- "integrity": "sha512-R6JIgPRvS9xXp0xb6BYO3rauv+72r35GIiDrJcvbjPdTSHFZXr8/jRxFOgoF8yTed/5xyGFF3ey3DI9bOjmHVA==",
16
+ "version": "0.14.5",
17
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.5.tgz",
18
+ "integrity": "sha512-qVEYOocnwn97uJ0eDzmuwN06CYsYWr2t3+iKylpisBHX4wlLWk7P8EmFFdBlx7NyKVIZDDeYT164Gd7/T8UGgw==",
19
19
  "license": "ISC",
20
20
  "bin": {
21
21
  "ssss": "scripts/ssss.mjs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tr-plugin-ssss",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Canonical SSSS distribution, scaffolding and validation for Total Recall installations.",
@@ -19,7 +19,7 @@
19
19
  "check:ssss-conformance": "node cli.mjs conformance"
20
20
  },
21
21
  "dependencies": {
22
- "@gregiteen/ssss-cli": "0.14.4"
22
+ "@gregiteen/ssss-cli": "0.14.5"
23
23
  },
24
24
  "license": "MIT"
25
25
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "ssss",
4
4
  "name": "SSSS",
5
- "version": "0.1.2",
5
+ "version": "0.1.3",
6
6
  "description": "Canonical SSSS distribution, scaffolding and validation for Total Recall installations.",
7
7
  "license": "MIT",
8
8
  "use_cases": ["software-development", "state-conformance"],
@@ -80,11 +80,10 @@ export const CLIENTS = {
80
80
  },
81
81
  openclaw: {
82
82
  label: 'OpenClaw',
83
- mode: 'openclaw',
83
+ mode: 'symlink',
84
84
  enforcement: 'enforced',
85
85
  enforcementReason: 'before_tool_call hook denies unreceipted tool calls with exit 2',
86
- memoryTarget: 'MEMORY.md',
87
- agentsTarget: 'AGENTS.md',
86
+ target: 'AGENTS.md',
88
87
  },
89
88
  cursor: {
90
89
  label: 'Cursor',
@@ -880,32 +879,6 @@ export default async function connect(args) {
880
879
  result.notes.push(` DeepSeek Harness MEMORY.md → ${globalMemPath}`);
881
880
  }
882
881
  result.notes.push(` DeepSeek Harness AGENTS.md → INSTRUCTIONS.md`);
883
- } else if (preset.mode === 'openclaw') {
884
- // OpenClaw: write MEMORY.md + symlink AGENTS.md in cwd
885
- const instructions = readInstructions(cwd, agentDir);
886
- if (!instructions) throw new Error('No INSTRUCTIONS.md found. Run npx total-recall init or compile first.');
887
- // MEMORY.md — curated facts extracted from instructions
888
- const memPath = path.join(cwd, preset.memoryTarget);
889
- if (!fs.existsSync(memPath) || opts.force) {
890
- fs.writeFileSync(memPath, instructions, 'utf8');
891
- }
892
- // AGENTS.md — symlink to INSTRUCTIONS.md
893
- const agentsPath = path.join(cwd, preset.agentsTarget);
894
- if (pathExists(agentsPath)) {
895
- if (opts.force) {
896
- try {
897
- fs.unlinkSync(agentsPath);
898
- } catch {
899
- fs.rmSync(agentsPath, { force: true, recursive: true });
900
- }
901
- fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
902
- }
903
- } else {
904
- fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
905
- }
906
- result.projection = { targetPath: memPath, action: 'written' };
907
- result.notes.push(` OpenClaw MEMORY.md written to ${memPath}`);
908
- result.notes.push(` OpenClaw AGENTS.md → INSTRUCTIONS.md`);
909
882
  } else if (preset.mode === 'vault') {
910
883
  result.projection = writeVaultProjection(agentDir, preset, opts);
911
884
  const folderName = result.projection.folderName || preset.folderName;
@@ -61,6 +61,7 @@ async function printCompacted({ query, actions, budget, format, version, rulesHa
61
61
  if (format !== 'json') return result.context;
62
62
  const payload = { ready: true, compacted: true, ranking: result.ranking, context: result.context, counts: result.counts,
63
63
  stats: { version, rules_hash: rulesHash, budget, retrieval: summarizeRetrieval(knowledge?.summary), token_measurement: 'estimated_chars_divided_by_four' } };
64
+ if (result.evidenceStart !== undefined) payload.evidence_start = result.evidenceStart;
64
65
  for (let index = 0; index < 10; index++) {
65
66
  const tokens = estimateTokens(`${JSON.stringify(payload)}\n`);
66
67
  if (tokens === payload.stats.total_tokens) break;
@@ -11,7 +11,7 @@ export async function runOkfIngest(args) {
11
11
  dryRun: false,
12
12
  category: null,
13
13
  importance: null,
14
- onConflict: 'warn',
14
+ onConflict: 'skip',
15
15
  typeMap: null,
16
16
  help: false
17
17
  };
@@ -118,10 +118,10 @@ function printIngestOkfHelp() {
118
118
 
119
119
  Options:
120
120
  --dry-run Validate concepts and preview import without writing
121
- --category <name> Override and force category for all imported concepts
121
+ --category <name> Choose a descriptive category for imported concepts
122
122
  --importance <1-5> Override default importance (default: 3)
123
- --on-conflict <strategy> Strategy for duplicate slugs: 'skip' | 'warn' | 'overwrite' (default: warn)
124
- --type-map <mapping> Custom type mappings. Format: "Type A=facts,Type B=concepts"
123
+ --on-conflict <strategy> 'skip' (default), 'warn' (report and skip), or 'overwrite' (same imported concept)
124
+ --type-map <mapping> Map types to descriptive categories, e.g. "Type A=facts,Type B=concepts"
125
125
  --global Target the global brain
126
126
  --project Target the project brain (default)
127
127
  --help, -h Show this help
@@ -73,7 +73,8 @@ function printHelp() {
73
73
  plan Resolve the release version and report gate/consumer readiness. No writes.
74
74
  publish Run the package's prepublish gate, then npm publish. Requires --apply.
75
75
  propagate Pin the published version across every registered consumer (update --all-brains).
76
- verify Independently confirm the registry artifact and each consumer's resolved install.
76
+ verify Independently confirm the registry artifact, each consumer's resolved install,
77
+ and that the server on this host runs the release.
77
78
  all plan → publish → propagate → verify, stopping at the first failure.
78
79
 
79
80
  Rules enforced here:
@@ -140,7 +141,10 @@ export async function tarballServed(pkg, version, { exec = spawnSync, fetchImpl
140
141
  * Wait until a just-published version can be installed: listed as latest, and its tarball served.
141
142
  * 3.42.0 took about six minutes and 3.42.1 about eight, its listing four minutes ahead of its tarball.
142
143
  */
143
- export async function awaitPublished(pkg, version, { attempts = 45, delayMs = 20000, exec = spawnSync, sleep, tarball = tarballServed } = {}) {
144
+ /** npm has taken 8 and then 23 minutes to serve an accepted publish, so a shorter wait reports failure on a good release. */
145
+ export const REGISTRY_WAIT = Object.freeze({ attempts: 120, delayMs: 20000 });
146
+
147
+ export async function awaitPublished(pkg, version, { attempts = REGISTRY_WAIT.attempts, delayMs = REGISTRY_WAIT.delayMs, exec = spawnSync, sleep, tarball = tarballServed } = {}) {
144
148
  const pause = sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
145
149
  let last = null;
146
150
  for (let i = 0; i < attempts; i++) {
@@ -295,6 +299,20 @@ export function verifyConsumers(pkg, version, opts = {}) {
295
299
  };
296
300
  }
297
301
 
302
+ /**
303
+ * What the server on this host runs. Files moving does not replace a running process, so a
304
+ * release is not verified here until the process is on it. Packages other than the brain and
305
+ * explicit --root runs have no server to ask about.
306
+ */
307
+ export async function verifyRunningServer(pkg, version, { check, explicitRoot = false } = {}) {
308
+ if (pkg !== 'total-recall-brain' || explicitRoot) return { checked: false, ok: true };
309
+ const server = await (check || (async (args) => {
310
+ const [{ runningServerStatus }, { brainDir }] = await Promise.all([import('../core/running-server.mjs'), import('../core/config.mjs')]);
311
+ return runningServerStatus({ brainDir, ...args });
312
+ }))({ latest: version });
313
+ return { checked: true, ok: !['stale', 'down'].includes(server.status), server };
314
+ }
315
+
298
316
  function runPrepublish(dir, pkgJson, { exec = spawnSync, timeoutMs } = {}) {
299
317
  if (!pkgJson?.scripts?.prepublishOnly) return { ok: true, skipped: true };
300
318
  const manager = fs.existsSync(path.join(dir, 'pnpm-lock.yaml')) ? 'pnpm' : 'npm';
@@ -374,6 +392,13 @@ export async function run(argv = process.argv, deps = {}) {
374
392
  if (consumers.failing > 0) {
375
393
  return fail(`${pkg}@${version}: ${consumers.failing}/${consumers.consumers} consumers not satisfied. First: ${consumers.failures[0].root} (${consumers.failures[0].installed ?? 'not installed'}).`);
376
394
  }
395
+ const running = await verifyRunningServer(pkg, version, { check: deps.runningServerStatus, explicitRoot: Boolean(opts.root) });
396
+ entry.running_server = running.server || null;
397
+ if (!running.ok) {
398
+ return fail(`${pkg}@${version}: ${running.server.reason} Replace it with: total-recall deploy release ${version} --adapter-secret <DEPLOY_MESH_REFERENCE>, or restart it on a host that is not production.`);
399
+ }
400
+ if (running.server?.status === 'current') log(` ✓ the server on this host runs ${version}`);
401
+ else if (running.server?.status === 'unknown') log(` The server on this host could not be read: ${running.server.reason}`);
377
402
  log(` ✓ ${pkg}@${version} verified: registry + ${consumers.consumers}/${consumers.consumers} consumers`);
378
403
  if (consumers.pin_behind.length) log(` ${consumers.pin_behind.length} run it with an older pin in package.json, such as ${consumers.pin_behind[0].root}`);
379
404
  if (consumers.dev_not_installed.length) log(` ${consumers.dev_not_installed.length} keep no dev copy of it and run under the shared install`);
@@ -36,6 +36,7 @@ export default async function startup(args = []) {
36
36
  if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
37
37
  else {
38
38
  console.log(`Startup: required plugins ${report.required_plugins?.status || 'unknown'}; server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
39
+ if (report.server?.stale) console.log(`Server: running ${report.server.stale.running} with ${report.server.stale.on_disk} on disk; it has not been replaced.`);
39
40
  for (const error of report.required_plugins?.errors || []) console.log(`Required plugin: ${error}`);
40
41
  const harnesses = await queryHarnessUsage().catch(() => []);
41
42
  const availH = harnesses.filter(h => h.available);
@@ -20,6 +20,7 @@ import { installAutoPull } from '../core/autopull-install.mjs';
20
20
  import { updateAllBrains } from '../core/brain-updates.mjs';
21
21
  import { installHarnessHooks } from '../core/harness-hooks.mjs';
22
22
  import { automaticUpdateStatus, runAutomaticBrainUpdates } from '../core/automatic-brain-updates.mjs';
23
+ import { runningServerStatus } from '../core/running-server.mjs';
23
24
 
24
25
  /** @param {string[]} args */
25
26
  export function parseArgs(args) {
@@ -78,21 +79,26 @@ function printHelp() {
78
79
  writing package.json or the lockfile. Register it to develop it here.
79
80
  total-recall update --automatic [--json]
80
81
  Daemon maintenance: update both packages, ensure defaults, update every
81
- installed plugin from its recorded source and refresh surfaces.
82
+ installed plugin from its recorded source and refresh surfaces. Where a
83
+ per-user service (LaunchAgent, systemd user unit) runs the server from
84
+ the global install, also restart a stale server and verify /health.
82
85
  total-recall update --status [--json]
83
86
  Read the actual last automatic maintenance report without starting work.
87
+ Also reports what the server on this host runs against the release.
84
88
 
85
89
  This brain's own checkout (git):
86
90
  total-recall update --install-autopull [--port N] [--no-build] [--dry-run]
87
91
  Every 5 minutes fast-forward this checkout to origin/main, install and
88
92
  rebuild what changed, restart the brain and verify /health reports the
89
- new version (scripts/auto-pull.sh). macOS: LaunchAgent
93
+ new version (scripts/auto-pull.sh). Refused where systemd serves the
94
+ checkout, because a restart there has a gap: use total-recall deploy. macOS: LaunchAgent
90
95
  com.totalrecall.autopull; Linux: crontab. A dev checkout (other branch,
91
96
  uncommitted or unpushed work) is never touched. --no-build: never build
92
97
  the dashboard on this host (laptops).
93
98
 
94
99
  Env:
95
100
  TR_AUTO_UPDATE_PACKAGE=0 Disable daemon cron auto-update (default: on)
101
+ TR_SERVER_AUTO_RESTART=0 Report a stale server instead of restarting it (default: on)
96
102
  TR_SYNC_REPOS=/a:/b Extra project roots
97
103
 
98
104
  Source monorepos named ${PACKAGE_NAME} are skipped (use git, not npm into self).
@@ -106,7 +112,12 @@ export default async function update(args = []) {
106
112
  return;
107
113
  }
108
114
 
109
- if(opts.status){const status=automaticUpdateStatus(brainDir);console.log(JSON.stringify(status,null,2));return status;}
115
+ if(opts.status){
116
+ const status=automaticUpdateStatus(brainDir);
117
+ status.server=await runningServerStatus({brainDir,latest:status.last?.versions?.['total-recall-brain']});
118
+ console.log(JSON.stringify(status,null,2));
119
+ return status;
120
+ }
110
121
  if(opts.automatic){
111
122
  const report=await runAutomaticBrainUpdates({brainDir,force:opts.force,fleet:!opts.localOnly});
112
123
  console.log(JSON.stringify(report,null,2));
@@ -12,6 +12,8 @@ import { updateGlobalBrainPackages } from './global-brain-updates.mjs';
12
12
  import { updateMeshBrainPackages } from './mesh-brain-updates.mjs';
13
13
  import { updateSourceBrainPackages } from './source-brain-updates.mjs';
14
14
  import { updateInstalledPlugins } from './plugin-updates.mjs';
15
+ import { runningServerStatus } from './running-server.mjs';
16
+ import { replaceStaleServer } from './server-restart.mjs';
15
17
 
16
18
  export function automaticUpdateStatus(brainDir) {
17
19
  let last;
@@ -36,7 +38,7 @@ export function startAutomaticBrainUpdates({brainDir,intervalMs=60*60*1000,run=r
36
38
  return {tick,stop(){stopped=true;clearInterval(timer);}};
37
39
  }
38
40
 
39
- export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,now=Date.now}={}) {
41
+ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,serverCheck=runningServerStatus,replaceServer=replaceStaleServer,now=Date.now}={}) {
40
42
  if(!force && !isPackageAutoUpdateEnabled())return {skipped:true,reason:'disabled',results:[]};
41
43
  const file=path.join(brainDir,'memory-derived/automatic-brain-updates.json');
42
44
  let saved;try{saved=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
@@ -66,11 +68,19 @@ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false
66
68
  const changed=summary.updated>0 || plugins.updated>0 || global.results.some(row=>row.status==='updated') || sources.results.some(row=>row.status==='updated') || JSON.stringify(saved?.versions)!==JSON.stringify(summary.versions);
67
69
  const skills=await skillMaintenance({brainDir});
68
70
  const surfaces=await compile({brainDir,force:changed || skills.updated > 0});
71
+ // Last, so a replaced server starts on everything this run moved. Where maintenance cannot
72
+ // replace it, a lagging server is reported and not counted as a failure, which would retry
73
+ // every five minutes for nothing: in production that is a deploy decision. Where it can, a
74
+ // hold or a failed attempt is this machine's own failure, which is what makes a peer dispatch it again.
75
+ let server=await serverCheck({brainDir,latest:summary.versions?.['total-recall-brain']});
76
+ if(server.status==='stale')server=await replaceServer({brainDir,server,globalRoot:global.root,previous:saved?.server});
77
+ if(server.status==='stale' || server.status==='down')logger.error('automatic-brain-updates',`Running server: ${server.replacement?.reason || server.reason}`);
78
+ const serverFailed=['held','failed'].includes(server.replacement?.state)?1:0;
69
79
  // local_failed is this node's own maintenance. Peers judge a node by it, not by
70
80
  // `failed`, which also counts the node's view of its peers: judging by that made
71
81
  // every fleet pass mark every peer failed forever, each because the others had.
72
- const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length);
73
- report={...summary,global,sources,plugins,mesh,skills,surfaces,local_failed,failed:local_failed+mesh.failed,checkedAt:now()};
82
+ const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length)+serverFailed;
83
+ report={...summary,global,sources,plugins,mesh,server,skills,surfaces,local_failed,failed:local_failed+mesh.failed,checkedAt:now()};
74
84
  }catch(error){report={failed:1,error:error.message,checkedAt:now(),results:[]};}
75
85
  const temporary=`${file}.${process.pid}.tmp`;fs.writeFileSync(temporary,JSON.stringify(report,null,2));fs.renameSync(temporary,file);
76
86
  return report;
@@ -122,6 +122,16 @@ function readPlistValue(file, key) {
122
122
  }
123
123
  }
124
124
 
125
+ /**
126
+ * Whether systemd serves this checkout. auto-pull stops the brain and starts another with
127
+ * nohup, so on such a host it would cause an outage and leave a process outside the unit.
128
+ */
129
+ export function systemdServesCheckout(repoDir, { exec = spawnSync } = {}) {
130
+ const shown = exec('systemctl', ['show', 'total-recall-server.service', '-p', 'LoadState', '-p', 'ExecStart'], { encoding: 'utf8', timeout: 5000 });
131
+ if (shown.status !== 0) return false;
132
+ return /^LoadState=loaded$/m.test(shown.stdout || '') && (shown.stdout || '').includes(`${repoDir}/`);
133
+ }
134
+
125
135
  /**
126
136
  * Install (or refresh) the timer. Returns what it did; `dryRun` returns the
127
137
  * plan without writing.
@@ -133,12 +143,16 @@ export function installAutoPull({
133
143
  platform = process.platform,
134
144
  home = os.homedir(),
135
145
  dryRun = false,
146
+ exec = spawnSync,
136
147
  } = {}) {
137
148
  const script = path.join(repoDir, 'scripts', 'auto-pull.sh');
138
149
  if (!fs.existsSync(path.join(repoDir, '.git'))) {
139
150
  throw new Error(`${repoDir} is not a git checkout; npm installs update through the daemon (total-recall update --apply).`);
140
151
  }
141
152
  if (!fs.existsSync(script)) throw new Error(`Missing ${script}`);
153
+ if (platform === 'linux' && systemdServesCheckout(repoDir, { exec })) {
154
+ throw new Error(`systemd serves ${repoDir}, and a restart there has a gap. Replace it with: total-recall deploy release <version> --adapter-secret <DEPLOY_MESH_REFERENCE>`);
155
+ }
142
156
 
143
157
  const resolvedPort =
144
158
  port || Number(process.env.TR_PORT) || (platform === 'darwin' ? detectLaunchAgentPort(repoDir, { home }) : null) || 3000;
@@ -19,6 +19,13 @@ const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'pe
19
19
  const ignored = new Set(['node_modules', 'vendor', 'dist', 'build', 'coverage', 'venv',
20
20
  'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch', 'scaffold', 'memory-derived']);
21
21
  const canonical = key => key === '@ssss/cli' ? '@gregiteen/ssss-cli' : key;
22
+ // Plugins also force the engine alias through `overrides`. Those pins move with the dependency,
23
+ // or a consumer ends up asking for two versions of the same package.
24
+ const overrideKeys = pkg => Object.entries(pkg.overrides || {})
25
+ .filter(([key, spec]) => typeof spec === 'string' && BRAIN_PACKAGES.includes(canonical(key)) && canonical(key) !== pkg.name)
26
+ .map(([key]) => key);
27
+ const overridesLag = (pkg, versions) => overrideKeys(pkg)
28
+ .some(key => versions[canonical(key)] && pkg.overrides[key] !== targetSpec(key, versions[canonical(key)]));
22
29
  const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
23
30
  const isLink = file => { try { return fs.lstatSync(file).isSymbolicLink(); } catch { return false; } };
24
31
  export const brainUpdateLockPath = owner => path.join(os.tmpdir(),
@@ -196,7 +203,7 @@ const absentDevDep = (dep) => dep.section === 'devDependencies' && !dep.file;
196
203
  * The daemon runs with NODE_ENV=production, and under that npm and pnpm remove dev dependencies
197
204
  * from whatever they install in. An install that wants a runtime tree says so with a flag.
198
205
  */
199
- function installEnv(env = process.env) {
206
+ export function installEnv(env = process.env) {
200
207
  const { NODE_ENV, npm_config_omit, NPM_CONFIG_OMIT, npm_config_production, NPM_CONFIG_PRODUCTION, ...kept } = env;
201
208
  return kept;
202
209
  }
@@ -261,7 +268,7 @@ export async function updateAllBrains(opts = {}) {
261
268
  row.status = 'failed'; row.error = 'Newer local/preview package; refusing an implicit downgrade'; continue;
262
269
  }
263
270
  const shared = isLink(path.join(owner.owner, 'node_modules')) || isLink(path.join(consumer.root, 'node_modules'));
264
- const needsChange = shared || consumer.deps.some(dep => {
271
+ const needsChange = shared || (!consumer.runtimeOnly && overridesLag(consumer.pkg, versions)) || consumer.deps.some(dep => {
265
272
  const pinLags = !consumer.runtimeOnly && dep.declared !== targetSpec(dep.key, versions[dep.package]);
266
273
  if (absentDevDep(dep)) return pinLags;
267
274
  return pinLags || dep.version !== versions[dep.package]
@@ -296,6 +303,9 @@ export async function updateAllBrains(opts = {}) {
296
303
  saved.set(consumer.file, text);
297
304
  const pkg = read(consumer.file);
298
305
  for (const dep of consumer.deps) pkg[dep.section][dep.key] = targetSpec(dep.key, versions[dep.package]);
306
+ for (const key of overrideKeys(pkg)) {
307
+ if (versions[canonical(key)]) pkg.overrides[key] = targetSpec(key, versions[canonical(key)]);
308
+ }
299
309
  const indent = /\n([ \t]+)"/.exec(text)?.[1] || 2;
300
310
  fs.writeFileSync(consumer.file, JSON.stringify(pkg, null, indent) + '\n');
301
311
  }
@@ -62,6 +62,9 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
62
62
  if (unknown.length) throw new Error(`Cannot expand rules not in this capsule: ${unknown.join(', ')}`);
63
63
  const duplicateOf = findDuplicates(ranked.filter(item => !expand.includes(item.slug)));
64
64
  const tiers = new Map(ranked.map(item => [item.slug, 'omitted']));
65
+ const isEvidence = item => item.taskEvidence && !item.required;
66
+ const supplement = supplemental.filter(item => item.text && !isEvidence(item)).map(item => item.text.trim());
67
+ const evidence = supplemental.filter(item => item.text && isEvidence(item)).map(item => item.text.trim()).join('\n\n---\n\n');
65
68
 
66
69
  const render = () => {
67
70
  const of = tier => ranked.filter(item => tiers.get(item.slug) === tier);
@@ -71,10 +74,11 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
71
74
  const blocks = [
72
75
  ...full.map(item => item.text.trim()),
73
76
  ...summary.map(item => `[${item.slug}] ${summarizeRule(item.body)}`),
74
- ...supplemental.filter(item => item.text).map(item => item.text.trim()),
77
+ ...supplement,
75
78
  ];
76
79
  if (reference.length) blocks.push(`Referenced: ${reference.map(item => item.slug).join(', ')}`);
77
80
  if (duplicate.length) blocks.push(`Duplicates: ${duplicate.map(item => `${item.slug} (of ${duplicateOf.get(item.slug)})`).join(', ')}`);
81
+ if (evidence) blocks.push(evidence);
78
82
  const body = blocks.join('\n\n---\n\n');
79
83
  // The token count sits inside the text it counts, so iterate to a stable value.
80
84
  let tokens = estimateTokens(body);
@@ -142,5 +146,6 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
142
146
  for (const slug of duplicateOf.keys()) tryTier(ranked.find(item => item.slug === slug), 'duplicate');
143
147
 
144
148
  const { context, counts, tokens } = render();
145
- return { context, counts, tokens, tiers: Object.fromEntries(tiers) };
149
+ return { context, counts, tokens, tiers: Object.fromEntries(tiers),
150
+ ...(evidence && { evidenceStart: context.length - evidence.length }) };
146
151
  }
@@ -109,7 +109,10 @@ export async function compileContext({
109
109
  }
110
110
 
111
111
  const cliContributions = cliVerbContribution ? [cliVerbContribution] : [];
112
- const supporting = [...knowledge.contributions, ...cliContributions];
112
+ const evidence = knowledge.summary.selected
113
+ ? knowledge.contributions.map(item => ({ ...item, taskEvidence: true }))
114
+ : knowledge.contributions;
115
+ const supporting = [...evidence, ...cliContributions];
113
116
  const result = assembleContext([...required, ...supporting, ...contributions], { total: b.total });
114
117
  const evidenceMissing = supporting.some(item => !result.stats.contributions.some(placed => placed.id === item.id));
115
118
  // Compaction may reclaim rule explanation space; never claim readiness after losing selected task evidence.
@@ -163,6 +166,8 @@ export function capsuleResponse(result, { total = 4000, debug = false, format =
163
166
  retrieval: summarizeRetrieval(result.stats.retrieval),
164
167
  };
165
168
  const response = { ready: result.ready, context: result.context, stats };
169
+ // This offset refers to the decoded context string, not the escaped JSON bytes.
170
+ if (result.evidenceStart !== undefined) response.evidence_start = result.evidenceStart;
166
171
  stats.budget = total;
167
172
  // Counts affect serialization themselves. Iterate to a stable representation.
168
173
  for (let i = 0; i < 20; i++) {
@@ -100,14 +100,18 @@ export function assembleContext(contributions, { total = 4000 } = {}) {
100
100
  if (estimateTokens(render([...selected, item])) <= total) selected.push(item);
101
101
  else excluded.push(item.id);
102
102
  }
103
- const context = render(selected);
103
+ const isEvidence = item => item.taskEvidence && !item.required;
104
+ const evidence = selected.filter(isEvidence);
105
+ const ordered = [...selected.filter(item => !isEvidence(item)), ...evidence];
106
+ const context = render(ordered);
107
+ const evidenceText = render(evidence);
104
108
  const totalTokens = estimateTokens(context);
105
- return { context, ready: requiredTokens <= total, stats: {
109
+ return { context, ...(evidenceText && { evidenceStart: context.length - evidenceText.length }), ready: requiredTokens <= total, stats: {
106
110
  token_measurement: 'estimated_chars_divided_by_four', total_tokens: totalTokens,
107
111
  budget_used: totalTokens, budget_remaining: Math.max(0, total - totalTokens),
108
112
  required_tokens: requiredTokens, overflow_tokens: Math.max(0, requiredTokens - total),
109
113
  required_ids: required.map(c => c.id), excluded_ids: excluded,
110
- contributions: selected.map(c => ({ id: c.id, required: Boolean(c.required), chars: c.text.length, estimated_tokens: estimateTokens(c.text) })),
114
+ contributions: ordered.map(c => ({ id: c.id, required: Boolean(c.required), chars: c.text.length, estimated_tokens: estimateTokens(c.text) })),
111
115
  version: crypto.createHash('sha256').update(JSON.stringify({ total, contributions })).digest('hex'),
112
116
  } };
113
117
  }
@@ -37,6 +37,28 @@ export function getDaemonStatus() {
37
37
  return status === 'not-started' ? 'not_started' : status === 'stopped' ? 'dead' : status;
38
38
  }
39
39
 
40
+ /**
41
+ * Remove the lock of a daemon that is gone, for a server told to run none.
42
+ *
43
+ * startDaemon is the only other thing that recovers this lock, and such a server never
44
+ * calls it. A daemon that went down with the machine left a lock nothing would ever
45
+ * clear, so the server reported `dead` and `degraded` for good. Only a lock whose process
46
+ * is verified gone is removed: a live process or one that cannot be identified keeps it.
47
+ * @returns {boolean} True if a lock was removed.
48
+ */
49
+ export function clearStaleDaemonLock({ observe = getDaemonObservation } = {}) {
50
+ try {
51
+ const before = fs.readFileSync(PID_FILE, 'utf8');
52
+ if (observe().status !== 'stopped') return false;
53
+ if (fs.readFileSync(PID_FILE, 'utf8') !== before) return false;
54
+ fs.unlinkSync(PID_FILE);
55
+ logger.info('daemon-control', `Removed the lock of daemon PID ${before.trim()}, which is gone. This server runs no daemon.`);
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
40
62
  /**
41
63
  * Detaches the daemon script in the background and writes its PID to file.
42
64
  * @returns {number} The PID of the newly spawned daemon.