@prismer/runtime 2.0.7 → 2.2.55
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/CHANGELOG.md +3631 -0
- package/README.md +34 -12
- package/apc/skills/FIELD-DICTIONARY.md +111 -0
- package/apc/skills/bug-reproduce/SKILL.md +150 -0
- package/apc/skills/bug-reproduce/skill.json +96 -0
- package/apc/skills/code-review/SKILL.md +198 -0
- package/apc/skills/code-review/skill.json +124 -0
- package/apc/skills/design-review/SKILL.md +122 -0
- package/apc/skills/design-review/skill.json +88 -0
- package/apc/skills/doc-sync/SKILL.md +168 -0
- package/apc/skills/doc-sync/skill.json +81 -0
- package/apc/skills/env-doctor/SKILL.md +194 -0
- package/apc/skills/env-doctor/skill.json +209 -0
- package/apc/skills/git-ops/SKILL.md +189 -0
- package/apc/skills/git-ops/skill.json +94 -0
- package/apc/skills/impact-trace/SKILL.md +168 -0
- package/apc/skills/impact-trace/skill.json +104 -0
- package/apc/skills/observability/SKILL.md +195 -0
- package/apc/skills/observability/skill.json +116 -0
- package/apc/skills/release-db-config-sync/SKILL.md +186 -0
- package/apc/skills/release-db-config-sync/skill.json +109 -0
- package/apc/skills/release-ota-promote/SKILL.md +195 -0
- package/apc/skills/release-ota-promote/skill.json +176 -0
- package/apc/skills/release-preflight/SKILL.md +174 -0
- package/apc/skills/release-preflight/skill.json +175 -0
- package/apc/skills/release-rollback/SKILL.md +214 -0
- package/apc/skills/release-rollback/skill.json +230 -0
- package/apc/skills/release-tag/SKILL.md +194 -0
- package/apc/skills/release-tag/skill.json +94 -0
- package/apc/skills/releasing-prod/SKILL.md +49 -0
- package/apc/skills/releasing-test/SKILL.md +135 -0
- package/apc/skills/sdk-release/SKILL.md +200 -0
- package/apc/skills/spec-intake/SKILL.md +169 -0
- package/apc/skills/spec-intake/skill.json +93 -0
- package/apc/skills/test-result-feedback/SKILL.md +239 -0
- package/apc/skills/test-result-feedback/skill.json +193 -0
- package/apc/skills/test-runner/SKILL.md +169 -0
- package/apc/skills/test-runner/skill.json +103 -0
- package/apc/skills/ui-align/SKILL.md +209 -0
- package/apc/skills/ui-align/skill.json +114 -0
- package/apc/skills/ui-canvas/SKILL.md +148 -0
- package/apc/skills/ui-canvas/skill.json +127 -0
- package/built-in-skills/agent-coordination/SKILL.md +257 -0
- package/built-in-skills/agent-meta/SKILL.md +53 -0
- package/built-in-skills/assets/SKILL.md +133 -0
- package/built-in-skills/browser-use/SKILL.md +93 -0
- package/built-in-skills/canvas-design/LICENSE.txt +202 -0
- package/built-in-skills/canvas-design/SKILL.md +157 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/built-in-skills/claim-agent-ownership/SKILL.md +255 -0
- package/built-in-skills/claude-api/LICENSE.txt +202 -0
- package/built-in-skills/claude-api/SKILL.md +325 -0
- package/built-in-skills/claude-api/csharp/claude-api.md +402 -0
- package/built-in-skills/claude-api/curl/examples.md +216 -0
- package/built-in-skills/claude-api/curl/managed-agents.md +336 -0
- package/built-in-skills/claude-api/go/claude-api.md +421 -0
- package/built-in-skills/claude-api/go/managed-agents/README.md +561 -0
- package/built-in-skills/claude-api/java/claude-api.md +432 -0
- package/built-in-skills/claude-api/java/managed-agents/README.md +442 -0
- package/built-in-skills/claude-api/php/claude-api.md +375 -0
- package/built-in-skills/claude-api/php/managed-agents/README.md +435 -0
- package/built-in-skills/claude-api/python/claude-api/README.md +420 -0
- package/built-in-skills/claude-api/python/claude-api/batches.md +185 -0
- package/built-in-skills/claude-api/python/claude-api/files-api.md +165 -0
- package/built-in-skills/claude-api/python/claude-api/streaming.md +162 -0
- package/built-in-skills/claude-api/python/claude-api/tool-use.md +590 -0
- package/built-in-skills/claude-api/python/managed-agents/README.md +332 -0
- package/built-in-skills/claude-api/ruby/claude-api.md +113 -0
- package/built-in-skills/claude-api/ruby/managed-agents/README.md +389 -0
- package/built-in-skills/claude-api/shared/agent-design.md +101 -0
- package/built-in-skills/claude-api/shared/error-codes.md +213 -0
- package/built-in-skills/claude-api/shared/live-sources.md +135 -0
- package/built-in-skills/claude-api/shared/managed-agents-api-reference.md +378 -0
- package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +209 -0
- package/built-in-skills/claude-api/shared/managed-agents-core.md +238 -0
- package/built-in-skills/claude-api/shared/managed-agents-environments.md +215 -0
- package/built-in-skills/claude-api/shared/managed-agents-events.md +195 -0
- package/built-in-skills/claude-api/shared/managed-agents-memory.md +197 -0
- package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +99 -0
- package/built-in-skills/claude-api/shared/managed-agents-onboarding.md +114 -0
- package/built-in-skills/claude-api/shared/managed-agents-outcomes.md +106 -0
- package/built-in-skills/claude-api/shared/managed-agents-overview.md +68 -0
- package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
- package/built-in-skills/claude-api/shared/managed-agents-tools.md +321 -0
- package/built-in-skills/claude-api/shared/managed-agents-webhooks.md +110 -0
- package/built-in-skills/claude-api/shared/model-migration.md +779 -0
- package/built-in-skills/claude-api/shared/models.md +121 -0
- package/built-in-skills/claude-api/shared/prompt-caching.md +171 -0
- package/built-in-skills/claude-api/shared/tool-use-concepts.md +327 -0
- package/built-in-skills/claude-api/typescript/claude-api/README.md +333 -0
- package/built-in-skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/built-in-skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/built-in-skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +527 -0
- package/built-in-skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/built-in-skills/codebase-design/DEEPENING.md +37 -0
- package/built-in-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/built-in-skills/codebase-design/LICENSE +21 -0
- package/built-in-skills/codebase-design/SKILL.md +116 -0
- package/built-in-skills/conversation-compaction/SKILL.md +114 -0
- package/built-in-skills/council-creator/SKILL.md +426 -0
- package/built-in-skills/diagnosing-bugs/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/SKILL.md +136 -0
- package/built-in-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/built-in-skills/doc-coauthoring/SKILL.md +376 -0
- package/built-in-skills/document-generation/SKILL.md +105 -0
- package/built-in-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/built-in-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/built-in-skills/domain-modeling/LICENSE +21 -0
- package/built-in-skills/domain-modeling/SKILL.md +76 -0
- package/built-in-skills/frontend-design/LICENSE.txt +177 -0
- package/built-in-skills/frontend-design/SKILL.md +43 -0
- package/built-in-skills/human-approval/SKILL.md +129 -0
- package/built-in-skills/image-generate/SKILL.md +128 -0
- package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +289 -0
- package/built-in-skills/ingest/SKILL.md +73 -0
- package/built-in-skills/internal-comms/LICENSE.txt +202 -0
- package/built-in-skills/internal-comms/SKILL.md +33 -0
- package/built-in-skills/internal-comms/examples/3p-updates.md +47 -0
- package/built-in-skills/internal-comms/examples/company-newsletter.md +65 -0
- package/built-in-skills/internal-comms/examples/faq-answers.md +30 -0
- package/built-in-skills/internal-comms/examples/general-comms.md +16 -0
- package/built-in-skills/liteparse/SKILL.md +176 -0
- package/built-in-skills/mcp-builder/LICENSE.txt +202 -0
- package/built-in-skills/mcp-builder/SKILL.md +237 -0
- package/built-in-skills/mcp-builder/reference/evaluation.md +602 -0
- package/built-in-skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/built-in-skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/built-in-skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/built-in-skills/mcp-builder/scripts/connections.py +151 -0
- package/built-in-skills/mcp-builder/scripts/evaluation.py +373 -0
- package/built-in-skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -0
- package/built-in-skills/memory/SKILL.md +471 -0
- package/built-in-skills/memory-dream/SKILL.md +339 -0
- package/built-in-skills/office-artifacts/SKILL.md +211 -0
- package/built-in-skills/okr/SKILL.md +154 -0
- package/built-in-skills/persona/SKILL.md +81 -0
- package/built-in-skills/persona-generator/SKILL.md +296 -0
- package/built-in-skills/pkf-svg/SKILL.md +253 -0
- package/built-in-skills/pkf-writing/SKILL.md +236 -0
- package/built-in-skills/prismer-im-collab/SKILL.md +168 -0
- package/built-in-skills/proactivity/SKILL.md +84 -0
- package/built-in-skills/remotion/SKILL.md +431 -0
- package/built-in-skills/role-builder/SKILL.md +203 -0
- package/built-in-skills/role-builder/scripts/author-role.mjs +334 -0
- package/built-in-skills/role-builder/scripts/ingest-role.mjs +223 -0
- package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +290 -0
- package/built-in-skills/role-builder/scripts/operation-harness.mjs +267 -0
- package/built-in-skills/skill-authoring/SKILL.md +134 -0
- package/built-in-skills/skill-authoring/skill.json +74 -0
- package/built-in-skills/skill-builder/SKILL.md +171 -0
- package/built-in-skills/skill-builder/scripts/ingest.mjs +265 -0
- package/built-in-skills/skill-creator/LICENSE.txt +202 -0
- package/built-in-skills/skill-creator/SKILL.md +227 -0
- package/built-in-skills/skill-creator/agents/analyzer.md +274 -0
- package/built-in-skills/skill-creator/agents/comparator.md +202 -0
- package/built-in-skills/skill-creator/agents/grader.md +223 -0
- package/built-in-skills/skill-creator/assets/eval_review.html +146 -0
- package/built-in-skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/built-in-skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/built-in-skills/skill-creator/references/external-library-import.md +110 -0
- package/built-in-skills/skill-creator/references/schemas.md +430 -0
- package/built-in-skills/skill-creator/scripts/__init__.py +0 -0
- package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/built-in-skills/skill-creator/scripts/generate_report.py +326 -0
- package/built-in-skills/skill-creator/scripts/import-library.mjs +475 -0
- package/built-in-skills/skill-creator/scripts/improve_description.py +247 -0
- package/built-in-skills/skill-creator/scripts/package_skill.py +136 -0
- package/built-in-skills/skill-creator/scripts/quick_validate.py +103 -0
- package/built-in-skills/skill-creator/scripts/run_eval.py +310 -0
- package/built-in-skills/skill-creator/scripts/run_loop.py +328 -0
- package/built-in-skills/skill-creator/scripts/utils.py +47 -0
- package/built-in-skills/slack-gif-creator/LICENSE.txt +202 -0
- package/built-in-skills/slack-gif-creator/SKILL.md +291 -0
- package/built-in-skills/slack-gif-creator/core/easing.py +234 -0
- package/built-in-skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/built-in-skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/built-in-skills/slack-gif-creator/core/validators.py +136 -0
- package/built-in-skills/slack-gif-creator/requirements.txt +4 -0
- package/built-in-skills/tasks/SKILL.md +413 -0
- package/built-in-skills/tdd/LICENSE +21 -0
- package/built-in-skills/tdd/SKILL.md +110 -0
- package/built-in-skills/tdd/mocking.md +59 -0
- package/built-in-skills/tdd/refactoring.md +10 -0
- package/built-in-skills/tdd/tests.md +61 -0
- package/built-in-skills/team/SKILL.md +77 -0
- package/built-in-skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/built-in-skills/web-artifacts-builder/SKILL.md +105 -0
- package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +334 -0
- package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/built-in-skills/webapp-testing/LICENSE.txt +202 -0
- package/built-in-skills/webapp-testing/SKILL.md +97 -0
- package/built-in-skills/webapp-testing/examples/console_logging.py +35 -0
- package/built-in-skills/webapp-testing/examples/element_discovery.py +40 -0
- package/built-in-skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/built-in-skills/webapp-testing/scripts/with_server.py +106 -0
- package/built-in-skills/wechat-pay/SKILL.md +59 -0
- package/dist/cli.cjs +72577 -16101
- package/dist/cli.js +72752 -16232
- package/dist/index.cjs +72632 -16024
- package/dist/index.d.cts +4956 -640
- package/dist/index.d.ts +4956 -640
- package/dist/index.js +72564 -15963
- package/package.json +39 -6
- package/plugins/memory/prismer/__init__.py +1211 -0
- package/plugins/memory/prismer/plugin.yaml +8 -0
- package/plugins/memory/prismer/tool-schemas.generated.json +249 -0
- package/plugins/tools/prismer-recall/__init__.py +282 -0
- package/plugins/tools/prismer-recall/plugin.yaml +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,3625 @@
|
|
|
1
1
|
# Changelog — @prismer/runtime
|
|
2
2
|
|
|
3
|
+
## 2.2.13 — 2026-08-17
|
|
4
|
+
|
|
5
|
+
- ACS boot recovery fixes (merged via fix/acs-boot-recovery): manifest Pod
|
|
6
|
+
reachability base, Dockerfile Hermes install retry on China egress,
|
|
7
|
+
canonical millisecond ISO timestamps in the sandbox manager, runner empty
|
|
8
|
+
adoption storm guard, and dev-local stream + reclaim.
|
|
9
|
+
- Runtime version bumped 2.2.12 → 2.2.13 because daemon sources changed
|
|
10
|
+
since the 2.2.12 GA publish (immutable content-addressed releases).
|
|
11
|
+
|
|
3
12
|
## Unreleased
|
|
4
13
|
|
|
14
|
+
### SDK 边界 Wave2 B2/B3 — daemon 离线改名落库 + CLI rename --handle
|
|
15
|
+
|
|
16
|
+
- **fix(daemon): `onAgentChanged` 补 local.db `agents` 表持久化**——改名
|
|
17
|
+
(PATCH /agents/:id)不 bump profile version ⇒ host.acked / syncProfileFromCloud
|
|
18
|
+
不会触发,旧实现改完只驻内存,重启后 declare/healthz/task-agentName 全回退旧名。
|
|
19
|
+
现在 `agent.changed` 的 displayName 直达 `UPDATE agents SET name`(dirty=0),
|
|
20
|
+
是离线窗口改名收敛的唯一落点;`AgentChangedPayload.fields` 同步补 `username`
|
|
21
|
+
字段声明(daemon 不持久化——本地表无 username 列)。
|
|
22
|
+
- **feat(cli): `prismer agent rename` 增 `--handle <new-username>`**——PATCH 体
|
|
23
|
+
`{ displayName, username? }`(handle 传时才带),与 PATCH /agents/:id 双字段
|
|
24
|
+
契约对齐;handle 在 CLI 侧先按 `/^[a-z][a-z0-9-]{2,30}$/` 校验(坏轴根本不出网)。
|
|
25
|
+
- **fix(cli): register 校验收窄到 `/^[a-z][a-z0-9-]{2,30}$/`**——旧轴
|
|
26
|
+
`[a-zA-Z0-9_-]`(大写/下划线,如 `Prod_Manager`)不再可新建(CLI 侧 400
|
|
27
|
+
之前拒绝;云端 register 端点不动,存量 handle 保留)。`slugifyUsername`
|
|
28
|
+
同步收窄:不再产出 `_`,数字开头派生名补 `agent-` 前缀保证字母开头。
|
|
29
|
+
|
|
30
|
+
### bugfix211 G2-R R-2 — runtime terminal-sessions 插件模块(workspace 终端)
|
|
31
|
+
|
|
32
|
+
- **feat(daemon): 新增 `daemon/terminal-sessions/` 插件模块**——daemon 协议
|
|
33
|
+
PTY(spec §T-1 唯一轨),wire 四方法 `terminal.open/write/resize/close` +
|
|
34
|
+
事件 `terminal.opened/data/exit/error`;会话卫生:per-daemon 并发上限 2、
|
|
35
|
+
空闲回收(`PRISMER_TERMINAL_IDLE_TIMEOUT_MS`,默认 10min、floor 30s,按
|
|
36
|
+
device-metrics interval 的 ConfigDelivery 模式)、ACL 逐字复用
|
|
37
|
+
shell-executor 的 `enabled`/`allowedWorkspaces`/cwd 语义。能力位
|
|
38
|
+
`runtime.terminal` 仅在 node-pty 真正加载到时宣告。
|
|
39
|
+
- **node-pty 分发形态(owner 裁决方案 A)**:只进 devDependencies,OTA
|
|
40
|
+
bundle 持续 JS-only(staging `npm ci --omit=dev --ignore-scripts` 不装
|
|
41
|
+
dev + `assertNoNativePayload` 对任何 `.node` die 兜底);pod 侧经 image
|
|
42
|
+
native ABI floor 供给(Dockerfile.base 源码编译 + install-builtin-runtime
|
|
43
|
+
`--native-module-root` 借用,循 better-sqlite3 先例)。`loadPty()` 两路
|
|
44
|
+
懒加载:常规 require(本机 dev/测试)→ floor 根
|
|
45
|
+
`/opt/prismer/native/node_modules` → null ⇒ 模块降级(不宣告能力位,
|
|
46
|
+
terminal.open 回类型化 `terminal_unavailable`,绝不伪造 PTY)。
|
|
47
|
+
- 测试:`test/terminal-sessions.test.ts` 30 例(真 PTY 生命周期 echo/exit
|
|
48
|
+
码/resize、上限/ACL/cwd 拒、idle reaper fake timers、负控:loadPty 强制
|
|
49
|
+
null ⇒ 降级、malformed 帧吞掉不进 dispatch、runner 未挂载 ⇒ 无能力位);
|
|
50
|
+
darwin spawn-helper 执行位兜底进 `test/setup/node-pty-exec-bit.ts`
|
|
51
|
+
(上游 microsoft/node-pty#850)。
|
|
52
|
+
- **feat(daemon): daemon 侧窗口输出帽(spec §T-5 follow-up 落地)**——per-
|
|
53
|
+
session 滚动窗字节预算(`PRISMER_TERMINAL_OUTPUT_BUDGET_BYTES` /
|
|
54
|
+
`PRISMER_TERMINAL_OUTPUT_BUDGET_WINDOW_MS`,默认 32MiB/10s,与 idle
|
|
55
|
+
timeout 同一条 ConfigDelivery 面)。洪流超预算 ⇒ kill 会话 +
|
|
56
|
+
`terminal.exit` 带 typed `reason=terminal_output_budget`(relay 透传、
|
|
57
|
+
前端独立横幅);kill 触发 onExit 二次广播已抑制。cloud relay 512KB/1s
|
|
58
|
+
保持浏览器侧收口,此帽是源端封顶——空闲回收在输出洪流下不再被续命。
|
|
59
|
+
测试新增 5 例(resolver 契约 + fake pty 越限 kill + 窗内正常 + 未装配
|
|
60
|
+
负控)。
|
|
61
|
+
- **fix(daemon): 输出帽系统评审三项修复**——① 窗口账本每次无条件排空(此前
|
|
62
|
+
length>64 才 filter,稀疏流下旧账不滚、累计误杀);② kill 后 onData 存活守卫
|
|
63
|
+
(孙进程持 slave fd 续 drain 时不再 touch/timer 重挂/重复 exit 帧);
|
|
64
|
+
③ env 显式 `0`/`off`/`disabled` 可关帽(此前任何 env 组合都落默认常开,
|
|
65
|
+
ConfigDelivery 无 off 面)。评审回归钉 3 例 + 语义测试更新。
|
|
66
|
+
|
|
67
|
+
### bugfix211 G3 — host.acked adopt/reclaim × rejection 分离(declare↔ack 自噬环)
|
|
68
|
+
|
|
69
|
+
- **fix(daemon): adoption 不再清掉同一轮 ack 刚拉黑的所有权黑名单。** 半成型
|
|
70
|
+
agent(有 profile 行、无 owner userId、无 binding)每轮 declare 同时命中
|
|
71
|
+
rejectedAgents("不是你的")与 adoptAgents("没人绑,来收养"):ownership-lock
|
|
72
|
+
删除+拉黑,syncHostAckAdoptions 又清黑名单重建,环实测 ~350ms/轮
|
|
73
|
+
(skills/ack+declare+me/agents+runtime 连环刷屏)。daemon 侧
|
|
74
|
+
syncHostAckAdoptions 增 rejectedThisRound 参数,本轮被拒 agent 跳过
|
|
75
|
+
reclaim/adopt(保留黑名单);cloud 侧在 ack 组装口做统一剔除(见主仓
|
|
76
|
+
handler.ts partitionAdoptableAgainstRejections)。
|
|
77
|
+
|
|
78
|
+
### bugfix211 G2 — ConfigDelivery 容器分类修正(workspace pod 被误判 ACS)
|
|
79
|
+
|
|
80
|
+
- **fix(daemon): 环境判据从镜像默认 `ALLOW_FAKE_API_KEY` 换成 env key 本身。**
|
|
81
|
+
dev 镜像把 `PRISMER_ALLOW_FAKE_API_KEY=true` 烧为默认(v200 时代 ACS 占位
|
|
82
|
+
key boot 的遗留),ACS checkpoint 与 workspace k8s pod 共享同一镜像,于是
|
|
83
|
+
持有真 `sk-prismer-live-*` 凭证的 workspace pod 被判成 "no env-injected
|
|
84
|
+
key" 的 ACS 世界:启动即打 "ACS sandbox detected",env-inference 兜底被
|
|
85
|
+
禁,B1 永久 401/403 时拒绝安全的最后手段而 Hermes 永不配置。新判据
|
|
86
|
+
`containerEnvIsKeyless`(runner.ts,三处消费点统一):container 且 env 无
|
|
87
|
+
`sk-prismer-live-` 形态 key 才是 keyless 沙箱;持真 key 一律走 ACK 分支。
|
|
88
|
+
配套 cloud 侧 pod-spec 显式注入 `PRISMER_ALLOW_FAKE_API_KEY=false`(覆盖
|
|
89
|
+
镜像 ENV;ACS pod 不受影响)。测试 `test/container-env-keyless.test.ts` 5 例。
|
|
90
|
+
|
|
91
|
+
### memory211 — pkf_reply_inline cross-turn delivery + office-artifacts interpreter
|
|
92
|
+
|
|
93
|
+
- **fix(pkf): `pkf_reply_inline` read roots now include the session dir.** In a
|
|
94
|
+
hermes LONG session the task dirs persist across turns under
|
|
95
|
+
`sessions/<sid>/tasks/<tid>/`, and the agent re-delivers a `.pkf` it authored
|
|
96
|
+
in a PREVIOUS turn's scratch; `bindPkfReplyInlineScope` computed `sessionDir`
|
|
97
|
+
but never added it to `allowedReadRoots`, so the new dispatch rejected that
|
|
98
|
+
path (`pkf_reply_inline_path_outside`) at tool time AND at terminal
|
|
99
|
+
re-validation. Rooting the session dir makes directory-prefix containment
|
|
100
|
+
cover every historical `tasks/*/scratch` under the session (referencing old
|
|
101
|
+
scratch is the natural shape, not an escape). Unbind stays taskId-keyed, so
|
|
102
|
+
the wider read set cannot outlive its own dispatch. Tests: red→green pair in
|
|
103
|
+
`pkf-reply-inline-tool.test.ts` — cross-turn delivery through `handleDispatch`
|
|
104
|
+
with the marker landing in THIS run's scratch, plus the same-session-bound
|
|
105
|
+
negative control (another session's file is still rejected).
|
|
106
|
+
- **docs(office-artifacts skill): the baked office libs live in the image venv
|
|
107
|
+
interpreter `/home/user/.venv/bin/python3`**, not the system
|
|
108
|
+
`/usr/bin/python3` (which has none of them). The Runtime baseline section now
|
|
109
|
+
says to run generator scripts with the venv interpreter and to re-probe there
|
|
110
|
+
before concluding a dependency is missing (agents probing system python were
|
|
111
|
+
reporting a false "missing deps"). Still no `pip install` on the fly. The
|
|
112
|
+
authoritative copy is `sdk/cloud/catalog/skills/office-artifacts/SKILL.md`
|
|
113
|
+
(`sdk/prismer/built-in-skills/` is the gitignored build-time copy, refreshed
|
|
114
|
+
in this checkout to stay byte-identical); the catalog source-contract
|
|
115
|
+
skills-manifest hash was re-pinned accordingly
|
|
116
|
+
(`catalog-source-contract.test.ts`).
|
|
117
|
+
|
|
118
|
+
### memory211 external review — P1 (chunk lane cap boundary)
|
|
119
|
+
|
|
120
|
+
- **fix(P1, security, asset raw text): the daemon `memory_search` chunk lane
|
|
121
|
+
was fail-OPEN for asset ACLs.** `runWorkspaceSearch` post-filtered hits with
|
|
122
|
+
`store.loadById(r.pageId)`, but a T3 chunk hit's `pageId` mirrors an ASSET id
|
|
123
|
+
and `memory_pages` never has that row — every chunk hit fell through the
|
|
124
|
+
"no page row" pass and was KEPT, so a scoped sub-agent cap on a shared daemon
|
|
125
|
+
could read any materialized asset's raw body (the hook recall path fails
|
|
126
|
+
CLOSED on the same shape). Chunk hits are now judged by the new
|
|
127
|
+
`canCapReadAsset(cap, asset)` boundary predicate (acl-predicate.ts) against
|
|
128
|
+
the asset's `visibility` + `ownerImUserId`, mirrored onto the daemon's own
|
|
129
|
+
asset projection: `asset_metadata_index` gains both columns (local schema
|
|
130
|
+
v17) fed by the SAME `/assets/index` DTO it already mirrors — no endpoint or
|
|
131
|
+
wire change — and the runner wires `resolveAssetAcl` (a synchronous local
|
|
132
|
+
read, no I/O in the search hot path). Strictly narrower than the cloud asset
|
|
133
|
+
ACL by design: `workspace` or own-asset grants, everything else DENIES
|
|
134
|
+
(`user` / `task:*` / `quarantined` / `aclJson` grants stay cloud-side). An
|
|
135
|
+
asset with no verdict (never synced, pre-v17 mirror row, older cloud) is
|
|
136
|
+
DENIED — fail-closed — so the lane degrades to wiki-only instead of leaking.
|
|
137
|
+
Tests: `memory-search-asset-cap.test.ts` (real RPC surface; the system cap
|
|
138
|
+
sees the same chunk the scoped cap is denied, proving the fixture) +
|
|
139
|
+
`canCapReadAsset` matrix in `memory-acl-predicate.test.ts` + the projection
|
|
140
|
+
round-trip in `asset-metadata-index.test.ts`; mutation (removing the
|
|
141
|
+
chunk-lane boundary) turns 2/4 red.
|
|
142
|
+
|
|
143
|
+
### memory211 review round 2 — P3 + P5
|
|
144
|
+
|
|
145
|
+
- **fix(P3, recall ranking): `mergeChunkHits` compared the COMPOSITE score
|
|
146
|
+
instead of the LEXICAL evidence it documented.** Its comment promised that
|
|
147
|
+
workspace priors never bury the upload, but `existing.score` already folds
|
|
148
|
+
recency (+0.2) / supersede (−0.8) / stale (−0.5) — so a freshly-touched wiki
|
|
149
|
+
page outranked a chunk that carried the query term far more strongly (the
|
|
150
|
+
exact D9 shape). The merge now reads `components.matchScore` (the
|
|
151
|
+
pre-adjustment BM25 relevance), the same basis the cloud `mergeRawTier`
|
|
152
|
+
compares on. `MemorySearchResult` gains the additive optional
|
|
153
|
+
`components: { matchScore }` decomposition (cloud parity), populated on both
|
|
154
|
+
the wiki FTS leg and the chunk leg.
|
|
155
|
+
- **fix(P5, prompt doctrine): the dispatched agent prompt still taught the
|
|
156
|
+
retired ">1M sharding" trigger.** `MEMORY_CORE_DIRECTIVE` (injected into
|
|
157
|
+
every dispatch) plus five comment sites (extract.ts ×3, runner.ts,
|
|
158
|
+
hook-server.ts) predate §6.9 裁决 4's 64K-character re-threshold, teaching an
|
|
159
|
+
agent to shard a source the gate now admits whole up to 64K characters. All
|
|
160
|
+
six now carry the 64K-character口径; `dispatch.test.ts` and
|
|
161
|
+
`memory-skill-degrammar.test.ts` pin it (and pin that "1M" never returns).
|
|
162
|
+
- **fix(P6, deliverable gate): an image-only deliverable's page was gated out
|
|
163
|
+
wholesale.** The pairing side declares a deliverable source on ANY
|
|
164
|
+
`prismer://asset/<id>` occurrence — including the `<figure><img src>` form the
|
|
165
|
+
extraction prompt itself teaches for IMAGE/CHART assets — while G9 recognized
|
|
166
|
+
only `<a rel="derived-from">`. The gate now accepts the inline image pointer
|
|
167
|
+
as the same materializable jump pointer (an image cannot be distilled into
|
|
168
|
+
prose; its embed IS the jump), and the 422 message teaches both forms. An
|
|
169
|
+
`<img>`/`<a>` pointing at a DIFFERENT asset, or an untyped `<a>`, still fails.
|
|
170
|
+
|
|
171
|
+
- **fix(recall): the T3 chunk lane collapsed an upload's chunks to ONE hit**
|
|
172
|
+
(found as the W4 span-mint suite's order-dependent red). The lane deduped its
|
|
173
|
+
FTS candidates by `path`, but every chunk of an asset shares the same
|
|
174
|
+
`asset:<id>#<hash>` path — so an 11-chunk upload recalled exactly one chunk,
|
|
175
|
+
whichever the (near-tied) bm25 ordering surfaced first, and mint-即升层 was
|
|
176
|
+
invisible whenever the cited chunk lost that coin flip. Deduping is now by
|
|
177
|
+
chunk identity (`assetId#ordinal`), and bm25-equal chunks tie-break by ordinal
|
|
178
|
+
so the window cannot reshuffle between recalls on an unchanged store.
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
### memory211/01 §6.11 — W7 behavioural-closure wave (read-side telemetry)
|
|
182
|
+
|
|
183
|
+
- **Read-side `[memory-trace]` pipeline (D11-5)** — search / load / browse
|
|
184
|
+
(place-context) / the search miss-lane's navigation now emit the same
|
|
185
|
+
`[memory-trace] stage=…` lines the write lane has logged since memory203/18
|
|
186
|
+
R8.2 (13 write sites, 0 read sites before W7). Lines carry the query COUNT,
|
|
187
|
+
`navigation_used`, the tier histogram, result count, duration and a traceId
|
|
188
|
+
(`?traceId=` honoured, else minted `rd_…`). The query TEXT never enters a
|
|
189
|
+
read trace. Full-volume local stderr, no sampling.
|
|
190
|
+
- **CLI recall telemetry (D11-1)** — `prismer memory recall/search` enqueues one
|
|
191
|
+
`recall_pull` (metadata `via: 'cli'`) into the daemon outbox via the existing
|
|
192
|
+
`/local/memory/observability/emit` pass-through, attributed to the presented
|
|
193
|
+
cap's `sub` claim (no subject ⇒ no event, never a fabricated actor). Best-effort:
|
|
194
|
+
a telemetry failure never changes the command's exit code. NOTE: a scoped-cap
|
|
195
|
+
CLI run now produces both a daemon tool-channel row and this `via:'cli'` row for
|
|
196
|
+
the same intent — consumers filter on `via`.
|
|
197
|
+
- **CLI batch faces (D11-2)** — `prismer memory search --queries '["a","b"]'`
|
|
198
|
+
(array passed through; the daemon owns the ≤8 bound and its `truncated` flag)
|
|
199
|
+
and `prismer memory load-batch <paths…>` (≤10 point loads, per-path verdict,
|
|
200
|
+
exit 1 only on a total miss). New `daemonGet` keeps a load 404 as a real
|
|
201
|
+
per-path verdict instead of `tryDaemon`'s route-probing 404-continue. Contract:
|
|
202
|
+
`maxItems` now survives into the regenerated plugin schema artifact; the
|
|
203
|
+
memory SKILL CLI appendix teaches both faces.
|
|
204
|
+
- **Tool-sequence ring + turn metrics (D11-4)** — new
|
|
205
|
+
`daemon/memory/tool-sequence.ts`: per-workspace ring (cap 200, in-process)
|
|
206
|
+
recording one entry per search/load/browse/write the daemon serves. PRIVACY
|
|
207
|
+
BOUNDARY: verb + page path + duration + timestamp + the `navigation` /
|
|
208
|
+
query-COUNT flags only; `ALLOWED_ENTRY_KEYS` is a closed set enforced by test
|
|
209
|
+
and the query text is unrecoverable. The dispatch finally block reads the
|
|
210
|
+
turn's slice and emits `turn.navigation_used` (browse, or a load after a
|
|
211
|
+
miss-lane search — repeated searches read 0) and `turn.shortcuts_taken`
|
|
212
|
+
(loads with no preceding browse), only when the turn made a memory call.
|
|
213
|
+
|
|
214
|
+
### memory211/01 §6.9 — W6 (owner 终裁执行)
|
|
215
|
+
|
|
216
|
+
- memory211/01 §6.9 裁决 2 (W6) — **digest budget cap replaced by the 32K extreme
|
|
217
|
+
guard** (`MEMORY_DIGEST_EXTREME_GUARD_TOKENS`, Nacos/env key
|
|
218
|
+
`PRISMER_MEMORY_DIGEST_BUDGET_TOKENS` kept as the knob name). Full injection is
|
|
219
|
+
the default; the guard is the only cut and it marks itself. The INDEX-TOC is no
|
|
220
|
+
longer pre-budgeted to a share of the guard — that share silently
|
|
221
|
+
skeletonized the map of a workspace whose digest was still far below the
|
|
222
|
+
ceiling (covered by a red/green test in `memory-w3-upload-pipeline`).
|
|
223
|
+
|
|
224
|
+
- memory211/01 §6.9 裁决 1 (W6 fix round 1) — **the daemon miss navigation now
|
|
225
|
+
respects the per-cap read boundary**. `runWorkspaceSearch` filtered hits
|
|
226
|
+
through `canCapReadPage` but passed `navigation.startPoints` straight through,
|
|
227
|
+
so a hub/INDEX the actor cannot read still surfaced as path + title +
|
|
228
|
+
childrenCount. Start points are now filtered by the same predicate (an
|
|
229
|
+
unreadable entry is dropped; if none survive the navigation is omitted) —
|
|
230
|
+
matching the cloud side, which already applies `allowedPageIds` + the per-row
|
|
231
|
+
read verdict.
|
|
232
|
+
|
|
233
|
+
- memory211/01 §6.9 裁决 5 ①② (W6) — the ingest bookkeeping gaps:
|
|
234
|
+
① **ingest terminal jump** (`reconcileWorkspaceIngestTasks` /
|
|
235
|
+
`reconcileIngestTasks`): a completed ingest task flips its asset to
|
|
236
|
+
`ingested`, a failed/cancelled one requeues it as `pending` with the attempt
|
|
237
|
+
recorded on the asset row, and the third failure parks it `failed` (cap 3,
|
|
238
|
+
mirroring the dream merge leg). The scheduler's ingest drain runs the
|
|
239
|
+
reconciliation first. Fix round 1: a LIVE (non-terminal) task outranks a stale
|
|
240
|
+
terminal one, so a gen1 failure no longer gets re-applied over a gen2 that is
|
|
241
|
+
still running — that misjudgement burned the retry budget twice, surfaced
|
|
242
|
+
gen1's error as if fresh, and could park `failed` an asset whose current
|
|
243
|
+
generation was fine (a false stall, since the drain never sweeps `failed`).
|
|
244
|
+
② **`prismer memory ingest`** — the workspace upload-ingest operational switch
|
|
245
|
+
(owner/admin, cloud `/api/im/memory/ingest-settings`, merge-safe: it patches
|
|
246
|
+
one metadata key instead of replacing the object).
|
|
247
|
+
|
|
248
|
+
- memory211/01 §6.9 裁决 5 ③ (W6) — **daemon-first search scaffold removed** rather than wired: `setDaemonRpcSender`
|
|
249
|
+
had no responder (nothing in `src/im/ws/` ever registered presence or answered a
|
|
250
|
+
`memory.search` RPC), so `FF_MEMORY_SEARCH_DAEMON_FIRST` was a flag whose ON path
|
|
251
|
+
was unreachable in production. Cloud is the single authoritative recall path;
|
|
252
|
+
`SearchHit` collapses back to the cloud payload shape. Wiring daemon-first recall
|
|
253
|
+
would be a new designed seam (WS route + trace + parity tests), not a resurrected
|
|
254
|
+
stub.
|
|
255
|
+
|
|
256
|
+
- memory211/01 §6.9 裁决 4 (W6) — **sharding threshold 1M → 64K characters**.
|
|
257
|
+
The daemon deliverable gate's constant is renamed
|
|
258
|
+
`SHARDING_THRESHOLD_BYTES` → `SHARDING_THRESHOLD_CHARS` (`64 * 1024`) because
|
|
259
|
+
the ruling's 口径 is characters, not bytes (a CJK source is ~3 bytes per
|
|
260
|
+
char). The gate now applies the ceiling to the PAGE BODY in characters
|
|
261
|
+
(每页 ≤64K, 422 `sharding_required`); the cloud pipeline plans the source half
|
|
262
|
+
from the exact chunk character count.
|
|
263
|
+
|
|
264
|
+
- memory211/01 §6.9 裁决 3 (W6) — **memory/SKILL.md regenerated for the new loop**
|
|
265
|
+
(three-stage recall protocol: structure route → semantic search → navigation
|
|
266
|
+
miss-fallback; batch `queries[]` usage; the wiki/asset/raw tier trust bands;
|
|
267
|
+
copy + reference writing rules; 64K-char sharding). The skill now teaches
|
|
268
|
+
"startPoints are NOT answers — walk them". The prompt-size receipt budget is
|
|
269
|
+
rebased 360 → 460 lines for the mandated content (owner ruling: rebuild without
|
|
270
|
+
historical baggage, request budget rather than drop content). The plugin tool
|
|
271
|
+
schema artifact was regenerated from the TS spec (memory_search description now
|
|
272
|
+
teaches the navigation contract).
|
|
273
|
+
|
|
274
|
+
- memory211/01 §6.9 裁决 2 (W6) — **digest budget cap removed**: the W3-era
|
|
275
|
+
~2K-token routine cap on the turn-start digest is GONE. `buildMemoryDigest`
|
|
276
|
+
now injects the FULL map (INDEX-TOC + every hub summary); the only remaining
|
|
277
|
+
bound is the extreme guard `MEMORY_DIGEST_EXTREME_GUARD_TOKENS = 32_000`
|
|
278
|
+
(env/Nacos key `PRISMER_MEMORY_DIGEST_BUDGET_TOKENS` keeps its name — it now
|
|
279
|
+
tunes the guard, not a routine cap), above which the body is cut with the
|
|
280
|
+
existing observable `digest truncated` marker. Determinism and content-hash
|
|
281
|
+
versioning are unchanged.
|
|
282
|
+
- memory211/01 §6.9 裁决 1 (W6) — **miss-path navigation redesign**: on a text
|
|
283
|
+
miss `memory_search` no longer returns the structural lane dressed up as a
|
|
284
|
+
ranked hit list (a miss carries no rank evidence — the old band was a random
|
|
285
|
+
order). The response now carries `navigation: {reason:'text-miss',
|
|
286
|
+
startPoints:[{path,title,pageType,childrenCount,why:'structural-entry'}],
|
|
287
|
+
guidance}` (INDEX + hubs, children first) on the daemon response, the TS
|
|
288
|
+
adapter types (`MemoryNavigation`) and — via the plugin's JSON passthrough —
|
|
289
|
+
the tool result. `hybrid()` keeps its shape for existing callers; use
|
|
290
|
+
`hybridWithNavigation()` for the payload. The W1a dynamic miss band
|
|
291
|
+
(`GRAPH_MISS_PROMOTED_BAND` / `applyMissBands`) is removed: graph hits now
|
|
292
|
+
exist only as a supplement to real FTS evidence and always land in the sink
|
|
293
|
+
band. Hits with lexical evidence (FTS pages, T3 chunks) are unchanged.
|
|
294
|
+
- memory211/01 v3.3 W5 (轴H 单源契约 + 轴G section curate 动词):
|
|
295
|
+
- **Single schema source (D1 extinction)**: the five memory tool schemas in
|
|
296
|
+
`plugins/memory/prismer/__init__.py` are no longer hand-written — they are
|
|
297
|
+
generated from the frozen TS spec (`src/adapters/memory-tools.ts`) into
|
|
298
|
+
`tool-schemas.generated.json` and LOADED at runtime. Regenerate with
|
|
299
|
+
`npx tsx scripts/memory211/generate-memory-tool-contract.ts`; the
|
|
300
|
+
three-surface contract test
|
|
301
|
+
(`scripts/__tests__/memory211-tool-contract.test.ts` on the cloud repo)
|
|
302
|
+
fails on any divergence between tool schema, plugin handler, CLI flags and
|
|
303
|
+
the SKILL.md parameter table. The placement parameter is now camelCase
|
|
304
|
+
`parentHubPath` on EVERY surface (was `parent_hub_path` on the Hermes
|
|
305
|
+
plugin while the skill taught `parentHubPath`).
|
|
306
|
+
- Plugin forwards the batch `queries` + `pageType` memory_search params
|
|
307
|
+
(W1a leftover) and the `visibility` write param.
|
|
308
|
+
- Daemon RPC: `/local/memory/curate` routes the new `section_merge` /
|
|
309
|
+
`section_supersede` / `rewire` ops to cloud; new `/local/memory/delete`
|
|
310
|
+
(cloud-adjudicated soft delete + local mirror invalidation) and
|
|
311
|
+
`/local/memory/sync` (alias of flush).
|
|
312
|
+
- CLI: `prismer memory delete|sync` now hit the real daemon routes (they
|
|
313
|
+
previously probed only legacy paths no daemon exposes), `memory write
|
|
314
|
+
--title`, `memory curate promote-to-hub --child-paths`, and three new
|
|
315
|
+
subcommands `curate section-merge|section-supersede|rewire`.
|
|
316
|
+
|
|
317
|
+
- memory211/01 v3.3 W4 (轴F 溯源闭合), daemon runtime side — **lazy span mint +
|
|
318
|
+
mint 即升层**:
|
|
319
|
+
- Local store schema V5 → V6: `asset_chunks` gains `sid` (additive column,
|
|
320
|
+
pragma-guarded ALTER). NULL = the chunk was never cited by a distilled page
|
|
321
|
+
= still T3 `raw`; non-NULL = cited + minted = T2 `asset`.
|
|
322
|
+
- New `daemon/memory/span-mint.ts`: after an agent-authored write
|
|
323
|
+
(`memory_write`) or a post-turn extracted page commits, the content is
|
|
324
|
+
scanned for asset provenance pointers (`prismer://workspace/<ws>/asset/<sha256>`
|
|
325
|
+
scoped, `prismer://asset/<id>` bare, `asset:<id>#<hash>` token — each with an
|
|
326
|
+
optional `#chunk-<ordinal>` span anchor). Every cited chunk of the asset's
|
|
327
|
+
CURRENT revision gets a DETERMINISTIC span sid (`sp-<16hex>`, derived from
|
|
328
|
+
assetId + contentHash + ordinal + text hash — byte-identical to the cloud
|
|
329
|
+
twin `src/im/services/memory-span-sid.ts#mintChunkSid`, so both sides
|
|
330
|
+
converge on one identity), the local mirror row is promoted, and the mint
|
|
331
|
+
rides the SAME idempotent `asset.chunk.upsert` channel with the `sid`
|
|
332
|
+
attached so the cloud authoritative row promotes too. The mint is a derived
|
|
333
|
+
projection: it never throws into the write path and is a one-way latch
|
|
334
|
+
(a sid-less replay never un-mints).
|
|
335
|
+
- Local recall honours the promotion: a chunk hit whose row carries a sid
|
|
336
|
+
surfaces as `tier:'asset'` with `spanSid`, and no longer pays the
|
|
337
|
+
`RAW_TRUST_DISCOUNT` (spec 轴C: T2 sits above T3).
|
|
338
|
+
|
|
339
|
+
- memory211/01 v3.3 W3 (upload ingestion pipeline + turn-start warmup), daemon
|
|
340
|
+
runtime side:
|
|
341
|
+
- **T3 raw-chunk mirror (轴D, F2 裁决)** — local store schema V4 → V5 adds
|
|
342
|
+
`asset_chunks` + `asset_chunks_fts` (additive migration, no rebuild). A
|
|
343
|
+
materialized upload is chunked locally (deterministic ~2K-token chunks with
|
|
344
|
+
10% overlap; byte-identical contract with the cloud chunker) and recalled as
|
|
345
|
+
`tier:'raw'` hits, so offline recall now includes uploads, not only wiki
|
|
346
|
+
pages. The same rows ride the existing memory outbox to the cloud
|
|
347
|
+
authoritative `im_asset_chunks` via the new `asset.chunk.upsert` event
|
|
348
|
+
(idempotent per `workspaceId:assetId:contentHash:ordinal`).
|
|
349
|
+
- **pdf ingestion** — pdf uploads go through the bundled liteparse `lit`
|
|
350
|
+
(digital fast path `--no-ocr`, OCR fallback, 60s kill). A missing/failed
|
|
351
|
+
parser SKIPS loudly; it never throws into the `asset.materialize` ack.
|
|
352
|
+
- **Stable memory digest (轴E)** — new `daemon/memory/digest.ts` builds the
|
|
353
|
+
turn-start digest (INDEX-TOC + hub one-liners): deterministic (path-sorted,
|
|
354
|
+
no timestamps/volatile fields), sha256 versioned, bounded to ~2K tokens
|
|
355
|
+
(`PRISMER_MEMORY_DIGEST_BUDGET_TOKENS`), with an observable truncation
|
|
356
|
+
marker. `FF_MEMORY_INDEX_INJECT_ENABLED` now defaults ON (explicit
|
|
357
|
+
`false|0|off` to disable).
|
|
358
|
+
- **Hermes assembly (轴E)** — the adapter injects the digest at the TAIL of
|
|
359
|
+
the per-turn `instructions` (i.e. the tail of the composed system message)
|
|
360
|
+
so the rarely-changing blocks above it keep their prompt-cache prefix; the
|
|
361
|
+
block is byte-stable for a given digest version.
|
|
362
|
+
- **Miss-lane anchors (轴D 追加项 D①)** — when the text leg misses entirely,
|
|
363
|
+
the daemon miss lane now RETURNS the INDEX/hub anchors as `via:'graph'`
|
|
364
|
+
hits (they were seeds only) and a routing term (childrenCount) keeps a hub
|
|
365
|
+
above its own leaves in the elevated band.
|
|
366
|
+
- **Review fixes** — the deliverable gate no longer strips
|
|
367
|
+
`contentHash`/`sizeBytes` from a declared source (the G10 sharding trigger
|
|
368
|
+
was unreachable from extraction), both extraction legs report
|
|
369
|
+
`extractionGatedOut`/`shardingRequired`, and the dead `distillOverBudget`
|
|
370
|
+
counter is gone.
|
|
371
|
+
|
|
372
|
+
- memory211/01 v3.3 W2 (内容模型 + 写入门 + CJK 分词) — the extraction-thinning
|
|
373
|
+
reversal, on the daemon runtime side:
|
|
374
|
+
- **CJK recall fixed (W2 #7, W0 baseline finding)** — the daemon FTS index
|
|
375
|
+
gains a `cjk` bigram projection column (`memory_fts.cjk`, store schema
|
|
376
|
+
V4 with a drop/recreate/re-index migration) and the query side offers the
|
|
377
|
+
query's own CJK bigrams as FTS alternatives. Before: FTS5's unicode61
|
|
378
|
+
tokenizer kept a whole CJK run as ONE token, so a 2-char Chinese term inside
|
|
379
|
+
a longer run could never match — a pure-Chinese query was a guaranteed zero.
|
|
380
|
+
ASCII queries are byte-for-byte unchanged.
|
|
381
|
+
- **G10 redefined in place (轴A / §0.1)** — the anti-copy budget
|
|
382
|
+
(`min(32KiB, 10% × source)` → 422 `distill_over_budget`) is GONE; copy +
|
|
383
|
+
reference means a distilled page may carry the source's near-full content.
|
|
384
|
+
The only budget left is the sharding trigger: a source over 1,000,000 bytes
|
|
385
|
+
is refused with 422 `sharding_required` (the W5 sharding pipeline will own
|
|
386
|
+
the actual splitting). G9 (derived-from pointer) is unchanged.
|
|
387
|
+
- **one gate, two surfaces (轴H / D3)** — the deliverable gate moved to
|
|
388
|
+
`daemon/memory/deliverable-gate.ts` and the MAIN extraction path now runs
|
|
389
|
+
it: a page that declares a deliverable source but references none of them is
|
|
390
|
+
dropped loudly (trace + `extractionGatedOut` counter) instead of written.
|
|
391
|
+
- **write gates ②③ (轴H)** — `memory_write` now rejects a NEW PKF page with no
|
|
392
|
+
frontmatter description (422 `description_required`) and a body that fails
|
|
393
|
+
the bundled `validatePkf` structure audit (422 `pkf_invalid`, warnings never
|
|
394
|
+
reject). Markdown (legacy input) is exempt from both, edits of existing
|
|
395
|
+
pages are exempt from the description gate, and the PKF gate runs AFTER the
|
|
396
|
+
bare-asset upgrade so the taught `prismer://asset/<id>` pointer form is not
|
|
397
|
+
re-flagged. The placement gate additionally honours an existing child-of
|
|
398
|
+
graph edge (轴H ①).
|
|
399
|
+
- **extraction prompt re-cast (轴A)** — the "pointers not copies" doctrine is
|
|
400
|
+
replaced by copy + reference, with two acceptance rules the model must
|
|
401
|
+
self-check: LEXICON COVERAGE (proper nouns / codes / 中英对照 terms survive
|
|
402
|
+
verbatim) and SECTION EDGES (typed `<a rel>` links anchored at `#section`,
|
|
403
|
+
which the cloud materializer lands as `im_memory_links.sourceSection` /
|
|
404
|
+
`targetSection`). `MAX_PAGES_PER_TURN` and the 32768 extract budget are
|
|
405
|
+
unchanged, and the recorded `lexicon_coverage` metric
|
|
406
|
+
(`extractLexicon` / `coverage`) is the W2 acceptance measure — recorded,
|
|
407
|
+
never gating.
|
|
408
|
+
- memory search surface re-cast (memory211/01 §3 轴C W1a) — the recall payload
|
|
409
|
+
now carries what an agent needs to decide 直读 vs 多跳 without a second call,
|
|
410
|
+
and recall no longer dies when the text leg does:
|
|
411
|
+
- **hop-decision payload**: every `memory_search` hit adds `pagePath`
|
|
412
|
+
(mirror of the frozen `path`), `hubPath` (reverse `child-of` placement,
|
|
413
|
+
null for a root/INDEX page or a dangling edge), `version`,
|
|
414
|
+
`sectionAnchor`/`sectionPreview`, `tier: 'wiki'` (the T1-only enum is
|
|
415
|
+
pinned now; T2/T3 land with the W3 ingestion pipeline),
|
|
416
|
+
`inboundLinkCount`, `childrenCount` and `outboundPreview[]`. All of it is
|
|
417
|
+
aggregated IN PLACE from the existing `memory_links` mirror
|
|
418
|
+
(`MemoryStore.pageGraphContext`) — no new table, no new sync channel.
|
|
419
|
+
Legacy fields (`path`/`title`/`snippet`/`score`/`tokenCount`) are
|
|
420
|
+
untouched, so an un-upgraded consumer keeps working.
|
|
421
|
+
- **dynamic graph band** (spec open ruling point 3, W1 experiment): the old
|
|
422
|
+
constant "every graph hit one unit below the weakest FTS hit" is replaced.
|
|
423
|
+
When the text leg has hits, graph hits stay the supplementary band (the
|
|
424
|
+
old invariant still holds and is still tested). When text misses
|
|
425
|
+
ENTIRELY — the memory211 §1 paraphrase failure, where the answering page
|
|
426
|
+
shares zero tokens with the query — the graph leg becomes the recall lane:
|
|
427
|
+
it seeds from INDEX + hubs and scores a structurally-anchored hit
|
|
428
|
+
(≥1 inbound link or a resolvable hub) in the normal 0-1 band instead of
|
|
429
|
+
the sub-zero decay band. `graph: false` keeps the legacy pure-FTS path.
|
|
430
|
+
- **batch recall**: `GET /local/memory/search` accepts `queries` (JSON
|
|
431
|
+
array, ≤8; over-limit is sliced with `truncated: true`) and returns
|
|
432
|
+
`resultsByQuery: [{query, results}…]` plus the legacy `query`/`results`
|
|
433
|
+
fields, which stay the FIRST query's so an old consumer sees today's
|
|
434
|
+
shape. One `recall_pull` event is emitted per query (a batch carries N
|
|
435
|
+
recall intents, not one). The single-`q` form is a batch of one.
|
|
436
|
+
- **tree-shaped browse**: every hub in `GET /local/memory/place-context`
|
|
437
|
+
(and in the `memory_browse` payload) now carries
|
|
438
|
+
`children: [{path, title}]` from the local `child-of` edges; `nearest` is
|
|
439
|
+
deliberately unchanged (graph-distance ordering is deferred to W1b).
|
|
440
|
+
- **load 离线 links**: when the cloud does NOT answer `GET
|
|
441
|
+
/memory/pages/:id/links` (unreachable, non-2xx, or never wired), the load
|
|
442
|
+
response now falls back to the LOCAL link mirror instead of silently
|
|
443
|
+
omitting `links`, assembled in the same `{outbound, backlinks}` envelope.
|
|
444
|
+
An authoritative cloud answer is never overridden.
|
|
445
|
+
- Covered by `test/memory-search-payload.test.ts` (payload fields + 升带,
|
|
446
|
+
with the re-band and the offline-links fallback each carrying a mutation
|
|
447
|
+
negative control), `test/memory-search-batch.test.ts`,
|
|
448
|
+
`test/memory-browse-children.test.ts` and
|
|
449
|
+
`test/memory-load-links-offline.test.ts`.
|
|
450
|
+
- metric outbox replay (B-P1a): the per-agent `metrics.jsonl` offline outbox
|
|
451
|
+
(written by `daemonMetricEmit` whenever cloud is unreachable) finally has a
|
|
452
|
+
reader. `MetricOutboxReplayWorker` (new `daemon/metric-outbox-replay.ts`,
|
|
453
|
+
wired into the runner lifecycle) drains it into `POST /api/im/metrics/batch`
|
|
454
|
+
— the same endpoint the online path uses, with the same per-event field
|
|
455
|
+
shape (`eventType` splits at the first dot into `namespace.name`; value /
|
|
456
|
+
dims / ts pass through as serialised; the entry's top-level workspace /
|
|
457
|
+
project / task mirrors repair dims lost in the payload). Delivery is
|
|
458
|
+
**at-least-once**: the file is frozen with `renameSync` BEFORE it is read and
|
|
459
|
+
unlinked only after every ≤500-event batch chunk came back HTTP-ok, so a
|
|
460
|
+
crash anywhere replays the file (additive metrics tolerate the duplicate).
|
|
461
|
+
Freezing before reading (rather than truncating in place) is what makes a
|
|
462
|
+
line appended by a concurrent emit during the cloud round-trip survive — it
|
|
463
|
+
lands in a fresh live file and drains next tick. Bad-JSON lines and non
|
|
464
|
+
`metric.event` kinds are counted, logged and dropped (they would retry
|
|
465
|
+
forever otherwise); cloud-side per-event rejections are likewise logged and
|
|
466
|
+
dropped, HTTP-level failures retain the whole file with 5s→10min backoff and
|
|
467
|
+
a warn→error escalation after 5 failed ticks. Triggered on daemon startup and
|
|
468
|
+
every 60s (timer unref'd); no notify hook on the write path — offline metrics
|
|
469
|
+
are additive and a ≤60s lag is acceptable. Covered by
|
|
470
|
+
`test/metric-outbox-replay.test.ts` (batch content, retention + retry,
|
|
471
|
+
empty/missing no-op, chunking, backoff escalation, and a mutation negative
|
|
472
|
+
control proving a mid-flight append is not lost under truncate).
|
|
473
|
+
- turn metrics (B-P0, agent performance pipeline): the daemon now emits eight
|
|
474
|
+
turn-level metric events from the dispatch `finally` block alongside the
|
|
475
|
+
existing `agent.dispatch` / `skill.invoked` batch — `turn.count`,
|
|
476
|
+
`turn.tokens_input`, `turn.tokens_output`, `turn.tokens_cache_read`,
|
|
477
|
+
`turn.tokens_cache_write`, `turn.duration_ms`, `turn.first_event_ms`,
|
|
478
|
+
`turn.tool_calls`. Dims: required `workspaceId` + `agentId`; optional
|
|
479
|
+
`conversationId`, `taskId`, `model`, `provider`, `status` (`ok`|`error`).
|
|
480
|
+
Usage comes from the TERMINAL attempt's hermes usage only — retry inflation
|
|
481
|
+
stays in `agent.dispatch`. Token rows are emitted per-field only when the
|
|
482
|
+
terminal attempt reported them. `turn.first_event_ms` is new instrumentation
|
|
483
|
+
on the hermes sessions lane: `consumeSessionsSse` captures the first
|
|
484
|
+
stall-guard activation (gateway acks excluded) and derives it against the
|
|
485
|
+
dispatcher's `startedAt`; `TaskResult.metrics` carries the new optional
|
|
486
|
+
`firstEventMs`. Emit failures stay non-fatal (observability path). Covered
|
|
487
|
+
by `test/daemon-metric-emit.test.ts` + `test/sessions-sse-first-event.test.ts`.
|
|
488
|
+
- config bootstrap (B1): 403 `SETUP_PIN_PENDING` (cloud: the workspace setup
|
|
489
|
+
runner has provisioned the pod but has not persisted the canonical API-key
|
|
490
|
+
pin yet) now backs off with the existing curve instead of terminal-stopping,
|
|
491
|
+
bounded by `SETUP_PIN_RETRY_WINDOW_MS` (5 min) after which it degrades to the
|
|
492
|
+
ordinary terminal 403. `BootstrapFetchResult` carries the cloud `error.code`
|
|
493
|
+
and `BootstrapState.pinPendingSince` holds the window anchor — cleared by
|
|
494
|
+
`resetBootstrapStopped` AND on every successful fetch (per-episode, so a
|
|
495
|
+
later absent-pin episode gets a fresh window). Fixes fresh workspaces coming
|
|
496
|
+
up memoryless when the
|
|
497
|
+
pod daemon's first B1 fetch raced the pin persist (2026-09-02 ws-72d282f9).
|
|
498
|
+
Covered by `test/config-bootstrap.test.ts`.
|
|
499
|
+
- memory write: write-time bare-asset-URI upgrade (pkf v1.1 §4.7). The
|
|
500
|
+
memory skill / dispatch guidance / extraction prompt teach the bare
|
|
501
|
+
`prismer://asset/<id>` pointer form (G9), but v1.1 strict validation
|
|
502
|
+
rejects it on read-back (`bare-asset-uri`, compat-read only) — the
|
|
503
|
+
canonical authoring form is `prismer://workspace/<wsId>/asset/<contentHash>`.
|
|
504
|
+
`POST /local/memory/write` now deterministically rewrites every bare
|
|
505
|
+
href/src pointer AFTER the G9 gate and BEFORE the page lands (store + outbox
|
|
506
|
+
up-sync both carry the upgraded form; rel / link text / quote style /
|
|
507
|
+
attribute order survive). assetId → contentHash resolves from the workspace
|
|
508
|
+
AssetMetadataIndex (one throttled delta pull forced on a miss); unresolvable
|
|
509
|
+
assets degrade to the workspace-scoped assetId form
|
|
510
|
+
`prismer://workspace/<ws>/asset/<assetId>` (still v1.1-valid) — the write is
|
|
511
|
+
NEVER blocked. Counters: `memory.counters.bareUriUpgraded` /
|
|
512
|
+
`bareUriScopedDegrade` + `[memory-trace] stage=bare_uri_upgrade`. Covered by
|
|
513
|
+
`test/memory-write-bare-uri-upgrade.test.ts`.
|
|
514
|
+
|
|
515
|
+
- daemon local gateway (desktop205 alignment round, 2026-08-31):
|
|
516
|
+
- `PRISMER_LOCAL_GATEWAY` is now injected by the desktop embedded fork —
|
|
517
|
+
the local data plane (SWR reads, SSE mirror, outbox writes) activates on
|
|
518
|
+
desktop instead of being structurally unreachable.
|
|
519
|
+
- user-Bearer acceptance: the gateway now serves the renderer's USER session
|
|
520
|
+
JWT / sk-prismer key in addition to the daemon key. Introspected against
|
|
521
|
+
the cloud (`/api/im/me`) and BOUND TO THE DAEMON OWNER — any other valid
|
|
522
|
+
account token is refused (review hardening: previously any valid token
|
|
523
|
+
could read the owner's SWR cache from any same-machine page via the
|
|
524
|
+
permissive CORS origin). Cache is keyed by token SHA-256 digest with
|
|
525
|
+
positive/negative TTLs; the raw bearer is never retained.
|
|
526
|
+
- CORS: Allow-Headers now includes X-IM-Workspace/X-Request-Id/
|
|
527
|
+
X-Idempotency-Key and Expose-Headers includes X-Data-Stale (the desktop
|
|
528
|
+
offline indicator can now light on gateway-served responses).
|
|
529
|
+
- SWR revalidation for /api/im/conversations is scoped to the daemon's
|
|
530
|
+
declared workspace (`workspaceId()` dep); passthrough forwards the
|
|
531
|
+
caller's X-IM-Workspace/X-Request-Id so multi-workspace identity survives
|
|
532
|
+
the loopback hop.
|
|
533
|
+
|
|
534
|
+
- hermes adapter: `stopHermesGatewayForProfile` is now a logged no-op when the
|
|
535
|
+
profile config fails `HermesProfileConfigSchema` (e.g. ACS sandbox fixtures
|
|
536
|
+
carry no `apiKey`). Previously the uncaught ZodError escaped the
|
|
537
|
+
`ServicePool.invalidate` disposer in `Runner.rebindHermesMemoryCapabilities`,
|
|
538
|
+
crashed the daemon (exit 1), and after two retries the bootstrapper
|
|
539
|
+
blacklisted the whole runtime bundle — sandbox healthz stuck at 503
|
|
540
|
+
(reproduced 2026-08-30 by the dev-local fxr acceptance fixture). Covered by
|
|
541
|
+
`test/hermes-stop-gateway-noop.test.ts`.
|
|
542
|
+
- sandbox-manager: removed the orphaned `resolve_daemon_id` wrapper (its only
|
|
543
|
+
caller switched to `resolve_daemon_id_for_boot` in d74e00074); the dead
|
|
544
|
+
symbol failed the dev-local rust-manager clippy gate (`-D warnings`).
|
|
545
|
+
- Memory post-turn extraction no longer mints pages from agent
|
|
546
|
+
self-introductions (kickoff / "你们都能干什么" turns): a deterministic
|
|
547
|
+
`agent_self_intro` skip in `shouldSkipExtraction` (narrow shape match on the
|
|
548
|
+
reply head; an explicit user retention contract still passes) plus an
|
|
549
|
+
`AGENT SELF-DESCRIPTIONS ARE NOT DURABLE` rule in the extraction prompt.
|
|
550
|
+
2026-08-29 test incident: one role page per self-intro reply in a 3-agent
|
|
551
|
+
kickoff (see docs/product210/03 附录 A-4). Tests:
|
|
552
|
+
`sdk/prismer/test/memory-extract-self-intro.test.ts`.
|
|
553
|
+
- Filesystem-checkpoint restores now rebind every cached Hermes gateway after
|
|
554
|
+
the per-boot Cloud Memory authority snapshot first becomes available. The
|
|
555
|
+
gateway is atomically invalidated and prewarmed with a freshly minted
|
|
556
|
+
`PRISMER_MEMORY_CAP`, including when the Runtime config bytes are unchanged.
|
|
557
|
+
Explicit Memory GET tools also preserve structured daemon 401/403 responses
|
|
558
|
+
instead of misreporting them as empty data or `daemon_unreachable`. Managed
|
|
559
|
+
Hermes plugins now stage and directory-swap upgrades, so 0444 files restored
|
|
560
|
+
from a Checkpoint no longer turn a valid Runtime update into EACCES.
|
|
561
|
+
|
|
562
|
+
- Replayed Hermes profile preparation now treats an already byte-identical
|
|
563
|
+
`prismer-recall` plugin as a no-write success. Restored/hardened profile
|
|
564
|
+
trees no longer emit false `EACCES` install warnings on repeated
|
|
565
|
+
`host.acked`, while changed plugin bytes still fail visibly if unwritable.
|
|
566
|
+
|
|
567
|
+
- ACS workspace adoption now overlaps independent agents with a sliding,
|
|
568
|
+
bounded three-agent worker pool after the initial 0/0 `host.declare`;
|
|
569
|
+
profiles belonging to one agent remain ordered, failures remain isolated,
|
|
570
|
+
and the daemon emits one aggregate adoption timing before the final roster
|
|
571
|
+
declaration.
|
|
572
|
+
|
|
573
|
+
- Aligned Runtime package metadata with monorepo version `2.2.39` for a new
|
|
574
|
+
immutable sandbox image containing the workspace-neutral template marker and
|
|
575
|
+
clone identity behavior; Runtime OTA publication remains a separate chain.
|
|
576
|
+
|
|
577
|
+
- Promoted the canonical `remotion` skill into the common Agent baseline and
|
|
578
|
+
calibrated its sandbox workflow against current Remotion guidance. Rendered
|
|
579
|
+
media now has an explicit artifacts-dir → `cloud deliver` receipt contract,
|
|
580
|
+
plus version/composition, async-asset, deterministic-media, writable-cache,
|
|
581
|
+
browser-compatibility, adjacent-frame, and probe gates.
|
|
582
|
+
|
|
583
|
+
- ACS environment-template sources now initialize the full adapter floor without
|
|
584
|
+
opening Cloud transport or binding a workspace. Template clones discard the
|
|
585
|
+
source daemon identity and resume normal bootstrap before Cloud dispatch.
|
|
586
|
+
- Objective dispatch prompts now preserve the task/objective mirror fields used
|
|
587
|
+
by Cloud OKR flows, so hosted runtime turns keep objective context attached
|
|
588
|
+
through dispatch and post-turn durability.
|
|
589
|
+
- Sandbox runtime readiness now requires the daemon health identity to match the
|
|
590
|
+
claimed workspace and daemon id before Cloud accepts an ACS dialback as ready.
|
|
591
|
+
|
|
592
|
+
- Image generation delivery is now single-path and run-aware. The bundled
|
|
593
|
+
`image-generate` helper performs model discovery/generation and invokes
|
|
594
|
+
`cloud deliver` once. Runtime marks that file snapshot handled so the
|
|
595
|
+
dispatch-final scan makes no second upload; scan-only and offline-replay
|
|
596
|
+
fallbacks use the same unbound `/runs/<id>` scope for chat runs. The result
|
|
597
|
+
is one upload path, one user-visible root IMAsset (plus normal internal
|
|
598
|
+
preview derivatives), and one reply attachment.
|
|
599
|
+
- Explicit-delivery snapshot de-dup is scoped to the cloud dispatch and cleared
|
|
600
|
+
at teardown instead of living for the daemon process lifetime. This also
|
|
601
|
+
bridges Hermes-local run ids to cloud dispatch ids, preventing both
|
|
602
|
+
same-turn double uploads and later-dispatch false skips. A local :3000 live
|
|
603
|
+
acceptance now verifies one user-visible Asset, one attachment, and no JSON
|
|
604
|
+
in the message body through the real helper/CLI/daemon path.
|
|
605
|
+
|
|
606
|
+
- SS-01 bundle parser parity with Cloud: YAML `|` / `>` block-scalar
|
|
607
|
+
descriptions are supported, and the 50-unit description floor is
|
|
608
|
+
language-aware so concise CJK metadata does not require filler.
|
|
609
|
+
|
|
610
|
+
- Closed the Runtime PKF/Memory/Dream contract: bundled `memory` and
|
|
611
|
+
`memory-dream` now mirror the carrier transition and authoritative-candidate
|
|
612
|
+
rules; `parentHubPath` is consistent across schemas/prompts/errors; curate
|
|
613
|
+
schema exposes `conflicts` and `oversized`; and `rebuild_index` is described
|
|
614
|
+
truthfully as hub-TOC maintenance with graph-derived INDEX Contents. Daemon
|
|
615
|
+
section addressing now reads complete PKF `<section>` wrappers so section
|
|
616
|
+
edits cannot consume neighbouring tags.
|
|
617
|
+
- Long/structured replies now default to validated inline PKF across hosted
|
|
618
|
+
adapters without asking the user to choose a format. Runtime passes the
|
|
619
|
+
canonical inline source—not only its short Markdown projection—into post-turn
|
|
620
|
+
durability, so lasting conclusions can be distilled into searchable Memory.
|
|
621
|
+
- Automatic Memory extraction now rejects unclassified leaves, verifies model
|
|
622
|
+
placement against the browse snapshot, promotes unmatched new topics to hubs,
|
|
623
|
+
and emits knowledge-profile PKF with semantic sections and real descriptions.
|
|
624
|
+
The direct write RPC enforces placement by default.
|
|
625
|
+
- Removed the obsolete daemon Dream trigger path. `FF_MEMORY_DREAM_ENABLED`
|
|
626
|
+
cannot revive the retired page-dream scheduler; Cloud scheduling followed by
|
|
627
|
+
the appointed orchestrator is the sole automatic authority.
|
|
628
|
+
|
|
629
|
+
- Headless SERP + cache regression (runtime210/01, 2026-08-23): agent-first
|
|
630
|
+
local web search/read for in-pod agents using the sandbox image's existing
|
|
631
|
+
Playwright+Chromium, with results flowing back into the cloud
|
|
632
|
+
`im_context_cache` (decentralized data regression).
|
|
633
|
+
- Engine chain (`daemon/web/headless-serp.ts`): Bing (chromium, with
|
|
634
|
+
`bing.com/ck/a` base64url redirect decoding), Google (chromium,
|
|
635
|
+
`FF_WEB_HEADLESS_SERP_GOOGLE` opt-in; consent/challenge detection), DDG
|
|
636
|
+
(plain HTTP with `uddg=` decoding + anomaly detection). Coalescing
|
|
637
|
+
single-flight + 2s interval rail; engine-chain fallthrough; SSRF gate
|
|
638
|
+
(http(s)-only, public-address-only resolution, subresource/redirect
|
|
639
|
+
interception, final-URL recheck; TUN fake-IP range blocked by default,
|
|
640
|
+
explicit `PRISMER_HEADLESS_ALLOW_FAKE_IP=1` opt-in for dev machines).
|
|
641
|
+
- Deposit-dedup journal (`daemon/web/seen-journal.ts`) so agents never
|
|
642
|
+
overwrite each other's cache entries (ContextCacheService.deposit is a
|
|
643
|
+
blind upsert).
|
|
644
|
+
- `daemon/web/rpc.ts`: `FF_WEB_HEADLESS_SERP` routing — **default
|
|
645
|
+
`fallback`** (cloud Exa+Serper keeps relevance; headless rescues only when
|
|
646
|
+
cloud fails, relevance not promised). Explicit `off` / `local-first` still
|
|
647
|
+
available. Local responses are
|
|
648
|
+
load-API-shaped (`success/requestId/mode/results/summary/cost/
|
|
649
|
+
processingTime`) plus `degraded`/`providerAttempts`, so provider-shell
|
|
650
|
+
tools and payload bounding are untouched. Reads/search hits deposit back
|
|
651
|
+
via the existing free-tier `POST /api/context/save`. New
|
|
652
|
+
`GET /local/web/doctor` health probe.
|
|
653
|
+
- Real-scenario validation (2026-08-23): C6 corpus 20/20 on Bing, fetch of
|
|
654
|
+
real pages (~2s for a 57KB Wikipedia article), Google consent + DDG
|
|
655
|
+
anomaly challenge detection verified live. Expression-matrix finding:
|
|
656
|
+
search operators (site:/quotes/boolean) are ignored on cookieless
|
|
657
|
+
automated traffic — engine-side behavior, not a parser defect.
|
|
658
|
+
- Added the embedded `pi-core` runtime adapter floor: the SDK now pins
|
|
659
|
+
`@earendil-works/pi-agent-core@0.84.2` and `@earendil-works/pi-ai@0.84.2`,
|
|
660
|
+
registers `pi-core` as the canonical adapter/provider identity, injects
|
|
661
|
+
Prismer gateway routes through `PRISMER_PI_*`, and validates jailed cwd
|
|
662
|
+
read/write/edit tool execution. Daemon/desktop/K8s runtime bundle gates now
|
|
663
|
+
share the `runtime-required-entries.json` manifest so Pi Core dependencies
|
|
664
|
+
cannot drift across packers and loaders.
|
|
665
|
+
|
|
666
|
+
- Hosted Hermes turns now bind each native `run_*` row to its exact provider
|
|
667
|
+
`api_*` session and capture bounded post-response model/provider evidence via
|
|
668
|
+
the signed `prismer-recall` lifecycle bridge. Post-turn Memory resolution
|
|
669
|
+
excludes synthetic session-cache rows, rejects unknown or identity-mismatched
|
|
670
|
+
evidence, and no longer falls into ambiguous-agent / configured-model
|
|
671
|
+
fallbacks for a real sessions-API turn.
|
|
672
|
+
|
|
673
|
+
- Closed the hosted PKF execution gap found by a real Hermes task: the shipped
|
|
674
|
+
Python provider now registers `pkf_validate` / `pkf_outline` / `pkf_search` /
|
|
675
|
+
`pkf_read` and forwards them to bounded `/local/pkf/*` Runtime routes backed
|
|
676
|
+
by the staged `@prismer/pkf-core`. Native tool names are no longer presented
|
|
677
|
+
as shell binaries. Hermes startup also installs a direct managed `cloud`
|
|
678
|
+
symlink to the signed bundle's `@prismer/sdk/dist/cli.js`, including a safe
|
|
679
|
+
`~/.local/bin` alias for login-shell PATH resets.
|
|
680
|
+
|
|
681
|
+
- Agent-output policy now accepts `.pkf` as
|
|
682
|
+
`application/vnd.prismer.pkf+html` with the 5 MiB source budget, so a
|
|
683
|
+
dispatch-final forced artifact scan can upload PKF deliverables. Expensive
|
|
684
|
+
`host.acked` catch-up is connection-epoch/workspace idempotent: every ack
|
|
685
|
+
still handles governance/profile deltas, while transport (retry-on-failure),
|
|
686
|
+
Memory and Asset catch-up no longer repeat on each 30-second heartbeat.
|
|
687
|
+
|
|
688
|
+
- Runtime built-in delivery now ships canonical `pkf-svg` (with historical
|
|
689
|
+
`pkf-visual` resolved through its metadata alias), installs both `pkf-svg`
|
|
690
|
+
and `pkf-writing` as common coding skills, and verifies byte-identical
|
|
691
|
+
catalog → bundled-fallback mirroring. The skill preserves the current PKF
|
|
692
|
+
v1.1 production boundary: Mermaid/d3, not inline SVG authoring.
|
|
693
|
+
|
|
694
|
+
- `pkf-writing` now defines message-inline, Library `.pkf` Asset, and Memory
|
|
695
|
+
Page carriers with a unique Runtime extraction sentinel contract plus
|
|
696
|
+
canonical validation/persist/readback/Markdown-projection receipts.
|
|
697
|
+
|
|
698
|
+
- `pkf-writing` skill acceptance moved to the single-file contract
|
|
699
|
+
(product209/19 WP2): SKILL.md < 180 lines with the references/ directory
|
|
700
|
+
gone; the acceptance gates now carry explicit negative controls (over-budget
|
|
701
|
+
content, invented commands, references/ import, bare asset URI all go red);
|
|
702
|
+
the trigger matrix covers the Chinese recall intent ("recall 我的记忆" →
|
|
703
|
+
memory). The prebuild built-in-skills mirror follows the catalog.
|
|
704
|
+
|
|
705
|
+
- Hermes gateway health check accepts `degraded` (product209/18): hermes
|
|
706
|
+
0.20.0 reports top-level status `degraded` under disk pressure (>= 90%)
|
|
707
|
+
while gateway + api_server are fully working; the old `status === 'ok'`
|
|
708
|
+
gate bricked dispatch (30s x 3 retries + kill-respawn storm). The
|
|
709
|
+
evaluator now requires gateway_state=running + api_server connected and
|
|
710
|
+
accepts ok|degraded; the spawn stamp is written before the health wait so
|
|
711
|
+
a slow/degraded gateway can't be misread as an un-witnessed orphan.
|
|
712
|
+
|
|
713
|
+
- Attachment blocks carry executable addresses (product209/18 Part A):
|
|
714
|
+
non-text non-vision attachments now emit `prismer://workspace/<ws>/asset/
|
|
715
|
+
<hash>` (stable reference) plus `path=file://<localPath>` for adapters
|
|
716
|
+
with real filesystem tools (hermes gated by toolsetScope terminal/file;
|
|
717
|
+
claude-code/codex/opencode). Tool-less adapters keep the legacy reminder
|
|
718
|
+
and never see a path.
|
|
719
|
+
|
|
720
|
+
- Asset materialization push (product209/18 Part B): new WS frames
|
|
721
|
+
`asset.materialize.request` (cloud → daemon, requestId=assetId) and
|
|
722
|
+
`asset.materialize.reply` (daemon → cloud). The daemon materializes
|
|
723
|
+
uploaded asset bytes into the local asset-cache on demand so the upload
|
|
724
|
+
progress bar completes only when bytes are in the pod; failures reply
|
|
725
|
+
with ok:false and cloud records no state (frontend polls and degrades).
|
|
726
|
+
|
|
727
|
+
- Canonical `pkf-writing` skill (product209/15 PKF-C1): slim SKILL.md + 6
|
|
728
|
+
progressive-disclosure references (core format / semantic HTML / media-data-
|
|
729
|
+
files / math / harness / validation-projection-export). Capability-based lane
|
|
730
|
+
choice (structured tools vs native filesystem lane); resolved readback
|
|
731
|
+
required before completion. Installed on coding agents via the common
|
|
732
|
+
allowlist; description routes recall/curation to `memory`.
|
|
733
|
+
|
|
734
|
+
- PKF filesystem service (product209/15 PKF-H5): durable `pkf_checkouts`
|
|
735
|
+
registry (daemon SQLite, idempotent boot migration) + four PATH-ONLY
|
|
736
|
+
lifecycle specs. These names are not advertised to Hermes until a shipped
|
|
737
|
+
provider handler can supply the task-root context. Ordinary UTF-8 files in
|
|
738
|
+
the task root; symlink/path
|
|
739
|
+
escape/device rejection; CRLF/BOM churn detected (explicit flag required);
|
|
740
|
+
final file bytes commit through the Cloud carrier CAS
|
|
741
|
+
(`POST /api/im/pkf/commit`); unlink never triggers a remote delete.
|
|
742
|
+
|
|
743
|
+
- SQLite V3 + snapshot-fenced full reconcile (product209/16 MA-2, Task 12):
|
|
744
|
+
`memory.db` migrates 2 → 3 in ONE transaction — `memory_replica_state`
|
|
745
|
+
(per-workspace cursor / accessVersion / replicaSubjectHash /
|
|
746
|
+
contentHighWatermark / reconciling|ready|suspended|stale / lease) plus
|
|
747
|
+
`memory_pages.sourceKind` and `replicaActorIdsJson`; the old
|
|
748
|
+
`memory_inbox_cursor` is migrated read-only (accessVersion=0, suspended —
|
|
749
|
+
never an authorization input), an in-flight `reconciling` flips to
|
|
750
|
+
`suspended` on restart, and a newer-schema db fails closed with a typed
|
|
751
|
+
`MemorySchemaIncompatibleError` (never a downgrade write). The runtime
|
|
752
|
+
consumes the Task 11 manifest/content ports: first manifest page pins
|
|
753
|
+
epoch/subject/high-watermark, content comes ONLY via
|
|
754
|
+
`POST /memory/sync/content` (snapshotToken + sourceRevisionId +
|
|
755
|
+
transport/content hashes), encrypted-pkf verifies transportHash → decrypts
|
|
756
|
+
→ verifies plaintext contentHash, legacy-html lands via the compat text
|
|
757
|
+
path (never the local PKF write/outbox path), replicated rows carry the
|
|
758
|
+
sorted exact actor set, and the ACL-shrink diff (complete set vs local
|
|
759
|
+
controlled rows) deletes page/version/content/link/FTS — heads, deletes,
|
|
760
|
+
tombstones and cursor/epoch/hash/lease/ready commit atomically, followed
|
|
761
|
+
by a catch-up pass for content created mid-reconcile. Agent recall fails
|
|
762
|
+
closed (typed `MemoryReplicaNotReadyError`) unless the replica is ready
|
|
763
|
+
AND the live registered authority snapshot still matches the pinned
|
|
764
|
+
epoch/subject hash AND neither the snapshot nor the local lease expired;
|
|
765
|
+
`memory.invalidate` suspends the replica first. The boundary ACL predicate
|
|
766
|
+
now checks `replicaActorIdsJson` membership (cap `sub` ∈ exact set,
|
|
767
|
+
empty set denies all, tampered JSON fails closed) BEFORE coarse
|
|
768
|
+
visibility/principal rules. Legacy daemon sync receiving 426
|
|
769
|
+
`MEMORY_RUNTIME_UPGRADE_REQUIRED` stops typed (no fallback) and surfaces
|
|
770
|
+
`SyncResult.upgradeRequired { minRuntimeVersion,
|
|
771
|
+
requiredRuntimeCapabilities }`; strict workspaces are routed to the V3
|
|
772
|
+
port automatically. `MIN_STRICT_RUNTIME_VERSION = '2.2.12'` (first build
|
|
773
|
+
with SQLite V3, same source as the Cloud rollout constant).
|
|
774
|
+
|
|
775
|
+
- Memory authority snapshot + cap v2 (product209/16 MA-1B, Task 10): the
|
|
776
|
+
daemon registers the Cloud-delivered `memoryAuthority` snapshot bundle
|
|
777
|
+
(optional field on the RuntimeConfigBundle, 60m lease) and mints cap v2
|
|
778
|
+
ONLY from a valid snapshot — server-derived actor claims (principal /
|
|
779
|
+
taskIds / councilIds / verbs), 15m TTL clamped to
|
|
780
|
+
`snapshot.validUntil`. No valid snapshot (never fetched / expired /
|
|
781
|
+
tampered / epoch regression) → NO cap is injected and agent Memory RPC
|
|
782
|
+
fails closed. v1 tokens still verify for one compat release cycle (the v1
|
|
783
|
+
minter is retained only as the compat encoder; production mint sites
|
|
784
|
+
migrated to `mintCapV2`). The boundary ACL predicate now adjudicates
|
|
785
|
+
actor/principal/task claims: a deputy cap reads its bound member's
|
|
786
|
+
`human:*` pages (strict principal-id equality, never as `sub`, never
|
|
787
|
+
widened to other agents/members). `agent.host.declare` now carries
|
|
788
|
+
`runtimeCapabilities` (the three §8.2 v1 capabilities) and the daemon
|
|
789
|
+
handles the `memory.authority.invalidate` owned-channel frame (drop
|
|
790
|
+
snapshot + forced bootstrap refresh).
|
|
791
|
+
|
|
792
|
+
- Memory RPC write/outbox atomicity (product209/16 MA-0S, M-OUTBOX-001):
|
|
793
|
+
`POST /local/memory/write` now commits the page aggregate
|
|
794
|
+
(page/version/content/FTS) and its outbox events (page upsert + placement
|
|
795
|
+
link) in ONE SQLite transaction, reusing the post-turn
|
|
796
|
+
extracted-page-applicator pattern. An outbox enqueue failure rolls the whole
|
|
797
|
+
aggregate back and the RPC returns 500 instead of the old success that
|
|
798
|
+
stranded the page local-only. Success responses now also carry
|
|
799
|
+
`localVersion` and `outboxEventId`; the idempotency key stays derived from
|
|
800
|
+
the logical write identity (page id + parentVersion + contentHash), so
|
|
801
|
+
retries with a stable caller-supplied page id keep the same key.
|
|
802
|
+
|
|
803
|
+
- Native bounded query tools `pkf_outline` / `pkf_search` / `pkf_read`
|
|
804
|
+
(product209/15 PKF-H2): in-process over the bundled core — 32 KiB output
|
|
805
|
+
budget, signed paged cursors, 16/64 KiB read bounds, no includeSource
|
|
806
|
+
escape, no regex query surface. Offline; same receipts as Cloud.
|
|
807
|
+
|
|
808
|
+
- Native `pkf_validate` tool (product209/15 PKF-D3): in-process PKF validation
|
|
809
|
+
over the bundled pkf-core — no shell-out, no daemon RPC, offline by
|
|
810
|
+
construction. Registered across the Hermes / Claude Code / Codex tool
|
|
811
|
+
surfaces; same golden-fixture results as Cloud.
|
|
812
|
+
|
|
813
|
+
- PKF core staging (product209/15 PKF-D2): the runtime now stages a
|
|
814
|
+
hash-verified `@prismer/pkf-core` build into its local node_modules during
|
|
815
|
+
`prebuild` (`sdk/build/stage-pkf-core.cjs`), so offline parsing/validation
|
|
816
|
+
runs from the bundled core with no cloud network and no monorepo root source
|
|
817
|
+
(`test/pkf-core-staged.test.ts`). The staged copy never lands in the
|
|
818
|
+
published tarball; the standalone pack gate verifies both tarballs unpack
|
|
819
|
+
and run without the repo root.
|
|
820
|
+
|
|
821
|
+
- The bundled Remotion catalog now ships one self-contained canonical
|
|
822
|
+
`remotion` skill instead of twelve duplicate top-level skills plus a set of
|
|
823
|
+
short topic references. Runtime fallback resolves historical slugs through
|
|
824
|
+
canonical metadata aliases, and installed-skill delivery collapses multiple
|
|
825
|
+
legacy edges to one prompt section while preserving the selected historical
|
|
826
|
+
skill ID for sync acknowledgements.
|
|
827
|
+
- Hermes session creation and every chat turn now lock the configured `model`
|
|
828
|
+
(`require_model_lock: true`) while deliberately omitting `provider`. The
|
|
829
|
+
dedicated per-profile gateway resolves its authoritative named provider
|
|
830
|
+
before session creation; model-only locks let pre-lock sessions self-heal
|
|
831
|
+
after an OTA without Hermes v2026.8.3's named-provider identity mismatch.
|
|
832
|
+
- Runtime OTA bundles now carry the in-tree Cloud CLI/SDK and its bundled AIP
|
|
833
|
+
package. The frozen sandbox image no longer supplies these JavaScript product
|
|
834
|
+
packages; the signed boot bundle is their single delivery path. The resident
|
|
835
|
+
Rust manager now starts health before bundle resolution, retries when a fresh
|
|
836
|
+
Pod has no bundle, prepends the bundle CLI path, and is the sole owner of
|
|
837
|
+
Runtime child respawn.
|
|
838
|
+
- Signed Runtime manifests now record exact Runtime, Cloud SDK, and AIP package
|
|
839
|
+
provenance and reject native `.node` payloads. Packing also requires the
|
|
840
|
+
signing private key to match either the explicit rotated daemon public key or
|
|
841
|
+
the bootstrapper's built-in public key, preventing bundles that self-verify
|
|
842
|
+
during packing but cannot verify inside a Pod.
|
|
843
|
+
- Sandbox Pod probes now separate manager liveness (`:7890/healthz`) from
|
|
844
|
+
Runtime readiness (`:7890/readyz`). A fresh Pod waiting for a signed bundle
|
|
845
|
+
remains manager-alive but NotReady; Runtime child failure no longer makes
|
|
846
|
+
kubelet kill the manager that owns recovery.
|
|
847
|
+
- Bundle pointers and boot markers are atomically written as `0640 user:user`,
|
|
848
|
+
so both the Runtime owner and the group-member resident manager can read
|
|
849
|
+
rollback state even under a strict `0077` launcher umask.
|
|
850
|
+
- Sandbox Manager S2 (WP-E r4): Parameterize `execute_disk_cleanup` slow-ms switch
|
|
851
|
+
as a function parameter (previously read from process-global env). Eliminates
|
|
852
|
+
non-deterministic test races under parallel `cargo test` — unit tests now pass
|
|
853
|
+
slow_ms directly. Production path reads env once in `execute_action`. No
|
|
854
|
+
protocol/behavior change.
|
|
855
|
+
- Sandbox Manager S2 (WP-E, product209/09 S2): OTA settle logic (strike/rollback/
|
|
856
|
+
blacklist) migrated from TS (`ota-check.ts settlePreviousBoot`) into the Rust
|
|
857
|
+
manager (`ota.rs`). File formats unchanged — boot-attempt.json, pointer files,
|
|
858
|
+
blacklist.json match TS `bundle-store.ts` byte-for-byte. Manager settles
|
|
859
|
+
unconfirmed boot markers BEFORE OTA resolve; `SANDBOX_MANAGER_PRESENT` env
|
|
860
|
+
disables TS-side settle to prevent double-settle. Config backup point:
|
|
861
|
+
`config-bootstrap.ts applyBundle` now writes backup to
|
|
862
|
+
`~/.prismer/config-backup/` (atomic temporary+rename) before applying new
|
|
863
|
+
Hermes config. Rescue actions (cmd channel + inotify polling): `rollback_config`
|
|
864
|
+
(restore from backup → validate → restart), `ota_rollback` (current ← previous),
|
|
865
|
+
`restart_daemon`, `diag_bundle` (tar.gz of config+logs+resources),
|
|
866
|
+
`disk_cleanup`. Command idempotency via command-id result cache. Failure
|
|
867
|
+
classification: FATAL (config syntax, OTA failure, DB corruption) → Fail-slow
|
|
868
|
+
(diag pack + stay alive); TRANSIENT → Fail-fast (immediate restart, max 3
|
|
869
|
+
consecutive → Fail-slow). Design: docs/product209/09-resident-sandbox-manager.md
|
|
870
|
+
§3.3, §3.5, §3.6, §4, §6.
|
|
871
|
+
- Observability (WP-D, product209/07 §3.7.4): daemon /healthz `config` segment
|
|
872
|
+
(`configVersion` / `lastApplyAt` / `lastApplyError` / `pending`) derived from
|
|
873
|
+
bootstrapStates via spread discipline — absent when no ConfigDelivery state,
|
|
874
|
+
keeping CLI/K8s healthz shape unchanged. `runtime.incident` WS emission on
|
|
875
|
+
bootstrap errors (401/403 stop, 404/500/network backoff) for cloud-side
|
|
876
|
+
audit-row consumption. `LocalServerState.config` interface added.
|
|
877
|
+
- Sandbox Manager S1 (09): Resident sandbox-manager — Rust static binary
|
|
878
|
+
(`sdk/prismer/src/daemon/sandbox-manager/`) that supervises the daemon as a
|
|
879
|
+
child process under tini. Entrypoint init logic (config.toml first-write,
|
|
880
|
+
fake key fallback, static binding validation, OTA resolve) migrated from
|
|
881
|
+
`daemon-entrypoint.sh` into the Rust binary. Healthz HTTP endpoint on port
|
|
882
|
+
7890 (K8s probes target the manager, not daemon). Daemon heartbeat file
|
|
883
|
+
(`~/.prismer/daemon-heartbeat`) written every 15s by the new
|
|
884
|
+
`DaemonHeartbeat` class (`daemon/daemon-heartbeat.ts`) for manager-side
|
|
885
|
+
liveness monitoring. Design: docs/product209/09-resident-sandbox-manager.md.
|
|
886
|
+
- ConfigDelivery P1: RuntimeConfigBundle types (`RuntimeConfigBundle`,
|
|
887
|
+
`HermesProviderConfig`, `HermesBundleConfig`, `BootstrapRequest`,
|
|
888
|
+
`BootstrapResponse`, `ConfigApplyState`) and daemon-side bootstrap module
|
|
889
|
+
(`fetchBootstrapBundle`, `applyBundle`, `isValidBundle`,
|
|
890
|
+
`createBootstrapState`, `resetBootstrapStopped`, `computeBootstrapErrorAction`,
|
|
891
|
+
`toApplyState`) for fetching workspace-level runtime config from
|
|
892
|
+
`GET /api/im/runtime/bootstrap` (B1 endpoint). Design:
|
|
893
|
+
docs/product209/07-config-delivery-runtime-bootstrap.md §3.3–3.4.
|
|
894
|
+
- Adoption path in runner.ts now calls `triggerBootstrap()` (cloud B1 fetch)
|
|
895
|
+
instead of direct `reprovisionHermesProfiles()` when `FF_CONFIG_DELIVERY` is
|
|
896
|
+
ON (default). The cloud is the single source of truth for provider + model
|
|
897
|
+
+ key assembly. §3.6: explicit OFF falls back to 2.2.8 env-inference path.
|
|
898
|
+
- Coding Agent Line (08 P2/P3): CodingSessionControls UI component for
|
|
899
|
+
workspace coding agent session lifecycle — adapter selection (claude-code /
|
|
900
|
+
codex / opencode), forced-mode display, new-session / continue-session
|
|
901
|
+
actions. Reuses `POST /api/conversations/direct` (zero new API). Session
|
|
902
|
+
lifecycle tests for code-agent-driver resume semantics (same-key cache hit,
|
|
903
|
+
restore-after-restart resumeSession, different-conversation creates new
|
|
904
|
+
session), TRAP 1 single-flight guard (duplicate taskId replay), and
|
|
905
|
+
autonomous launch on resume. Design:
|
|
906
|
+
docs/product209/08-coding-agent-line.md §4–5.
|
|
907
|
+
- Backoff schedule for bootstrap fetch: 5s → 10s → 30s → 60s cap
|
|
908
|
+
(`BOOTSTRAP_BACKOFF_MS`). Error semantics: 404/500 → backoff, 401/403 → stop
|
|
909
|
+
retries; 401 stopped state reset by key adoption event (§3.4).
|
|
910
|
+
- Memory cap v1 fail-closed (spec16 §8.1 MA-0S, M-CAP-001): the daemon memory
|
|
911
|
+
RPC no longer has an enforce-off branch — `PRISMER_MEMORY_CAP_ENFORCE` is
|
|
912
|
+
deleted and every `/local/memory/*` call must carry a valid
|
|
913
|
+
`x-prismer-memory-cap` (missing → 401 `memory_cap_required`; expired /
|
|
914
|
+
tampered / forged → 401 `memory_cap_invalid`; a cross-workspace request is
|
|
915
|
+
a cap-layer ws mismatch → 401 `memory_cap_invalid` per §13.4 — the old 403
|
|
916
|
+
`memory_ws_scope_violation` is retired). The `prismer memory` CLI and the
|
|
917
|
+
shared adapter tool client auto-carry the cap from `$PRISMER_MEMORY_CAP`.
|
|
918
|
+
The acting identity is always the verified cap subject — body/query actor
|
|
919
|
+
fields are never trusted — and a signed wildcard-scope cap under a
|
|
920
|
+
non-system subject (or a cap whose own ws claim disagrees with its scope)
|
|
921
|
+
is rejected at verify. Deny logs carry only a sha256 prefix of the
|
|
922
|
+
presented token plus a reasonCode. Daemon-internal maintenance (outbox
|
|
923
|
+
flush, WS invalidate, extraction) keeps writing the store in-process
|
|
924
|
+
(system channel), never through the agent RPC gate.
|
|
925
|
+
|
|
926
|
+
### memory211/03 §7 B1 — memory_browse recency signal(`updatedAt` + `hubsByRecent[]`)
|
|
927
|
+
|
|
928
|
+
- **feat(daemon):** `memory_browse` 结果 hub 行新增 `updatedAt`(epoch ms——hub 行自身
|
|
929
|
+
与其 child-of 后代的最新更新时间 spread;hub 行在叶节点写入时不会被 re-touch,故按
|
|
930
|
+
spread 算)与 `hubsByRecent[]`(同集 hub 按 `updatedAt` DESC 排序的 recency 信号)。
|
|
931
|
+
Additive:`hubs[]` 保持既有结构序,`index`/`nearest` 不变;无新工具、无 schema 变更
|
|
932
|
+
(`/local/memory/place-context` 同 helper,两处不可漂移)。
|
|
933
|
+
|
|
934
|
+
### memory211/03 §7.2 B4 — R5 recall-methodology turn metrics
|
|
935
|
+
|
|
936
|
+
- **feat(daemon):** dispatch 度量随既有 `turn.navigation_used` / `turn.shortcuts_taken`
|
|
937
|
+
追加三枚 `turn.*`:`first_round_hybrid`(第一轮即 batch search ≥2 且同轮 browse)、
|
|
938
|
+
`first_round_direct_read`(第一轮即 load 且无前导 browse——direct-recall shortcut)、
|
|
939
|
+
`tool_rounds`(批内按 `R5_ROUND_GAP_MS` 计工具轮数)。分析式归并 daemon-side 的
|
|
940
|
+
`tool-sequence.ts` ring;与既有两枚同纪律:turn 无 memory 调用 ⇒ 无行(不伪造 0)。
|
|
941
|
+
|
|
942
|
+
## 2.2.10 (2026-08-07)
|
|
943
|
+
|
|
944
|
+
- Fix Hermes "No LLM provider configured" on sandbox agents: the provider
|
|
945
|
+
bootstrap wrote `model.provider: custom:prismer`, but hermes' custom-provider
|
|
946
|
+
resolution matches `custom_providers[].name` verbatim — `custom:prismer`
|
|
947
|
+
never matched the `prismer` entry, the gateway fell through with
|
|
948
|
+
provider='custom' and no key, and every turn failed at AIAgent init.
|
|
949
|
+
Exposed on 2.2.9: the dual-write made the gateway read the profile config
|
|
950
|
+
(HERMES_HOME override) for the first time, surfacing the format mismatch.
|
|
951
|
+
Write the bare provider name (`prismer`); verified in-pod that
|
|
952
|
+
`_resolve_runtime_agent_kwargs` then resolves api_key + base_url correctly.
|
|
953
|
+
|
|
954
|
+
## 2.2.9 (2026-08-07)
|
|
955
|
+
|
|
956
|
+
- Fix Hermes sessions EMPTY reply after a process-level interrupt (sandbox
|
|
957
|
+
agent observed live on 2026-08-07): the S10 pollution-rotation state was
|
|
958
|
+
process-memory only, so a daemon restart (OTA kill1 / re-adoption) wiped
|
|
959
|
+
"this session is polluted" and the next turn reused the polluted hermes
|
|
960
|
+
transcript → `empty_reply`. Three changes:
|
|
961
|
+
- `session-health` interrupted/empty-streak state now persists to
|
|
962
|
+
`~/.prismer/hermes-session-health.json` (env `HERMES_SESSION_HEALTH_FILE`
|
|
963
|
+
to override, `setSessionHealthFile(null)` to disable) and reloads on boot —
|
|
964
|
+
a daemon restart keeps the rotation signal.
|
|
965
|
+
- The dispatcher catch marks the session interrupted on CONNECTION-level
|
|
966
|
+
breaks too (gateway killed by adoption re-provision, network drop), not
|
|
967
|
+
only `task_cancelled` / `upstream stall` — sessions-sse never throws on
|
|
968
|
+
application errors, so a thrown error is always an interrupted turn.
|
|
969
|
+
- EMPTY rotation threshold defaults to 1 (`HERMES_SESSION_EMPTY_ROTATE_THRESHOLD`
|
|
970
|
+
to raise) — one empty reply rotates the session instead of burning a
|
|
971
|
+
second user-visible failed turn.
|
|
972
|
+
Design: docs/product209/07 §2 (plan 2 closure).
|
|
973
|
+
|
|
974
|
+
## 2.2.8 (2026-08-06)
|
|
975
|
+
|
|
976
|
+
- Hermes provider bootstrap written to the ROOT `~/.hermes/config.yaml` +
|
|
977
|
+
`~/.hermes/.env`, not the per-profile dir — Hermes' provider resolution
|
|
978
|
+
(runtime_provider / AIAgent) only reads the root config, so every sandbox
|
|
979
|
+
agent failed with "No LLM provider configured" (2026-08-06). Profile dir kept
|
|
980
|
+
for the gateway `-p` flag.
|
|
981
|
+
- Re-provision Hermes profiles after the WS handshake adopts the real
|
|
982
|
+
per-workspace API key: ACS sandboxes boot with the entrypoint placeholder key
|
|
983
|
+
and any gateway spawned before adoption holds it; on adoption re-run
|
|
984
|
+
`prepareProfile` (rewrites the root config with the adopted key) and kill the
|
|
985
|
+
gateways so the next dispatch respawns with the real env/config. No-op for
|
|
986
|
+
k8s/local (real key from boot, byte-identical rewrite).
|
|
987
|
+
|
|
988
|
+
## 2.2.7 (2026-08-05)
|
|
989
|
+
|
|
990
|
+
- Fix ACS handshake key adoption reaching the HTTP layer: `CloudClient` froze
|
|
991
|
+
the boot-time placeholder key in its opts at construction, so after the
|
|
992
|
+
handshake adopted the real per-workspace key every HTTP call still sent the
|
|
993
|
+
placeholder and 401'd ("API key not found or revoked"). Profile sync then
|
|
994
|
+
failed, declare stayed 0/0 and the setup parked at `agents_bound`. New
|
|
995
|
+
`CloudClient.setApiKey()` + the adoption block now re-points the live client
|
|
996
|
+
and `PRISMER_API_KEY` env. No-op for desktop/local daemons (real key from
|
|
997
|
+
boot).
|
|
998
|
+
|
|
999
|
+
## 2.2.6 (2026-08-05)
|
|
1000
|
+
|
|
1001
|
+
- Adopt the cloud-delivered daemonId on the ACS binding handshake
|
|
1002
|
+
(`authenticated` ack extra.daemonId), exactly like the existing apiKey
|
|
1003
|
+
adoption: the sandbox boots with a derived `container:<hostname>` id that
|
|
1004
|
+
never matches the `im_containers` row's canonical UUID, so `declare` would
|
|
1005
|
+
otherwise fail the setup's daemonHasSignal lookup and `daemon_connected`
|
|
1006
|
+
would park forever. Only the ACS binding path sends daemonId, so
|
|
1007
|
+
desktop/local daemons are unaffected.
|
|
1008
|
+
|
|
1009
|
+
## 2.2.5 (2026-08-02)
|
|
1010
|
+
|
|
1011
|
+
- Established `@prismer/runtime` as the Runtime host under `sdk/prismer`, with
|
|
1012
|
+
Cloud SDK dependencies removed from its control-plane boundary.
|
|
1013
|
+
- Replaced external coding-agent Marketplace plugin lifecycle control with
|
|
1014
|
+
Runtime-owned Claude adapter hooks and a durable provider-independent
|
|
1015
|
+
post-turn job ledger.
|
|
1016
|
+
- Added remote-first skill catalog resolution with verified cache and bundled
|
|
1017
|
+
fallback, including resolver state and post-turn counters in `/healthz`.
|
|
1018
|
+
- Retired Go/Rust SDK builds and four-segment hotfix releases; active packages
|
|
1019
|
+
now use the shared `X.Y.Z` release version and npm/PyPI release matrix.
|
|
1020
|
+
|
|
1021
|
+
### Added — D1/D2 裁决落地:两个新终态 code + `daemon.declare.retry` 解 park
|
|
1022
|
+
|
|
1023
|
+
cloud 侧本轮实施了 product206/13 的两条用户裁决(D1「显式归属,一次性选择」/ D2「凭证指纹
|
|
1024
|
+
现在收窄」),daemon 侧要认它们的拒绝,并且要能被**解 park**。
|
|
1025
|
+
|
|
1026
|
+
- **`declare-guard.ts` 分类表新增两个终态 code**(判定逻辑与阶梯一行未动,只是表多了两行):
|
|
1027
|
+
- `WORKSPACE_DAEMON_MISMATCH`(D1)—— 这个 workspace 的**本机槽位**显式归另一台设备,
|
|
1028
|
+
而归属**刻意不自动漂移**。归为终态正是裁决的要点:把它当可重试等于把「等对方掉线就
|
|
1029
|
+
抢过来」这条隐式规则从后门放回来。
|
|
1030
|
+
- `DAEMON_CREDENTIAL_MISMATCH`(D2)—— daemonId 属于本账号,但这把 API key 不是认领时那把。
|
|
1031
|
+
daemon 只有一把 key,重试永远拿同一个答案。
|
|
1032
|
+
- 两条都带用户能照做的 hint(`prismer status` / `/healthz.declareBlocked` 逐字透出)。
|
|
1033
|
+
cloud 同时发 `retryable: false`,所以**即使是不认识这两个 code 的老 daemon** 也会按终态
|
|
1034
|
+
处理(wire hint 压过本地表,R5 既有契约)。
|
|
1035
|
+
- **新增入站帧 `daemon.declare.retry`(cloud → daemon)** —— 用户在云端做完补救动作
|
|
1036
|
+
(批量重认领 / 改派 workspace 槽位)后,cloud 主动叫 daemon 再试一次。**它是这两条出路
|
|
1037
|
+
端到端能用的那一半**:R5 的终态 park 是有界探测(+5min/+10min 之后彻底停发),没有这个
|
|
1038
|
+
推送,一个 20 分钟后才被修好的 daemon 只能靠重启进程恢复 —— 而「重启 daemon」正是 R5
|
|
1039
|
+
出入1 拒绝交付的答案。实现上复用 `onExplicitDeclare()` 这个既有的解 park 缝
|
|
1040
|
+
(`POST /v1/workspace` 走的同一条),**不新增状态机**。帧按 `daemonIds` 过滤(rooms 按
|
|
1041
|
+
人的 IMUser id 寻址,同账号所有 daemon 都会收到),不点名自己就忽略。纯加法:不认识这个
|
|
1042
|
+
type 的旧 daemon 落进 `handleIncoming` 的静默 default。
|
|
1043
|
+
|
|
1044
|
+
### Added — R5:declare 被 cloud 拒绝时有出路(分类 + 退避 + 用户可见),local-first 不变
|
|
1045
|
+
|
|
1046
|
+
`agent.host.declare` 被拒时,daemon 过去**把拒绝丢在地上**:`runner.ts` 的 `ws.on('message')`
|
|
1047
|
+
只对三个 `AUTH_*` code 有动作,其余 error 帧写完 stderr 就落进 `handleIncoming()` 的
|
|
1048
|
+
switch 被 default 吞掉;同时 30s 心跳**无条件**重发同一份注定失败的 declare。用户看到的是
|
|
1049
|
+
「设备连上了、但一个 agent 都不托管」,没有任何面能说出原因。cloud 侧的 R4(daemonId 认领
|
|
1050
|
+
鉴权)落地后这条从体验缺陷变成**必现路径**:认领冲突就会走到这里。
|
|
1051
|
+
|
|
1052
|
+
- **新增 `daemon/declare-guard.ts`** —— 分类表 + 退避阶梯 + 阻塞态,一个模块决定这三件事:
|
|
1053
|
+
- **终态**(`DAEMON_ID_CLAIMED` / `DAEMON_FORGOTTEN` / `NO_USER`):重试永远不会成功,
|
|
1054
|
+
只有人能解。**不再无限重发** —— 拒后只留 2 次有界探测(10×/20× tick,默认 5min/10min)
|
|
1055
|
+
然后彻底停发。留探测而不是归零,是因为 forgotten / 认领释放这类终态常常几分钟内被人修好,
|
|
1056
|
+
daemon 应该自己发现,不该要求用户重启。
|
|
1057
|
+
- **可重试**(`WORKSPACE_ACTIVE_DEVICE_BUSY` / `DAEMON_IDENTITY_UNAVAILABLE` /
|
|
1058
|
+
`NO_IMUSER_LINKED` / `SHADOW_JOIN_FAILED` / `INTERNAL`):继续重试但**退避**
|
|
1059
|
+
2×/4×/8×/10× tick(默认 60s→120s→240s→300s 封顶),不再恒 30s 打。
|
|
1060
|
+
- **认证类**(`AUTH_FAILED` / `AUTH_REQUIRED` / `auth_invalid`):**行为不变**,仍是整机停机。
|
|
1061
|
+
凭证坏了退避没有意义 —— 这条显式确认,不再隐含在「反正都被 default 吞掉」里。
|
|
1062
|
+
- **非 declare 通道的 error**(`WITHDRAW_DAEMON_MISMATCH` / `UNKNOWN_EVENT`):完全不碰
|
|
1063
|
+
declare 状态机(wire 没有可信的 per-frame 关联 id,这张排除表就是关联)。
|
|
1064
|
+
- **未知 code 一律降级为可重试**:新 cloud 发一个老 daemon 没见过的 code,绝不能把设备
|
|
1065
|
+
变成永久沉默。
|
|
1066
|
+
- **wire hint 压过本地表**:cloud 可在 error 帧带可选的 `retryable: false | {afterMs}`
|
|
1067
|
+
(`ServerEvents.error` 第 4 参,本包外配套)。双向可选、各自兜底,**不引入
|
|
1068
|
+
protocolVersion 协商**。
|
|
1069
|
+
- **`/healthz` 新增 `declareBlocked`**(`LocalServerState`)—— `{code, message, retryable,
|
|
1070
|
+
since, attempts, nextRetryAt, hint}`。这是「**连不上**」(`wsConnected:false`)与
|
|
1071
|
+
「**连上了但被拒**」(`wsConnected:true` + `declareBlocked`)唯一可区分的地方。字段用
|
|
1072
|
+
spread 发出:没被拒的 daemon 的 healthz 形状与 R5 之前逐字节一致。
|
|
1073
|
+
- **`prismer status` 打印拒绝原因**(新增导出的纯函数 `formatDeclareBlocked`)——「设备未被
|
|
1074
|
+
cloud 接受: <code>」+ cloud 原话 + 怎么办 + 下次尝试时间(park 时明说「不再自动尝试」)。
|
|
1075
|
+
同一改动里 `readDaemonStatus()` 改为认 `PRISMER_DAEMON_PORT`(原来硬编码 3210,导致
|
|
1076
|
+
desktop / agent-rt pod / 集成 rig 上 `prismer status` 一律误报「daemon 没在跑」)。
|
|
1077
|
+
- **不变量(每条都是 j48 的一道门)**:① local-first —— 被拒**不停机**,本地 run 照跑照写产物;
|
|
1078
|
+
② 离线 ≠ 被拒 —— socket 断只走 `ws-client` 自己的重连退避,**绝不**置 `declareBlocked`;
|
|
1079
|
+
③ 正常重连不吃退避 —— 新 `authenticated` 重置阶梯(重连是高频路径);④ 显式用户意图
|
|
1080
|
+
(装 agent / 切 workspace)解 park,这也是「我修好了,再试一次」的入口。
|
|
1081
|
+
- **可调**:`PRISMER_DECLARE_INTERVAL_MS`(默认 30_000,钳 1s–300s)—— 心跳 declare 周期,
|
|
1082
|
+
整条阶梯以它为单位。生产默认不变,只给运维 / 集成测试压缩墙钟用。
|
|
1083
|
+
- 测试:`test/declare-guard.test.ts` 16 条(分类表 + 阶梯算术 + 四条不变量,注入时钟)。
|
|
1084
|
+
贯通门 `scripts/test203/journeys/j48-declare-rejection-backoff.ts`:**真 daemon 进程**
|
|
1085
|
+
(从源码起)+ 逐帧记录的 stub cloud,6 条断言全取副作用(wire 上的 declare 帧数与间隔 /
|
|
1086
|
+
`/healthz` / `prismer status` 输出 / 磁盘产物)。负控 `--tamper` 用**源码级自动注入**
|
|
1087
|
+
(把 R5 从一份 src 拷贝里拆掉再起 daemon):实测终态 3 帧 → 30 帧、退避间隔 6/8/16s → 恒 2s、
|
|
1088
|
+
`declareBlocked` 消失、CLI 无输出,四处如期翻红;local-first 与离线两条在 tamper 下仍绿
|
|
1089
|
+
(证明它们不是靠 R5 假绿)。
|
|
1090
|
+
|
|
1091
|
+
### Fixed — 桌面本地 provider 选择落不了库:`proxyProvider` 新增 `local:<profileId>` 命名空间
|
|
1092
|
+
|
|
1093
|
+
`AgentProfile.config.proxyProvider` 一个命名空间装两类值:云端 provider chain(云端全知)
|
|
1094
|
+
与**桌面本地 provider profile**(只存在于用户机器的 `~/.prismer/config.toml`
|
|
1095
|
+
`[[providers]]` + keychain,**云端结构性不可知**)。cloud 侧的写入校验只放行它认得的 id,
|
|
1096
|
+
于是桌面用户从三个入口(`ProxyProviderSelect` / `ModelPicker` / composer 模型切换器)选中
|
|
1097
|
+
本地 provider 时,每一次写入都 400 `invalid_proxy_provider` —— 这条漂移全仓无任何 doc 或
|
|
1098
|
+
代码记录过,实测复现(`PATCH /api/im/agent_profiles/<id>` config.proxyProvider='qwen' → 400)。
|
|
1099
|
+
|
|
1100
|
+
修法是**加命名空间**而不是放松校验:`local:<profileId>` 自描述,cloud 零知识放行;裸的未知
|
|
1101
|
+
链 id 照旧被拒(release202/12 C2 的防拼错能力一点没丢)。
|
|
1102
|
+
|
|
1103
|
+
- **`resolveLocalProvider()` 认两种形态**(`adapters/shared/local-provider.ts`)——
|
|
1104
|
+
`local:<id>` strip 前缀后按 `profile.id` 匹配;**裸 id 照旧匹配**(前缀出现之前写下的
|
|
1105
|
+
profile、手写 config.toml、既有测试全部不受影响)。新增导出 `LOCAL_PROVIDER_PREFIX` /
|
|
1106
|
+
`isLocalProviderSelector()` / `localProviderProfileId()`(cloud 侧的镜像副本在
|
|
1107
|
+
`src/lib/llm/local-provider-ref.ts`,**前缀字符串必须同步**)。
|
|
1108
|
+
`newapi` / `default` 的保留短路只对**裸** selector 生效 —— 显式 `local:newapi` 指的是用户
|
|
1109
|
+
正好这么命名的本地 profile,前缀说得毫不含糊。
|
|
1110
|
+
- **未解析的 `local:` 不再被当成云端链走**(hermes `resolvePrismerProviderBaseUrl` +
|
|
1111
|
+
codex `resolveCodexPrismerProvider`)。profile 不在这台机器上 / key 未配 / 类型与该
|
|
1112
|
+
adapter 的 wire 不兼容时,旧逻辑会拼出 `/api/v1/proxy/local%3Aqwen` —— 每一发都 404。
|
|
1113
|
+
现在落回聚合器 `/api/v1`,与「没选」同一个降级面。**真的云端链 id 仍走
|
|
1114
|
+
`/api/v1/proxy/<chain>`**(这条闸严格限定在 `local:` 命名空间内)。
|
|
1115
|
+
- 测试:`test/local-provider.test.ts` 新增 `local:` 命名空间 6 条 + codex 路由 2 条;
|
|
1116
|
+
新增 `test/hermes-local-provider-selector.test.ts` 5 条 —— oracle 取 hermes 真正跑的字节
|
|
1117
|
+
(`<HERMES_HOME>/profiles/<p>/config.yaml` 的 `custom_providers[].base_url` + `api_mode`),
|
|
1118
|
+
不是返回值。负控:拿掉 strip → 7 条红(实测)。
|
|
1119
|
+
贯通门 `scripts/test203/journeys/j44-local-provider-selector.ts`(真 endpoint + DB oracle)
|
|
1120
|
+
正向 PASS;`--tamper`(发裸 id = 修复前的前端)如期红,复现的正是那条 400。
|
|
1121
|
+
|
|
1122
|
+
⚠️ 本条需要 cloud 端配套(不在本包):`src/im/api/agent-profiles.ts::validateProxyProvider`
|
|
1123
|
+
放行 `local:` 前缀,三个前端选择器发带前缀的值。缺任一层用户仍撞 400。
|
|
1124
|
+
|
|
1125
|
+
### Added — F1-b:运行中的 daemon 可以改 workspace(`POST /v1/workspace` + `Runner.setDeclaredWorkspace()`)
|
|
1126
|
+
|
|
1127
|
+
用户在 daemon **已经在跑**的时候新建 / 切换 / 删除 workspace,daemon 与桌面此前**零感知**:
|
|
1128
|
+
`config.toml` 没有 workspace 字段,`PRISMER_WORKSPACE_ID` 在 spawn 时就冻死,
|
|
1129
|
+
`GET /workspaces/sync` 有 client 方法但**零调用方**(死代码)。daemon 会一直服务它启动时
|
|
1130
|
+
的那个 workspace,直到整个 app 重启。
|
|
1131
|
+
|
|
1132
|
+
- **`Runner.setDeclaredWorkspace(workspaceId)`**(`daemon/runner.ts`)—— 新增
|
|
1133
|
+
`workspaceOverride` 字段,优先级**高于** spawn 冻结的 `PRISMER_WORKSPACE_ID`(原
|
|
1134
|
+
`sendDeclare()` 的取值链 `env || this.workspaceId` 里,env 永远赢,所以只改
|
|
1135
|
+
`this.workspaceId` 是够不到的)。改完立即 `sendDeclare()` 重新声明;socket 断开时
|
|
1136
|
+
只记录意图,下一次 `authenticated` 时带上。**幂等**:同 id ⇒ `changed:false` 且不发帧。
|
|
1137
|
+
- **`POST /v1/workspace { workspaceId }`**(`daemon/local-server.ts`)—— 桌面主进程
|
|
1138
|
+
用它把用户当前的 workspace 推给在跑的 daemon。未接 `onSetWorkspace` 的 embedder 答
|
|
1139
|
+
**501**(而不是假装成功)。空串 / 非字符串 / 坏 JSON 一律 400 且不触达 runner。
|
|
1140
|
+
|
|
1141
|
+
**选 redeclare 而不是「切换即重启 daemon」**:重启会杀掉在飞 run;而 `agent.host.declare`
|
|
1142
|
+
在 cloud 侧本来就是 refresh-on-redeclare("the latest declare wins",shadow 全拆重建、
|
|
1143
|
+
binding 走 `handleHostDeclare` 对同一 daemon 返回 `refreshed`)。**实测**二次 declare 既
|
|
1144
|
+
不报 409 也不报 `DAEMON_FORGOTTEN`,cloud 正常回 `host.acked`。
|
|
1145
|
+
|
|
1146
|
+
⚠️ **本条需要配套的 cloud 端修复才生效**(`src/im/ws/handler.ts`,不在本包):cloud 的
|
|
1147
|
+
workspace 解析链把 `im_containers` 行排在 `payload.workspaceId` 之前,而 local daemon 的
|
|
1148
|
+
那行正是 handler 自己上次 declare 时写的 —— 于是 daemon 被**永久钉死**在第一次 declare 的
|
|
1149
|
+
workspace 上。实测二次 declare 带新 workspaceId,**同一条 socket 上**和**整条连接重连之后**
|
|
1150
|
+
cloud 都照样 ack 回旧的,静默无错。(这也是为什么「重启 daemon」那条退路同样修不好。)
|
|
1151
|
+
|
|
1152
|
+
- 测试:`test/local-server-set-workspace.test.ts`(5 条,真 HTTP,非 mock 路由)绿。
|
|
1153
|
+
真栈门:`scripts/test203/journeys/j43-daemon-workspace-redeclare.ts` —— 真 WS declare +
|
|
1154
|
+
`im_containers` DB oracle。正向 PASS;负控 `--tamper`(撤掉 redeclare)必红,实测红。
|
|
1155
|
+
|
|
1156
|
+
### Fixed — desktop205 R1-e:资产投递链的三条残留(`pendingByTask` 泄漏 / auto-scan 去重 / `bindSourceTask` 判别)
|
|
1157
|
+
|
|
1158
|
+
- **`bindSourceTask` 不再靠 id 形状猜**(`daemon/asset/deliver.ts`)。旧判据
|
|
1159
|
+
`taskId.length <= 30 && !/^(session:|run[:_])/` 是**已经在错**而不只是脆弱:
|
|
1160
|
+
`IMTask.id` 与 `IMTaskRun.id` 都是 `@default(cuid()) @db.VarChar(30)`,形状不可分;
|
|
1161
|
+
cloud 对聊天 dispatch 下发的 `payload.taskId` 就是 `IMTaskRun.id`,经
|
|
1162
|
+
`PRISMER_RUN_ID`(spawn 适配器)或从 `<execution_context><run_id>` 抄出的 `--run-id`
|
|
1163
|
+
(hermes)原样走到这里时,形状闸答"看板卡"并 stamp `sourceTaskId` = 一个 `im_tasks` 里
|
|
1164
|
+
不存在的 id(装得下 VarChar(30),所以不 500,只是静默错绑 + 落进 `/tasks/<runId>`)。
|
|
1165
|
+
改为按判别式取真值:auto-resolve ⇒ run · 本机在飞 dispatch 的 `payload.kind`(新增
|
|
1166
|
+
`ArtifactsWatcher.activeTaskKind()`,由 dispatch.ts 用 `dispatchKind` 立键)⇒ 权威 ·
|
|
1167
|
+
`mode:'task-attach'` ⇒ 调用方显式声明 · 都问不到才回退形状闸**并打警告行**。
|
|
1168
|
+
- **合成会话行不再往 `pendingByTask` 立无人抽干的桶**(`daemon/asset/deliver.ts`)。
|
|
1169
|
+
auto-resolve 落在 `task_id` 为 NULL 的行(`HermesSessionMapper` 的 `session:<id>` 合成行)
|
|
1170
|
+
时没有可搭载的 dispatch,`flushPending` 的两处生产调用都按 cloud dispatch id 取键,
|
|
1171
|
+
写进去的桶永远没人读、随 daemon 生命周期单调增长。现在不 record,`mode:'attach'` 的响应
|
|
1172
|
+
改带 `ridesReply: false` + 说明(资产仍上传并锚在 `/runs/<runId>`)。
|
|
1173
|
+
- **auto-scan 上传方补上与 `recordDeliveredAsset` 同口径的 assetId 去重**
|
|
1174
|
+
(`daemon/artifacts-watcher.ts`)。cloud `POST /assets` 按
|
|
1175
|
+
`(workspaceId, contentHash[, sourceTaskId])` 去重并返回既有行,于是同一份文件走
|
|
1176
|
+
「写进 artifactsDir + 显式 `cloud deliver --mode attach`」两条路时同一个 assetId 会被裸
|
|
1177
|
+
`push` 两次,在 `reply.assetIds` 里出现两次。
|
|
1178
|
+
- 测试:`test/desktop205-run-artifact-folder.test.ts` 新增 R1-e 7 条(每条正控配反向负控;
|
|
1179
|
+
fake cloud 复刻真 dedup 规则)。逐条注入故障验红:A `expected 1 to be +0`、
|
|
1180
|
+
B `expected [ 'ast_1', 'ast_1' ] to deeply equal [ 'ast_1' ]`、
|
|
1181
|
+
C `expected 'cmq5r8t1v0002wxyz' to be null`。
|
|
1182
|
+
|
|
1183
|
+
### Tests — `prismer banner` 的欢迎词行现在被 T0 钉住(apc/04 §5 样例 2)
|
|
1184
|
+
|
|
1185
|
+
`prismer banner`(非 `--compact`)在 banner 之前输出一行固定欢迎词
|
|
1186
|
+
`Welcome to Prismer Cloud`(`src/cli/commands/banner.ts:13`,2026-07-26 由并行线落盘)。
|
|
1187
|
+
这一行是**桌面本地 feed OTA 环的可观测 oracle**,但此前全仓没有任何测试断言它——
|
|
1188
|
+
「测试选层绿」对这条改动是空的,apc/04 §5 强制的负控(欢迎词打错字 ⇒ T0 断言红)
|
|
1189
|
+
**根本红不起来**。
|
|
1190
|
+
|
|
1191
|
+
新增 `test/banner-welcome.test.ts`(真 `buildBannerCommand()` + `parseAsync`,
|
|
1192
|
+
不 mock 命令本体):非 compact 必须写出该行,`--compact` 必须不写。
|
|
1193
|
+
|
|
1194
|
+
- commit `b3821c22`(首轮 2 条断言)+ `a3478605`(同一 task 的第二轮,锚定「欢迎词必须是 stdout
|
|
1195
|
+
第一行」并补 `--compact` 非空断言,共 6 条)
|
|
1196
|
+
(task `cms3dwas3034qxze2p6t857ch`,进开发审批 `cms3dwva3034xxze2md8n54yj`,
|
|
1197
|
+
发版审批 `cms3e9y7b03dlxze2c9fg2fva`)
|
|
1198
|
+
- 绿:`apc test --tier=T0 --json` exit 0 —— T0(root) 56/56 · T0(runtime) 191/191 · T0(desktop) 13/13
|
|
1199
|
+
- 负控红:同一命令在欢迎词打错字后 exit 1 —— T0(runtime) 190/191,
|
|
1200
|
+
`failedNames = ["test/banner-welcome.test.ts (vitest)"]`;红触发 H2 acceptance hook
|
|
1201
|
+
自动 redispatch(`im_tasks.metadata.acceptanceRedispatchCount = 1`)
|
|
1202
|
+
- OTA 触达(本机 daemon channel local feed):bundle `2.2.4-ota.1785166882`
|
|
1203
|
+
|
|
1204
|
+
### BREAKING — built-in role template `--from-template ceo` renamed to `--from-template team-manager`
|
|
1205
|
+
|
|
1206
|
+
`sdk/prismer-cloud/runtime/src/templates/roles/ceo.json`'s top-level `templateName` field changed
|
|
1207
|
+
from `"ceo"` to `"team-manager"` (CEO→Team Manager internal naming pass, no compat alias kept —
|
|
1208
|
+
scripts hardcoding `prismer profile create --from-template ceo` will now fail with "Unknown
|
|
1209
|
+
template"; re-run with `--from-template team-manager`). This only renames the CLI-facing template
|
|
1210
|
+
lookup key surfaced by `prismer profile templates` / `getRoleTemplate()` / `listRoleTemplates()`.
|
|
1211
|
+
The role's internal routing identifier `roleTemplate.slug` stays `"ceo"` — unrelated code paths
|
|
1212
|
+
(`profile-defaults.ts`, `orchestrator-lock.ts`, `workspace-orchestrator.service.ts`, …) that branch
|
|
1213
|
+
on `slug === 'ceo'` are unaffected.
|
|
1214
|
+
|
|
1215
|
+
### Added — `gh-claim-readback`:第一个"他证"型判据(apc/17 §3 相 1 · W1.1 / W1.2)
|
|
1216
|
+
|
|
1217
|
+
`structured-criteria.ts` 既有 6 个 checker 的 ground truth **全部是被判进程自己能写的东西**
|
|
1218
|
+
(它跑的那个文件系统、它能改写的 git 对象库、它自己建的 task 行)。apc/12 §0.10 记的实证:
|
|
1219
|
+
一棵从没参与本仓库任何发版的独立代码树跑出的 `ota-promote` 产物,与本仓库产物**逐字节相同** ——
|
|
1220
|
+
本地产物不携带运行身份。
|
|
1221
|
+
|
|
1222
|
+
新 checker 读的是 **GitHub 服务端签发、且绑死在一个仓库上的 id**:PR number / Actions `run_id` /
|
|
1223
|
+
check-run `conclusion` / branch protection。声明词表 `GH-REPO:` / `PR:` / `RUN:` / `SHA:` /
|
|
1224
|
+
`CHECK: <name> = <conclusion>` / `PROTECTION:`。
|
|
1225
|
+
|
|
1226
|
+
- **跨绑定比对**:回读对象的 `base.repo.full_name` / `repository.full_name` 必须等于声明的
|
|
1227
|
+
`GH-REPO` —— 一个真实但属于**另一个仓库**的 id 是 §0.10 的 free-ride 形态,即使传输层肯服务
|
|
1228
|
+
它也死在这一比。`repo` arg(skill.json 钉死)另挡"换成自己的 fork"
|
|
1229
|
+
- **fail-closed 三态**(§7 边界 3):无 `APC_GH_TOKEN` / 不可达 / 非 2xx **一律红**,不降级为
|
|
1230
|
+
`env_blocked`。branch protection 的 403 是只读边界在如实作答,仍然是红
|
|
1231
|
+
- **`headShaMustEqualLocal`(W1.2)**:服务端 head sha 必须同时等于声明的 `SHA:` 与本地
|
|
1232
|
+
`git rev-parse HEAD` —— §0.10 列为"待建"的 run-scoped 运行标识的第一个真实现(三方必须同名,
|
|
1233
|
+
agent 只控其二)
|
|
1234
|
+
- **判据侧不 spawn `gh` 二进制**(§3 相 1 ⚠️):`gh` 对"没有 run"答 exit 0,而 doc12 §0.9 的
|
|
1235
|
+
同一形状已复发 5 次。走 REST + 强制字段比对,从源头不引入那个退出码语义家族。`apiBase` 只是
|
|
1236
|
+
skill.json arg、**故意不给 env 覆盖**:判据回读的端点不该由被判 agent 所处的环境来指定
|
|
1237
|
+
- 测试:**传输不 mock** —— GitHub 响应形态的 fixture server 跑在**独立进程**里
|
|
1238
|
+
(`syncHttpGetJson` 是同步阻塞的,同进程 server 必死锁),checker 走真 HTTP。四条负控
|
|
1239
|
+
(编造 id / 跨仓库真 id / 无 token / 非 2xx)+ 变异证明:摘掉判定后这 11 条负控全部转绿
|
|
1240
|
+
|
|
1241
|
+
### Security — coding agent spawn env: 凭据 denylist 反转为 allowlist(apc/17 §8.1 W2.1′)
|
|
1242
|
+
|
|
1243
|
+
`filterCredentialBaseEnv` 之前问的是「这个名字是不是**已知的坏**」。拿本仓库 `.env.local` 的
|
|
1244
|
+
57 个真实变量名实测:**拦 21 / 透传 36**,透传里包括一把**活的** SMTP 口令(`SMTP_PASS` —
|
|
1245
|
+
`_PASS` 匹配不上 `_PASSWORD`)、`REDIS_URL`(URL 形态携带 `user:pass@`)、`SMS_ACCOUNT`、
|
|
1246
|
+
`SMTP_USER`、`K8S_CLUSTER_URL`、`AUTH_URL`。补前缀只是同一形态的又一次枚举。
|
|
1247
|
+
|
|
1248
|
+
- **反转**:新 `isAllowlistedSpawnEnvKey()` 是唯一的门 —— 只有**显式列出**的键能从 daemon 自身
|
|
1249
|
+
`process.env` 继承进 coding agent。原来的名字形态规则降级为**第二道门**,防前缀族
|
|
1250
|
+
(`NODE_AUTH_TOKEN` 这类)夹带。判别式:一个谁都没枚举过的
|
|
1251
|
+
`FUTURE_VENDOR_CREDENTIAL` 现在**默认被拦**(denylist 下必漏)
|
|
1252
|
+
- 大小写归一化到 UPPER:一次同时吃下 Windows 的 `Path`/`ProgramFiles`/`SystemRoot` 与小写
|
|
1253
|
+
`http_proxy`/`no_proxy`,不需要重复条目
|
|
1254
|
+
- **`claude-code-cli` / `codex-cli`(runner.ts 的 D21 fallback adapter)接进同一个漏斗** ——
|
|
1255
|
+
这两条之前用裸 `{...process.env}` spawn,driver 那条修好了它们还开着,等于没修
|
|
1256
|
+
- overlay 语义不变:`launchEnv` / `taskEnv` / `runtimeSettings.env` / `config.envVars` 在过滤
|
|
1257
|
+
**之后**合入,永远不被剥 —— 那是 code agent 唯一的鉴权通路(网关注入)
|
|
1258
|
+
- 逃生门不变:`PRISMER_CC_NO_CRED_FILTER=1`,且只从 `baseEnv` 读(overlay 关不掉)
|
|
1259
|
+
- ⚠️ **未纳入**:hermes gateway(`{...process.env}` 逐字继承,**零过滤**)、
|
|
1260
|
+
`daemon/shell-executor.ts`、`runner.ts::runShellCommand` —— 三条都不走本漏斗,见报告
|
|
1261
|
+
|
|
1262
|
+
### Fixed — task 产物根本不过 outbox(desktop205 O14)
|
|
1263
|
+
|
|
1264
|
+
W8 把断云契约修好了,但 `docs/desktop205/03-acceptance.md` §3.3 的「产物 = 落本地 + 进 outbox /
|
|
1265
|
+
复联 = 补传成功」**只对 drop-folder 成立**。task 产物走的是完全另一条路:
|
|
1266
|
+
`ArtifactsWatcher.deliverFile` / `upload()` 里的裸 `uploader.uploadAsset(...)` —— 不入队,失败
|
|
1267
|
+
直接抛给调用方,文件留在 task workdir,**复联永不补传**(auto-scan 那条的重试还是纯内存的,
|
|
1268
|
+
进程一死就没了)。`asset/origin/agent-gen.ts` 这个适配器早就在,但从没接线。
|
|
1269
|
+
|
|
1270
|
+
- **接线**:task 产物在**瞬时**上传失败(`isTransientUploadError`,W8 已有的分类器)时进
|
|
1271
|
+
`OriginOutbox`,由**同一个** `UploadRunner` tick 补传 —— 与 drop-folder 同一个队列、同一份
|
|
1272
|
+
持久化重试保证、同一个 tray/healthz 积压计数(W13)
|
|
1273
|
+
- **语义选择:在线路径不变,只在「云不可达」时转异步**。不是"一律入队立即返回"——
|
|
1274
|
+
`send` / `message-attach` 两个 mode 结构上需要用 assetId 再打一次 cloud,一律入队会让这两个
|
|
1275
|
+
功能彻底不可用;而永久拒绝(4xx / policy / MIME)继续抛回调用方,agent 必须知道自己的文件被
|
|
1276
|
+
拒收,把它排进一个注定 dead-letter 的队列只会把可行动的错误藏起来
|
|
1277
|
+
- **返回**:`attach` / `task-attach`(交付方式就是上传本身)⇒ **202** `{ok:true, queued:true,
|
|
1278
|
+
outboxId}`;`send` / `message-attach` ⇒ 字节照样入队(产物不丢)但**报 502**,因为消息动作
|
|
1279
|
+
确实没发生,说成功是 agent 会照着行动的谎。`ArtifactsWatcher.deliverFile` 返回类型改为
|
|
1280
|
+
`{status:'uploaded'|'queued'}` 判别联合(唯一调用方是 `asset/deliver.ts`)
|
|
1281
|
+
- ⚠️ **`AgentGenAdapter.fetch` 原来读 `detail.bytes`(内存 Buffer),结构上无法过 outbox**
|
|
1282
|
+
—— outbox 存的是 `payloadJson = JSON.stringify(detail)`,Buffer round-trip 回来是
|
|
1283
|
+
`{type:'Buffer',data:[…]}` 而不是 Buffer(outbox 文件头也明写"不存字节")。task 产物因此
|
|
1284
|
+
按 **path** 入队、drain 时从磁盘重读(与 drop-folder 同构);`observedAt` 取 **mtime**,
|
|
1285
|
+
同一份未修改文件重复交付收敛成一行
|
|
1286
|
+
- `SourceHints` 补 `assetKind` / `sourceTaskId` / `adapter` 三个透传位,否则补传出来的资产会
|
|
1287
|
+
是 `kind=file` 且**不绑看板卡** —— 那是"补传成功"的假象
|
|
1288
|
+
- `test/desktop205-task-artifact-outbox.test.ts`(真 http server + 真 socket + 真 SQLite +
|
|
1289
|
+
真文件系统,零 mock;断云 = 真关端口)。负控四条,全部实测转红:不复联就断言补传 ⇒ 红 ·
|
|
1290
|
+
永久 4xx 仍必须 dead-letter(把分类器改成永不判死 ⇒ 红)· 摘掉入队那一步 ⇒ 正控 6/8 转红 ·
|
|
1291
|
+
同步路径遇 4xx 不得入队。另加 daemon 重启后仍补传(证明持久化不是内存重试)
|
|
1292
|
+
|
|
1293
|
+
### Fixed — 「归属换人」被当成「服务器确认它没了」(desktop205 O15 残留)
|
|
1294
|
+
|
|
1295
|
+
收紧到「404 且是我方信封」之后仍有一条通道能通过确认:cloud 的
|
|
1296
|
+
`GET /agent_profiles/:id` 的 where 带 `workspace: { ownerImUserId }`,所以 **profile 还在、
|
|
1297
|
+
只是 workspace 换了主人**(转移 / 换账号)时也回我方信封的 404 ⇒ 本地 profile + agent 行照删。
|
|
1298
|
+
「不是你的」是权限变化,不是资源消失。
|
|
1299
|
+
|
|
1300
|
+
- **cloud 侧**(`src/im/api/agent-profiles.ts` GET `/:id`):miss 分支再查一次「不带 owner 约束」
|
|
1301
|
+
的同 id,回 `{ok:false, error:{code:'forbidden'|'not_found', message:'Profile not found'}}`。
|
|
1302
|
+
判别位放 `error.code`,**HTTP 状态与 message 文本两个分支完全一致**(沿用 `workdirs.ts` 的既有
|
|
1303
|
+
范式;403 会把「这个 id 存在」抬到最易探测的那层)。额外查询只在 miss 分支
|
|
1304
|
+
- **daemon 侧**:`confirmProfileGoneOnCloud` 的判据由「信封对」升级为
|
|
1305
|
+
**`error.code === 'not_found'`** —— 正向凭据。`forbidden` / 无 code 的老信封 / HTML / `{}` /
|
|
1306
|
+
其它状态码 / 网络失败一律不删
|
|
1307
|
+
- **版本漂移是刻意的保守方向**:老 cloud 回裸字符串(无 code)⇒ 不确认 ⇒ 保留行。代价有界
|
|
1308
|
+
(脏 profile 留到 cloud 升级;`host.acked` 的 tombstone 路径 `computeProfilesToDelete`
|
|
1309
|
+
**本来就没有 owner 过滤**,仍会清真正删掉的 profile),反方向(误删活 agent 的行)不可恢复
|
|
1310
|
+
- 负控实测:把判据改回「只要信封对」⇒ 归属换人那条**当场删光两张表的行**
|
|
1311
|
+
(`profiles:0, agents:0`),2 条转红;cloud 侧 `src/im/tests/acp-agent-profile-ownership-404.test.ts`
|
|
1312
|
+
在改路由前 4/7 红(两种 404 字节完全同形)
|
|
1313
|
+
|
|
1314
|
+
### Fixed — 删本地 profile 行用弱判据(desktop205 O15)
|
|
1315
|
+
|
|
1316
|
+
`syncProfileFromCloud` 只要 `CloudError.status === 404` 就删掉本地 profile + agent 行。真断云
|
|
1317
|
+
给的是 `status:0`,所以当时是安全的 —— 但「404」≠「服务器确认这个 profile 没了」:captive
|
|
1318
|
+
portal、反代误路由、滚动发布期的 404 都会命中,而这是**破坏性操作**。
|
|
1319
|
+
|
|
1320
|
+
- `CloudError.code` 帮不上忙:`CloudClient.request` 在 body 不是我们 `{error:{code}}` 对象时会
|
|
1321
|
+
**合成** `code:'not_found'`,nginx 的 HTML 404 与真 404 在 CloudError 上完全同形
|
|
1322
|
+
- 改为要求一次**正向确认** `confirmProfileGoneOnCloud()`:用 `fetchRaw` 重读同一路由(保留了
|
|
1323
|
+
`request` 丢掉的原始 body),必须是 404 **且** body 是我们自己的 JSON 错误信封
|
|
1324
|
+
(`{ok:false,…}`,cloud 实回 `{ok:false, error:'Profile not found'}`)。HTML / 非 JSON /
|
|
1325
|
+
`{}` / 其它状态码 / 网络失败一律**不删**。只在 404 分支多打一次请求,正常路径不加
|
|
1326
|
+
- `test/desktop205-profile-delete-warrant.test.ts`(真 http server 分别回我方信封 / nginx HTML /
|
|
1327
|
+
关端口,真 CloudClient,真 local.db;oracle 是两张表的行数)。把判据改回裸 404 ⇒ HTML 404 与
|
|
1328
|
+
`{}` 404 **当场删光本地行**,实测转红
|
|
1329
|
+
|
|
1330
|
+
### Fixed — 断云会永久毁掉产物投递(desktop205 W8,两个真 bug)
|
|
1331
|
+
|
|
1332
|
+
`docs/desktop205/03-acceptance.md` §3.3 的断云契约要求「产物 = 落本地 + 进 outbox」且
|
|
1333
|
+
「复联 = outbox 补传成功」。实测(`test/desktop205-offline-outbox.test.ts`,真 http server +
|
|
1334
|
+
真 socket + 真 SQLite + 真文件系统,零 mock)两条都不成立,成因是 `UploadRunner` 里两个叠加的
|
|
1335
|
+
缺陷:
|
|
1336
|
+
|
|
1337
|
+
- **一次 `drainOnce` 烧光全部 attempts**。`recordFailure` 把非终态行直接放回 `'pending'`,
|
|
1338
|
+
而 `drainOnce` 的 `while(true)` 会在同一轮里**立刻重新 claim 这同一行** ⇒ `maxAttempts=3`
|
|
1339
|
+
的三次重试全部发生在同一次 drain、彼此之间没有任何时间间隔。配合 runner.ts 每 1 秒一次的
|
|
1340
|
+
drop-folder tick,**一次 1 秒的网络抖动就足以把一个产物判死**。
|
|
1341
|
+
修:一次 drain 对同一行最多消耗一次尝试(重复 claim 即释放并跳出)。
|
|
1342
|
+
- **ECONNREFUSED 被算进 dead-letter 预算**。cloud 不可达属于"稍后重试",不是"这些字节
|
|
1343
|
+
永远不会被接受";旧行为把断云变成**永久数据丢失**(文件被移进 `upload-failed/`,复联后
|
|
1344
|
+
再也不会补传)。修:新增 `isTransientUploadError()`(socket 级错误码 / `fetch failed` /
|
|
1345
|
+
超时 / 408·429·5xx),瞬时失败**永不** dead-letter;4xx 等永久拒绝仍照旧判死。
|
|
1346
|
+
|
|
1347
|
+
负控(防"把 dead-letter 整个拆了"骗绿):永久性 HTTP 400 仍必须在 maxAttempts 次后
|
|
1348
|
+
dead-letter 并把文件移进 `upload-failed/`;以及"不复联就断言复联结果必须红"。
|
|
1349
|
+
|
|
1350
|
+
### Added — daemon 侧上报 tray 可观测性数据(desktop205 W13)
|
|
1351
|
+
|
|
1352
|
+
2026-07-26 裁决把桌面 tray 定位成**普通用户唯一的可观测面**(daemon runtime 是服务器形态,
|
|
1353
|
+
`prismer` CLI 对桌面 daemon 结构性失明)。daemon 补上它此前从不上报的两项:
|
|
1354
|
+
|
|
1355
|
+
- **`/healthz.assetOutbox`** `{pending, uploaded, deadLetter, sampledAt}` —— 产物待传/失败积压。
|
|
1356
|
+
那次清理撞见 2 在飞 + 4 dead-letter,用户完全无从知晓。
|
|
1357
|
+
⚠️ **计数由 producer 侧采样**:healthz 是纯内存零 I/O 快照而 tray 定时轮询它,
|
|
1358
|
+
handler 里做 `COUNT(*)` 会把每次轮询变贵。采样点是 drop-folder tick(本就每秒开这个 db),
|
|
1359
|
+
复用既有的 `pendingCount/uploadedCount/deadLetterCount`,新增的只是组合器
|
|
1360
|
+
`snapshotOriginOutboxCounts`
|
|
1361
|
+
- **`/tasks/running.tasks`** `[{taskId, agentName?, kind?, scopeLabel?, startedAt}]` —— 在跑的是谁、
|
|
1362
|
+
归属哪个 workspace、跑了多久。`scopeLabel` 是 cloud 预渲染的 `identityContext.scope`
|
|
1363
|
+
**原样透传**(不解析)。**不含任务名**:wire 上根本没有 title,只有 `prompt`,而 prompt 是
|
|
1364
|
+
用户内容,不该画进菜单栏
|
|
1365
|
+
- 两者都走既有 **spread 范式**:runner 没接对应子系统 ⇒ 字段整个缺席,
|
|
1366
|
+
**CLI / K8s 的 healthz 与 `/tasks/running` 形状逐字节不变**;且「没数据」与「数据是 0」
|
|
1367
|
+
在 wire 上就是可区分的(消费端不得 `?? 0`)
|
|
1368
|
+
- `test/tray-outbox-healthz.test.ts`:真 SQLite outbox → 真 HTTP /healthz 断言积压数;
|
|
1369
|
+
负控三条(数据源置反 / 停止采样 / state 不带字段),并断言**反复轮询 healthz 不会改变读数**
|
|
1370
|
+
(证明零 I/O 没被破坏)
|
|
1371
|
+
|
|
1372
|
+
### Fixed — held-out deny 规则不再落共享 settings.json(desktop205 W4)
|
|
1373
|
+
|
|
1374
|
+
`ensureApcHeldOutDeny` 把 deny 规则 read-merge-write 进 **per-daemon** 的
|
|
1375
|
+
`~/.prismer/claude-config/.claude/settings.json`,合并是 `new Set([...existing, ...rules])`
|
|
1376
|
+
**只增不删** ⇒ 一次 APC run 置位后,同机此后所有 coding agent(含无关的用户 agent)
|
|
1377
|
+
永久继承 held-out deny,且规则藏在 hermetic 目录里用户无从得知。
|
|
1378
|
+
|
|
1379
|
+
- `ensureApcHeldOutDeny`(有文件副作用)**删除**,换成纯函数
|
|
1380
|
+
`apcHeldOutDenySettings()` / `apcHeldOutSettingsArg()`,零文件系统副作用
|
|
1381
|
+
- 规则改由 **per-run** 的 Agent SDK `Options.settings` 承载(= CC 的 `--settings` flag,
|
|
1382
|
+
CC 自己落到临时文件再读)。共享 settings.json 全程不被写
|
|
1383
|
+
- 置位点从 `buildClaudeSpawnEnv`(env builder 不该有文件副作用)挪到 `buildOptions`
|
|
1384
|
+
- ⚠️ 选 `Options.settings` 而非 `extraArgs.settings`:两者编译成**同一个** `--settings`
|
|
1385
|
+
flag,而 SDK 让 `Options.settings` 覆盖 extraArgs 项(`sdk.mjs`:
|
|
1386
|
+
`if (this.options.settings) k9.settings = this.options.settings`)。`buildFastModeOptions`
|
|
1387
|
+
已经在 fast-mode 机型上设 `Options.settings`,走 extraArgs 会在那些 run 上**静默丢掉**边界
|
|
1388
|
+
- ~~已知残留:老机器上**已被污染**的 `settings.json` 不会自己消失,需一次性清理~~
|
|
1389
|
+
→ 已由下面的 W4 迁移收掉
|
|
1390
|
+
|
|
1391
|
+
### Fixed — held-out deny 的**开关**也变成 per-dispatch(desktop205 W5 / F7)
|
|
1392
|
+
|
|
1393
|
+
W4 只消除了「污染永久化」,开关本身仍是 daemon 全局:`buildOptions` 调
|
|
1394
|
+
`apcHeldOutDenySettings()` **不传参**,读的是 daemon 的 `process.env`。⇒ 想开这个边界,
|
|
1395
|
+
只能给整台机器上所有 coding agent 一起开 —— 正是 W4 刚刚拆掉的爆炸半径又从正门走了回来。
|
|
1396
|
+
|
|
1397
|
+
- `buildOptions` 改为 `apcHeldOutDenySettings(sdkEnv)`:判据取**这次 spawn 真正拿到的
|
|
1398
|
+
env**。`buildClaudeSpawnEnv` 已经把 dispatch 的 per-task env(`extra.claude.env` →
|
|
1399
|
+
`taskEnv`,以及承载 cloud `metadata.skillConfigEnv` / `roleParamsEnv` 的
|
|
1400
|
+
`launchContext.env`)叠在 daemon env 之上,所以 per-task `APC_HELDOUT_DENY=1`
|
|
1401
|
+
只窄化这一次 run,同 daemon 并发的其他 coding agent 不受影响
|
|
1402
|
+
- `test/apc-heldout-per-task.test.ts`:oracle 是**注入 queryFactory 捕获的真 SDK
|
|
1403
|
+
`Options`**(真 `ClaudeAgentSession` 跑真 `buildOptions`,探针不复刻合并逻辑)。
|
|
1404
|
+
负控 = 同一 client(同 daemon、`process.env` 全程不置位)两个 session,只有带标记的那个
|
|
1405
|
+
拿到规则;另一条负控 = per-task `'0'` 压过 daemon 全局 `'1'`
|
|
1406
|
+
- ⚠️ **仍未接线**:cloud / daemon 两侧都**不存在**「这次 run 是 APC coding-scope」这个判据
|
|
1407
|
+
(`workdir.sourceRef` 从不与 repo 身份或 held-out 列表比对),所以通道就绪、置位方待定。
|
|
1408
|
+
⚠️ 定位是 **hint 不是安全边界**——`Bash` 结构上就在 CC 的 file-permission check 之外
|
|
1409
|
+
|
|
1410
|
+
### Fixed — 一次性清理被 pre-W4 代码污染的 settings.json(desktop205 W4 迁移)
|
|
1411
|
+
|
|
1412
|
+
- 新增 `pruneApcHeldOutDenyFromSettings(configDir)`:从 `permissions.deny` 里移除**恰好等于**
|
|
1413
|
+
旧写入方生成过的规则(含已废弃的 `Write(...)` / `MultiEdit(...)` / `NotebookEdit(...)`
|
|
1414
|
+
三种拼法——那正是老机器上的大头),用**精确字符串相等**而非前缀/模式匹配,用户自建规则
|
|
1415
|
+
逐字保留;deny 变空则删该键,文件里其他内容一律不动。非抛出:文件缺失 / 不可解析 /
|
|
1416
|
+
不可写都是返回值,不是异常
|
|
1417
|
+
- 落点是 `ensureClaudeConfigIsolation`:它本就拥有 hermetic 目录、在任何 CC spawn 前必跑、
|
|
1418
|
+
已是 per-daemon-process 缓存(= 一次性迁移想要的节奏),且只在隔离开启时触发——污染只可能
|
|
1419
|
+
在那个前提下产生。失败被吞,清理不阻断 dispatch
|
|
1420
|
+
- `test/apc-heldout-deny-migration.test.ts`:oracle 是 settings.json 的**字节**。
|
|
1421
|
+
最吃重的负控是「用户自建规则必须存活」,含形似但不在列表里的 `Edit(scripts/test203)`
|
|
1422
|
+
(held-out 路径的前缀)、`Edit(scripts/test203/journeys/j11-…)`(held-out 目录**内部**)等;
|
|
1423
|
+
把实现换成子串匹配即变红
|
|
1424
|
+
- 已知边界:若将来从 `APC_HELD_OUT_PATHS` **删除**某路径,磁盘上它的旧规则将不再可清理,
|
|
1425
|
+
需要单独补一条
|
|
1426
|
+
|
|
1427
|
+
### Fixed — deny 规则表按 CC 2.1.220 实际语法换代(desktop205 W3)
|
|
1428
|
+
|
|
1429
|
+
`APC_DENY_TOOLS` 从 `["Write","Edit","MultiEdit","NotebookEdit"]` 收敛为 `["Edit"]`。
|
|
1430
|
+
CC 2.1.220 实跑证实:`Edit(path)` 是**唯一**参与 file permission check 的路径匹配器,
|
|
1431
|
+
且它覆盖全部 file-editing 工具;`Write(p)` / `NotebookEdit(p)` 不参与,`MultiEdit`
|
|
1432
|
+
已不是已知工具。**语义不减弱**,去掉的是每个 held-out 路径 6 条死规则(每次 spawn 54 行
|
|
1433
|
+
stderr 告警)。
|
|
1434
|
+
|
|
1435
|
+
- 新增 `test/apc-heldout-cc-syntax.test.ts`(desktop205 G1):拿**实际发出的** `--settings`
|
|
1436
|
+
payload 起一次真 `claude`(临时 HOME、无凭证、无网络——CC 在鉴权前就校验规则),
|
|
1437
|
+
断言 permission-rule 告警 **0 条**;负控塞一条 `MultiEdit(path)` 必须出告警。
|
|
1438
|
+
CC 二进制不可得时写 evidence 文件并 **loud skip**,不静默通过
|
|
1439
|
+
- 旧 drift-guard 只比对**路径列表**、不校验规则语法,所以这次 CC 改语法我们完全不知道;
|
|
1440
|
+
G1 把「CC 升级悄悄废掉我们的规则」变成可检出
|
|
1441
|
+
|
|
1442
|
+
### Fixed — APC skill 源随包分发(desktop205 D8)
|
|
1443
|
+
|
|
1444
|
+
`sdk/apc/skills/` 此前**只存在于 repo working tree**。`resolveApcSkillsRoot()` 从运行
|
|
1445
|
+
模块向上walk,因此在任何非 repo 宿主(OTA bundle / npm 装的 daemon / 桌面
|
|
1446
|
+
`resources/runtime`)都返回 null ⇒ `installPlatformApcSkills()` 返回 null ⇒ **什么都不装**。
|
|
1447
|
+
这是藏在平台门背后的第二道静默门,与 2026-07-19「bundle 漏 plugins/ ⇒ 记忆层全黑」
|
|
1448
|
+
**同一失败模式**(打包清单与真实依赖各自演化),修法沿用那次的范式。
|
|
1449
|
+
|
|
1450
|
+
- prebuild hook 现镜像 **两个**源进包根:`built-in-skills/` + 新增 `apc/skills/`
|
|
1451
|
+
- `package.json` `files` 增加 `apc`(npm tarball 已实测含全部 35 个文件)
|
|
1452
|
+
- 两个 OTA 打包器(K8s `build-daemon-runtime-bundle.ts` / 桌面 `build-daemon-bundle.cjs`)
|
|
1453
|
+
stage `apc/` 并在各自 REQUIRED_ENTRIES 里校验
|
|
1454
|
+
- `BUNDLE_REQUIRED_ENTRIES` 与桌面 `REQUIRED_ENTRIES_BY_KIND.daemon` **同步**加
|
|
1455
|
+
`apc/skills`——缺 skill 的 bundle 判为不可用,而不是静默降级
|
|
1456
|
+
- 桌面 `assemble-runtime.sh`(built-in 兜底层,被 `resolveDaemonBundlePath()`
|
|
1457
|
+
**不经校验**直接返回)补 stage `apc/` **与 `plugins/`**。后者是本次挖出的既有缺口:
|
|
1458
|
+
2026-07-19 那次断言「floor 总是 memory-capable」只对 K8s 镜像成立,桌面这层手工
|
|
1459
|
+
组装的 floor 从来没有 `plugins/`
|
|
1460
|
+
- 新增 `test/apc-skill-distribution.test.ts`:仓外临时目录里 esbuild 打真模块 → 断言
|
|
1461
|
+
`resolveApcSkillsRoot()` 真返回存在路径(正控);删掉 `apc/skills` 后断言解析为 null
|
|
1462
|
+
**且** `isValidBundleDir()` 判红(负控);四处清单再分叉即红(drift 门)
|
|
1463
|
+
|
|
1464
|
+
### Fixed — launchd boot shim 是第三份 REQUIRED_ENTRIES 且早就漂了(desktop205 O11)
|
|
1465
|
+
|
|
1466
|
+
`apps/desktop/electron/daemon-boot.cjs` 的注释自称与 `bundle-manager.ts`
|
|
1467
|
+
"same contract … keep them in sync",实际只要 `dist/cli.js` + `package.json`——
|
|
1468
|
+
2026-07-19 加的 `plugins/` 和 2026-07-26 加的 `apc/skills` 都没跟上。
|
|
1469
|
+
⇒ **launchd 会启动一个 host 进程判为无效的 bundle**,而「缺 plugins 的 bundle 被启动」
|
|
1470
|
+
正是那次记忆层全黑事故的形状。
|
|
1471
|
+
|
|
1472
|
+
- shim 的 **bundle** 判据收紧到与另两份逐字节一致(4 项)
|
|
1473
|
+
- **兜底层判据刻意更弱**(只要 `dist/cli.js` + `package.json`):floor 是用户与
|
|
1474
|
+
「这台机器没有 daemon」之间的最后一道,因为缺 `plugins/` 就拒绝启动它 = 把「降级的
|
|
1475
|
+
daemon」变成「没有 daemon」,还会被 KeepAlive 无限重放。这与 host 侧完全一致——
|
|
1476
|
+
`resolveDaemonBundlePath()` 校验 staged/current **bundle**,然后**不校验**地返回
|
|
1477
|
+
builtin。保持 floor 有能力是打包步骤的职责(`assemble-runtime.sh`,已有断言)
|
|
1478
|
+
- 不从共享模块 require 清单:该文件**只 import node builtin** 是它的立身之本
|
|
1479
|
+
(生成物 = 又一个能在「绝不许起不来」的路径上丢失的东西)。改用 drift 断言机械化
|
|
1480
|
+
- `apc-skill-distribution.test.ts` 的 drift 门从四处扩到**五处**,并把「floor 判据必须是
|
|
1481
|
+
bundle 判据的真子集」也钉住,让这条不对称保持是**决定**而不是退化成漂移
|
|
1482
|
+
- `apps/desktop/electron/__tests__/boot-shim.test.ts`:oracle 仍是 marker 文件
|
|
1483
|
+
(**哪个 cli.js 真的跑了**)。新增负控——缺 `plugins/` 的 bundle 与缺 `apc/skills` 的
|
|
1484
|
+
bundle 都必须被拒且回落到 floor;配套反向负控:完整 bundle 仍能启动(证明门不是无条件拒绝)、
|
|
1485
|
+
缺 `plugins/` 的 **floor** 仍能启动、缺 `dist/cli.js` 的 floor 仍 exit 1
|
|
1486
|
+
|
|
1487
|
+
### Added — skill 验收判据引擎:新增 `type:"structured"` 与 6 个 checker(补记)
|
|
1488
|
+
|
|
1489
|
+
> **补记说明**:这批改动分 4 个 commit 落地(`4f17c311` / `689ea719` / `e82db478` /
|
|
1490
|
+
> `c3d91481`)**均未更新本文件**,违反 CLAUDE.md「改 `sdk/*` 任何包必须更对应
|
|
1491
|
+
> CHANGELOG」。由 apc/12 的 code-review 验收跑在审 `c3d91481` 时作为 **convention
|
|
1492
|
+
> 段 major finding** 抓出——这正是该 skill 该抓的东西,故在此补齐而非静默略过。
|
|
1493
|
+
|
|
1494
|
+
`bundle` 的 `matchAcceptanceCriteria` 新增 `type:"structured"` 判据类型(`substring`
|
|
1495
|
+
/ `regex` 两条既有路径**逐字节不变**;未知 checker fail-closed)。动机:原判据全部
|
|
1496
|
+
读 agent 的**报告文本**,因此原则上无法区分「真跑」与「编造」——实证一份零检索、
|
|
1497
|
+
路径行号瞎写的纯编造报告可拿满分。
|
|
1498
|
+
|
|
1499
|
+
**层 2(复核报告关于「文件」的声称)**
|
|
1500
|
+
|
|
1501
|
+
- `cited-evidence` — 每个 `path:line` 读回磁盘(存在/行范围/非空行/锚定命中/行数下限)
|
|
1502
|
+
- `dimension-coverage` — 每维独占 keyed 行,答案须是可复核引用或 `N/A — 理由`;
|
|
1503
|
+
可选 `requiredVerdicts` 钉某维 verdict;可选 `anchorToKey` 要求引用落在**声明该维的那一行**
|
|
1504
|
+
- `doc-sync-obligations` — 从 delta 自行重算义务集与 gap 集再比对
|
|
1505
|
+
|
|
1506
|
+
**层 3(复核关于「运行时副作用」的声称)**
|
|
1507
|
+
|
|
1508
|
+
- `json-claim` — 重解析报告内 fenced JSON:钉 schema/形态、散文数值 ⇄ 产物字段、
|
|
1509
|
+
派生判定须由退出码推出、每层算术、`baselineRecompute`、`freshArtifacts`(产物 mtime
|
|
1510
|
+
与自带 timestamp 同期)、`filesExist`、`fileEquals`
|
|
1511
|
+
- `declared-id-readback` — `TASK:/ASSET:/CRITERION:/META:` 声明的 id **回读真 API**
|
|
1512
|
+
(含存储 JS 类型,`--set` 的数字化在此被逮)
|
|
1513
|
+
- `git-claim-readback` — 只读 git plumbing:commit 文件集恰好等于声明、branch tip、
|
|
1514
|
+
push 落点须为本地 bare 替身、被拒 prod tag 不在其上、冲突仓 `MERGE_HEAD` 仍在
|
|
1515
|
+
|
|
1516
|
+
I/O 走同步子进程(HTTP 经 `execFileSync(node -e fetch)`,token 由 env 传不进 argv;
|
|
1517
|
+
git 全只读),**`matchAcceptanceCriteria` 签名未变**,两个 CLI 的 `normalizeCriterion`
|
|
1518
|
+
同步保留 `checker`/`args`(否则结构化条目会降级成 substring 而恒红)。
|
|
1519
|
+
|
|
1520
|
+
### Fixed — `dimension-coverage` 的 `anchorToKey` 配错不再静默 fail-open
|
|
1521
|
+
|
|
1522
|
+
`anchorToKey` 此前经 `argRecord` 取值,对任何非 plain-object 返回 `undefined` ⇒
|
|
1523
|
+
`true` / `[{…}]` / `"minAnchored:1"` / 键名手滑,**都会静默退回加严前的行为并报满绿**。
|
|
1524
|
+
而该 arg 存在的唯一理由就是堵「label 承诺大于实现」那个洞 ⇒ 静默跳过使这道守卫沦为
|
|
1525
|
+
装饰。现改为显式 `checker misconfigured` 判红,与本文件既有惯例一致(`dimensions` /
|
|
1526
|
+
`requiredVerdicts` / `deltaFiles` / `json-claim` args / `allowed.*` 早已 fail-closed)。
|
|
1527
|
+
**缺省不置位时行为仍逐字节不变**,不追溯打红已按旧语义验收的判据。
|
|
1528
|
+
|
|
1529
|
+
### Fixed — `CITATION_RE` 缺 `%` 导致引用真实文件被判编造
|
|
1530
|
+
|
|
1531
|
+
路径字符类与 lookbehind 排除集均不含 `%`,而仓库有 19 个真实的 `%5F` 路径文件
|
|
1532
|
+
(admin debug endpoint)⇒ 被解析成 `5Fadmin/...` 判 FAKE。对 `cited-evidence`
|
|
1533
|
+
(`bad.length > 0 ⇒ fail`)是**无条件红**——**这类「如实判红」比「编造判绿」更隐蔽**,
|
|
1534
|
+
因为它看起来像判据很严。已补 `%` 并加回归;存在性检查未放宽。
|
|
1535
|
+
|
|
1536
|
+
### Fixed — APC skill 只投平台 workspace,源改工作树(判据修正)
|
|
1537
|
+
|
|
1538
|
+
上一版把「谁能拿 APC skill」判据挂在 **cloud 的逐 agent 安装记录**上。用户随后给出平台
|
|
1539
|
+
workspace 的正式定义,两条都推翻了它:
|
|
1540
|
+
|
|
1541
|
+
1. **APC 范围的 skill 只在唯一的平台 workspace 安装**,只用来做 prismercloud 工程本身,
|
|
1542
|
+
**不需要任何发布动作**。⇒ cloud catalog 不该出现在内容路径上(它当时只有 17 个里的 7 个,
|
|
1543
|
+
且比工作树旧几天)。
|
|
1544
|
+
2. **平台 workspace = 平台管理员账号绑定的 workspace**。⇒ 判据是 workspace 的 owner,不是
|
|
1545
|
+
agent 的安装行。旧判据选错了对象:真机上被投中的 agent 属于一个**非平台** workspace。
|
|
1546
|
+
|
|
1547
|
+
**新判据**(`isPlatformWorkspace`,daemon 侧):
|
|
1548
|
+
|
|
1549
|
+
- `GET /api/im/workspaces/:id` → `ownerImUserId`;`GET /api/im/users/lookup?identifier=<email>`
|
|
1550
|
+
→ 平台管理员的 imUserId;相等即平台 workspace。两个都是既有 endpoint,**cloud 侧零改动**。
|
|
1551
|
+
- 管理员白名单读 `ADMIN_EMAILS`(与 cloud `admin-rbac.ts` 同一个 env、同一套 lowercase 归一)。
|
|
1552
|
+
- **不问 cloud「我是不是 admin」**:`isSandboxAdmin()` 在 `NODE_ENV !== 'production'` 对任何人
|
|
1553
|
+
短路返回 true,拿它当高危投递的门在 dev 等于没门(apc/14 A3 的教训)。这里是
|
|
1554
|
+
`isAdminEmailStrict` 的 daemon 侧镜像。
|
|
1555
|
+
- **fail-closed 覆盖每一种 unknown**:白名单未配置/为空、profile 没有 workspaceId、workspace
|
|
1556
|
+
读不到(非成员 404 / 网络错 / 报文异常)—— 一律判 false,且 false 不只是「不装」,而是把
|
|
1557
|
+
entitled class **reconcile 成空**(非平台机器上残留的 APC 目录与新装同样是泄漏)。
|
|
1558
|
+
- verdict 有缓存:正判 30min、负判 60s(`/users/lookup` 是 30/min 限流;正判长 TTL 让已证实的
|
|
1559
|
+
平台机器在云端短暂不可达时不掉工具链)。
|
|
1560
|
+
|
|
1561
|
+
**源改工作树**:`resolveApcSkillsRoot()` 从模块位置向上找 `sdk/apc/skills/`(dist 3 跳、src 7
|
|
1562
|
+
跳),`installPlatformApcSkills()` 直接 cpSync。永远是最新版、无需发布、cloud 退出内容路径只
|
|
1563
|
+
留授权路径。npm 安装态找不到该目录 → null → 不投(只有平台 checkout 才有源)。
|
|
1564
|
+
|
|
1565
|
+
**保留未动**:`persistence → hermes only` 的门逐字节未动;`.prismer-managed` /
|
|
1566
|
+
`.prismer-entitled` 两个互不相交 managed class 与各自 reconciler 的归属护栏;
|
|
1567
|
+
`resolveCodingCwdSkillsDir()` 的 claude-code-only 目标解析。
|
|
1568
|
+
|
|
1569
|
+
测试 `test/coding-skill-entitlement.test.ts` 18 → **33 例**,四处判据变异全部真红(平台门恒真
|
|
1570
|
+
→ 6 红;白名单空时静默兜底 → 2 红;去掉 persistence 拒绝 → 2 红;去掉外来目录护栏 → 2 红)。
|
|
1571
|
+
真机(活 daemon 未停未重启,走真 endpoint):平台 workspace 的 coding agent 拿到 **17/17**
|
|
1572
|
+
且含工作树独有的「输出契约」节;非平台 workspace 的 agent **0 个**且旧的 7 个被 evict,12 个
|
|
1573
|
+
`.prismer-managed` 与用户的 `sdk-release` 完好;`ADMIN_EMAILS` 未配置时对**真平台 workspace**
|
|
1574
|
+
也是 0 且目录不创建。
|
|
1575
|
+
|
|
1576
|
+
### Fixed — structured acceptance checkers: three contract↔checker misalignments that reddened truthful reports (apc/12)
|
|
1577
|
+
|
|
1578
|
+
端到端真跑(真 daemon → claude-code → 真回帖)证实结构化 checker 的机制是对的(编造报告
|
|
1579
|
+
`18 citations / 0 verified on disk` 判红,同一份报告下三条旧 regex 判据仍全 PASS),但同时暴露
|
|
1580
|
+
**三条会把 100% 如实的报告判红**的缺陷。「无论对错都会红」与「无论对错都会绿」是同一类 bug。
|
|
1581
|
+
|
|
1582
|
+
- **`CHANGE-POINT` 正则拒绝整行反引号包裹**:前导字符类 `^[\s>*\-|]*` 不含反引号,而
|
|
1583
|
+
`impact-trace` sample prompt 自己给的范例就是整行包裹的 —— 照抄范例 → `no CHANGE-POINT
|
|
1584
|
+
declaration found`。前导类补反引号。
|
|
1585
|
+
- **`rg -c` 的 `path:COUNT` 被当行号复核**:契约明令跑 `rg -c` 对账,其输出正是 `path:数字`。
|
|
1586
|
+
新增 `parseClaimCitations()`:整行只有一个裸 `path:数字` 的行(即 `rg -c` 输出)不采为
|
|
1587
|
+
citation;`parseCitations()` 保持字面语义不变(调用方会传单行片段给它,容差不能落在那里)。
|
|
1588
|
+
契约侧同步要求计数写成 `count=N`、不得内联进句子——内联即恢复为声明并照常复核。
|
|
1589
|
+
- **缩写路径判 FAKE 且文案误导**:`verifyCitation` 的失败文案改为点名契约规则
|
|
1590
|
+
(REPO-ROOT-RELATIVE / `rg -c` 计数不是行号),契约侧显式写明必须 repo-root-relative。
|
|
1591
|
+
|
|
1592
|
+
### Added — `dimension-coverage` 支持钉死 verdict(`args.requiredVerdicts`)
|
|
1593
|
+
|
|
1594
|
+
`{ "<dimension>": "covered"|"gap"|"n/a" }` 要求该维度**自己那一行**以指定 verdict 开头
|
|
1595
|
+
(中文 `已覆盖`/`缺口`/`不适用` 同义)。没有该 arg 时行为逐字节不变;有该 arg 但行上无
|
|
1596
|
+
verdict token、或 verdict 不符、或 `requiredVerdicts` 指向 `dimensions` 之外的键 → fail-closed。
|
|
1597
|
+
这是 `design-review` 五维审计的承重 oracle:没有它,「五维全标 covered + 正文某处出现 gap 一词」
|
|
1598
|
+
是一次照样打绿的橡皮图章。
|
|
1599
|
+
|
|
1600
|
+
测试 `test/bundle-structured-criteria.test.ts` 25 → 36 例(新增 11,每条正控配负控,含
|
|
1601
|
+
「照契约范例逐字产出的报告必须绿」一组)。
|
|
1602
|
+
|
|
1603
|
+
### Added — APC held-out write DENY at dispatch (apc/14 D6,双重约束的 dispatch 侧)
|
|
1604
|
+
|
|
1605
|
+
docs/apc/02 §2 R4 的"双重约束"要求 held-out(`baseline.json` / journeys / contract gates /
|
|
1606
|
+
视觉基线)**物理上打不开写**。review 侧的 diff 门(`scripts/test203/run.ts --review-diff`,
|
|
1607
|
+
不依赖 agent 诚实)已成;本条补上 dispatch 侧的 deny。
|
|
1608
|
+
|
|
1609
|
+
隔离 CC 的 hermetic `settings.json`(`~/.prismer/claude-config/.claude/settings.json`,
|
|
1610
|
+
即 CC 的 `user` settings source)种入 `permissions.deny` 规则——`Write/Edit/MultiEdit/NotebookEdit`
|
|
1611
|
+
命中 held-out 路径即被 CC 二进制**硬拒**(deny 是硬边界,即使 `bypassPermissions` 模式
|
|
1612
|
+
`canUseTool` 谓词根本不被调用也生效,故落点选 settings.json 而非谓词)。
|
|
1613
|
+
|
|
1614
|
+
- **门**:`APC_HELDOUT_DENY=1`(dispatch 侧显式开,默认 OFF——普通 coding agent 写权限不受影响)。
|
|
1615
|
+
且只在 config 隔离开启(默认 ON)时生效,绝不写用户真实 `~/.claude`。
|
|
1616
|
+
- **单一真相源**:`config-isolation.ts::APC_HELD_OUT_PATHS` **镜像**
|
|
1617
|
+
`scripts/test203/heldout-guard.ts::HELD_OUT_PATHS`(runtime 包 `rootDir=./src` 无法跨包
|
|
1618
|
+
import,故镜像 + drift-guard 测试读源文件断言两份集合相等——漂移即红)。
|
|
1619
|
+
- **不误伤**:只拒 held-out 写;普通源文件(含 `scripts/test203/run.ts` 本身)、读操作不受限。
|
|
1620
|
+
- 落点 `config-isolation.ts`(`isApcHeldOutDenyEnabled` / `apcHeldOutDenyRules` /
|
|
1621
|
+
`ensureApcHeldOutDeny`,read-merge-write 幂等,不丢无关 settings),种入点在
|
|
1622
|
+
`agent.ts::buildClaudeSpawnEnv`。契约测试 `test/apc-heldout-deny.test.ts`(8 例,每正控配负控,
|
|
1623
|
+
三处变异各自转红)。端到端(真 CC 进程试写 baseline 被拒)本轮未跑(无活 daemon)。
|
|
1624
|
+
|
|
1625
|
+
### Fixed — daemon churn 放大成请求风暴(APC Root B:sync/ack 无 backoff + host.acked 全量再同步)
|
|
1626
|
+
|
|
1627
|
+
gap A(下条)用有界 undici dispatcher 封住了请求风暴的**爆炸半径**,但**没治源头**。源头
|
|
1628
|
+
是一个 churn 循环(`ownership rejected → adopt → re-declare`)让 `host.acked` 在短窗内**连发多次**,
|
|
1629
|
+
而每次 `host.acked` 都会 (a) 触发一遍**全 workspace** 的 cloud→local memory 再同步,(b) skill-sync
|
|
1630
|
+
路径**重发每个 skill 的 ack**——**攻击之间无 backoff、快速触发不合并**。打到慢 cloud 上就是自我放大
|
|
1631
|
+
(实测 `skill sync ack failed` ×85 / `Request aborted/timeout` ×107)。
|
|
1632
|
+
|
|
1633
|
+
**修**(新模块 `src/daemon/churn-guard.ts`,两个纯 + 可注入时钟/RNG 的原语):
|
|
1634
|
+
|
|
1635
|
+
- **`ExponentialBackoff`** — 连续失败 → 下次尝试延迟 `base·factor^n`(封顶 + ±抖动),成功即重置。
|
|
1636
|
+
用于 skill-sync **ack 门**(`skill-sync.ts::ackSkillSync`):按 `(agent,slug)` 记账,一个刚失败的
|
|
1637
|
+
ack 在其指数退避窗口内**跳过重发**(下个自然触发在窗口过后重试,不丢 ack、只是不再猛砸)。base 2s /
|
|
1638
|
+
cap 60s。env kill-switch `PRISMER_DAEMON_ACK_BACKOFF=off`。
|
|
1639
|
+
- **`CoalescingRunner`** — 把短窗内的 N 次 `trigger()` **合并成一次** trailing run,失败后按 backoff
|
|
1640
|
+
拉开重试间距。`memory/runner-wiring.ts::syncMemoryFromCloud` **改为 debounce**:churn 期一串
|
|
1641
|
+
`host.acked` 只落**一次**全 workspace fan-out(workspace 并集);整体不可达时按指数退避重试而非
|
|
1642
|
+
每 tick 再发火。默认 1s 窗口(非延迟敏感,见 cloud-dispatcher.ts 注释),env
|
|
1643
|
+
`PRISMER_DAEMON_MEMORY_SYNC_DEBOUNCE_MS=0` 可关。
|
|
1644
|
+
|
|
1645
|
+
只改 retry/trigger 的**节奏**,不掩盖竞态、不放松任何断言。单测断真行为(间隔指数增长、N 次触发只跑 1 次、
|
|
1646
|
+
失败按 backoff 拉开),每条修复配一条"原本会抓到它"的变异用例(拆 backoff 成恒定间隔 / 拆 debounce /
|
|
1647
|
+
拆 ack 门 → 分别红 4/4/1 条,已实测)。**诚实边界**:本轮只证 backoff/debounce 的**逻辑**有牙;真活
|
|
1648
|
+
daemon 下"连接不再涨"的前后对照未跑(有界 dispatcher 已封半径,主会话可随后验)。孤儿 council daemon 的
|
|
1649
|
+
**根治 = 孤儿 conversation soft-delete cascade**,属 `project_orphan_conversations_survive_workspace_soft_delete`
|
|
1650
|
+
深域,不在本次。
|
|
1651
|
+
|
|
1652
|
+
### Fixed — daemon cloud fetch 连接池无上限,churn 会撑爆慢 cloud(APC gap C)
|
|
1653
|
+
|
|
1654
|
+
daemon 的 cloud HTTP 客户端(`auth.ts CloudClient`)用 Node 内置 `fetch`,即 undici
|
|
1655
|
+
**默认** global Agent——对单一 origin 的连接数**无上限**。任何请求风暴(如 adopt/ownership
|
|
1656
|
+
churn 在每次 `host.acked` 重放 memory-sync + transport-probe + skill-sync-ack、且无 backoff)
|
|
1657
|
+
打到一个**慢** cloud(本机 `npm run dev`,HTTP/1.1)时,undici 每个并发请求开一条**新** socket
|
|
1658
|
+
而非复用 ⇒ 连接爆涨(实测单个 daemon 攥住 **345 条**空闲 ESTABLISHED socket 到 :3000),
|
|
1659
|
+
把单线程 dev server 的 event loop 饿死(连静态路由都从 0.005s 涨到 24s),并形成恶性循环
|
|
1660
|
+
(慢 → 30s 超时 → 重试 → 更多 socket → 更慢)。删掉该 daemon,server 连接 711→5、延迟 24s→5ms,
|
|
1661
|
+
把病因隔离到 daemon 的无界连接池。
|
|
1662
|
+
|
|
1663
|
+
**修**:daemon 前台启动时(`daemon.ts::runForeground`,任何 cloud 请求/SSE 之前)安装一个
|
|
1664
|
+
**有界** undici global dispatcher(新模块 `src/daemon/cloud-dispatcher.ts`):`connections` 上限
|
|
1665
|
+
(默认 24,`PRISMER_DAEMON_MAX_CLOUD_CONNECTIONS` 可调)+ keep-alive 复用。再大的风暴也只能开
|
|
1666
|
+
≤N 条 socket,超额请求在 undici 内排队而不是拿新 socket 砸服务器——结构性封住爆炸半径。
|
|
1667
|
+
实测 200 并发请求打慢服务器:修前 200 socket,修后 24 socket。WS(走 `ws` 包)与 asset 下载
|
|
1668
|
+
(自带 per-request dispatcher)不受影响。**注**:这只封上限,不治真正的驱动(churn 循环 +
|
|
1669
|
+
skill-sync-ack 无 backoff),后者是另一个更深的 bug。
|
|
1670
|
+
|
|
1671
|
+
### Fixed — daemon git RPC / workdir jail:三个对抗评审坐实的真缺陷
|
|
1672
|
+
|
|
1673
|
+
评审用真 git 复现,全部已修 + 补回归用例(`test/git-rpc.test.ts` 26 例,
|
|
1674
|
+
`test/workdir-materialize.test.ts` 25 例,全部带负控)。
|
|
1675
|
+
|
|
1676
|
+
1. **非冲突的 git 失败被误判成 `conflict`**(`git-rpc.ts`)。旧实现对 git 的**错误文本**
|
|
1677
|
+
做子串匹配(`/conflict|automatic merge failed|unmerged files/i`),而 git 会把调用方
|
|
1678
|
+
给的 ref 名回显进错误里 ⇒ `branch fix/conflict-handling`(分支已存在)和
|
|
1679
|
+
`merge feat/conflict-x`(ref 不存在)都被判成 `conflict` 且 `files=[]`,一路透到 CLI 打印
|
|
1680
|
+
"Merge conflict — NOT auto-resolved. Escalate to a human. Conflicted files (0)"——
|
|
1681
|
+
把拼写错误渲染成需要人类介入的冲突。**现在判据取副作用**:git 自己的未合并索引
|
|
1682
|
+
(`git diff --diff-filter=U`)或在场的 `MERGE_HEAD`,与错误文本无关。真冲突的
|
|
1683
|
+
`files` 因此恒非空。
|
|
1684
|
+
2. **jail root 可由 payload 抬高**(`git-rpc.ts::runGitExecRequest`)。`workspaceId` 是
|
|
1685
|
+
拼进 `workspacesDir` 的**路径段**,而它只校验了 `typeof === 'string'`;`path.join`
|
|
1686
|
+
会归一化 `..` ⇒ `workspaceId:'ws_A/..'` 把 jail root 抬到 workspaces 目录,
|
|
1687
|
+
实测在**另一个 workspace 的 repo 里建出了分支**。现在用 `isSafeSegment` 校验段。
|
|
1688
|
+
(cloud 侧 member 门此前挡住了 HTTP 可达性,所以这是纵深防御层的洞——而
|
|
1689
|
+
daemon jail 存在的全部意义正是"cloud 错了也不塌"。)
|
|
1690
|
+
3. **jail 是词法的,symlink 可越狱**(`git-rpc.ts` + `workdir-materialize.ts`)。包含判定
|
|
1691
|
+
走 `path.resolve`,不解 symlink;而能在 workdir 里种 symlink 的正是这道 jail 针对的
|
|
1692
|
+
coding agent 本人。实测 jail 内 `ln -s <外部 repo> link` + `cwd:'link'` ⇒ 外部 repo
|
|
1693
|
+
真被写了分支。现在包含判定走 **realpath**(新模块 `src/daemon/path-jail.ts`),
|
|
1694
|
+
且返回解析后的真实路径(不再从 symlink 穿过去执行)。
|
|
1695
|
+
|
|
1696
|
+
**新增内部模块** `src/daemon/path-jail.ts`:`isSafeSegment` / `realpathBestEffort` /
|
|
1697
|
+
`resolveWithinJail`。安全谓词只此一份——重复实现必然漂移。
|
|
1698
|
+
|
|
1699
|
+
**顺带修的既有债(与上面 3 条分开记账)**:`runner.ts` 的
|
|
1700
|
+
`agent.fs.list` / `agent.fs.read` / `agent.fs.write` / `agent.workdir.materialize`
|
|
1701
|
+
用同样的方式拼 jail root,同样没校验段,现在四处都加了同一个守卫;
|
|
1702
|
+
`resolveWorkdirCwd` 的词法 jail 同步改成 realpath 判定。
|
|
1703
|
+
|
|
1704
|
+
**行为变化**:`resolveGitCwd` / `resolveWorkdirCwd` 返回的 cwd 现在是 realpath。生产
|
|
1705
|
+
路径(`~/.prismer/workspaces/...`)无 symlink,解析前后一致;仅测试夹具里的
|
|
1706
|
+
`os.tmpdir()`(macOS `/var` → `/private/var`)会看出差别。
|
|
1707
|
+
|
|
1708
|
+
### Added — daemon git RPC 接线 + 硬化(APC P0-5 / apc/05 §1 A2)
|
|
1709
|
+
|
|
1710
|
+
`src/daemon/git-rpc.ts` 此前**已存在但零调用方**(只在 `index.ts` export)。本次接线成
|
|
1711
|
+
`agent.git.exec` reverse-RPC(`runner.ts` 新增 case,回 `agent.git.reply`,镜像
|
|
1712
|
+
`agent.workdir.materialize` 的形态),并补四项硬化:
|
|
1713
|
+
|
|
1714
|
+
1. **cwd jail** —— 新增 `resolveGitCwd(root, cwd)`(镜像 `resolveWorkdirCwd`)。
|
|
1715
|
+
`GitRpcRequest.root` 现为**必填**(fail-closed);root 由 daemon 自己从
|
|
1716
|
+
`workspaces/<workspaceId>` 算,不接受 payload 指定。
|
|
1717
|
+
2. **remote allowlist** —— push 的 remote 必须是白名单里的**纯名字**(默认 `['origin']`,
|
|
1718
|
+
`PRISMER_GIT_REMOTE_ALLOWLIST` 可覆盖)。URL 形态与 option 形态
|
|
1719
|
+
(`--receive-pack=…`,可在对端执行命令)在进 argv 前就被拒。
|
|
1720
|
+
3. **commit / merge 回 sha** —— `GitRpcResult.sha`,供 task↔commit provenance。
|
|
1721
|
+
4. **冲突返回文件清单** —— `GitRpcError.files`(`git diff --name-only --diff-filter=U`),
|
|
1722
|
+
worktree 保持现场(不 `merge --abort`、不 reset)。
|
|
1723
|
+
|
|
1724
|
+
其它:错误 detail 现同时读 stderr+stdout(git 把 CONFLICT 写 stdout,旧的 `||` 链在
|
|
1725
|
+
stderr 非空时会丢掉冲突文本);git 子进程加 120s 超时(旧实现对不可达 remote 会永久挂)。
|
|
1726
|
+
|
|
1727
|
+
**破坏性(内部 API)**:`gitRpc()` 的 `root` 必填。此前零调用方,无外部影响。
|
|
1728
|
+
|
|
1729
|
+
### Fixed — drain_respawn no longer hangs on stop()(product205 OTA)
|
|
1730
|
+
|
|
1731
|
+
`armDrainRespawnWatcher` 不再 `await this.stop()`——`stop()` 里的
|
|
1732
|
+
`await servicePool.shutdown()` / `await localServer.stop()` 可能无限卡住
|
|
1733
|
+
(adapter 子进程不退出 / HTTP 连接不关闭),导致 `process.exit(0)` 永远
|
|
1734
|
+
到不了,daemon 不退出,kubelet 不重启,OTA 中断,用户必须手动重建 pod。
|
|
1735
|
+
|
|
1736
|
+
修法:drain_respawn 只做同步清理(关 WS + abort in-flight + stop SSE)
|
|
1737
|
+
然后直接 `process.exit(0)`。K8s/desktop supervisor 重启进程,OS 回收
|
|
1738
|
+
所有资源。
|
|
1739
|
+
|
|
1740
|
+
### Fixed — OTA bundle 纯净化(product205 OTA 空腔修复)
|
|
1741
|
+
|
|
1742
|
+
OTA bundle 构建 6 个空腔全部修复:
|
|
1743
|
+
1. `127.0.0.1` artifactUrl 不可达 → `host.docker.internal`
|
|
1744
|
+
2. `redis-cli` 不存在 → 用 `ioredis` 清 apply blob
|
|
1745
|
+
3. 版本黑名单 → 发新版本绕过
|
|
1746
|
+
4. macOS native 二进制(`fsevents.node` / `rollup.darwin-arm64.node`)→ 删除
|
|
1747
|
+
5. `built-in-skills/` 缺失(`readSkillText` throw)→ 补进 bundle
|
|
1748
|
+
6. `better-sqlite3` 目录残留导致 `linkBuiltinNativeDeps` 跳过 → 整个目录删除
|
|
1749
|
+
|
|
1750
|
+
### Added — action governance runtime P1(product205 M6)
|
|
1751
|
+
|
|
1752
|
+
daemon-side runtime enforcement seam reopens(docs/product205/03 §3.4):
|
|
1753
|
+
|
|
1754
|
+
- `HERMES_YOLO_MODE` 从硬编码 `'true'` 改为 `runtimeApprovalMode` 驱动(team=gated
|
|
1755
|
+
→ YOLO off → dangerous-command 桥激活;personal=auto)
|
|
1756
|
+
- `--dangerously-skip-permissions`(claude-code)改为 `runtimeApprovalMode` 条件化
|
|
1757
|
+
- Hermes `approval.request` SSE 事件捕获 §3.5 bundle(`buildApprovalBundleFromHermesPayload`
|
|
1758
|
+
+ `classifyActionClass`),透传到 `SessionsSseResult.approvalBundle`
|
|
1759
|
+
- bundle 传播:sessions-sse → sessions-dispatcher → runs-dispatcher → AdapterResult.metadata
|
|
1760
|
+
- `dispatch.ts` `finalError` 附带 `approvalBundle`(runtime-hook 源)随 `task.dispatch.reply`
|
|
1761
|
+
上报 cloud(code=awaiting_human_approval)
|
|
1762
|
+
- `turn.ask_human`:`task.dispatch.reply` error code=awaiting_clarification + clarifyBundle
|
|
1763
|
+
|
|
1764
|
+
### Added — persona seatScope(product205)
|
|
1765
|
+
|
|
1766
|
+
`CreateAgentForWorkspaceInput` gains `seatScope`('workspace' | 'council')。
|
|
1767
|
+
Council convene 创建 persona agent 时标 `seatScope='council'`——不占 workspace
|
|
1768
|
+
席位、不出现在 agent roster。需配合 cloud 侧迁移 523。
|
|
1769
|
+
|
|
1770
|
+
### Added — general-assistant role template(product205 M4)
|
|
1771
|
+
|
|
1772
|
+
平台默认 deputy role 模板(通用助理),member first mile accept-provision 用。
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
### Fixed — persona role: cloud MCP allowlist(product205 M0 · AC-C6)
|
|
1776
|
+
|
|
1777
|
+
persona role 模板此前 `mcpServers: []`——persona-agent 在 cloud MCP 面无限制
|
|
1778
|
+
(可调 `prismer.task.create`/spend),与动作治理能力黑名单冲突(strip 后暴露为
|
|
1779
|
+
load-bearing)。补 discussion-only `toolsAllowlist`(memory read/recall ·
|
|
1780
|
+
agent/message send · asset 只读;task/skill/publish/spend 面按 allowlist 语义省略即拒)。
|
|
1781
|
+
oracle:`council-m2` 正测 `config.mcpAllowlist` 转绿 + 参数化负控(persona 拒
|
|
1782
|
+
`task.create`、orchestrator 同工具放行对照)。
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
### Added — role/council-scoped memory writes(product204 E4)
|
|
1786
|
+
|
|
1787
|
+
打通 role/council 可见性从 tool → daemon store → outbox envelope → cloud 的全链
|
|
1788
|
+
路(cloud 侧 `POST /api/im/memory/pages` 早已 honor `body.visibility`,缺口全在
|
|
1789
|
+
daemon/tool 侧):
|
|
1790
|
+
|
|
1791
|
+
- `MemoryVisibility` 联合新增 `{ kind:'role'; slug }` / `{ kind:'council'; id }`;
|
|
1792
|
+
所有 switch/consumer(store 写入+hydrate、cloud-sync `parseCloudVisibility`、
|
|
1793
|
+
boundary `acl-predicate`、rpc `visibilityToString`)穷尽处理新 kind。
|
|
1794
|
+
- `rpc.ts`:`visibilityToString` emit `role:<slug>` / `council:<id>`(cloud
|
|
1795
|
+
memory-acl 期望的确切串形态);新增 `parseVisibilityString` 把 memory_write
|
|
1796
|
+
工具传来的 `role:<slug>`/`council:<id>`/`agent:<id>`/`workspace` 串解析回联合,
|
|
1797
|
+
缺失/未知安全降级为 `workspace`(D3 保守默认)。
|
|
1798
|
+
- `memory-tools.ts`:`MemoryWriteToolInput` + `MEMORY_WRITE_INPUT_SCHEMA` 增可选
|
|
1799
|
+
`visibility?: string`(`required` 仍为 `['path','content']`)。
|
|
1800
|
+
- boundary ACL:role/council 作为粗粒度共享 scope 在 daemon 边界按 workspace-可见
|
|
1801
|
+
处理,真正的成员治理留 cloud 超集。
|
|
1802
|
+
- 新增回归 `test/memory-visibility-scope.test.ts`:断言 outbox envelope 串
|
|
1803
|
+
`council:<id>`/`role:<slug>` 精确落地 + 负控(缺失→workspace、未知→workspace)。
|
|
1804
|
+
|
|
1805
|
+
### Fixed — 代码评审整改(feature/release203 批次)
|
|
1806
|
+
|
|
1807
|
+
- **hermes gateway 每轮杀重启风暴**:`writeHermesGatewayStamp` 写盘失败(profile
|
|
1808
|
+
dir 不可写)时,`hermesGatewayConfigDrifted` 把本进程刚 spawn、只是没记下的
|
|
1809
|
+
gateway 当成无见证孤儿 → 每条消息 kill+respawn、清空会话上下文。spawn 时同时在
|
|
1810
|
+
内存里见证(键用 stamp 绝对路径,生产复用/测试隔离两不误),写盘失败也不再 churn。
|
|
1811
|
+
补回归负控 `test/hermes-gateway-stamp.test.ts`(抽掉内存兜底即翻红)。
|
|
1812
|
+
- **`cloud role test` 把瞬时网络错误当「skill 不存在」**:只有 404 才是真缺失;
|
|
1813
|
+
status 0 / 5xx / timeout 归为 `unreachable`(exit 2 + 「transient, retry; do NOT
|
|
1814
|
+
rebuild」),不再误导 agent 去重建已存在的 skill。
|
|
1815
|
+
|
|
1816
|
+
### Added — `prismer skill|role` 发布后治理七动词(product204/21 M-P W3)
|
|
1817
|
+
|
|
1818
|
+
发布之前有 `publish`,发布之后此前**什么都没有**。补齐镜像动作,skill 与 role
|
|
1819
|
+
两个命名空间同构(blueprint 无 CLI 命名空间,仅 HTTP + Studio):
|
|
1820
|
+
|
|
1821
|
+
- **`delist <slug> [--reason]`** / **`relist <slug> [--changelog]`** — 退出/回到
|
|
1822
|
+
Marketplace 的**可见性单轴翻转**。存量使用者零影响(skill 已装 agent 继续
|
|
1823
|
+
同步内容、role 继续投影)。relist 不是新端点,是重跑该资产自己的 publish
|
|
1824
|
+
端点——publish 的门(license / SS-02 / takedown 锁)因此只有一份实现。
|
|
1825
|
+
- **`deprecate <slug> --reason <r> [--successor <slug>]`** / **`undeprecate`** —
|
|
1826
|
+
软信号 + 替代品指针;**不拦截**新安装/新 apply,只让消费者看见理由与后继。
|
|
1827
|
+
- **`archive <slug> [--confirm]`** — 破坏性退役。**有活跃消费者时服务端 409**,
|
|
1828
|
+
CLI 把消费者计数打成人话并提示 `--confirm`("3 agents are still using this
|
|
1829
|
+
skill — archiving unbinds them")。public role 直接 archive → 提示先 delist。
|
|
1830
|
+
- **`transfer <slug> --to <imUserId>`** + **`transfer-accept`** / **`transfer-abort`** —
|
|
1831
|
+
两段式所有权转移;offer 阶段 owner 不变,受让人 accept 才落。
|
|
1832
|
+
- **`published`** — 我的发布清单表格:slug / 上架状态(listed·delisted·deprecated·
|
|
1833
|
+
taken_down·archived)/ **消费者数**(= archive 的爆炸半径)/ installs / 在途转让。
|
|
1834
|
+
|
|
1835
|
+
**错误码人话化**(新 `cli/publish-lifecycle.ts`,与 `@prismer/sdk` 的 `cloud`
|
|
1836
|
+
CLI **共享同一张表**,两包不允许漂移):`not_owner` / `has_active_consumers` /
|
|
1837
|
+
`taken_down` / `changelog_required` / `successor_not_listed` / `transfer_pending` /
|
|
1838
|
+
`not_transfer_target` / `must_delist_first` … 一律译成"原因 + 出路"的整句,
|
|
1839
|
+
不再抛裸 HTTP 码。
|
|
1840
|
+
|
|
1841
|
+
> **承重语义**(SKILL.md 与文案都必须传达):skill 安装与 role→agent 是**活引用,
|
|
1842
|
+
> 不是快照**——改已发布件的内容会自动传播到全部使用者,故内容改动强制带
|
|
1843
|
+
> `--changelog`;**下架不影响存量使用者,归档才解绑**。
|
|
1844
|
+
|
|
1845
|
+
同步更新 built-in skill `skill-builder` / `role-builder` 的 SKILL.md
|
|
1846
|
+
(新增「发布后管理」节)与 `docs/api/publish-lifecycle.md`。
|
|
1847
|
+
|
|
1848
|
+
### Added — `agent.host.declare` 上报 `bundleVersion`(product204/08 §2.3 step 4,M9 收口)
|
|
1849
|
+
|
|
1850
|
+
- declare payload 新增 additive 字段 `bundleVersion`:**实际在跑的 runtime 版本**
|
|
1851
|
+
(boot-time OTA 换入的 bundle 即 bundle 版本;builtin boot 即镜像/npm 版本——
|
|
1852
|
+
进程 exec 的是 bundle 的 cli.js,自身 package.json 就是真相)。cloud 侧落
|
|
1853
|
+
agent card `metadata.bundleVersion`,runtime-skew 探针 fleet 分布随之从
|
|
1854
|
+
`im_agent_bindings.daemonVersion` 升级为真实运行版本(daemonVersion 保留为
|
|
1855
|
+
legacy fallback)。老 daemon 不带该字段 → cloud 行为不变。
|
|
1856
|
+
|
|
1857
|
+
### Fixed — role-builder `ingest-role.mjs` 默认公域回归(M9 收口,随 bundle 分发)
|
|
1858
|
+
|
|
1859
|
+
- `built-in-skills/role-builder/scripts/ingest-role.mjs` 默认路径回归为 admin
|
|
1860
|
+
公域 catalog(违反 product204/16 §2.3「CLI 默认私域」;根因=M5 的改动落在
|
|
1861
|
+
**gitignored 的 runtime 拷贝**、被 runtime 重建用 canonical 旧版覆盖——
|
|
1862
|
+
lost-uncommitted-change,j25 A4 回归抓到)。修复落 **canonical**
|
|
1863
|
+
`sdk/prismer-cloud/built-in-skills/`:默认 `/mine`(private),公域须显式
|
|
1864
|
+
`--publish`(alias `--admin-catalog`),`--mine` 保留为 no-op 兼容。
|
|
1865
|
+
|
|
1866
|
+
### Fixed — 无 config 环境任何 `prismer` 子命令崩溃(M9 收口)
|
|
1867
|
+
|
|
1868
|
+
- `cli/commands/task.ts` 的 `mkTaskWaitAdapter` 与 `cli/commands/workspace.ts`
|
|
1869
|
+
的 `mkMemberAdapter` 在 **program 构建期** 急切 `mkCloud()`(→ `loadConfig`),
|
|
1870
|
+
导致没有 `~/.prismer/config.toml` 的主机上 `prismer --help` / `prismer ota
|
|
1871
|
+
status` / 一切子命令直接崩 "Config not found"。CloudClient 改为**首次调用时
|
|
1872
|
+
惰性创建**,与 daemon 轮询等 config 的设计对齐——OTA resolver(`prismer ota
|
|
1873
|
+
resolve`)必须在 pre-setup 环境可用(08 §2.3 entrypoint 钩子)。
|
|
1874
|
+
|
|
1875
|
+
### Added — boot-time runtime-bundle OTA 消费链(product204/08 §2.3,M9-β)
|
|
1876
|
+
|
|
1877
|
+
- **`daemon/ota/`(新模块)**:K8s/CLI 侧 bundle OTA 状态机——
|
|
1878
|
+
检查(manifest 3s+重试,候选序 `PRISMER_BUNDLE_MANIFEST_URL` env →
|
|
1879
|
+
`/api/runtime/update/manifest` → `/api/desktop/update/manifest`,两种
|
|
1880
|
+
payload 形态均容忍)→ 下载 → **sha256/sha512 + Ed25519 双验**(签 zip 原始
|
|
1881
|
+
字节;公钥 `DAEMON_BUNDLE_PUBKEY` env 可覆盖、内置常量与桌面同 keypair;
|
|
1882
|
+
验签失败=显式拒绝+incident 回传,**绝不落盘执行**)→ 系统 `unzip` 解包到
|
|
1883
|
+
`~/.prismer/bundle/<version>/` → 解包后 `package.json` 版本≠manifest 版本
|
|
1884
|
+
即拒(双版本源 drift 门禁)→ 原生依赖(better-sqlite3)解包时从镜像内置
|
|
1885
|
+
runtime **symlink 借入**(ESM 不查 NODE_PATH——桌面 bundle-manager 已
|
|
1886
|
+
code-verified 的教训,08 §2.3 "NODE_PATH 借用"的语义落点)→ 原子指针切换
|
|
1887
|
+
(`previous` = 回滚位)。
|
|
1888
|
+
- **回滚 + 熔断**:boot-attempt marker 跨容器原地重启存活(emptyDir 语义,
|
|
1889
|
+
**无 PVC**);上次 boot 未确认 → 计 strike + `boot_failed`/`rolled_back`
|
|
1890
|
+
回传 + 回退 previous;**连续 2 strikes → 本地拉黑 + 删目录(防复活)**,
|
|
1891
|
+
停留 previous/镜像 builtin(镜像永固兜底,任何失败路径都不 brick)。
|
|
1892
|
+
- **incident 回传**:走既有 `POST /api/desktop/update/report`(migration 499
|
|
1893
|
+
词表,`component='daemon'`)——`applied`/`boot_ok`(熔断统计分母)+
|
|
1894
|
+
`verify_failed`/`boot_failed`/`rolled_back`/`blacklisted`。⚠️ 该路由现有
|
|
1895
|
+
`DAEMON_ID_RE` 只认桌面形态 id,K8s `container:<pod>` 形态会被
|
|
1896
|
+
logged-then-dropped——server 侧放宽挂 α/收口波。
|
|
1897
|
+
- **`prismer ota (resolve|status)`(新 CLI verb)**:`resolve --exec-path`
|
|
1898
|
+
是 K8s entrypoint 钩子(stdout 只打 bundle cli.js 路径,builtin=空输出,
|
|
1899
|
+
日志全走 stderr);`PRISMER_BUNDLE_OTA=0` 本地跳过(server 侧 kill switch
|
|
1900
|
+
= manifest decision=none)。
|
|
1901
|
+
- **boot 确认**:`daemon start` 成功后 10s 稳定窗(`scheduleBundleBootConfirm`)
|
|
1902
|
+
清 marker + 清 strike 记录 + 回传 `boot_ok`;窗口内崩溃 ⇒ marker 存活 ⇒
|
|
1903
|
+
下次 boot 自动计 strike 回滚。
|
|
1904
|
+
- 测试:`test/ota-bundle.test.ts`(13 用例:真 keypair/真 zip/真本地 HTTP
|
|
1905
|
+
server 全链 + 篡改负控 + 回滚/拉黑注入 + 回传 payload 断言)。
|
|
1906
|
+
|
|
1907
|
+
### Removed — `PRISMER_UNIFIED_WS` 逃生口(product204/18 Wave 3,挂 M9 收割)
|
|
1908
|
+
|
|
1909
|
+
- `daemon/runner.ts` 不再读 `PRISMER_UNIFIED_WS`:unified `WS /ws/realtime`
|
|
1910
|
+
是唯一 realtime 路径,legacy `SseSubscriber`(`/api/im/sync/stream`)不再
|
|
1911
|
+
被 runner 实例化(模块保留,仅测试引用)。设 `PRISMER_UNIFIED_WS=0` 的部署
|
|
1912
|
+
自本版起为 no-op。
|
|
1913
|
+
|
|
1914
|
+
### Added — per-agent skill config env injection(product204/09 §2.3 Phase C)
|
|
1915
|
+
|
|
1916
|
+
- **`adapters/prismer-env.ts::skillConfigEnvFromMetadata`**:读取 cloud 派发时
|
|
1917
|
+
解析好的 `task.metadata.skillConfigEnv`(KEY→value,UPPER_SNAKE、禁
|
|
1918
|
+
`PRISMER_` 前缀——cloud 入库门禁保证),`applyPrismerScopeEnv` 尾部以
|
|
1919
|
+
**覆盖语义**并入子进程 env(三级解析 agent 覆盖/role 默认 高于 daemon 全局
|
|
1920
|
+
env;缺省 = 不注入 = global env 兜底)。coding 路径(claude-code / codex /
|
|
1921
|
+
provider-proxy-env)零改动即消费。
|
|
1922
|
+
- **hermes adapter(seam b)**:`dispatch()` 把 `skillConfigEnv` 循环
|
|
1923
|
+
`writeEnvValue` 进 per-profile `.env`(gateway 长驻进程不走 spawn env;
|
|
1924
|
+
per-profile 文件 ⇒ 同 daemon 多 agent 隔离是结构性的)。
|
|
1925
|
+
- **`daemon/skill-loader.ts::parseSkillConfig`**:SKILL.md frontmatter `config:`
|
|
1926
|
+
声明的 daemon 侧宽松镜像解析(诊断用;值永远来自 dispatch metadata,不做本地
|
|
1927
|
+
解析注入)。`LoadedSkill` 增 `config` 字段。
|
|
1928
|
+
|
|
1929
|
+
### Added — identityContext named sections(product204/07 Phase C · 分段注册制 D6)
|
|
1930
|
+
|
|
1931
|
+
- **Wire type** `TaskDispatchRequestPayload.identityContext.sections?: IdentitySection[]`
|
|
1932
|
+
(`{ id, owner, order, content }`,additive;旧 daemon 忽略该字段,旧 cloud 不发
|
|
1933
|
+
——双向 version-skew 安全)。
|
|
1934
|
+
- **`daemon/dispatch.ts::renderIdentityLines`** 扩展:校验(content string +
|
|
1935
|
+
order finite number)→ 按 `order` 升序稳定排序 → trim → 滤空,作为
|
|
1936
|
+
`sections: string[]` 与 identity/user/scope 一起进 `metadata.identityContext`;
|
|
1937
|
+
`hasIdentity` 判定包含 sections。
|
|
1938
|
+
- **三个 adapter 消费面**(coding `code-agent-driver` / `claude-code` legacy CLI /
|
|
1939
|
+
hermes instructions slot):sections 以 `\n\n` 连接在既有 identity 三行**之后**、
|
|
1940
|
+
同一 native identity slot。hermes 侧只进 per-turn `instructions`,**不写 SOUL.md**
|
|
1941
|
+
(D6 红线:动态 envelope 注入,不落任何持久文件)。
|
|
1942
|
+
- 首个注入段:cloud 侧 `platform-directives`(order 20,仅 active orchestrator
|
|
1943
|
+
dispatch 携带);order 30/40(config-directives / per-turn-voice)为 09/03 预留槽。
|
|
1944
|
+
|
|
1945
|
+
### Changed — `role create` defaults to the PRIVATE domain(product204/16 §2.2 M5)
|
|
1946
|
+
|
|
1947
|
+
**Behavioral flip, no version bump.** 创作默认私域,进公域是显式动作:
|
|
1948
|
+
|
|
1949
|
+
- **`prismer/cloud role create`** 默认改打 `POST /api/im/role-templates/mine`
|
|
1950
|
+
(owner-scoped private,任何已认证 key 可用,落 Studio `/mine`)。旧默认
|
|
1951
|
+
(admin 公域 catalog 直写)收敛为显式 `--admin-catalog`(非 admin 403)。
|
|
1952
|
+
`--mine` 保留为向后兼容 no-op。
|
|
1953
|
+
- 新 `--publish`:create 后自动 `POST /:slug/publish`(走 RO-11/RO-6 publish
|
|
1954
|
+
gate),一步进公域 Marketplace。
|
|
1955
|
+
- **`role-builder/scripts/ingest-role.mjs`** 同步反转默认(头注释与代码此前
|
|
1956
|
+
自相矛盾——注释声称默认 `--mine`、代码默认公域;现在代码与文档一致),同样
|
|
1957
|
+
新增 `--publish` / `--admin-catalog`。role-builder `SKILL.md` 口径同步。
|
|
1958
|
+
|
|
1959
|
+
### Added — `skill publish` + `role export`(product204/16 §2.2(c) / §2.5)
|
|
1960
|
+
|
|
1961
|
+
- **`skill publish <slugOrId> [--license --changelog]`** — 显式 owner 动作,把
|
|
1962
|
+
skill 从私域(`publishScope='workspace'`,create 默认)翻到公域 Marketplace
|
|
1963
|
+
(`POST /api/im/skills/:id/publish-template { scope:'marketplace' }`)。注意:
|
|
1964
|
+
marketplace 搜索面自本版起只见 `publishScope='marketplace'`——不 publish 的
|
|
1965
|
+
skill 他人搜不到。
|
|
1966
|
+
- **`role export <agentImUserId> [--out <dir>] [--ingest]`** — 把活 agent 的人格
|
|
1967
|
+
晶化为 role bundle(`GET /api/im/agents/:id/role-bundle`,SOUL 取 RAW
|
|
1968
|
+
operatingPrinciples,不含运行时注入子句,round-trip 不堆叠);`--ingest` 直接
|
|
1969
|
+
`POST /role-templates/mine` 落私有 role。
|
|
1970
|
+
|
|
1971
|
+
### Added — daemon declares its device identity(desktop204 D204-3)
|
|
1972
|
+
|
|
1973
|
+
`agent.host.declare` 现在恒带 `daemonLabel` + `daemonKind`。此前 daemon 只发
|
|
1974
|
+
`daemonId / daemonVersion / platform`,cloud 只能靠 `daemonId.startsWith('daemon-')`
|
|
1975
|
+
猜 kind、拿 daemonId 后缀当 label——对任何不遵守该前缀约定的 id 都是错的,且永远
|
|
1976
|
+
surface 不出用户自己起的设备名。
|
|
1977
|
+
|
|
1978
|
+
- 新 `src/daemon/device-identity.ts`:
|
|
1979
|
+
- `resolveDaemonKind()` — `PRISMER_DEVICE_KIND` > `KUBERNETES_SERVICE_HOST`(⇒ `k8s`)> `local`
|
|
1980
|
+
- `resolveDaemonLabel()` — `PRISMER_DAEMON_LABEL` > config.toml `daemon_label` > OS hostname > daemonId(永不为空)
|
|
1981
|
+
- config.toml 新增可选 `daemon_label`(用户自定义设备名)。
|
|
1982
|
+
- `device-dir.ts` 的 `inferDeviceKind()` 收敛到同一个 resolver —— `device.json` 与
|
|
1983
|
+
declare 线上的 kind 不可能再互相打架。
|
|
1984
|
+
- cloud 侧字段 / DB 列早已存在(`im_agent_bindings.boundDaemonKind/Label`),**无需迁移**;
|
|
1985
|
+
cloud 的猜测逻辑保留为旧 daemon 的 fallback。
|
|
1986
|
+
|
|
1987
|
+
### Added — `agent.host.withdraw` on intentional shutdown(desktop204 D204-4)
|
|
1988
|
+
|
|
1989
|
+
daemon 现在会在**主动关闭**时(SIGTERM/SIGINT ⇐ ⌘Q / `prismer daemon stop` / pod
|
|
1990
|
+
terminate)**drain 完成后、WS 关闭前**发 `agent.host.withdraw`。cloud 侧 handler /
|
|
1991
|
+
service / 错误码此前是**写好的死代码——没人发过这个事件**,于是 ⌘Q 后 cloud 要等满
|
|
1992
|
+
**3 分钟**心跳陈旧才把 agent 标离线,这 3 分钟内派给它的任务全是黑洞。
|
|
1993
|
+
|
|
1994
|
+
- `Runner.stop(opts?: { withdraw?: { reason, timeoutMs? } })` —— 只有**显式传 intent**
|
|
1995
|
+
才发(崩溃 / auth-failed / version-skew respawn 不发:同一个 daemonId 马上会回来,
|
|
1996
|
+
binding 不该被标成可回收)。
|
|
1997
|
+
- `inflightDrained` 取 stop 时是否还有在跑的 run(有 ⇒ `false`,cloud 知道该 requeue)。
|
|
1998
|
+
- 新 `WsClient.sendAndFlush(msg, timeoutMs)` —— 等帧真正写进 socket 再返回,
|
|
1999
|
+
避免 `close()` + `process.exit()` 把 withdraw 一起蒸发掉。
|
|
2000
|
+
**best-effort 是硬约束**:socket 不 OPEN / flush 超时 → 立刻 resolve `false`,
|
|
2001
|
+
绝不抛、绝不阻塞退出(离线 ⌘Q 最多多花 1.5s)。
|
|
2002
|
+
|
|
2003
|
+
## 2.0.9
|
|
2004
|
+
|
|
2005
|
+
### Fixed — sandbox daemon 镜像 stale-tag 陷阱(线上 daemon 落后 cloud)
|
|
2006
|
+
|
|
2007
|
+
镜像 tag 从 `/VERSION` 派生(`daemon-v$(cat VERSION)`),pod 按 **tag** 引用镜像
|
|
2008
|
+
(`getDaemonImage()` 返回 `canonical`,`digest` 仅供诊断),且 test/prod 是
|
|
2009
|
+
`imagePullPolicy: IfNotPresent`。
|
|
2010
|
+
|
|
2011
|
+
于是当一次发版没有 bump `/VERSION` 时:CI 的 `build_sandbox_image` 确实重新构建
|
|
2012
|
+
并 push 了新镜像,但**tag 名字不变** → kubelet 看到节点已缓存该 tag,永远复用旧
|
|
2013
|
+
层。cloud 前进、daemon 原地不动 —— test 环境因此用 07-07 的 daemon 跑
|
|
2014
|
+
release203/28 的 cloud(会话压缩 + systemHidden + memory 抽取/召回均不匹配)。
|
|
2015
|
+
|
|
2016
|
+
本次 bump `/VERSION` → `2.0.9` 使 tag 变为 `daemon-v2.0.9`,强制 kubelet 拉取新
|
|
2017
|
+
镜像。约束已写入 `infra/sandbox-image/image-pin.yaml` 顶部注释:**任何触及
|
|
2018
|
+
`sdk/prismer-cloud/runtime/` 的发版必须 bump `/VERSION`。**
|
|
2019
|
+
|
|
2020
|
+
## Unreleased
|
|
2021
|
+
|
|
2022
|
+
### Fixed — release203/10: skill 下发闭环三环修复(workspace203 体系化 e2e 全绿)
|
|
2023
|
+
|
|
2024
|
+
skill 从"安装"到"运行中 agent 真能用"的完整链路修复。根因:hermes gateway 的
|
|
2025
|
+
`HermesSkillLoader` 在 spawn 时从 `profile.config.skillsDir` 固定 skillsRoot;
|
|
2026
|
+
若启动时未注入 device dir,则读 profile dir,而 skill-sync 写 device dir → 错位。
|
|
2027
|
+
|
|
2028
|
+
- **目录错位** (`daemon/skill-sync.ts` + `daemon/runner.ts`): 新增纯函数
|
|
2029
|
+
`withPerAgentSkillsDir(profile, paths, daemonId)`;`syncProfileFromCloud` 的
|
|
2030
|
+
prewarm 用它注入 device skillsDir,使 gateway 从启动即读 skill-sync 写入的
|
|
2031
|
+
`devices/<did>/agents/<aid>/skills/`。此前 prewarm 用未注入的 profile 启动 →
|
|
2032
|
+
读 profile dir → 已安装 skill 对 agent 不可见。
|
|
2033
|
+
- **活进程缓存不刷新** (`daemon/dispatch.ts` + `daemon/runner.ts`): dispatch 的
|
|
2034
|
+
skill-sync `synced>0` 时经新增的 `DispatchDeps.dropService` drop 缓存的
|
|
2035
|
+
hermes 服务,`ensureService` 以含新 skill 的 device dir 重启。运行中 agent
|
|
2036
|
+
安装 skill 后**当次 dispatch** 即可用(此前需服务重启才可见)。revision-diff
|
|
2037
|
+
保证稳态 `synced==0` 不再重启,无 churn。
|
|
2038
|
+
- **首次冷启动根治** (`daemon/runner.ts`): `syncProfileFromCloud` 在 prewarm
|
|
2039
|
+
前,当确实注入了 device skillsDir,先 `syncInstalledSkillsForDispatch` 铺
|
|
2040
|
+
skill 到 device dir,再 `servicePool.drop` 旧服务,使 gateway 以 warmProfile
|
|
2041
|
+
(device-dir loader)重建、其 skill loader 首次扫描即读完整 skill 集。
|
|
2042
|
+
blueprint 实例化 / bind 的 agent **首次 invoke** 即命中,免冷启动窗口。
|
|
2043
|
+
|
|
2044
|
+
端到端验证:`docs/workspace203/20-systematic-test-execution.md` —— workspace203
|
|
2045
|
+
体系化 flow(前置1-4 + workspace1-2)S0-S7 真机全绿,全程 DeepSeek flash。
|
|
2046
|
+
|
|
2047
|
+
### Changed — memory203/20 W-B: extraction prompt v2 (richness-balanced) + `web_load` prismer:// + oversized advisory passthrough
|
|
2048
|
+
|
|
2049
|
+
- **Extraction prompt v2** (`daemon/memory/extract.ts`, ruling 0705-3/4
|
|
2050
|
+
"record, never limit"): removed the "emit minimal valid PKF" self-framing
|
|
2051
|
+
and the `BUDGET: … prefer 1-3 DENSE pages / concise` compression language
|
|
2052
|
+
(doc 19 §3.4 ①② — the structural scarcity that flattened pages to prose).
|
|
2053
|
+
New posture: richness-balanced PKF — frontmatter script with a **REQUIRED
|
|
2054
|
+
one-sentence `description`** (feeds the cloud description column, doc 20
|
|
2055
|
+
§1.1), `<h2 id>` sections, full typed-link vocabulary incl.
|
|
2056
|
+
`supports`/`contradicts`, the FULL `<prismer-data>` view set
|
|
2057
|
+
(`table|bar|line|scatter|area|heatmap` — "never flatten a source table into
|
|
2058
|
+
prose"), and asset POINTERS (short description +
|
|
2059
|
+
`<a rel="derived-from" href="prismer://asset/<id>">`, never a body copy —
|
|
2060
|
+
doc 20 §1.4 asset 零镜像). The ONLY token rules are anti-waste: ANTI-REPEAT
|
|
2061
|
+
(same-topic page in the recall context ⇒ placement MUST be extend/attach,
|
|
2062
|
+
never a near-duplicate new page) + circular-recall discipline (pages already
|
|
2063
|
+
in the provided context are not new sources to re-query). Prompt exported
|
|
2064
|
+
(`EXTRACTION_SYSTEM_PROMPT`) so the guidance lane (W-C skills) teaches
|
|
2065
|
+
consistently.
|
|
2066
|
+
- **`MEMORY_EXTRACT_MAX_TOKENS` default 2048 → 8192** (record-not-limit):
|
|
2067
|
+
usage is recorded, not constrained — gateway `usage.input_tokens`/
|
|
2068
|
+
`output_tokens` now flow onto `ExtractTurnResult.promptTokens`/
|
|
2069
|
+
`completionTokens` and into the `llm_response` stage line
|
|
2070
|
+
(`pages=N, chars=M[, tokens=in:X/out:Y][, truncated]`, exported
|
|
2071
|
+
`formatLlmResponseDetail` in `hook-server.ts`). Truncation salvage stays as
|
|
2072
|
+
the pure fallback. Paired: `MEMORY_EXTRACT_TIMEOUT_MS` default 60s → 120s
|
|
2073
|
+
(measured: 4096 tokens took 29–44s; the timeout must outlast the 8192
|
|
2074
|
+
ceiling or the abort-drops-memory bug returns).
|
|
2075
|
+
- **Attached-asset ids reach extraction** (additive contract): the provider
|
|
2076
|
+
shell's `sync_turn` now parses `<attached_assets><asset id="…"/></…>` out of
|
|
2077
|
+
the turn text and stamps `extra.attached_asset_ids` on the `post_llm_call`
|
|
2078
|
+
body; hook-server threads it into `ExtractInput.attachedAssetIds`, and
|
|
2079
|
+
`extract.ts` falls back to parsing the XML itself
|
|
2080
|
+
(`parseAttachedAssetIdsFromTurn`) for older shells. The user prompt lists
|
|
2081
|
+
the real `prismer://asset/<id>` pointer URIs so pages reference assets
|
|
2082
|
+
instead of mirroring them.
|
|
2083
|
+
- **`web_load` accepts `prismer://` URIs** (doc 20 §2.2 / 19 B9): scheme
|
|
2084
|
+
allowlist widened from http(s)-only to http(s) | `prismer://` in BOTH the
|
|
2085
|
+
provider shell (`plugins/memory/prismer/__init__.py`) and the daemon route
|
|
2086
|
+
(`daemon/web/rpc.ts`); the daemon still forwards to the cloud Load API
|
|
2087
|
+
unchanged (it natively resolves `prismer://<owner>/asset/<sha>` and
|
|
2088
|
+
`.../file/...`). Tool description retargeted: load web pages OR workspace
|
|
2089
|
+
assets/files — use it instead of re-reading raw sources memory already
|
|
2090
|
+
points at. Garbage schemes (file://, ftp://, …) still 400 `invalid_url`.
|
|
2091
|
+
Spec sync in `adapters/web-tools.ts`.
|
|
2092
|
+
- **`oversized` candidates kind** (doc 20 §1.2 hub-size ADVISORY,
|
|
2093
|
+
record-not-limit): added to the daemon `HEALTH_KINDS` whitelist
|
|
2094
|
+
(`daemon/memory/rpc.ts` → forwards `GET /api/im/memory/health/oversized`),
|
|
2095
|
+
the Python `memory_curate` `kind` enum, and the shared
|
|
2096
|
+
`MemoryCurateInput.kind` spec (`adapters/memory-tools.ts`, which also picks
|
|
2097
|
+
up the previously-drifted `conflicts` value). Advisory only — the cloud
|
|
2098
|
+
lane measures and suggests splits; nothing is machine-enforced.
|
|
2099
|
+
|
|
2100
|
+
### Changed — memory203/20 W-C: guidance rewrite (four-truths) — memory + memory-dream skills, MEMORY_CORE_DIRECTIVE
|
|
2101
|
+
|
|
2102
|
+
- **Teach only what W-A/W-B landed** (the initiative's lesson: never teach what
|
|
2103
|
+
the platform can't do). All guidance now consistent with
|
|
2104
|
+
`EXTRACTION_SYSTEM_PROMPT` v2 (`daemon/memory/extract.ts`): same rel
|
|
2105
|
+
vocabulary (`supports`/`contradicts`/`related`/`derived-from`/`references`/
|
|
2106
|
+
`cites` + structural `child-of`), same `<prismer-data>` view set
|
|
2107
|
+
(`csv|json` × `table|bar|line|scatter|area|heatmap`), same asset-POINTER
|
|
2108
|
+
form (short description + `<a rel="derived-from" href="prismer://asset/<id>">`,
|
|
2109
|
+
never a body copy), same anti-waste rules (no duplicate extraction, no
|
|
2110
|
+
circular recall — the ONLY token rules, 0705 record-not-limit rulings).
|
|
2111
|
+
- **`built-in-skills/memory/SKILL.md`** (canonical + runtime mirror): dual-
|
|
2112
|
+
section ownership section replaces the self-contradictory "hub needs a body"
|
|
2113
|
+
vs "never hand-edit INDEX/hub" doctrine (doc 19 D11) — `#overview` prose is
|
|
2114
|
+
agent-owned and rebuild-proof (W-A `rewriteTocSection`, test-verified),
|
|
2115
|
+
`#toc` is machine-owned; frontmatter `description` REQUIRED (one-sentence
|
|
2116
|
+
self-summary feeding TOC entries/chips) with the dual-track note (body prose
|
|
2117
|
+
carries inter-entity logic); de-hedged append flow — recall/browse shows an
|
|
2118
|
+
existing page on the topic ⇒ `memory_write op="append-section"` /
|
|
2119
|
+
`"rewrite-section"` is a MUST (the "may not be available / fallback" hedging
|
|
2120
|
+
is gone — the ops are live in daemon rpc + Python tool schema); richness
|
|
2121
|
+
palette with three worked PKF examples (prismer-data page, asset-pointer
|
|
2122
|
+
page incl. `<img src="prismer://asset/…">` media, supports/contradicts pair
|
|
2123
|
+
with `#section` anchors) — media/section-granularity teachings lifted from
|
|
2124
|
+
the deprecated memory-curation skill (doc 19 D12 迁移, that skill stays
|
|
2125
|
+
deprecated); size discipline (INDEX/hub lean as taught discipline, enforced
|
|
2126
|
+
by nothing; `kind="oversized"` split suggestions; ordinary pages unlimited,
|
|
2127
|
+
richness encouraged); consolidated anti-patterns (re-reading raw files
|
|
2128
|
+
memory already distills, circular recall, duplicate extraction, copying
|
|
2129
|
+
asset bodies, hand-editing `#toc`).
|
|
2130
|
+
- **`built-in-skills/memory-dream/SKILL.md`** (v2 → v3, canonical + runtime
|
|
2131
|
+
mirror): convergence loop gains STEP 1c (review `kind="oversized"` hub-split
|
|
2132
|
+
advisories — suggestions, never enforcement) and STEP 6 (MAINTAIN the
|
|
2133
|
+
`#overview` prose on INDEX/hubs via `op="rewrite-section"` — the
|
|
2134
|
+
orchestrator's editorial duty; the rebuild only regenerates `#toc` with
|
|
2135
|
+
per-entry descriptions); candidates envelope documents the `oversized`
|
|
2136
|
+
surface; "rebuild_index is the ONLY INDEX write" reworded to the dual-
|
|
2137
|
+
section truth; section-op hedging removed from the enact/fold steps.
|
|
2138
|
+
- **`MEMORY_CORE_DIRECTIVE`** (`daemon/dispatch.ts`): "NEVER write or
|
|
2139
|
+
hand-edit INDEX.pkf (machine-generated)" replaced with the dual-section
|
|
2140
|
+
truth ("`#toc` machine-owned — never hand-edit; `#overview` prose IS yours
|
|
2141
|
+
to write and maintain"); adds the richness line (description-required +
|
|
2142
|
+
rich blocks + asset pointers + work-from-memory-not-raw-files + anti-waste);
|
|
2143
|
+
extend decisions name the live section ops; orchestrator loop gains
|
|
2144
|
+
overview maintenance + the conflicts/oversized candidates kinds.
|
|
2145
|
+
|
|
2146
|
+
### Added — release203 web-capability fix: `workspace_web_search`/`web_load` tools + terminal toolset restore
|
|
2147
|
+
|
|
2148
|
+
- **`workspace_web_search` / `web_load` provider tools** (`plugins/memory/prismer/__init__.py`):
|
|
2149
|
+
workspace-CONTEXT tools (not memory ops) registered through the provider
|
|
2150
|
+
shell's `get_tool_schemas()` seam (7 tools total now). Handlers POST the
|
|
2151
|
+
new daemon routes; descriptions steer agents away from scripting HTTP via
|
|
2152
|
+
`execute_code`+subprocess (the observed fallback: 197 execute_code vs 0
|
|
2153
|
+
web calls in 14 days — Hermes' native web toolset is schema-dropped in pods
|
|
2154
|
+
because no upstream search backend is configured, and per the user ruling
|
|
2155
|
+
we ARE the search backend: no third-party keys/packages enter the pod).
|
|
2156
|
+
`web_load` validates http(s)-only shell-side before any daemon call.
|
|
2157
|
+
Name note: the search tool CANNOT be `web_search` — Hermes v0.17 rejects
|
|
2158
|
+
provider tools shadowing reserved CORE tool names even when the core tool
|
|
2159
|
+
is check_fn-dropped ("Core tools always win", live-hit 2026-07-03); the
|
|
2160
|
+
event-stream mapper (`tool-call-mapper.ts`) maps `workspace_web_search`
|
|
2161
|
+
onto the first-class `web_search` search row (and `web_load` onto the
|
|
2162
|
+
`fetch` row) so the timeline surface is unchanged.
|
|
2163
|
+
- **Daemon `/local/web/search` + `/local/web/load` routes** (`daemon/web/rpc.ts`,
|
|
2164
|
+
new `attachWeb` hook in `local-server.ts`, wired in `runner.ts`): FORWARD to
|
|
2165
|
+
the cloud Load API `POST /api/context/load` (`{input: query|url|urls}` —
|
|
2166
|
+
search + cache + compress + deposit, Exa server-side) with the daemon's own
|
|
2167
|
+
`Authorization: Bearer <sk-prismer>` (same credential lane as extract.ts;
|
|
2168
|
+
the Load API's `apiGuard` accepts sk-prismer Bearer directly, no JWT
|
|
2169
|
+
exchange). Forward timeout 120s default (live-measured: cold 3-result query
|
|
2170
|
+
= 59s), env-tunable `PRISMER_WEB_LOAD_TIMEOUT_MS`; the daemon also sends
|
|
2171
|
+
`x-forwarded-proto` matching its cloud baseUrl scheme (the Load API
|
|
2172
|
+
self-fetches `/api/search` at `x-forwarded-proto || https`, which 500s
|
|
2173
|
+
against a plain-HTTP dev/pod cloud). Search passes `search.topK = limit`
|
|
2174
|
+
alongside `return.topK` — the route's default searched set is 15 and it
|
|
2175
|
+
compresses EVERY uncached hit before ranking (59–118s cold for a 3-result
|
|
2176
|
+
ask). Every text field in the passthrough bounded to ~8k chars with an
|
|
2177
|
+
explicit truncation marker; `[web-tool]`-prefixed structured logs (trace
|
|
2178
|
+
id, query/url count, status, ms).
|
|
2179
|
+
- **Explicit `platform_toolsets.api_server` pin** (`adapters/persistence/hermes/index.ts`
|
|
2180
|
+
`configurePrismerProvider`, exported `HERMES_API_SERVER_PLATFORM_TOOLSETS`):
|
|
2181
|
+
bypasses the hermes v0.17.0 subset-inference bug that silently dropped the
|
|
2182
|
+
`terminal` toolset. The pinned list is the `hermes-api-server` composite
|
|
2183
|
+
reverse-mapped to configurable toolset keys minus default-off (verified
|
|
2184
|
+
against the hermes-agent reference `hermes_cli/tools_config.py`); explicit
|
|
2185
|
+
membership flips hermes to deterministic direct resolution. Union-merged
|
|
2186
|
+
with operator entries, idempotent; per-role `agent.disabled_toolsets`
|
|
2187
|
+
(toolsetScope deny) still applies LAST hermes-side, so governance wins.
|
|
2188
|
+
- **`WEB_TOOL_DIRECTIVE`** (`daemon/dispatch.ts`): hermes-only teaching line on
|
|
2189
|
+
the same `composedSystemPrompt` seam as `MEMORY_CORE_DIRECTIVE` ("Web
|
|
2190
|
+
research → `web_search`/`web_load` (workspace-billed, cached). CLI →
|
|
2191
|
+
`terminal`. Do NOT script HTTP or subprocess for these.").
|
|
2192
|
+
- **Frozen spec** `adapters/web-tools.ts` (sibling of `memory-tools.ts`):
|
|
2193
|
+
`WebSearchInput/Output`, `WebLoadInput/Output` + naming rationale
|
|
2194
|
+
(`web_search` matches the event-stream mapper's `SEARCH_TOOLS` so calls
|
|
2195
|
+
render as first-class search rows).
|
|
2196
|
+
- Tests: `test/web-rpc.test.ts` (forward+auth header+bounding+non-http
|
|
2197
|
+
reject+degrade), `test/web-provider-shell.test.ts` (real python3 exec of the
|
|
2198
|
+
shell: 7-tool registration + shell-side invalid_url negatives),
|
|
2199
|
+
`test/hermes-platform-toolsets.test.ts` (pin content, operator merge,
|
|
2200
|
+
idempotency, deny coexistence).
|
|
2201
|
+
|
|
2202
|
+
### Added — memory203/18 §11.4 residual #3: conflict-lifecycle review surface (`kind=conflicts`)
|
|
2203
|
+
|
|
2204
|
+
- **`GET /local/memory/health?kind=conflicts`** (`daemon/memory/rpc.ts`
|
|
2205
|
+
`HEALTH_KINDS`): passthrough to the new cloud
|
|
2206
|
+
`GET /api/im/memory/health/conflicts` — live remote-conflict pages, each
|
|
2207
|
+
carrying `metadata.latestTwoVersionSummaries` (`{version, changeSummary,
|
|
2208
|
+
authoredBy, createdAt}` from the version DAG) + `metadata.currentVersion`,
|
|
2209
|
+
so the orchestrator can judge which LWW side owns the head without loading
|
|
2210
|
+
full contents. `kind=all` now returns four surfaces
|
|
2211
|
+
(`{orphans, duplicates, stale, conflicts}`).
|
|
2212
|
+
- **`memory_curate` python tool** (`plugins/memory/prismer/__init__.py`):
|
|
2213
|
+
`candidates` `kind` enum gains `"conflicts"`. Cloud-side, conflict is now a
|
|
2214
|
+
STATE, not a brand — a curation touch or clean (non-conflicting) rewrite of
|
|
2215
|
+
a conflict-flagged page restores its `sourceKind` to `daemon-sync`, so
|
|
2216
|
+
reviewed pages drop off this surface; the `memory-dream` skill's convergence
|
|
2217
|
+
loop gained a STEP 1b teaching the review flow.
|
|
2218
|
+
|
|
2219
|
+
### Fixed — memory203/18 W5 (final-round5 P0): two-phase stall watchdog + silent-empty-reply guard
|
|
2220
|
+
|
|
2221
|
+
- **Two-phase upstream-stall watchdog** (`adapters/persistence/hermes/sessions-sse.ts`):
|
|
2222
|
+
the W4 single 120s threshold killed HEALTHY long-context turns — after
|
|
2223
|
+
round-3 bulk ingestion the recall probes carried ~120k promptTokens and the
|
|
2224
|
+
upstream legitimately took >120s to the FIRST token (3/3 probes
|
|
2225
|
+
watchdog-aborted; the 15s dispatch heartbeats already protected those turns
|
|
2226
|
+
from the 300s reaper). Now the guard runs two phases: before the first
|
|
2227
|
+
MODEL-activity event it uses `PRISMER_UPSTREAM_FIRST_EVENT_MS` (default
|
|
2228
|
+
270 000 ms — under the 300s reaper, generous for long-context first tokens);
|
|
2229
|
+
after it, the original inter-event `PRISMER_UPSTREAM_STALL_MS` (default
|
|
2230
|
+
120 000 ms). Both env-tunable, resolved per call. ⚠️ hermes enqueues
|
|
2231
|
+
`run.started`/`message.started` BEFORE the LLM call (api_server.py
|
|
2232
|
+
`_run_and_signal`), so those acks re-arm but do NOT end the first-event
|
|
2233
|
+
phase — gating on the literally-first frame would have made the fix inert.
|
|
2234
|
+
Abort messages name the phase (`no first event for Ns` vs `no events for
|
|
2235
|
+
Ns`); `dispatch.ts` `isLimiterClassError`/`retryReasonToken` match both
|
|
2236
|
+
wordings (requeue lane + `reason=stall` unchanged). Clarify-disarm and
|
|
2237
|
+
keepalive-comment semantics unchanged. Covers both hermes lanes (shared
|
|
2238
|
+
`consumeSessionsSse`).
|
|
2239
|
+
- **Silent-empty-reply guard + transcript-tail recovery**
|
|
2240
|
+
(`adapters/persistence/hermes/sessions-dispatcher.ts`): after a stall-abort,
|
|
2241
|
+
hermes keeps generating server-side (`_run_agent` runs `run_conversation` in
|
|
2242
|
+
a thread executor the disconnect-cancel cannot preempt) and flushes the turn
|
|
2243
|
+
into the session store on completion; the daemon retry re-sent the same
|
|
2244
|
+
prompt into the SAME session and the retried stream could complete with
|
|
2245
|
+
EMPTY content — the adapter forwarded ok=true output:'' → cloud marked the
|
|
2246
|
+
run completed and the DM message-post gate (`output || attachments`)
|
|
2247
|
+
silently skipped the post (run=completed, error=NULL, DM empty, credits
|
|
2248
|
+
burned; the killed attempt's answer later leaked into the next turn's
|
|
2249
|
+
reply). Now an empty-output completion (approval/clarify suspensions
|
|
2250
|
+
excluded) is never a silent success: the dispatcher first tries to RECOVER
|
|
2251
|
+
the reply from `GET /api/sessions/{id}/messages` (last non-empty assistant
|
|
2252
|
+
row after the first user row carrying this dispatch's id — the id is
|
|
2253
|
+
stamped in the `<execution_context>` XML; 2 bounded polls), surfacing it
|
|
2254
|
+
with `metadata.hermes.replyRecovered='session_transcript_tail'`; otherwise
|
|
2255
|
+
it fails LOUDLY with `empty_reply`, which `daemon/dispatch.ts` treats as
|
|
2256
|
+
terminal (no 3× re-burn of a ~120k-token prompt into the polluted session)
|
|
2257
|
+
so the cloud posts a visible "Agent failed" system event.
|
|
2258
|
+
- **Best-effort upstream cancel on stall-abort** (same file): fires
|
|
2259
|
+
`POST /v1/runs/{runId}/stop` fire-and-forget before the retry. Known
|
|
2260
|
+
ineffective on current hermes — the sessions chat/stream handler never
|
|
2261
|
+
registers its run in `_active_run_agents` (only the /v1/runs path does), so
|
|
2262
|
+
this 404s today; kept as the correct semantic signal for the day hermes
|
|
2263
|
+
registers sessions runs. A clean per-turn abort therefore CANNOT be done
|
|
2264
|
+
from the adapter yet — the guard above is the real mitigation.
|
|
2265
|
+
|
|
2266
|
+
### Changed — memory203/18 W4 runtime tail: upstream-stall watchdog + CLI gateway-detection fix
|
|
2267
|
+
|
|
2268
|
+
- **In-flight upstream-stall watchdog** (`adapters/persistence/hermes/sessions-sse.ts`
|
|
2269
|
+
`createStallGuard` + `resolveUpstreamStallMs`): the W2-gate's dominant residual
|
|
2270
|
+
failure mode was a SILENT upstream LLM hang — no error, no SSE events — so the
|
|
2271
|
+
adapter emitted no progress, the 300s daemon reaper killed the run
|
|
2272
|
+
(`daemon_task_timeout`), the cloud requeued, the same stall repeated until the
|
|
2273
|
+
requeue cap (4) exhausted → 3/18 permanent burst failures + starved recall
|
|
2274
|
+
probes. The W3 retry-loop progress frames couldn't cover this: the call never
|
|
2275
|
+
RETURNED to enter the retry loop. Now, when an in-flight sessions SSE stream
|
|
2276
|
+
produces NO event frames for `PRISMER_UPSTREAM_STALL_MS` (default 120 000 ms,
|
|
2277
|
+
env-tunable, read per-call), the guard cancels the stream and throws
|
|
2278
|
+
`upstream stall: no events for Ns (…)` — a plain (non-AbortError) error that
|
|
2279
|
+
`categorizeDispatchError` maps to a retryable `adapter_dispatch_failed`.
|
|
2280
|
+
Covers BOTH hermes lanes (sessions-dispatcher + runs-dispatcher share
|
|
2281
|
+
`consumeSessionsSse`). The reaper window is untouched (last-resort backstop);
|
|
2282
|
+
`clarify.request` disarms the guard while a human answers (re-armed by the
|
|
2283
|
+
next event frame).
|
|
2284
|
+
- **Stall-class rides the limiter/requeue lane** (`daemon/dispatch.ts`):
|
|
2285
|
+
`isLimiterClassError` now matches `upstream stall: no events for` — the retry
|
|
2286
|
+
loop uses the jittered limiter backoff with `retrying(attempt=N, reason=stall)`
|
|
2287
|
+
progress frames (new `retryReasonToken` token), and on exhaustion the failure
|
|
2288
|
+
lands `dispatch_precondition_unavailable` so the cloud's transient-requeue
|
|
2289
|
+
channel re-delivers instead of a permanent red failure. Generic `HTTP 504`
|
|
2290
|
+
wording remains terminal (unchanged).
|
|
2291
|
+
- **`prismer memory list` gateway-detection fix** (`cli/commands/memory.ts`):
|
|
2292
|
+
`list` never fell back to `$PRISMER_WORKSPACE_ID` (unlike search/read/recall/
|
|
2293
|
+
write), so a bare `prismer memory list` in an agent pod skipped the modern
|
|
2294
|
+
`/local/memory/list` route entirely, probed only the legacy 404 paths, and
|
|
2295
|
+
reported `memory_gateway_unavailable` against a live daemon RPC (W2-gate
|
|
2296
|
+
item 3 FAIL, R7 CLI projection). Now `list` honours the env workspace;
|
|
2297
|
+
explicit `--workspace-id` still wins. Verified inside the live agent-rt pod
|
|
2298
|
+
(patched CLI bundle, direct node invocation): `memory list --page-type hub`
|
|
2299
|
+
returns the workspace hubs with exit 0.
|
|
2300
|
+
Known residual: `memory delete` / `memory sync` still only probe legacy
|
|
2301
|
+
routes (no modern `/local/memory/*` equivalents wired) — unchanged.
|
|
2302
|
+
|
|
2303
|
+
### Changed — memory203/18 W3 lane: failure self-heal (R3.1–R3.3) + event-stream fidelity (R5.2)
|
|
2304
|
+
|
|
2305
|
+
- **R3.1 — limiter-exhaustion now self-heals instead of terminal-failing**
|
|
2306
|
+
(`daemon/dispatch.ts`): when the local retry loop (3 attempts) exhausts and the
|
|
2307
|
+
LAST error is limiter-class (HTTP 429 queue-full / RPM "Rate limit exceeded",
|
|
2308
|
+
504 "slot not available before deadline"), the synthesised failure carries
|
|
2309
|
+
`dispatch_precondition_unavailable` (original message preserved) instead of
|
|
2310
|
+
`daemon_local_retry_exhausted` — the cloud's EXISTING transient-requeue channel
|
|
2311
|
+
(`requeueTransientRun`, cap 4) re-delivers the run once the queue drains.
|
|
2312
|
+
Non-limiter exhaustion (e.g. HTTP 500 ×3) still lands
|
|
2313
|
+
`daemon_local_retry_exhausted` terminal. Cloud side (same change set,
|
|
2314
|
+
`src/im/ws/handler.ts` + `task.service.ts::isTransientRequeueErrorCode`): the
|
|
2315
|
+
requeue branch also accepts reaper `daemon_task_timeout` for CHAT runs
|
|
2316
|
+
(`run.source !== 'task'`), mirroring the kanban-task retry policy that chat
|
|
2317
|
+
runs never had — same bounded counter.
|
|
2318
|
+
- **R3.2 — limiter-aware backoff** (`daemon/dispatch.ts`): limiter-class retry
|
|
2319
|
+
waits honor an explicit Retry-After hint in the message (`Retry-After: n` /
|
|
2320
|
+
`Retry in ns`, clamped 60s) and otherwise use jittered windows [3–6s, 8–15s]
|
|
2321
|
+
instead of the fixed [1s, 3s] that burnt all 3 attempts in ~4s. Attempts stay
|
|
2322
|
+
at 3 (the requeue channel owns the long tail). `PRISMER_DISPATCH_RETRY_BACKOFF_MS`
|
|
2323
|
+
(comma list) overrides all classes for ops/testing.
|
|
2324
|
+
- **R3.3 — progress during retry** (`daemon/dispatch.ts`): before each backoff
|
|
2325
|
+
the dispatcher emits `task.dispatch.progress` with
|
|
2326
|
+
`retrying(attempt=N, reason=<status>)` (+ structured `detail.kind='retry'`),
|
|
2327
|
+
resetting the runner reaper's inactivity window so the 300s reaper no longer
|
|
2328
|
+
kills runs that are mid-retry, and giving the UI something to show.
|
|
2329
|
+
- **R5.2 — event-stream CLI extraction** (`adapters/persistence/hermes/tool-call-mapper.ts`):
|
|
2330
|
+
`execute_code` wrapping a CLI in python (`subprocess.run(['prismer',…])`,
|
|
2331
|
+
`subprocess.*` string form, `os.system(…)`, `!cmd` lines) now maps
|
|
2332
|
+
`detail.command` to the reconstructed CLI (multiple calls join with ` && `,
|
|
2333
|
+
capped 400 chars) with `detail.commandSource='argv'`; the FULL python source
|
|
2334
|
+
rides additively on `detail.script` (capped/spilled by the step recorder like
|
|
2335
|
+
`output`). Dynamic argv (variables/f-string elements) is never guessed — no
|
|
2336
|
+
match keeps the previous whole-source behaviour. The workspace timeline's
|
|
2337
|
+
collapsed row now renders `` $ prismer memory search … `` instead of
|
|
2338
|
+
`import subprocess (+9 行)` (`src/app/workspace/components/agent-message/adapter.ts`).
|
|
2339
|
+
- Additive type fields: `ToolCallDetail.shell.{script,commandSource}`
|
|
2340
|
+
(`adapters/coding/shared/agent-sdk-types.ts` + UI mirror in
|
|
2341
|
+
`agent-message/types.ts`); step recorder caps `shell.script` like `shell.output`.
|
|
2342
|
+
|
|
2343
|
+
### Changed — memory203/18 W2 guidance lane: teach the primitives that now exist (§2.3/R6.5/R4.1/R4.2)
|
|
2344
|
+
|
|
2345
|
+
- **`built-in-skills/memory/SKILL.md` rewritten tool-centric** (native `memory_*` tools primary;
|
|
2346
|
+
short `prismer memory` CLI appendix for code agents): browse-first write flow
|
|
2347
|
+
(`memory_browse` → extend / attach via `parent_hub_path` / new-hub-then-leaf → verify via
|
|
2348
|
+
`memory_search`), prefix-free path convention ("use paths exactly as returned by
|
|
2349
|
+
browse/search"), structural edges over prose (`child-of` direction taught explicitly: edge
|
|
2350
|
+
points FROM child TO hub — the W1-gate found 8 hand-written inverted links), INDEX.pkf is
|
|
2351
|
+
machine-generated and never hand-written, never whole-page-rewrite another agent's page.
|
|
2352
|
+
Section ops (`op="append-section"` / `"rewrite-section"`) are taught with an explicit
|
|
2353
|
+
landing note — they ship in the same daemon image (R6.4, parallel lane).
|
|
2354
|
+
- **`built-in-skills/memory-dream/SKILL.md` rewritten around the tight convergence loop**:
|
|
2355
|
+
`candidates` → `memory_browse` → cluster (your LLM) → `promote_to_hub` **WITH `childPaths[]`**
|
|
2356
|
+
(no hollow promotes, no hand-written re-anchor links — the old "no move-anchor op" §Re-anchor
|
|
2357
|
+
workaround is deleted) → `rebuild_index` once per batch → `candidates` again to verify →
|
|
2358
|
+
REPORT the structural delta in the reply. (W1-gate: CEO ran promote without childPaths because
|
|
2359
|
+
the param was undocumented, and hand-wrote 8 inverted child-of hrefs.)
|
|
2360
|
+
- **`built-in-skills/memory-curation/SKILL.md`** (deprecated/reference): placement guidance
|
|
2361
|
+
aligned to browse-first + `parent_hub_path`; removed "anti-orphan is discipline only, the
|
|
2362
|
+
platform won't enforce it" apologetics and the `memory/`-prefixed path convention.
|
|
2363
|
+
- **`MEMORY_CORE_DIRECTIVE` updated** (`daemon/dispatch.ts`): names all five tools (adds
|
|
2364
|
+
`memory_browse`), hardened RECALL rule ("when answering anything about workspace knowledge you
|
|
2365
|
+
did not just read, run `memory_search` FIRST; attached files are raw sources — check memory for
|
|
2366
|
+
distilled knowledge before re-reading them"), browse-first write flow, and the orchestrator
|
|
2367
|
+
convergence loop with `childPaths` + verify + report duty.
|
|
2368
|
+
- **R4.2 — builtin `memory` skill now installs into every hermes profile**
|
|
2369
|
+
(`adapters/persistence/hermes/index.ts`): `installPrismerImSkill` generalized to
|
|
2370
|
+
`installBuiltInHermesSkill(profileName, slug)` (per-slug content cache); both install sites
|
|
2371
|
+
(ensureService + per-dispatch) land `prismer-im-collab` + `memory`, and `memory-dream` for
|
|
2372
|
+
profiles with `taskAuthority: 'orchestrator'` (first consumer of that config bit).
|
|
2373
|
+
- **R4.1 — orchestrator prompt no longer routes file questions straight to raw sources**
|
|
2374
|
+
(`src/lib/role-runtime-policy/prompt-contract.ts`, cloud side): "For uploaded files, first
|
|
2375
|
+
`memory_search` for existing distilled knowledge; use `assets`/`ingest` when memory misses."
|
|
2376
|
+
- **R4.3 — `<execution_context>` dirs annotated** (`daemon/conversation-context.ts`): a
|
|
2377
|
+
`<dirs_note>` leaf marks artifacts/scratch dirs as per-run working space, pointing prior-fact
|
|
2378
|
+
questions at `memory_search` (the W1-gate read-file-over-recall bypass).
|
|
2379
|
+
|
|
2380
|
+
### Added — memory203/18 W2 daemon lane: path/URI normalization + section write + placement guard + 0-yield root fix
|
|
2381
|
+
|
|
2382
|
+
- **P0 path/URI namespace normalization** (`daemon/memory/store.ts` → `rpc.ts handleWrite`,
|
|
2383
|
+
`hook-server.ts writeExtractedPage`, `cloud-sync.ts fetchPageFromCloudByPath`): ONE shared
|
|
2384
|
+
normalizer — `normalizeMemoryPath` (strips leading `/` + `memory/` segments) +
|
|
2385
|
+
`memoryPathToUri(workspaceId, path)` — used by every link/URI composition site, so a page path
|
|
2386
|
+
(or `parentHubPath`) that already starts with `memory/` no longer produces
|
|
2387
|
+
`prismer://…/memory/memory/…` (the W1-gate: 3/3 agent child-of `link.upsert` events silently
|
|
2388
|
+
dropped cloud-side, 0 rows). `memory.link.upsert` envelopes additionally carry plain
|
|
2389
|
+
`sourcePath` / `targetPath` (normalized; OPTIONAL + additive on `envelope.ts`, no schemaVersion
|
|
2390
|
+
bump) — the cloud lane prefers these over URI parsing, and trace path-mode matches on them.
|
|
2391
|
+
Local store lookups are variant-tolerant via `store.loadByAnyPath`
|
|
2392
|
+
(`memoryPathLookupVariants`: as-given, ±`memory/`, ±`.pkf`); a repeat write under the OTHER
|
|
2393
|
+
path convention extends the EXISTING page (existing row's path wins) instead of forking a
|
|
2394
|
+
near-duplicate.
|
|
2395
|
+
- **R1.4 write-time 回源 (write hygiene)** (`rpc.ts handleWrite` op=replace, `cloud-sync.ts`,
|
|
2396
|
+
`store.ts`, `types.ts`): when the target page is absent locally or still `local-only`, the
|
|
2397
|
+
daemon first pulls the cloud head via the SAME single-page回源 `handleLoad` uses
|
|
2398
|
+
(`fetchPageFromCloudByPath`); `materialisePage` now passes the cloud `version` and
|
|
2399
|
+
`store.write` adopts `max(localExisting+1, version)` — so the write's outbox `parentVersion`
|
|
2400
|
+
continues the cloud head (cloud v4 + empty subset → parentVersion=4, not 0; dissolves the
|
|
2401
|
+
W1-gate's cross-agent full-page-rewrite `remote-conflict base v4 vs head v1` class). Fail-open:
|
|
2402
|
+
offline → proceed exactly as before (a local-first write never blocks on the cloud).
|
|
2403
|
+
- **R6.4 section-level write** (`rpc.ts handleWrite`/`handleSectionWrite`, `__init__.py`,
|
|
2404
|
+
`adapters/memory-tools.ts`, `cli/commands/memory.ts --op/--section`): `memory_write` gains
|
|
2405
|
+
`op` (`replace`|`append-section`|`rewrite-section`, default replace) + `section` (heading id,
|
|
2406
|
+
required for section ops). Section ops FORWARD to the cloud section verbs
|
|
2407
|
+
(`POST /api/im/memory/pages/:id/sections/append|rewrite`, actor-override header like curate),
|
|
2408
|
+
then refresh the local subset from the cloud response (adopting the cloud version,
|
|
2409
|
+
syncStatus→`acked`) — and deliberately emit **NO** `memory.page.upsert` outbox event (the
|
|
2410
|
+
cloud write is authoritative; an upsert on top would double-apply the edit). No cloud wired →
|
|
2411
|
+
explicit 503 `section_write_requires_cloud` (never a silent no-op); unreachable cloud → 5xx
|
|
2412
|
+
passthrough with the local page untouched.
|
|
2413
|
+
- **R6.3 placement guard (ramp, gated LAST per §9.3)** (`rpc.ts handleWrite`): a NEW un-anchored
|
|
2414
|
+
leaf (no existing page at path, not `pageType=hub`, no `parentHubPath`, no `rel="child-of"`
|
|
2415
|
+
content link) is gated by `PRISMER_MEMORY_PLACEMENT_ENFORCE` = `off` | `warn` (DEFAULT) |
|
|
2416
|
+
`enforce`. warn → `[memory-trace] stage=placement_warn` + `placementWarn` counter, write
|
|
2417
|
+
proceeds; enforce → 422 `{code:'placement_required', hubs:[{path,title,snippet}], message}`
|
|
2418
|
+
(hub candidates from the SAME `assemblePlaceContext` browse assembly). The Python tool's
|
|
2419
|
+
memory_write POST is now status-preserving so the structured rejection reaches the model
|
|
2420
|
+
verbatim (was: swallowed into `daemon_unreachable`).
|
|
2421
|
+
- **Extraction 0-yield root fix + terminal states** (`extract.ts`, `hook-server.ts`): live-
|
|
2422
|
+
diagnosed 2026-07-03 on the W1-gate pod (manual post_llm_call + direct gateway replay of the
|
|
2423
|
+
real gate turn): kimi's multi-page JSON stochastically exceeds `max_tokens=2048`
|
|
2424
|
+
(`stop_reason=max_tokens`, observed 996/1924/2048-cut across 3 identical trials), and the
|
|
2425
|
+
truncated payload JSON.parse-failed into a SILENT `[]` — the gate's
|
|
2426
|
+
`llm_called 3/3 → extracted 0/3`. Fix: (a) string-aware salvage parser recovers every
|
|
2427
|
+
COMPLETE page object from a truncated `{"pages":[…` array (the REAL truncated gate-shape
|
|
2428
|
+
payload now yields 3 pages instead of 0); (b) `stop_reason` surfaced —
|
|
2429
|
+
`stage=llm_truncated(max_tokens)` + `truncated` flag on the result; (c) terminal stages
|
|
2430
|
+
`stage=llm_response(pages=N, chars=M[, truncated])` and `stage=skipped(reason=no_pages)` +
|
|
2431
|
+
`extractedEmpty` counter close the llm_called-then-silence gap; (d) a BUDGET line in the
|
|
2432
|
+
extraction prompt biases toward fewer, denser pages. `/healthz` `memory.counters` is now
|
|
2433
|
+
`{received, skipped, extracted, writeFailed, extractedEmpty, placementWarn}`.
|
|
2434
|
+
- **R9.2 extraction deferred retry (doc 18 §9.4)** (`hook-server.ts`, `extract.ts errorStatus`):
|
|
2435
|
+
under burst the in-pod extraction call is starved by the workspace concurrency limiter
|
|
2436
|
+
(429 / 504 slot-deadline / status=0 cloud_unreachable) and the turn's knowledge — from exactly
|
|
2437
|
+
the busiest ingestion window — was dropped forever. Limiter-class failures now land on a
|
|
2438
|
+
bounded in-memory deferred queue (max 32, drop-oldest + `stage=deferred_dropped`); a lazy,
|
|
2439
|
+
jittered ~60s pump (`MEMORY_EXTRACT_RETRY_INTERVAL_MS`, unref'd, self-stopping) drains ONE
|
|
2440
|
+
entry per tick (serial — deliberately gentle on the limiter), max 3 total attempts, then
|
|
2441
|
+
`stage=extraction_abandoned` + counter. A successful retry runs the normal written/synced path
|
|
2442
|
+
with the ORIGINAL traceId (the trace shows the gap AND the recovery); each retry rebuilds a
|
|
2443
|
+
FRESH place-context. Non-limiter failures keep the writeFailed path (never queued). Counters:
|
|
2444
|
+
`memory.counters` gains `{deferred, deferredRetried, deferredAbandoned}` (deferred counts
|
|
2445
|
+
entries, not tries). Stage logs: `stage=deferred(reason=limiter, attempt=N)` /
|
|
2446
|
+
`deferred_retry(attempt=N)`. KNOWN BOUND (deliberate): the queue is in-memory only — a pod
|
|
2447
|
+
restart loses pending entries (no persistence by design; this module has no daemon shutdown
|
|
2448
|
+
hook to log the residual size).
|
|
2449
|
+
- Tests: `test/memory-write-w2.test.ts` (20, negative controls for every mode),
|
|
2450
|
+
`test/memory-extract-salvage.test.ts` (4, incl. the real-payload shape),
|
|
2451
|
+
`test/memory-extract-deferred.test.ts` (9: defer on 429/504/0, non-limiter + 0-page negative
|
|
2452
|
+
controls, traceId-preserving recovery, 3-attempt abandonment, hard-failure-on-retry, 32-cap
|
|
2453
|
+
drop-oldest), `test/memory-stage-counters.test.ts` (+1 extractedEmpty terminal state,
|
|
2454
|
+
counters shape ×9).
|
|
2455
|
+
|
|
2456
|
+
### Added — memory203/18 W0+W1 daemon lane: write-time placement + browse + trace observability
|
|
2457
|
+
|
|
2458
|
+
- **R1.1 `memory_write` structural placement** (`plugins/memory/prismer/__init__.py`,
|
|
2459
|
+
`daemon/memory/rpc.ts handleWrite`): optional `parent_hub_path` + `relation`
|
|
2460
|
+
(`child-of`|`related`, default child-of) on the tool schema, forwarded as
|
|
2461
|
+
`parentHubPath`/`relation` to `POST /local/memory/write`. When present the daemon enqueues a
|
|
2462
|
+
`memory.link.upsert` GRAPH event (page→hub) mirroring the auto-extract leg — the edge that
|
|
2463
|
+
actually nests the page cloud-side. A locally-missing hub still emits (warn-not-reject; the
|
|
2464
|
+
fail-closed guardrail is R6.3/W2, gated on browse being live). Response additively echoes
|
|
2465
|
+
`link: { targetPath, relation }`.
|
|
2466
|
+
- **R1.3 `memory_curate` promote_to_hub `childPaths[]`** (`__init__.py`, `rpc.ts handleCurate`,
|
|
2467
|
+
`adapters/memory-tools.ts`): pass-through to the cloud POST body so the promoted hub can attach
|
|
2468
|
+
children in the same transaction (cloud lane consumes; older clouds ignore the extra field).
|
|
2469
|
+
- **R6.1 hub snippet wiring** (`daemon/memory/hook-server.ts`): the extraction recall context now
|
|
2470
|
+
carries WHAT each hub is about — `description`, else first ~200 chars of content, else `''`.
|
|
2471
|
+
(The old call site mapped hubs without a snippet, so the extract model was blind to hub topics
|
|
2472
|
+
and rationally fell back to `placement=new` → orphan leaves.)
|
|
2473
|
+
- **R6.2 `memory_browse` / `GET /local/memory/place-context`** (`rpc.ts`, `__init__.py`,
|
|
2474
|
+
`adapters/memory-tools.ts`): write-time structure view `{ index, hubs[], nearest[] }` (each
|
|
2475
|
+
`{path,title,pageType,snippet}`), assembled by the SHARED `assemblePlaceContext` helper the
|
|
2476
|
+
extraction leg uses — browse and extraction cannot drift. `q=` adds nearest pages via the same
|
|
2477
|
+
local hybrid search; F5 cap predicate applies (private pages hidden). `memory_load` additively
|
|
2478
|
+
returns the page's outbound `links` (best-effort cloud `GET /pages/:id/links`; omitted offline).
|
|
2479
|
+
FROZEN spec (`adapters/memory-tools.ts`) now lists `memory_write` + `memory_browse` (R6.5 partial).
|
|
2480
|
+
- **R8.1 traceId 贯穿** (`__init__.py` → `hook-server.ts` → `extract.ts` → `envelope.ts` → `rpc.ts`):
|
|
2481
|
+
`sync_turn` stamps `extra.trace_id` (session id + random suffix); the daemon threads it through
|
|
2482
|
+
extraction into the `memory.page.upsert` / `memory.link.upsert` outbox envelopes (optional
|
|
2483
|
+
`traceId` added to `MemoryEventCommon` — additive, no schemaVersion bump). `handleWrite` accepts
|
|
2484
|
+
an optional `traceId` (else mints `wr_<10hex>`).
|
|
2485
|
+
- **R8.2 stage counters + standardized stage logs** (`hook-server.ts`, `extract.ts`,
|
|
2486
|
+
`daemon/local-server.ts`): module-level `{received, skipped, extracted, writeFailed}` counters
|
|
2487
|
+
surfaced on `/healthz` as `memory.counters`; every pipeline stage logs one grep-able line —
|
|
2488
|
+
`[memory-trace] stage=received|skipped(reason=…)|llm_called|llm_failed(status=…)|written(paths=…)|synced traceId=…`.
|
|
2489
|
+
- **R9.1 sync_turn delivery hardening** (`__init__.py`): the bare `except: pass` around the
|
|
2490
|
+
post_llm_call POST is replaced with at-least-once delivery — one retry after ~1s; terminal
|
|
2491
|
+
outcome always lands as ONE structured stderr line (`[memory-trace] sync_turn delivered …` /
|
|
2492
|
+
`… delivery FAILED err=<class>: <msg>`). Still fire-and-forget: never raises into the turn.
|
|
2493
|
+
- Tests: `test/memory-place-context.test.ts` (7), `test/memory-write-placement.test.ts` (8),
|
|
2494
|
+
`test/memory-stage-counters.test.ts` (5) — each new behavior with a negative control.
|
|
2495
|
+
|
|
2496
|
+
### Fixed — memory extraction dropped under real docs (generation outran the 30s client timeout)
|
|
2497
|
+
|
|
2498
|
+
- **Why:** test-verified (2026-07-02, memory203 systematic doc-ingest) — a full-doc extraction
|
|
2499
|
+
prompt with `max_tokens: 4096` on `us-kimi-k2.6` takes **29–44s** to generate. The daemon's
|
|
2500
|
+
extraction gateway call was bounded at **30s**, so under real documents the call aborted
|
|
2501
|
+
mid-flight (`status=0 cloud_unreachable "Request aborted/timeout"`), the 2-retry budget was
|
|
2502
|
+
exhausted, and the memory was silently dropped — 6 docs yielded ~1 page, 0 hubs. (The cloud
|
|
2503
|
+
proxy completed the work at ~29s, but the daemon had already given up.) A control run with the
|
|
2504
|
+
workspace concurrency limiter OFF reproduced identically → the limiter was **exonerated**; the
|
|
2505
|
+
cause was purely extraction generation-time vs client-timeout.
|
|
2506
|
+
- **What changed** (`daemon/memory/extract.ts`): `max_tokens` 4096 → **2048** (fits 6 concise
|
|
2507
|
+
pages at ~300 tok each while keeping generation well under the bound) and
|
|
2508
|
+
`GATEWAY_TIMEOUT_MS` 30s → **60s** (margin over the measured worst case). Both are now
|
|
2509
|
+
env-overridable — `MEMORY_EXTRACT_MAX_TOKENS`, `MEMORY_EXTRACT_TIMEOUT_MS` — so ops can tune
|
|
2510
|
+
without a daemon rebuild.
|
|
2511
|
+
|
|
2512
|
+
### Changed — memory skills/seeds repointed from `cloud memory` → `prismer memory` (post-2822f36f CLI separation)
|
|
2513
|
+
|
|
2514
|
+
- **Why:** commit `2822f36f` dropped the erroneous `cloud` bin from @prismer/runtime (it
|
|
2515
|
+
collided with @prismer/sdk's `cloud` bin → EEXIST on global install). The two CLIs are now
|
|
2516
|
+
cleanly separated: `cloud` = @prismer/sdk (cloud-side HTTP, incl. the memory203/13 decision-D
|
|
2517
|
+
**retired** memory HTTP impl), `prismer` = @prismer/runtime (this canonical daemon CLI). In
|
|
2518
|
+
the agent-rt image both tarballs install, so in-pod `cloud memory` would now resolve to the
|
|
2519
|
+
RETIRED sdk HTTP path — the daemon-backed memory CLI is `prismer memory`.
|
|
2520
|
+
- **What changed:** repointed every agent-facing `cloud memory …` reference to `prismer memory …`
|
|
2521
|
+
across the memory built-in skills, seeds, and adapter/CLI docs:
|
|
2522
|
+
`built-in-skills/memory/SKILL.md`, `built-in-skills/memory-dream/SKILL.md`,
|
|
2523
|
+
`src/im/data/memory-seed/{index.ts,sdk-intro.md}` (memory/recall moved to the `prismer` family;
|
|
2524
|
+
task/send/asset/load/parse/deliver/okr/pay stay `cloud`), the claude-code/codex
|
|
2525
|
+
`adapters/coding/*/memory-tools.ts` doc comments, this package's `cli/commands/memory.ts`
|
|
2526
|
+
header, and the desktop copy `apps/desktop/resources/runtime/built-in-skills/memory/SKILL.md`
|
|
2527
|
+
(whose stale `cloud memory extract/consolidate/compact` were aligned to the canonical set —
|
|
2528
|
+
`extract` → direct `prismer memory write` + automatic background review; `consolidate` →
|
|
2529
|
+
`prismer memory curate`; `compact` dropped, no canonical equivalent). `prismer memory` covers
|
|
2530
|
+
recall/search/read/list/write/delete/curate. No behavior change to the daemon `/local/memory/*`
|
|
2531
|
+
RPC; this is a CLI-name correction so in-pod agents hit the daemon, not the retired sdk HTTP.
|
|
2532
|
+
|
|
2533
|
+
### Clarified — code-agent memory path is the `prismer memory` CLI, NOT a native tool surface (memory203/09 §① gap G, resolved to §10 decision)
|
|
2534
|
+
|
|
2535
|
+
- **Context:** doc memory203/09 §① flagged 🔴 "CC/codex adapter 工具未接 dispatch
|
|
2536
|
+
(`memory-tools.ts:10`)" — i.e. the per-adapter memory tool schemas were never wired into
|
|
2537
|
+
the running code agent. Investigation shows the gap is **narrower than the 🔴 implies and
|
|
2538
|
+
already resolved by design**: doc memory203/10 §56 decides code agents
|
|
2539
|
+
(claude-code/codex/opencode) have no Hermes-style programmatic provider seam (they run as
|
|
2540
|
+
filesystem CLI subprocesses), so their memory path is the **`prismer memory` CLI + the
|
|
2541
|
+
`memory` built-in skill**, which hits the SAME daemon `/local/memory/*` RPC as the Hermes
|
|
2542
|
+
tool impls. That CLI is fully implemented (`src/cli/commands/memory.ts`:
|
|
2543
|
+
recall/read/write/list/curate, registered in `cli/index.ts`) and the `memory` skill
|
|
2544
|
+
(`scope: common`) is seeded into every coding workdir's `.claude/skills/` via
|
|
2545
|
+
`CODING_COMMON_ALLOWLIST` (`adapters/coding/shared/coding-skill-set.ts`), with the
|
|
2546
|
+
AGENTS.md/CLAUDE.md preamble pointing the agent at `prismer memory`. So recall/read/write/
|
|
2547
|
+
curate are available to code agents out of the box — through the CLI, not a tool schema.
|
|
2548
|
+
- **What changed:** corrected the now-misleading "adapter integration owner work / NOT shipped
|
|
2549
|
+
in phase-0" header comments in `adapters/coding/claude-code/memory-tools.ts` and
|
|
2550
|
+
`adapters/coding/codex/memory-tools.ts` to record the §56 decision (CLI is the path; the
|
|
2551
|
+
native tool surface is intentionally Hermes-only; these schemas are a consumer-less FORMAT
|
|
2552
|
+
FREEZER kept for parity / a possible future programmatic seam — do NOT force-wire them).
|
|
2553
|
+
- **No behavior change** — comment-only; the runtime memory path for code agents already works
|
|
2554
|
+
via the CLI + skill.
|
|
2555
|
+
|
|
2556
|
+
### Added — Dream CONVERGENCE: orchestrator can now READ candidates + a concrete convergence oneshot (memory203/13 §P4 + §0.5)
|
|
2557
|
+
|
|
2558
|
+
- **Why:** the write path produces well-placed anchored leaves, but at SCALE the wiki
|
|
2559
|
+
degrades into a **FLAT STAR** (`INDEX → many leaves directly`), not a navigable
|
|
2560
|
+
hierarchy. The curation WRITE verbs (promote-to-hub / supersede / rebuild-index)
|
|
2561
|
+
existed, but the orchestrator had **no way to READ what to converge** — orphan leaves,
|
|
2562
|
+
near-duplicate clusters, stale pages all lived cloud-side with no daemon passthrough.
|
|
2563
|
+
The "brain" (orchestrator's own LLM) had hands but no eyes; it could not DECIDE clusters.
|
|
2564
|
+
- **Daemon READ passthrough (`daemon/memory/rpc.ts`):** new `GET /local/memory/health?workspaceId=&kind=&limit=`
|
|
2565
|
+
forwards to the cloud candidate surfaces `GET /api/im/memory/health/{orphans|duplicates|stale}`
|
|
2566
|
+
and returns `{ ok, candidates: { orphans, duplicates, stale } }` (each `{ items, total }`).
|
|
2567
|
+
`kind=all` (default) fetches all three. Same passthrough shape as `handleCurate` — ws cap
|
|
2568
|
+
gated daemon-side, ACL gated cloud-side, **ZERO cloud LLM** (it only scans the page graph).
|
|
2569
|
+
Offline (`cloud` unwired) → 200 degraded empty, honouring the same "降级不中断" contract.
|
|
2570
|
+
Forwards the verified acting agent in `X-Prismer-Memory-Actor` so the workspace ACL
|
|
2571
|
+
projection is correct.
|
|
2572
|
+
- **CLI (`cli/commands/memory.ts`):** new `cloud memory curate candidates [--kind orphans|duplicates|stale|all] [--limit N]`
|
|
2573
|
+
— the READ half. GET to the daemon health route. Read-only (any agent), unlike the three
|
|
2574
|
+
orchestrator-gated write verbs.
|
|
2575
|
+
- **Tool (`adapters/memory-tools.ts`):** `memory_curate` gains a `candidates` op (additive to
|
|
2576
|
+
the existing `promote_to_hub` / `supersede` / `rebuild_index`) with `kind` + `limit` inputs;
|
|
2577
|
+
it branches to the daemon's GET health route and returns the `candidates` payload. Read-only,
|
|
2578
|
+
so it is offered to every agent (the LLM clusters; the cloud does not).
|
|
2579
|
+
- **`built-in-skills/memory-dream/SKILL.md`:** new **"## Convergence flow (oneshot)"** — STEP 1
|
|
2580
|
+
READ candidates → STEP 2 DECIDE clusters (the agent's LLM) → STEP 3 per-cluster `promote-to-hub`
|
|
2581
|
+
a representative + re-anchor members under it (typed `<a rel="parent" prismer://…>` write) →
|
|
2582
|
+
STEP 4 `rebuild-index` so INDEX becomes a TOC of hubs. One worked example (8 scattered
|
|
2583
|
+
`project/helios-*` orphan leaves → `project/helios` hub → re-anchor → rebuild). Fixed the
|
|
2584
|
+
stale `cloud memory curate run-dream` reference (that retired cloud-LLM command no longer
|
|
2585
|
+
exists — replaced by `candidates` + the write verbs).
|
|
2586
|
+
- **Re-anchor note (flagged, not built here):** there is no single "move-anchor" verb. Re-anchor
|
|
2587
|
+
is `promote-to-hub` (mints the hub) + a `cloud memory write` adding the member→hub `rel="parent"`
|
|
2588
|
+
link; `rebuild-index` then drops hub-parented leaves out of the top-level INDEX automatically.
|
|
2589
|
+
This reuses existing cloud endpoints; no new cloud verb requested.
|
|
2590
|
+
|
|
2591
|
+
### Changed — `memory` skill: add a concrete oneshot WRITE flow so agents actually persist PKF
|
|
2592
|
+
|
|
2593
|
+
- **Why:** a live agent ran a turn but wrote NO memory page — the skill's write guidance was
|
|
2594
|
+
too abstract. The corrected, baked-in flow is **construct-PKF-first, then find where to
|
|
2595
|
+
store it**: (1) CONSTRUCT the full PKF page from the conversation, (2) PLACE by consulting
|
|
2596
|
+
the index (`cloud memory read INDEX.pkf` + `cloud memory recall`) and deciding extend /
|
|
2597
|
+
attach-under-hub / new-semantic-path (never an orphan leaf), (3) WRITE to the decided path.
|
|
2598
|
+
- `built-in-skills/memory/SKILL.md`: new **"## Write flow (oneshot)"** section with a single
|
|
2599
|
+
copy-pasteable end-to-end example (conversation excerpt → constructed `decision` PKF →
|
|
2600
|
+
index-consultation commands + placement reasoning → `cloud memory write`). Reconciled the
|
|
2601
|
+
Operating Rules → Write list so reading `INDEX.pkf` is a WRITE step, not only a Read step.
|
|
2602
|
+
PKF format rules are referenced, not duplicated. The example PKF validates clean against
|
|
2603
|
+
`src/lib/pkf` (frontmatter + `<h2 id>` sections + typed `prismer://workspace` link).
|
|
2604
|
+
- New standalone reference `scripts/cookbook/memory-write-oneshot.md` (the canonical example
|
|
2605
|
+
the skill points to).
|
|
2606
|
+
|
|
2607
|
+
### Added — DAEMON-SIDE automatic memory extraction (the "自动" leg) + remove cloud-Dream from curate (memory203/13 §0.5 + line 101, Fix Batch B)
|
|
2608
|
+
|
|
2609
|
+
- **Why:** automatic extraction was DEAD. Hermes's native `background_review`
|
|
2610
|
+
spawns with `skip_memory=True`, so our `memory_write` provider tool is never
|
|
2611
|
+
injected into the review fork — whatever it "remembers" lands in Hermes's
|
|
2612
|
+
built-in MEMORY.md, never our cloud PKF wiki. So the "自动" leg is now
|
|
2613
|
+
implemented OURSELVES, in the agent runtime (the daemon in the agent's pod).
|
|
2614
|
+
- **In-pod gateway extraction (`extract.ts` rewritten):** instead of forwarding
|
|
2615
|
+
to the retired cloud `/api/im/memory/extract` (410), `extractFromTurn` now calls
|
|
2616
|
+
the LLM gateway DIRECTLY from the daemon — `${cloud.baseUrl}/api/v1/messages`
|
|
2617
|
+
(Anthropic wire) authed with `Bearer ${cloud.apiKey}`, the SAME agent-credentialed
|
|
2618
|
+
gateway path the code-agent providers use (`PRISMER_BASE_URL` + sk-prismer token,
|
|
2619
|
+
injected for the hosted agent at runner.ts:325-329). The INITIATOR is the agent
|
|
2620
|
+
runtime; the cloud only proxies. **NO cloud LLM call remains.** The prompt carries
|
|
2621
|
+
a recall context built from the LOCAL store (INDEX + hubs + nearest pages) so the
|
|
2622
|
+
model decides placement (extend / attach-under-hub / new leaf) against the real
|
|
2623
|
+
wiki, and returns minimal valid PKF (`<h1>`/`<h2 id>`/`<p>`; cloud materialize
|
|
2624
|
+
validates). Bounded retry (2 attempts) on transient gateway failure; never blocks.
|
|
2625
|
+
- **Post-turn hook re-purposed (`hook-server.ts`):** `handlePostLlmCall` (a no-op
|
|
2626
|
+
after P3') now responds 204 immediately, then fire-and-forget builds the recall
|
|
2627
|
+
context, runs the in-pod extraction, and writes each PKF page via the SAME direct
|
|
2628
|
+
path `handleWrite` uses — `slot.store.write` + outbox `memory.page.upsert` — so
|
|
2629
|
+
pages up-sync and get anti-orphan-anchored on cloud. Scratch/eval sessions skipped.
|
|
2630
|
+
`extract.ts` is re-wired (dead cloud-extract imports removed).
|
|
2631
|
+
- **`run_dream` removed from curate:** the Python provider `memory_curate` no longer
|
|
2632
|
+
advertises `op=run_dream` to every agent, and the daemon `handleCurate` (rpc.ts)
|
|
2633
|
+
no longer forwards `run_dream` to the cloud `/page-dream` LLM. Dream is the
|
|
2634
|
+
orchestrator's memory-dream skill calling the write VERBS, not a cloud-LLM trigger.
|
|
2635
|
+
The three write-verb ops (promote_to_hub / supersede / rebuild_index) are kept.
|
|
2636
|
+
|
|
2637
|
+
### Changed — retire cloud-side memory extraction; extraction moves to the agent runtime (memory203/13 §0.5, P3')
|
|
2638
|
+
|
|
2639
|
+
- **Governance (用户裁决 2026-06-30):** all memory LLM work runs in the agent's
|
|
2640
|
+
OWN runtime; the cloud does ZERO LLM for memory. The cloud is storage only
|
|
2641
|
+
(materialize page upsert + anti-orphan-anchor to INDEX + recall queries).
|
|
2642
|
+
- `memory_write` is a DIRECT write again. The daemon `POST /local/memory/write`
|
|
2643
|
+
handler reverts to `slot.store.write(...)` + outbox `memory.page.upsert`
|
|
2644
|
+
enqueue (the page then syncs to cloud, materializes, and is anchored to INDEX
|
|
2645
|
+
by the existing cloud path). It no longer wraps the content into a synthetic
|
|
2646
|
+
turn and routes it through any cloud-extract lane — that prior design is废'd.
|
|
2647
|
+
`memory_write` = the agent persisting a PKF page it ALREADY authored in its own
|
|
2648
|
+
runtime (主动 explicit, or Hermes `background_review` 自动).
|
|
2649
|
+
- The daemon `extract-turn` / `extract-compress` routes (which forwarded turns to
|
|
2650
|
+
cloud `/api/im/memory/extract`) are RETIRED — they now short-circuit to an
|
|
2651
|
+
inert no-op and forward NOTHING to any cloud LLM. The `runExtract` /
|
|
2652
|
+
`runExtractInBackground` / `handleExtractTurn` / `handleExtractCompress`
|
|
2653
|
+
helpers (and the `extractFromTurn` import) were removed from `rpc.ts`.
|
|
2654
|
+
- The Python Hermes provider: `memory_write` reverts to "write the PKF page you
|
|
2655
|
+
authored" (direct write; `path`/`content` are the literal page, not extraction
|
|
2656
|
+
hints). `sync_turn` / `on_pre_compress` are now NO-OPs (they no longer forward
|
|
2657
|
+
to the retired cloud-extract routes); automatic auto-extraction is Hermes's
|
|
2658
|
+
native `background_review` in-runtime. The unused `_http_post_async` helper was
|
|
2659
|
+
removed.
|
|
2660
|
+
- (cloud, src/im) `extractMemories` (the cloud LLM extractor) + its
|
|
2661
|
+
`buildWikiRecallContext` structured-recall helper are retired: `memory-extract.ts`
|
|
2662
|
+
is now an inert tombstone that throws, and `POST /api/im/memory/extract` returns
|
|
2663
|
+
410 Gone. No cloud LLM call remains for memory.
|
|
2664
|
+
|
|
2665
|
+
### Changed — auto-extraction is now non-blocking + index-TOC injection is structure-preserving (memory203 scale prep)
|
|
2666
|
+
|
|
2667
|
+
- **Non-blocking extract** — `sync_turn` (provider shell) now dispatches the
|
|
2668
|
+
`extract-turn` POST on a daemon thread (`_http_post_async`) and returns
|
|
2669
|
+
instantly, so an agent reading many docs never stalls the turn loop on the
|
|
2670
|
+
cloud LLM round-trip. The daemon `extract-turn` handler ACKs `202 {queued}`
|
|
2671
|
+
immediately and runs `extractFromTurn` in a detached promise (errors logged,
|
|
2672
|
+
never lost). In-process callers (`hook-server`) keep the awaitable
|
|
2673
|
+
`{pages,extracted,error}` contract — fire-and-forget is the HTTP-path default
|
|
2674
|
+
only. New `test/extract-nonblocking.test.ts`.
|
|
2675
|
+
- **Index-TOC scaling** — `index-toc.ts` no longer drops the tail when the
|
|
2676
|
+
injected workspace index exceeds the (hard, Hermes-2200-capped) ≤1,800-char
|
|
2677
|
+
managed-section budget. It now truncates DEPTH-FIRST — every top-level hub
|
|
2678
|
+
heading (the navigational spine) survives; only the deepest leaf detail is
|
|
2679
|
+
trimmed — and appends an observable marker (`… N more sections — load the
|
|
2680
|
+
index page`) so the agent knows to drill in rather than assume it saw
|
|
2681
|
+
everything. Stays a pure function.
|
|
2682
|
+
|
|
2683
|
+
### Added — multi-device remote-conflict status surfaced to the daemon (memory203 doc 07)
|
|
2684
|
+
|
|
2685
|
+
- The distributed reconcile core (invalidate fan-out + LWW/DAG conflict
|
|
2686
|
+
resolution + idempotency) already worked (7/7 e2e), but a daemon never learned
|
|
2687
|
+
its write LOST a conflict — the `remote-conflict` status lived only in cloud
|
|
2688
|
+
metadata. Now it travels cloud→daemon two ways: (A) inlined in the sync-inbox
|
|
2689
|
+
ACK (`SyncInboxResult.conflicts[]`) so an online loser learns immediately, and
|
|
2690
|
+
(B) on down-sync re-pull, the cloud page summary/detail carries
|
|
2691
|
+
`syncStatus:'remote-conflict'` (computed in `enrichSummaries` from the live
|
|
2692
|
+
curation-conflict version pointer) and `cloud-sync.materialisePage` stamps the
|
|
2693
|
+
local row via the new `MemoryStore.setSyncStatus`.
|
|
2694
|
+
- New `GET /local/memory/conflicts` (peek, not resolve) lists local
|
|
2695
|
+
remote-conflict pages to the host, behind the same F5 boundary predicate.
|
|
2696
|
+
- Tests: cloud `conflict-status.test.ts` 4/4 + daemon `memory-conflict-status.test.ts`
|
|
2697
|
+
3/3, both with negative controls; existing `memory-e2e-sync.test.ts` stays 7/7.
|
|
2698
|
+
Offline catch-up cursor (C7) remains the pre-existing deferred limitation.
|
|
2699
|
+
|
|
2700
|
+
### Added — at-rest memory encryption ACTIVATION behind flag (memory203 M-ENC, default OFF)
|
|
2701
|
+
|
|
2702
|
+
- The AES-256-GCM primitives (`crypto-cipher.ts`, `key-manager.ts`), the outbox
|
|
2703
|
+
encrypt path, and the cloud decrypt/FTS-exclude were already present but never
|
|
2704
|
+
fired — no caller marked a page `encrypted=true`. Activated the daemon write
|
|
2705
|
+
side: `MemoryStore` gains an `encryptionPolicy?` seam; `runner-wiring` installs
|
|
2706
|
+
a policy that returns true only when `isEncryptionEnabled()`
|
|
2707
|
+
(`FF_MEMORY_ENCRYPTION_ENABLED==='true'`, dynamic, default OFF) AND storage is
|
|
2708
|
+
not ephemeral AND a durable workspace key exists. `store.write` consults it
|
|
2709
|
+
only when the caller didn't set `encrypted` explicitly (down-sync's explicit
|
|
2710
|
+
per-page flag is never overridden). Fail-closed: flag off / ephemeral / no key
|
|
2711
|
+
→ plaintext, never keyless ciphertext.
|
|
2712
|
+
- New `test/memory-encryption-roundtrip.test.ts` (6 tests): encrypt→outbound
|
|
2713
|
+
ciphertext→down-sync decrypt round-trip + 2 negative controls. Cross-device key
|
|
2714
|
+
sharing stays out of scope (doc 06 deferral).
|
|
2715
|
+
|
|
2716
|
+
### Fixed — auto-extract error surfacing + read-after-write + PKF round-trip (memory203 Wave 1)
|
|
2717
|
+
|
|
2718
|
+
- **`daemon/memory/extract.ts#extractFromTurn`** — no longer returns a bare
|
|
2719
|
+
`ExtractedPage[]` (which collapsed LLM timeout / bad-response into a silent
|
|
2720
|
+
`extracted:0`). Now returns `{ pages, extracted, error }`: `error` is set on a
|
|
2721
|
+
cloud/LLM transport failure OR when the cloud produced candidates that were all
|
|
2722
|
+
rejected by the PKF round-trip gate (`saved=0 && skipped>0`), so a degraded
|
|
2723
|
+
extract is observable instead of masquerading as "nothing to save". Adds a
|
|
2724
|
+
bounded 1-retry on transient (status 0 / 5xx / network) failures with warn logs.
|
|
2725
|
+
- **read-after-write** — after mirroring an extracted page into the local
|
|
2726
|
+
`ScopedMemoryStore`, `confirmMirror()` re-reads it via the same bucket so the
|
|
2727
|
+
next turn's recall is guaranteed to see it; a miss logs loudly (best-effort, the
|
|
2728
|
+
cloud holds the canonical row).
|
|
2729
|
+
- **`rpc.ts` / `hook-server.ts`** — both extract consumers propagate the new
|
|
2730
|
+
`error` field instead of dropping it.
|
|
2731
|
+
- New `test/memory-extract-turn.test.ts` (9 tests incl. negative controls: all-
|
|
2732
|
+
rejected candidates → error surfaced, 5xx → retried, malformed PKF → not
|
|
2733
|
+
persisted) + cloud-side `assertPkfRoundTrips` gate in `memory-extract.ts`.
|
|
2734
|
+
|
|
2735
|
+
### Changed — memory203 agent-integration convergence onto the native MemoryProvider (doc 10 §3)
|
|
2736
|
+
|
|
2737
|
+
- **`adapters/persistence/hermes/index.ts#configurePrismerProvider`** — when the
|
|
2738
|
+
Prismer MemoryProvider is active (`installMemoryProvider` / `PRISMER_MEMORY_PROVIDER=1`)
|
|
2739
|
+
it now SUPERSEDES the two fragmented paths: the curl `installMemoryHooks` block is
|
|
2740
|
+
skipped (the provider's in-process `sync_turn`/`prefetch` are the native equivalent —
|
|
2741
|
+
running both double-extracts the same turn) and the standalone recall-tools plugin is
|
|
2742
|
+
NOT installed (the provider's `get_tool_schemas` already exposes `memory_search`/
|
|
2743
|
+
`memory_load`/`memory_curate`). Both paths remain intact as the **no-provider
|
|
2744
|
+
fallback** (eval opt-out via `installMemoryHooks: false` preserved). `configurePrismerProvider`
|
|
2745
|
+
+ `HermesProfileConfigSchema` are now exported for the config-gen unit test.
|
|
2746
|
+
- **Port fix** — the daemon-port default across the hermes config-gen + the Python
|
|
2747
|
+
provider shell (`plugins/memory/prismer/__init__.py`) moved from the stale `3210`
|
|
2748
|
+
to `7878` (the daemon's real loopback port in agent-rt; `PRISMER_DAEMON_PORT` env
|
|
2749
|
+
still wins). The provider's `_http_get` + the `prismer memory` CLI both read
|
|
2750
|
+
`PRISMER_DAEMON_PORT`, which the config-gen writes into the profile `.env`.
|
|
2751
|
+
- **`test/memory-provider-configgen.test.ts`** — new: asserts provider-on pins
|
|
2752
|
+
`memory.provider: prismer`, writes `PRISMER_DAEMON_PORT` to `.env`, installs neither
|
|
2753
|
+
hooks nor double recall-tools; provider-off fallback still wires the curl hooks (7878).
|
|
2754
|
+
- **Provider-install path fix (`installMemoryProviderShell`)** — the shell was copied
|
|
2755
|
+
to `<profile>/plugins/memory/prismer/`, but Hermes's MEMORY-provider scanner
|
|
2756
|
+
(`plugins/memory/__init__.py` `_iter_provider_dirs`) discovers USER-installed
|
|
2757
|
+
providers ONE level deep at `<HERMES_HOME>/plugins/<name>/` — the extra `memory/`
|
|
2758
|
+
segment exists only for BUNDLED providers. So `find_provider_dir("prismer")` returned
|
|
2759
|
+
`None` and the provider was NEVER loaded: the agent saw no `memory_search` tool and
|
|
2760
|
+
reported "memory not available" despite correct config + a working daemon RPC. Fixed
|
|
2761
|
+
to install at `<profile>/plugins/prismer/`. Verified live in agent-rt: relocation →
|
|
2762
|
+
provider loads (`3 tools`) → agent recalls the seeded Helios corpus (18,400 ev/s)
|
|
2763
|
+
end-to-end. The two install/config-gen tests asserted the broken nested path (green
|
|
2764
|
+
while encoding the bug); both updated to the discoverable layout + a guard that the
|
|
2765
|
+
nested path is absent.
|
|
2766
|
+
|
|
2767
|
+
### Fixed — daemon FTS recall is AND-first with OR-fallback (not AND-only)
|
|
2768
|
+
|
|
2769
|
+
- **`search.ts#hybrid`** — the local FTS MATCH joined every term with implicit
|
|
2770
|
+
AND, so a natural multi-word recall ("helios throughput target") only hit pages
|
|
2771
|
+
containing ALL terms; a padded query silently returned zero. Now runs AND first
|
|
2772
|
+
(precision), then relaxes to OR when AND yields nothing and the query has ≥2
|
|
2773
|
+
terms (recall gate). BM25 still ranks pages matching more terms higher.
|
|
2774
|
+
|
|
2775
|
+
### Fixed — cloud→daemon subset projection preserved visibility + canonical id (live MVP3/MVP4)
|
|
2776
|
+
|
|
2777
|
+
- **ACL leak (`cloud-sync.ts#materialisePage`)** — the visibility projection was
|
|
2778
|
+
`page.visibility === 'agent'`, but the cloud stores an owner-PREFIXED string
|
|
2779
|
+
(`agent:<imUserId>`). The exact match never hit, so EVERY agent-private page
|
|
2780
|
+
was stored `{kind:'workspace'}` and recallable by every in-workspace agent (a
|
|
2781
|
+
second agent surfaced another agent's private cost page in the live MVP3 run).
|
|
2782
|
+
New `parseCloudVisibility()` maps `workspace` / `agent:<id>` / fail-closed
|
|
2783
|
+
`private:<id>` (human:/task:/unknown owner kinds), preserving the owner id the
|
|
2784
|
+
boundary predicate (`canCapReadPage`) gates on.
|
|
2785
|
+
- **Curate 404 (`store.ts#write` + `MemoryWriteInput.id`)** — the subset minted a
|
|
2786
|
+
local `page_<uuid>` id instead of the cloud's canonical id, so `memory_curate`
|
|
2787
|
+
promote-to-hub/supersede forwarded an id the cloud didn't know → 404 (the agent
|
|
2788
|
+
then faked success). `materialisePage` now passes `page.id`; the subset mirrors
|
|
2789
|
+
the superset's id. Daemon-authored writes still mint a local id; path conflicts
|
|
2790
|
+
retain the existing id (content/fts/links FK-safe).
|
|
2791
|
+
- **`test/memory-cloud-sync.test.ts`** — extended with agent:/human: pages
|
|
2792
|
+
asserting visibility kind+owner AND the preserved cloud id (the corpus only
|
|
2793
|
+
exercised `visibility:'workspace'`, which is why both bugs hid).
|
|
2794
|
+
|
|
2795
|
+
### Added — memory203 local-first load fallback + invalidate re-pull + F5 search收口 (doc 07 §3/§4, doc 08 §4a)
|
|
2796
|
+
|
|
2797
|
+
- **`daemon/memory/cloud-sync.ts#fetchPageFromCloudByPath`** — targeted single-page
|
|
2798
|
+
回源 for the local-first `load` fallback: a subset MISS pulls just that page from
|
|
2799
|
+
the cloud superset (`GET /api/im/memory/resolve?uri=`), materialises it via the
|
|
2800
|
+
existing `materialisePage` (identical decrypt + visibility + write), and the handler
|
|
2801
|
+
re-loads it locally (<5ms FTS). Strictly best-effort + local-first: offline → the
|
|
2802
|
+
fetch returns false → genuine 404, never a 5xx ("断云仍 load 本地").
|
|
2803
|
+
- **`daemon/memory/rpc.ts`** — `handleLoad` is now async; `attachMemoryRpc` gained a
|
|
2804
|
+
`keyManager` option (runner wires `memoryWiring.keyManager`) so回源'd encrypted pages
|
|
2805
|
+
decrypt to local plaintext (fail-closed to sentinel when keyless).
|
|
2806
|
+
- **`daemon/memory/ws-invalidate.ts`** — a non-`soft_delete` cloud invalidate
|
|
2807
|
+
(visibility_changed / promoted / archive) now fires a best-effort
|
|
2808
|
+
`initialSyncFromCloud` to re-pull the fresh SUBSET projection (watermark-bounded,
|
|
2809
|
+
subset fields only — never the full aclJson). `soft_delete` stays mark-only.
|
|
2810
|
+
- **`daemon/memory/rpc.ts#handleSearch`** — F5 search收口 (option a): when a cap is
|
|
2811
|
+
present, search hits are filtered through `canCapReadPage` (bounded `loadById` point
|
|
2812
|
+
lookup per hit, topK ≤ 20) so another agent's private-page snippet never leaks via
|
|
2813
|
+
search — F5 is now uniform across load / list / search.
|
|
2814
|
+
- **Tests** — `memory-load-fallback` (4), `memory-ws-invalidate` (+1 re-pull),
|
|
2815
|
+
`memory-acl-predicate` (+1 search filter). True daemon↔cloud contract проven by
|
|
2816
|
+
`scripts/cookbook/e2e-memory-sync.ts` (real outbox envelope → real `ingestSyncInbox`
|
|
2817
|
+
→ materialises `IMMemoryPageSection` + `IMMemorySyncEvent`; flushed:1 deadLettered:0).
|
|
2818
|
+
|
|
2819
|
+
### Added — P0 memory security spine: per-agent scoped capability tokens (memory203 doc 08, F1–F5)
|
|
2820
|
+
|
|
2821
|
+
Closes the 🔴 daemon memory空腔 (doc 06 §2): `/local/memory/*` RPC + key access had
|
|
2822
|
+
no credential / workspace scope — any same-pod agent could pass another `workspaceId`
|
|
2823
|
+
to read/write/decrypt across workspaces. The fix is mechanism **C3**: the daemon
|
|
2824
|
+
self-signs + self-verifies a per-agent **capability** with a per-boot key.
|
|
2825
|
+
|
|
2826
|
+
- **`daemon/memory/cap.ts`** (new) — `mintCap(sub, ws)` / `mintSystemCap()` /
|
|
2827
|
+
`systemCap()` / `verifyCap()` / `capAllowsWorkspace()` / `isSystemCap()`. Token =
|
|
2828
|
+
`v1.<b64url(payload)>.<HMAC>`; payload = `{aud:'memory', sub, ws, scope:['ws:'+ws],
|
|
2829
|
+
iat, exp}`. The per-boot key is `randomBytes(32)` held ONLY in module memory — never
|
|
2830
|
+
written, serialized, returned, or injected (daemon restart → new key → all prior
|
|
2831
|
+
caps reject). Agent caps are single-workspace; `ws:*` is hard-rejected for agents and
|
|
2832
|
+
reserved for the daemon-internal system cap.
|
|
2833
|
+
- **`daemon/memory/rpc.ts`** — every handler now passes a verified cap and gates the
|
|
2834
|
+
effective workspaceId (query / body / `?uri=`) via `scopeOk`/`effectiveWs`: cross-ws
|
|
2835
|
+
→ 403 `memory_ws_scope_violation`. `PRISMER_MEMORY_CAP_ENFORCE` (default OFF one
|
|
2836
|
+
release cycle): no/invalid cap → 401 `memory_cap_invalid` when on, warn+proceed when
|
|
2837
|
+
off — but a present cap is ALWAYS scope-checked. Out-of-process provider routes now
|
|
2838
|
+
stamp `actorImUserId` from `cap.sub` instead of `''`.
|
|
2839
|
+
- **`daemon/memory/key-manager.ts`** — `getKey(workspaceId, cap)` gates key access via
|
|
2840
|
+
`capAllowsWorkspace` and returns null WITHOUT touching the filesystem when the cap is
|
|
2841
|
+
not authorized (fail-closed; the AES key is never the cap). `getKeyOrNull` is now the
|
|
2842
|
+
internal raw loader. Daemon-internal flush/write-down callers
|
|
2843
|
+
(`outbox-worker.ts`, `cloud-sync.ts`) pass `systemCap()`.
|
|
2844
|
+
- **`daemon/memory/acl-predicate.ts`** (new) — `canCapReadPage(cap, page)`: the daemon's
|
|
2845
|
+
BOUNDARY projection of the shared ACL predicate (ws scope + page visibility kind),
|
|
2846
|
+
wired into `load` (cross-agent private → 404, no existence leak) and `list` (filter).
|
|
2847
|
+
The cloud keeps the FULL projection (`memory-acl.ts` aclJson) — two layers, no drift.
|
|
2848
|
+
- **Injection chain (F4)** — `adapters/prismer-env.ts` mints `PRISMER_MEMORY_CAP` from
|
|
2849
|
+
the dispatch metadata (covers claude-code/codex/provider-proxy via
|
|
2850
|
+
`applyPrismerScopeEnv`); the hermes gateway spawn injects it per-boot;
|
|
2851
|
+
`adapters/memory-tools.ts` and the two python shells
|
|
2852
|
+
(`plugins/{memory/prismer,tools/prismer-recall}/__init__.py`) forward it as the
|
|
2853
|
+
`x-prismer-memory-cap` header (env-default, zero extra wiring for in-process tools).
|
|
2854
|
+
- **Tests** — `memory-cap` (15), `memory-cap-rpc` (10), `memory-cap-injection` (5),
|
|
2855
|
+
`memory-acl-predicate` (6), `memory-encryption` (+2 key-gate). 274 memory tests green.
|
|
2856
|
+
|
|
2857
|
+
### Changed — `cloud task create` delegation is now a tracked board card (release203/21 §4 T1)
|
|
2858
|
+
|
|
2859
|
+
- **`cli/commands/task.ts`** — `cloud task create --agent <id>` now defaults to a
|
|
2860
|
+
**delegated work_item** (`metadata.kind='work_item'` + `dispatchPolicy='on-assign'`)
|
|
2861
|
+
instead of `kind='agent_run'`. The task therefore lands on the Kanban board (留痕/
|
|
2862
|
+
可观测) **and** auto-dispatches the moment the assignee is set — decoupling "shows
|
|
2863
|
+
on the board" (kind) from "auto-dispatches" (dispatchPolicy). Result write-back +
|
|
2864
|
+
session-anchor binding are unchanged (the cloud settles the board card itself via
|
|
2865
|
+
the IMTask fallback path).
|
|
2866
|
+
- **New `--no-card` flag** preserves the prior pure no-card run channel
|
|
2867
|
+
(`kind='agent_run'`) for internal/orchestrator sub-steps that should NOT surface
|
|
2868
|
+
as a board card.
|
|
2869
|
+
|
|
2870
|
+
### Added — `agent.fs.read` / `agent.fs.write` repo data-plane RPCs (release203/21 §6 R, C3)
|
|
2871
|
+
|
|
2872
|
+
- **`daemon/fs-read.ts`** (`readReposFile`) + **`daemon/fs-write.ts`** (`writeReposFile`)
|
|
2873
|
+
— new reverse-channel RPCs mirroring the existing `agent.fs.list`: same
|
|
2874
|
+
`resolveProjectReposDir` base + path-jail (no `../` escape out of the workspace
|
|
2875
|
+
root). `agent.fs.read` returns `{ content, encoding:'utf8'|'base64', sizeBytes,
|
|
2876
|
+
mtimeMs, sha256, mime? }`, rejects >1 MiB (`too_large`), base64s binary.
|
|
2877
|
+
`agent.fs.write` is atomic (temp+rename, `mkdir -p` parents), honours optional
|
|
2878
|
+
`ifMatchSha256` (mismatch → `conflict`, no overwrite).
|
|
2879
|
+
- **`daemon/runner.ts`** — wired `agent.fs.read` / `agent.fs.write` cases
|
|
2880
|
+
(`onAgentFsRead` / `onAgentFsWrite`, mirroring `onAgentFsList`); replies on
|
|
2881
|
+
`agent.fs.read.reply` / `agent.fs.write.reply`. Old daemons lacking these cases
|
|
2882
|
+
fall through to `unknown-message` → cloud invoke times out → frontend degrades to
|
|
2883
|
+
read-only (no version negotiation needed).
|
|
2884
|
+
- Cloud side: `POST /api/im/workspaces/:wid/fs/{read,write}` + `RepoCodePanel` /
|
|
2885
|
+
`/repo-code-lab` Monaco harness (see cloud repo). Live-verified: write→read exact
|
|
2886
|
+
roundtrip + `ifMatchSha256` conflict guard.
|
|
2887
|
+
|
|
2888
|
+
### Added — INDEX dynamic core-inject (memory202 doc 05 §4.2a, flag-gated OFF)
|
|
2889
|
+
|
|
2890
|
+
- **`daemon/memory/index-toc.ts`** — `buildIndexToc(indexMarkdown, budget)`: a
|
|
2891
|
+
PURE, section-aware, bounded TOC builder over the workspace INDEX page (the
|
|
2892
|
+
curated memory MAP). Within budget → the whole map prefixed with a short
|
|
2893
|
+
`# Memory Map` header; over budget → drops to the SKELETON of headings (the map
|
|
2894
|
+
shape) cut at heading boundaries, NEVER mid-section. Reuses the recall path's
|
|
2895
|
+
fence-aware ATX scanner (`section.ts#scanHeadingsForToc`) so "what is a section"
|
|
2896
|
+
is identical across slice + TOC. Empty / whitespace / budget≤0 → `''` (no throw).
|
|
2897
|
+
- **`daemon/memory/index-toc-inject.ts`** — flag-gated wiring that reads the local
|
|
2898
|
+
INDEX page → `buildIndexToc` → the EXISTING `coreInject` carrier
|
|
2899
|
+
(`createHermesMemoryIntegration(...).coreInject` → MEMORY.md managed section,
|
|
2900
|
+
already tracked by `recall-stats.recordCoreInject`). So an agent always has the
|
|
2901
|
+
current memory map in context. Gated on **`FF_MEMORY_INDEX_INJECT_ENABLED`**
|
|
2902
|
+
(default OFF → zero behaviour change, no `coreInject` call).
|
|
2903
|
+
- **`MemoryStore.loadIndexPageContent()`** — reads the workspace's
|
|
2904
|
+
`pageType='index'` page content (queried by the raw `'index'` string the cloud
|
|
2905
|
+
emits and `materialisePage` stores verbatim).
|
|
2906
|
+
- **Daemon trigger:** wired into `syncMemoryFromCloud` (runs on initial sync +
|
|
2907
|
+
post `host.acked`) — "dynamic" = re-injects whenever the synced map changes. The
|
|
2908
|
+
runner supplies the per-hermes-profile MEMORY.md carrier path(s); the wiring
|
|
2909
|
+
stops at "build TOC → call existing coreInject" and does NOT touch adapter-turn /
|
|
2910
|
+
envelope orchestration (that live-activation path stays held back).
|
|
2911
|
+
- **Deferred (follow-up):** a "memory map injected" UI indicator (dim 1) and the
|
|
2912
|
+
default-on activation (a separate convergence gate, like envelope).
|
|
2913
|
+
|
|
2914
|
+
### Added — Memory at-rest encryption (memory202 doc 06, MVP, flag-gated OFF)
|
|
2915
|
+
|
|
2916
|
+
- **Local-first AES-256-GCM encryption for memory pages.** The daemon holds a
|
|
2917
|
+
per-workspace symmetric key; the cloud stores only ciphertext and NEVER holds
|
|
2918
|
+
the key. Encrypted pages are excluded from cloud full-text search (cloud can't
|
|
2919
|
+
read them) — searchable only on the daemon (local plaintext). Non-encrypted
|
|
2920
|
+
pages are completely unaffected. Gated on `FF_MEMORY_ENCRYPTION_ENABLED`
|
|
2921
|
+
(default OFF → zero behavior change).
|
|
2922
|
+
- **`daemon/memory/crypto-cipher.ts`** — `encrypt`/`decrypt` over
|
|
2923
|
+
`crypto.createCipheriv('aes-256-gcm', …)` with a random 12-byte IV per call;
|
|
2924
|
+
self-describing packed format `v1:<iv>:<tag>:<ct>` (base64url). Authenticated:
|
|
2925
|
+
a GCM-tag/ciphertext tamper or wrong key throws on decrypt (never leaks
|
|
2926
|
+
plaintext).
|
|
2927
|
+
- **`daemon/memory/key-manager.ts`** — per-workspace 32-byte key generated on
|
|
2928
|
+
first need, persisted `<baseDir>/<workspaceId>/.memkey` (0600), reloaded on
|
|
2929
|
+
demand. The key never leaves the daemon. **Fail-closed:**
|
|
2930
|
+
`PRISMER_EPHEMERAL_STORAGE=true` (agent-rt emptyDir) → refuses to encrypt
|
|
2931
|
+
(returns null; caller writes plaintext with a loud warn) so a key written to
|
|
2932
|
+
ephemeral storage can never be lost-then-orphan-the-ciphertext. A persist /
|
|
2933
|
+
round-trip-verify failure likewise fails closed.
|
|
2934
|
+
- **`MemoryStore.write({ encrypted })`** threads the flag to the local
|
|
2935
|
+
`memory_pages.encrypted` column (was hardcoded 0); local SQLite + FTS keep
|
|
2936
|
+
PLAINTEXT (recall needs it) — encryption happens only on the cloud-bound
|
|
2937
|
+
outbox payload.
|
|
2938
|
+
- **Outbox flush encryption** (`outbox-worker.ts`): a `memory.page.upsert` whose
|
|
2939
|
+
page row is `encrypted=true` has its `payload.content` encrypted in the POST
|
|
2940
|
+
body only. A page that should be encrypted but cannot be (no key) is held back
|
|
2941
|
+
(left pending), NEVER POSTed in the clear.
|
|
2942
|
+
- **Sync-down decrypt** (`cloud-sync.ts`): ciphertext pulled from the cloud is
|
|
2943
|
+
decrypted with the local key before landing as plaintext in local FTS. **Cross-
|
|
2944
|
+
device deferral:** key absent / decrypt fails → stores an unreadable sentinel
|
|
2945
|
+
(never ciphertext-as-content), sync never crashes.
|
|
2946
|
+
- **Deferred (per doc 06):** agent-rt ephemeral-pod encrypted memory (fail-closed
|
|
2947
|
+
for now), cross-device key exchange, key rotation / re-encryption.
|
|
2948
|
+
|
|
2949
|
+
### Added — Hermes per-dispatch identifier auto-resolution (release203/15c WS-E3)
|
|
2950
|
+
|
|
2951
|
+
- **`cloud deliver` / `cloud task attach` / `cloud file send` / `cloud attach`
|
|
2952
|
+
now work on Hermes agents WITHOUT manually copying `--run-id` /
|
|
2953
|
+
`--conversation-id`.** Hermes (persistence) agents have no per-dispatch env
|
|
2954
|
+
(the gateway spawns once with a frozen env; per-dispatch ids only reach the
|
|
2955
|
+
model via `<execution_context>` XML, never the tool-shell). Previously the
|
|
2956
|
+
agent had to abstract the ids out of `<execution_context>` and pass them as
|
|
2957
|
+
flags or the daemon proxy returned "taskId required".
|
|
2958
|
+
- **`RunSessionRegistry.lookupActiveByAgent(agentImUserId, { adapterName?,
|
|
2959
|
+
conversationId?, ttlMs? })`** (`daemon/memory/run-session-map.ts`) — reverse-
|
|
2960
|
+
lookup the agent's CURRENT in-flight dispatch from `local_run_sessions`
|
|
2961
|
+
(reusing the existing `(conversation_id, agent_im_user_id, adapter_name)`
|
|
2962
|
+
index; NO new table/migration). Returns `null` (0 active), the ctx (exactly
|
|
2963
|
+
1), or `{ ambiguous, candidates }` (>1 with no narrowing key). 15min in-flight
|
|
2964
|
+
TTL window.
|
|
2965
|
+
- **`daemon/asset/deliver.ts`** `validate()` relaxed: `taskId` may be empty when
|
|
2966
|
+
`resolveActiveDispatch:true` + `agentImUserId` present; the handler then
|
|
2967
|
+
reverse-looks-up to fill taskId(=runId)/conversationId. Race-safety (doc §5):
|
|
2968
|
+
conversation-narrowed lookups are unambiguous; **>1 in-flight run with no
|
|
2969
|
+
narrowing → HTTP 409, NEVER a silent mis-attach**; miss → 400 "未找到活跃
|
|
2970
|
+
dispatch,请传 --run-id". `DeliverRequest` gains `agentImUserId?` /
|
|
2971
|
+
`resolveActiveDispatch?`; `DeliverHandlerResult` status union gains `409`.
|
|
2972
|
+
- **Hermes `terminal.env_passthrough` config (`adapters/persistence/hermes/
|
|
2973
|
+
index.ts`)** — registers `PRISMER_AGENT_USERNAME` / `PRISMER_AGENT_IM_USER_ID`
|
|
2974
|
+
/ `PRISMER_DAEMON_PORT` / `PRISMER_WORKSPACE_ID` as passthrough so they survive
|
|
2975
|
+
Hermes' `_scrub_child_env` (allowlist scrub: only `_SAFE_ENV_PREFIXES` +
|
|
2976
|
+
registered passthrough reach the tool subprocess). **This corrects the design
|
|
2977
|
+
doc's premise** that the identity vars were already present in the tool-shell
|
|
2978
|
+
env: they are present in the GATEWAY env but were silently scrubbed from the
|
|
2979
|
+
CHILD env, which would otherwise leave `cloud deliver` with neither a dispatch
|
|
2980
|
+
id NOR an agent identity to auto-resolve from. These vars are identity/
|
|
2981
|
+
transport (not credentials, no KEY/TOKEN substring → not GHSA-blocklisted).
|
|
2982
|
+
- Live cookbook `scripts/cookbook/regress-hermes-deliver-autoresolve.ts`: a real
|
|
2983
|
+
hermes agent runs `cloud deliver` with no flags → daemon log shows
|
|
2984
|
+
`[deliver] active-dispatch auto-resolve … runId=<this dispatch>` + the file
|
|
2985
|
+
lands as an asset; the >1-run ambiguity → 409 path is verified against the real
|
|
2986
|
+
migrated local SQLite schema.
|
|
2987
|
+
|
|
2988
|
+
### Added
|
|
2989
|
+
|
|
2990
|
+
- **Daemon-side Dream scheduler wired into boot** (memory202 doc 05). The
|
|
2991
|
+
`DreamScheduler` existed but was never instantiated (`new DreamScheduler`
|
|
2992
|
+
appeared only in tests) — the memory lifecycle was dead code. `attachMemoryRunner`
|
|
2993
|
+
now instantiates + `start()`s it behind `FF_MEMORY_DREAM_ENABLED` (default OFF,
|
|
2994
|
+
mirrors `FF_MEMORY_HTML_SIDECAR_ENABLED`), injecting a `CloudDreamRunner` that
|
|
2995
|
+
POSTs `/api/im/memory/page-dream` (fixed from a stale `/memory/page-dream` that
|
|
2996
|
+
would 404). Workspace tracking is fed via a new `MemoryOutboxWorker.onWorkspaceFlushed`
|
|
2997
|
+
hook → `scheduler.recordSessionEnd`. `recordActivity` is intentionally NOT fed
|
|
2998
|
+
from outbox flush (it drives the idle gate; feeding background flushes would
|
|
2999
|
+
perma-block dream) — the live-activity idle gate stays inert until adapter glue
|
|
3000
|
+
feeds `recordActivity` at LLM-call / tool-dispatch points (follow-up).
|
|
3001
|
+
`MemoryRunnerWiring.stop()` stops the scheduler. Flag OFF = zero behaviour change.
|
|
3002
|
+
|
|
3003
|
+
### Changed
|
|
3004
|
+
|
|
3005
|
+
- **`workspace member` list/add/update/remove logic extracted to a shared
|
|
3006
|
+
command-builder** (release203/15b WS-E4 §7.3 "B" rollout). The member-management
|
|
3007
|
+
verbs — identical endpoints (`/api/im/workspaces/:id/members`), identical role
|
|
3008
|
+
contract (`admin|member`; `owner` rejected as a v2.1+ RFC), identical
|
|
3009
|
+
remove-cascade — were duplicated verbatim against `cloud workspace member`. They
|
|
3010
|
+
now live once in `src/cli/shared/workspace-member-builder.ts`
|
|
3011
|
+
(`buildWorkspaceMemberCommand(adapter)`, parameterized by a
|
|
3012
|
+
`WorkspaceMemberAdapter` seam: `list/add/update/remove` + `emitList/emitMember/
|
|
3013
|
+
emitRemove` + `fail`). `prismer` injects a `CloudClient` + `ui.table`/`ui.line`
|
|
3014
|
+
adapter; `cloud` injects a `PrismerClient.workspaces.members` + padded-text
|
|
3015
|
+
adapter. Same no-dependency-edge / tsup-inline property as the `task wait` PoC
|
|
3016
|
+
(verified empirically: builder body inlined into `dist/cli.js`, no runtime
|
|
3017
|
+
`runtime/src` require). Behaviour preserved: success-path output and per-verb
|
|
3018
|
+
error codes byte-identical; the `--role` validation message is now the single
|
|
3019
|
+
canonical one (was already cloud's; prismer's variant retired). The `add` verb
|
|
3020
|
+
accepts BOTH the prismer positional (`<imUserId>`) and the cloud `--user` flag,
|
|
3021
|
+
so neither binary's existing invocation breaks. Gates:
|
|
3022
|
+
`regress-cloud-task-wait.ts` 4/4 + `regress-skill-cli-contract.ts` no new drift
|
|
3023
|
+
(workspace subcommands resolve via the followed builder import). The other
|
|
3024
|
+
candidate namespaces (`agent`/`memory`/`asset`) were audited and NOT extracted —
|
|
3025
|
+
their cross-CLI "overlap" is name-only over divergent transports/outputs, so a
|
|
3026
|
+
shared builder would change one side's UX (see `docs/release203/15b-*` §5).
|
|
3027
|
+
|
|
3028
|
+
- **`task wait` poll/settle logic extracted to a shared command-builder**
|
|
3029
|
+
(release203/15 WS-E4 §7.3 "B", PoC). The settle loop that both `prismer task
|
|
3030
|
+
wait` and `cloud task wait` (in `@prismer/sdk`) carried verbatim now lives once
|
|
3031
|
+
in `src/cli/shared/task-wait-builder.ts` — a transport/output-agnostic
|
|
3032
|
+
`buildTaskWaitCommand(adapter)` parameterized by an injected `TaskWaitAdapter`
|
|
3033
|
+
(`getTask`/`emit`/`fail`). `prismer task wait` supplies a `CloudClient` + JSON
|
|
3034
|
+
adapter; `cloud task wait` supplies a `PrismerClient.im` + text adapter. The
|
|
3035
|
+
two packages have no dependency edge — `@prismer/sdk` imports this module via a
|
|
3036
|
+
build-time relative path and `tsup` inlines it, so both tarballs stay
|
|
3037
|
+
standalone. Behaviour is byte-for-byte unchanged (settle on
|
|
3038
|
+
review/completed/failed/cancelled by default, `--terminal-only`,
|
|
3039
|
+
failed/cancelled → non-zero exit). Rollout for the remaining overlapping
|
|
3040
|
+
namespaces: `docs/release203/15b-shared-builder-plan.md`. Gates:
|
|
3041
|
+
`regress-cloud-task-wait.ts` 4/4 + `regress-skill-cli-contract.ts` green.
|
|
3042
|
+
|
|
3043
|
+
### Added
|
|
3044
|
+
|
|
3045
|
+
- **`cloud role create` / `validate` accept a role bundle DIRECTORY**
|
|
3046
|
+
(release203/16 §5.4 / §9 decision 2, P4). A role authoring unit is now either a
|
|
3047
|
+
single `role.json` (legacy, still accepted) OR a directory containing
|
|
3048
|
+
`role.json` + optional `SOUL.md`. When a directory carries a non-empty
|
|
3049
|
+
`SOUL.md`, its raw markdown becomes the role's `operatingPrinciples`
|
|
3050
|
+
(Hermes-compatible persona, slot#1), overriding/filling role.json's field;
|
|
3051
|
+
`AGENTS.md` is NOT part of the bundle (it's a per-role skill concept). Detection
|
|
3052
|
+
is by `statSync().isDirectory()`. New shared pure helpers `readRoleBundle` /
|
|
3053
|
+
`validateRoleBundle` (`src/bundle/index.ts`) back both the CLI and the zero-dep
|
|
3054
|
+
`role-builder/scripts/ingest-role.mjs` offline path. No cloud schema or
|
|
3055
|
+
service-signature change — only the source of `operatingPrinciples` (SOUL.md
|
|
3056
|
+
file vs json field). Tests: `bundle.test.ts` +7 dir-bundle cases,
|
|
3057
|
+
`skill-role-lifecycle-cli.test.ts` +2 CLI cases (49/49 green); runtime tsc clean.
|
|
3058
|
+
- **Daemon honors cloud version-skew directive on `host.acked`** (release203/19
|
|
3059
|
+
§1.2/§2.2/§3, P2). The daemon already reports `daemonVersion` on every
|
|
3060
|
+
`agent.host.declare`; the cloud now compares it against its configured
|
|
3061
|
+
`EXPECTED_DAEMON_VERSION` and, per the `DAEMON_VERSION_SKEW_POLICY` Nacos flag
|
|
3062
|
+
(default `warn` ⇒ observability only), may attach an `upgradeDirective` to
|
|
3063
|
+
`host.acked`. The daemon reacts via `applyUpgradeDirective`:
|
|
3064
|
+
- `warn` / absent — no-op (normal operation; the common case).
|
|
3065
|
+
- `refuse_dispatch` — reject NEW dispatches in `onTaskDispatch` while in-flight
|
|
3066
|
+
runs drain and the daemon stays up.
|
|
3067
|
+
- `drain_respawn` — reject NEW dispatches AND arm a one-shot drain watcher that,
|
|
3068
|
+
once `runningTasks` empties (or a 30-min cap elapses), runs `stop()` and
|
|
3069
|
+
`process.exit(0)` so the device/k8s controller re-pulls a new-image pod.
|
|
3070
|
+
|
|
3071
|
+
Backward compatible: a legacy cloud never sends a directive (field absent ⇒
|
|
3072
|
+
ignored). `HostAckedPayload.upgradeDirective` + `DaemonUpgradeDirective` added
|
|
3073
|
+
to `types/im-events.ts`. Covered by
|
|
3074
|
+
`test/daemon-version-skew-directive.test.ts`.
|
|
3075
|
+
|
|
3076
|
+
### Fixed
|
|
3077
|
+
|
|
3078
|
+
- **Daemon start-lock no longer self-locks on container restart** (release203/19
|
|
3079
|
+
§1.1/§2.1, P0). In a container the daemon runs as **pid 1** and writes
|
|
3080
|
+
`daemon.pid=1`. On a same-pod-sandbox container restart the emptyDir keeps the
|
|
3081
|
+
pidfile, so the new daemon — also pid 1 — saw `pidAlive(1)===true` and
|
|
3082
|
+
crash-looped with `Daemon already running (pid 1)` (Exited(1) loop hit by the
|
|
3083
|
+
doc17 e2e). The start guard now treats a **self-referential pidfile**
|
|
3084
|
+
(`existingPid === process.pid`) as our own stale leftover and proceeds,
|
|
3085
|
+
clearing the stale file before claiming it. New `daemonAlreadyRunning()` helper
|
|
3086
|
+
in `cli/util.ts` centralises the decision (`existingPid && existingPid !== ourPid
|
|
3087
|
+
&& pidAlive(existingPid)`); both `runForeground` and `startBackground` use it.
|
|
3088
|
+
The pidfile is retained for `status`/`logs`/`stop`, but is no longer the sole
|
|
3089
|
+
liveness authority. The crash-safe positive fix for the residual pid-reuse class
|
|
3090
|
+
on non-pid-1 hosts (OS advisory flock) is intentionally deferred — Node has no
|
|
3091
|
+
built-in flock and the runtime carries no `fs-ext`/`proper-lockfile` dep; the
|
|
3092
|
+
self-pid guard fully resolves the container scenario without a native addon.
|
|
3093
|
+
Covered by `test/daemon-start-lock.test.ts`.
|
|
3094
|
+
|
|
3095
|
+
### Added
|
|
3096
|
+
|
|
3097
|
+
- **`prismer task wait <id>` / `cloud task wait <id>`** (release203/15 §WS-E2).
|
|
3098
|
+
Block until an ALREADY-delegated task settles, then print it — the
|
|
3099
|
+
orchestrator's "delegate → wait → act on the result" primitive (the
|
|
3100
|
+
`tasks`/`agent-coordination` skills point here) so it doesn't hand-roll a
|
|
3101
|
+
`cloud task get` poll loop. Reuses `pollUntilTerminal` (now parameterised by
|
|
3102
|
+
stop-statuses). Settles on `review/completed/failed/cancelled` by default —
|
|
3103
|
+
`review` included because for a delegated task it means "assignee finished,
|
|
3104
|
+
awaiting YOUR approval"; blocking until `completed` would deadlock the very
|
|
3105
|
+
orchestrator that must approve. `--terminal-only` for strict terminal; exits
|
|
3106
|
+
non-zero on `failed`/`cancelled`. Gate: `regress-cloud-task-wait.ts` (real CLI
|
|
3107
|
+
against the live API on completed + review tasks).
|
|
3108
|
+
|
|
3109
|
+
- **Hermes (persistence) tool-call-mapper → structured `ToolCallDetail`**
|
|
3110
|
+
(release203/15 §WS-G). Hermes tool steps now carry a structured `detail` so
|
|
3111
|
+
`execute_code`/terminal/file/search/fetch calls render rich + expandable in the
|
|
3112
|
+
unified timeline, exactly like coding agents — instead of the bare row
|
|
3113
|
+
("import subprocess (+3 行)" = inputSummary) with no expand. New
|
|
3114
|
+
`adapters/persistence/hermes/tool-call-mapper.ts` reuses the
|
|
3115
|
+
`ToolCallDetail` union from `coding/shared/agent-sdk-types.ts` (imported, not
|
|
3116
|
+
redefined) and maps the Hermes tool `args` shape: `execute_code`/`terminal`/
|
|
3117
|
+
`bash`/`shell`/`run_command` → `shell{command}` (command pulled from
|
|
3118
|
+
`args.code`/`args.command`), read/write/edit file tools → `read`/`write`/`edit`,
|
|
3119
|
+
`search`/`grep`/`web_search` → `search`, `fetch`/`browser`/`open_url` → `fetch`;
|
|
3120
|
+
unknown tools → `undefined` (falls back to inputSummary, no regression).
|
|
3121
|
+
Wired at `sessions-sse.ts` `tool.started` (pass `{detail, status:'running'}`,
|
|
3122
|
+
which also fixes altitude — shell → `'action'` so consecutive `execute_code`
|
|
3123
|
+
collapse into a group like coding) and `tool.completed` (rebuilds the
|
|
3124
|
+
same-shape detail from started args + output/exitCode, stashed by `toolCallId`
|
|
3125
|
+
since the completion payload drops the original args). The entire downstream
|
|
3126
|
+
(recorder `capDetail` → cloud task-step-recorder → unified WS → agent-message
|
|
3127
|
+
decode → ActivityDetail rendering + `canExpand=detailHasBody`) already supported
|
|
3128
|
+
rich details — this is purely the missing producer half; no cloud/schema/render
|
|
3129
|
+
change. ⚠️ The Hermes gateway's `_tool_progress` callback frequently drops the
|
|
3130
|
+
real tool output (`sessions-sse.ts:375-401`); where output is absent,
|
|
3131
|
+
`detail.output`/`result` is left undefined (never a fake) — command + expand +
|
|
3132
|
+
exitCode-when-present still render. Real-output forwarding is a separate
|
|
3133
|
+
Hermes-gateway gap. 13 unit tests.
|
|
3134
|
+
|
|
3135
|
+
- **`cloud skill create` + `cloud role` commands** (Standardization SS-01/SS-02
|
|
3136
|
+
ingestion). New CLI verbs that push material-derived bundles into the live
|
|
3137
|
+
platform with the configured API key: `cloud skill create <bundleDir>
|
|
3138
|
+
[--install --agent <id>]` reads a SS-01 skill bundle (frontmatter + files),
|
|
3139
|
+
builds a server-matching merkle `contentManifest`, and `POST`s
|
|
3140
|
+
`/api/im/skills` (+ install). New `cloud role` group — `create <role.json>
|
|
3141
|
+
[--apply --agent <id> --workspace-id <id>]`, `apply <slug>`, `list`, `show` —
|
|
3142
|
+
ingests/applies SS-02 role templates (`/api/im/role-templates` + `/apply`).
|
|
3143
|
+
Backs the new `skill-builder` / `role-builder` built-in skills (each also ships
|
|
3144
|
+
a zero-dependency `scripts/ingest*.mjs` for SDK-only/CI environments). Note:
|
|
3145
|
+
community skill creates are slug-prefixed server-side (`name` →
|
|
3146
|
+
`community-<name>`); use the returned slug for install + role `requiredSkills`.
|
|
3147
|
+
Verified 8/8 against the local stack (skill create+install+GET, role
|
|
3148
|
+
create+apply+GET, profile roleTemplate projection).
|
|
3149
|
+
|
|
3150
|
+
- **Per-role Hermes-native skill scope** (release203/13 P1). Roles can now govern
|
|
3151
|
+
which of Hermes' ~90 bundled native skills an agent sees via `skills_list`, not
|
|
3152
|
+
just our injected built-ins. The hermes adapter reads `nativeSkillScope`
|
|
3153
|
+
(`{ mode: 'allow'|'deny', categories?, skills? }`) — projected from
|
|
3154
|
+
`IMRoleTemplate.nativeSkillScope` into `profile.config` (top-level, with a
|
|
3155
|
+
`roleTemplate` snapshot fallback; `resolveNativeSkillScope` mirrors
|
|
3156
|
+
`resolveMcpAllowlist`'s Priority-1/2). `deny` subtracts more on top of the
|
|
3157
|
+
global `HERMES_NATIVE_SKILL_DENYLIST` floor; `allow` keeps ONLY the listed
|
|
3158
|
+
categories/skills (net-body) — the bundled set (enumerated from the home-root
|
|
3159
|
+
`skills/` dir, cached by mtime) minus the allow-set. allow-mode fail-safes to
|
|
3160
|
+
the deny floor when the bundle can't be enumerated (fresh boot). Our injected
|
|
3161
|
+
skills are never disabled (enumeration reads the pure-bundle root, not
|
|
3162
|
+
`profileDir/skills/`). Verified: 9 unit tests + 11 e2e assertions on the real
|
|
3163
|
+
bundle.
|
|
3164
|
+
|
|
3165
|
+
### Changed
|
|
3166
|
+
|
|
3167
|
+
- **Daemon-side FORCE: all coding agents launch autonomous (no permission
|
|
3168
|
+
confirmation)** (release203). Coding agents run in agent-rt pods via the daemon
|
|
3169
|
+
and are ALWAYS non-interactive — a tool permission prompt can never be answered,
|
|
3170
|
+
so the provider blocked until the 300s watchdog (confirmed live: run f27vyi hung
|
|
3171
|
+
328s). The pod itself is the isolation boundary, so coding sessions now launch
|
|
3172
|
+
fully autonomous regardless of the stored profile config. Force-seam = the
|
|
3173
|
+
canonical `CodeAgentDriver` (`adapters/coding/shared/code-agent-driver.ts`):
|
|
3174
|
+
`withAutonomousLaunch(provider, config)` is applied on BOTH `createSession`
|
|
3175
|
+
(`buildSessionConfig`) and `resumeSession` (the resume overrides), so it covers
|
|
3176
|
+
EXISTING agents whose stored handle metadata carries a stale/absent `modeId`,
|
|
3177
|
+
not just new ones. Policy: keep an already-autonomous `modeId`, otherwise force
|
|
3178
|
+
the per-provider autonomous mode:
|
|
3179
|
+
- **claude-code** → `modeId: 'bypassPermissions'` (the claude-agent-sdk
|
|
3180
|
+
`permissionMode` equivalent of `--dangerously-skip-permissions`; the SDK skips
|
|
3181
|
+
`canUseTool` entirely in this mode).
|
|
3182
|
+
- **codex** → `modeId: 'full-access'` + `sandboxMode: 'danger-full-access'` +
|
|
3183
|
+
`approvalPolicy: 'never'` (≙ `--dangerously-bypass-approvals-and-sandbox`).
|
|
3184
|
+
- **opencode** → `modeId: 'build'` + `featureValues.auto_accept = true` (opencode
|
|
3185
|
+
has no CLI bypass flag — `auto_accept` drives its `tryAutoApproveToolPermission`
|
|
3186
|
+
path, auto-replying `"once"` to every tool permission request).
|
|
3187
|
+
|
|
3188
|
+
CLI fallback adapters (registered as `claude-code-cli` / `codex-cli` D21
|
|
3189
|
+
fallbacks in `daemon/runner.ts`) also launch with the real bypass flags:
|
|
3190
|
+
`claude --dangerously-skip-permissions` (`adapters/coding/claude-code/index.ts`)
|
|
3191
|
+
and `codex … --dangerously-bypass-approvals-and-sandbox` (supersedes `--sandbox`,
|
|
3192
|
+
`adapters/coding/codex/index.ts` `buildCodexArgs`; the codex `resume` subcommand
|
|
3193
|
+
rejects sandbox/approval flags and inherits the persisted session's policy from
|
|
3194
|
+
turn-1, so no re-pass). Cloud `buildProfileConfig` is unchanged — this daemon
|
|
3195
|
+
force is defense-in-depth. New assertions:
|
|
3196
|
+
`test/code-agent-driver-autonomous.test.ts` (+ colocated driver test) and a
|
|
3197
|
+
codex CLI-args case in `test/codex-adapter-session-streaming.test.ts`.
|
|
3198
|
+
|
|
3199
|
+
- **Persistence agents (Hermes) launch autonomous too — no approval gating**
|
|
3200
|
+
(release203, "Hermes 也全自治"). The persistence analog of the coding-agent
|
|
3201
|
+
force above. The hermes gateway spawn (`adapters/persistence/hermes/index.ts`)
|
|
3202
|
+
now sets `HERMES_YOLO_MODE: 'true'` in the child env, disabling hermes' native
|
|
3203
|
+
dangerous-command approval gate. Rationale: the non-interactive agent-rt pod has
|
|
3204
|
+
no human at a prompt to answer an `approval.request`, and that event tears down
|
|
3205
|
+
our SSE stream rather than routing to an approval UI — so a flagged command
|
|
3206
|
+
would just block ~5min then fail. The pod IS the sandbox; hermes' hardline
|
|
3207
|
+
catastrophic patterns still block even with YOLO on (desired floor). Companion
|
|
3208
|
+
change in `daemon/dispatch.ts`: `DEFAULT_OPERATING_PRINCIPLES` no longer carries
|
|
3209
|
+
an approval-seeking line — `resolveOperatingPrinciples()` appends the policy line
|
|
3210
|
+
per `approvalPolicy`, and the `autonomous` branch injects zero approval-seeking
|
|
3211
|
+
text. Role-template refs (`templates/roles/ceo.json`, `skill-author.json`) set
|
|
3212
|
+
`approvalPolicy: 'autonomous'`, drop `human-approval` from required skills, and
|
|
3213
|
+
rewrote the `[Authority and approval]` clauses to autonomous + an
|
|
3214
|
+
anti-confabulation rule. NOTE: the 205 DB-seeded role templates are NOT migrated
|
|
3215
|
+
— existing agents keep their snapshot and pick up the fix on recreation (same
|
|
3216
|
+
model as the coding `modeId` fix).
|
|
3217
|
+
|
|
3218
|
+
### Added
|
|
3219
|
+
|
|
3220
|
+
- **Daemon follows the unified single-WS realtime** (release203/12 P3.1). New
|
|
3221
|
+
`WsRealtimeSubscriber` (`daemon/gateway/ws-realtime-subscriber.ts`) consumes the
|
|
3222
|
+
cloud's unified `WS /ws/realtime?token=&since=` (the SAME endpoint the browser
|
|
3223
|
+
uses) instead of the legacy `GET /api/im/sync/stream` SSE. It demuxes the
|
|
3224
|
+
`{ch:'sync',name:'sync',data}` frames, ignores the `ch:'tasks'` projection (the
|
|
3225
|
+
daemon only ever materialized the sync fan-out), and feeds the SAME
|
|
3226
|
+
`Materializer` (rm_* upsert + per-conversation watermark + local relay) as the
|
|
3227
|
+
SSE path. Cursor parity: it persists the per-user seq under the SAME
|
|
3228
|
+
`chats`/`__sse__` watermark, so a daemon upgrading SSE→WS resumes from exactly
|
|
3229
|
+
where it left off; `sync.backfill.truncated{newestSeq}` still jumps the cursor
|
|
3230
|
+
(no replay storm). The runner constructs the WS subscriber by DEFAULT;
|
|
3231
|
+
`PRISMER_UNIFIED_WS=0` opts back to `SseSubscriber` (per-deploy kill switch,
|
|
3232
|
+
mirrors the client flag). Removes the daemon's second long-lived SSE connection
|
|
3233
|
+
to cloud — one user = one realtime connection.
|
|
3234
|
+
|
|
3235
|
+
- **Canonical agent identity injection** (release203/11 §2, Slice A — fixes净身
|
|
3236
|
+
coding agents answering "who are you?" as generic Claude). `TaskDispatchRequestPayload`
|
|
3237
|
+
gains an additive `identityContext?: { identity, user, scope }` (composed
|
|
3238
|
+
CLOUD-side — only cloud has the names). `dispatch.ts` now: (a) substitutes a
|
|
3239
|
+
new `CODING_SOUL_DEFAULT` engineering persona into the SOUL/persona slot when a
|
|
3240
|
+
coding agent (`claude-code`/`codex`/`opencode`) has an empty `config.systemPrompt`,
|
|
3241
|
+
and (b) forwards `identityContext` on a SEPARATE `metadata.identityContext` key
|
|
3242
|
+
(NOT folded into `metadata.systemPrompt`) so hermes' `SOUL.md` stays persona-only
|
|
3243
|
+
per Hermes docs. The hermes adapter places identity/user/scope in its per-turn
|
|
3244
|
+
`instructions` slot; the code-agent driver + legacy claude-code CLI prepend them
|
|
3245
|
+
to `--system-prompt`. New exported helpers `isCodingAdapter` / `renderIdentityLines`
|
|
3246
|
+
/ `CODING_SOUL_DEFAULT`. Purely additive — legacy dispatches with no
|
|
3247
|
+
`identityContext` are byte-identical on the wire.
|
|
3248
|
+
|
|
3249
|
+
- **Seed-on-dispatch for coding agents with no workdir** (release203/11 §2.4 —
|
|
3250
|
+
AGENTS-layer reliability fix). `seedDevPreset` previously only ran via
|
|
3251
|
+
`ensureWorkdir`, which is gated on a `payload.workdir` override; default
|
|
3252
|
+
RepoDirPicker coding agents carry no workdir, so their cwd never got
|
|
3253
|
+
`CLAUDE.md`/`AGENTS.md`. `dispatch.ts` now idempotently seeds the effective cwd
|
|
3254
|
+
(`profile.config.cwd`, `seedDevPreset(cwd, 'verified')` — append-only, never
|
|
3255
|
+
stomps user files, non-fatal) for any coding dispatch that didn't already
|
|
3256
|
+
materialize a workdir.
|
|
3257
|
+
|
|
3258
|
+
- **Dev preset bundle seeding** (release203/09 §7.5). New `src/daemon/seed-dev-preset.ts` `seedDevPreset(cwd, action)` seeds coding workdirs on `ensureWorkdir` (init → full: `scope ∈ {common,coding}` skills into `.claude/skills/` + AGENTS.md/CLAUDE.md managed block + `docs/agents/`; cloned/verified → append-only managed block, skills only if `.claude/skills` absent; reused → no-op). Gated by `PRISMER_SEED_DEV_PRESET` (default ON); non-fatal + idempotent (managed markers, skip-existing skill dirs).
|
|
3259
|
+
|
|
3260
|
+
- **Category-aware built-in skill filtering** (release203/09 §7.6.3). New runtime
|
|
3261
|
+
SSOT `src/adapters/agent-category.ts` (`ADAPTER_CATEGORY` / `categoryForAdapter`
|
|
3262
|
+
/ `skillScopeMatchesCategory`) mirrors the cloud-side adapter→category map.
|
|
3263
|
+
`SKILL.md` frontmatter `scope: common|persistence|coding` is now parsed into
|
|
3264
|
+
`LoadedSkill.scope` (defaults to `common`), and `FileSystemSkillLoader.loadForDispatch(profile)`
|
|
3265
|
+
filters to `scope ∈ {common, <adapter category>}`; unknown adapter or no profile
|
|
3266
|
+
loads everything (default-safe — an unrecognized scope never drops a skill). The
|
|
3267
|
+
hermes/openclaw persistence loaders now thread `profile` through instead of
|
|
3268
|
+
ignoring it.
|
|
3269
|
+
|
|
3270
|
+
- **`agent.workdir.materialize` control frame** (release203/09 §7.3 — on-demand
|
|
3271
|
+
persistent workdir provisioning for the Pro coding-agent picker). The daemon
|
|
3272
|
+
answers a new reverse-channel RPC (`{ workspaceId, projectId?, source:
|
|
3273
|
+
'clone'|'init'|'container-pick', sourceRef?, name?, cwd?, _rpcId }`): it
|
|
3274
|
+
resolves the per-project `repos/` base (`resolveProjectReposDir`), jails the
|
|
3275
|
+
target cwd inside `workspaces/<wid>`, then reuses `ensureWorkdir` to git-clone
|
|
3276
|
+
/ git-init / verify a container-picked path, and echoes `agent.workdir.reply`
|
|
3277
|
+
`{ _rpcId, ok, data:{ cwd, action: 'cloned'|'init'|'reused'|'verified' } |
|
|
3278
|
+
error:{ code: 'path_escape'|'materialize_failed'|'bad_request' } }`. clone/init
|
|
3279
|
+
require a single-segment `name` (`/`, `\`, `..`, empty → `bad_request`);
|
|
3280
|
+
container-pick verifies the supplied absolute `cwd` against the jail
|
|
3281
|
+
(`path_escape` on escape). The pure resolve+jail decision is extracted as
|
|
3282
|
+
`resolveWorkdirCwd` for unit testing (mirrors `fs-list.ts`).
|
|
3283
|
+
|
|
3284
|
+
- **`agent.fs.list` control frame + per-project `repos/` standard directory**
|
|
3285
|
+
(release203/09 — container directory picker for Pro coding-agent creation).
|
|
3286
|
+
New `resolveProjectReposDir(paths, workspaceId, projectId)`
|
|
3287
|
+
(`workspaces/<wid>/projects/<pid|_unscoped>/repos/`); the dispatcher now does a
|
|
3288
|
+
non-fatal `mkdir -p` of it alongside the session `artifacts/`/`scratch/` so a
|
|
3289
|
+
coding agent's default `cwd` always exists. The daemon answers a new
|
|
3290
|
+
`agent.fs.list` reverse-channel RPC (`{ workspaceId, projectId?, subpath?,
|
|
3291
|
+
_rpcId }`) by `readdir`-ing that `repos/` scope — jailed inside
|
|
3292
|
+
`workspaces/<wid>` (escape → `path_escape`), flagging each dir's `.git` as
|
|
3293
|
+
`isRepo`, dirs-first/alphabetical sort — and echoes `agent.fs.reply`
|
|
3294
|
+
`{ _rpcId, ok, data:{ absPath, parentRel, entries } | error:{ code } }`. A
|
|
3295
|
+
not-yet-created `repos/` returns `ok` with empty `entries` (never an error).
|
|
3296
|
+
|
|
3297
|
+
- **`cloud okr pack` subgroup** (release203 Task 2 — R4 Scenario Packs). Global,
|
|
3298
|
+
data-driven OKR templates resolved by `domain` KEY (no runtime branch).
|
|
3299
|
+
`okr pack list` (GET `/api/im/okr/packs`), `okr pack get <domain>` (GET
|
|
3300
|
+
`/api/im/okr/packs/:domain`), and `okr pack adopt <domain> <archetypeId>
|
|
3301
|
+
--workspace <id>` (POST `…/archetypes/:archetypeId/adopt`) which seeds a draft
|
|
3302
|
+
Objective + its KRs and stamps a commit-time pack snapshot
|
|
3303
|
+
(`metadataJson.scenarioPack {domain,version,archetypeId}`). The `software` pack
|
|
3304
|
+
is deliverable; `sales`/`marketing` are DATA-ONLY spec (`deliverable:false`) and
|
|
3305
|
+
adopting one returns `PACK_NOT_DELIVERABLE` (422). Dev KRs declare an internal
|
|
3306
|
+
source: task/acceptance-backed KRs emit today; latency/CI/defect/DORA KRs
|
|
3307
|
+
declare a `metricBinding` but `backedNow:false` → recompute returns null
|
|
3308
|
+
(—/pending, no fabricated numbers). approvalPolicy / resourcePolicy /
|
|
3309
|
+
proactivityTriggers / compensation are FROZEN and absent.
|
|
3310
|
+
|
|
3311
|
+
- **`cloud okr` command + `okr` built-in skill** (release203 Task 3 — OKR control
|
|
3312
|
+
loop). The canonical "agent drafts an OKR charter" path: from a human's plain-
|
|
3313
|
+
language goal an agent drafts ONE Objective + 2-5 Key Results, links existing
|
|
3314
|
+
tasks to the KR they serve, and routes the COMMIT to a human sponsor. Subcommands
|
|
3315
|
+
(all over the committed `/api/im/okr/*` + `/api/im/insights/okr` endpoints):
|
|
3316
|
+
`okr objective create|list|get|commit|close`, `okr kr add|recompute`,
|
|
3317
|
+
`okr link <objectiveId> <keyResultId> <taskId>`, `okr insights --workspace`.
|
|
3318
|
+
`kr add` assembles a `metricBinding` from `--metric-namespace/--metric-name/
|
|
3319
|
+
--metric-agg` and defaults `--source` to `agent-proposed`. Hard rules (enforced
|
|
3320
|
+
server-side, mirrored in the skill): an agent may PROPOSE but never COMMIT
|
|
3321
|
+
(`commit` → `AGENT_CANNOT_COMMIT` 403); a committed-type objective REQUIRES a
|
|
3322
|
+
human/admin sponsor (`SPONSOR_MUST_BE_HUMAN`); a qualitative KR may only be
|
|
3323
|
+
scored with a human-confirmed `--value` + `--evidence`. Guardrails / resource
|
|
3324
|
+
allocation / scenario packs / check-ins / evaluations / compensation are FROZEN
|
|
3325
|
+
and absent from both the CLI and the skill.
|
|
3326
|
+
|
|
3327
|
+
- **`cloud okr objective checkin|grade|archive`** (release203 Task 1 — OKR
|
|
3328
|
+
lifecycle back-half, `committed → graded → archived`). `checkin <id>
|
|
3329
|
+
[--note] [--confidence] [--decision continue|rescope|add-resource|pause|cancel]`
|
|
3330
|
+
records a check-in (snapshots the objective score + each KR's current/status);
|
|
3331
|
+
an AGENT may draft check-ins. `grade <id> [--score] [--narrative]` runs the
|
|
3332
|
+
formal evaluation (committed|at_risk|paused → graded) and `archive <id>`
|
|
3333
|
+
(graded|closed → archived, read-only) are **HUMAN decisions** — an agent caller
|
|
3334
|
+
gets `AGENT_CANNOT_GRADE` (403). Grade freezes the objective score. Reward /
|
|
3335
|
+
resource fields carry no token/credit meaning (compensation stays FROZEN).
|
|
3336
|
+
|
|
3337
|
+
- **Vision-gated image context for non-vision recipients** (release202/17). The
|
|
3338
|
+
daemon now consumes the envelope's new `assets.imageReferences` bucket and
|
|
3339
|
+
renders each as a one-line `[image: <filename> · asset <assetId> · <w>×<h>]`
|
|
3340
|
+
text token instead of vision pixels. A DEFENSIVE double-gate
|
|
3341
|
+
(`gateImageRefsByVision`, `adapters/shared/image-reference.ts`) re-checks the
|
|
3342
|
+
resolved model in the Hermes sessions-dispatcher, runs-dispatcher, and the
|
|
3343
|
+
envelope renderer (`hermes/context-render.ts`): when the model is NOT
|
|
3344
|
+
vision-capable, image inputs are degraded to the same reference lines and are
|
|
3345
|
+
never lifted into `image_url` — protecting against a cloud-side gating miss.
|
|
3346
|
+
Vision-capable / unknown models keep the current `image_url` behavior.
|
|
3347
|
+
|
|
3348
|
+
### Changed
|
|
3349
|
+
|
|
3350
|
+
- **zod 3 → 4.** `@anthropic-ai/claude-agent-sdk@^0.2.141` (newly added with the
|
|
3351
|
+
code-agent engine port) peer-requires `zod@^4.0.0` across its entire 0.2.x line,
|
|
3352
|
+
which conflicted with the previous `zod@^3.23.8` pin and broke `npm install`.
|
|
3353
|
+
Bumped to `zod@^4.0.0` and migrated the runtime's zod usage to the v4 API:
|
|
3354
|
+
`z.record(v)` → `z.record(z.string(), v)` (key type now mandatory),
|
|
3355
|
+
`ZodError.errors` → `.issues`, `z.ZodType<O, ZodTypeDef, I>` → `z.ZodType<O, I>`
|
|
3356
|
+
(`ZodTypeDef` removed in v4), and explicit transform-value typing where v4's
|
|
3357
|
+
stricter inference over generic `z.ZodTypeAny` schema params collapsed. No
|
|
3358
|
+
runtime behavior change. `zod-to-json-schema@^3.25.2` already supports zod 4.
|
|
3359
|
+
|
|
3360
|
+
### Removed
|
|
3361
|
+
|
|
3362
|
+
- **OpenClaw adapter retired** (release203/11 §2.5, Slice B). OpenClaw was a
|
|
3363
|
+
decay/version-rotting candidate and its identity model was already absorbed into
|
|
3364
|
+
the canonical four-piece model (§2.1). Deleted the whole
|
|
3365
|
+
`src/adapters/persistence/openclaw/` adapter (index / context-render /
|
|
3366
|
+
skill-loader / memory-tools), its registration in `daemon/runner.ts`, its
|
|
3367
|
+
`openclawAdapter` / `OpenClawProfileConfig` exports from `src/index.ts`, the
|
|
3368
|
+
`openclaw` pin in `known-versions.ts`, the openclaw branch in `agent-category.ts`
|
|
3369
|
+
`ADAPTER_CATEGORY`, the `FallbackAdapter` openclaw member, the openclaw
|
|
3370
|
+
`INSTALL_SPECS` / `BUILTIN_ADAPTERS` / auth + hook branches in the
|
|
3371
|
+
`prismer adapter` CLI, the openclaw `ADAPTER_BINARY` entry in the `prismer agent`
|
|
3372
|
+
CLI, and the openclaw branches in `dispatch.ts` (skill-dir gate),
|
|
3373
|
+
`skill-sync.ts` (skill-root resolution), and `local-server.ts` (state-root
|
|
3374
|
+
snapshot). Removed `openclawConfig` keys + `"openclaw"` from `applicableAdapters`
|
|
3375
|
+
in the role-template JSONs. Persistence is now hermes-only. Existing openclaw
|
|
3376
|
+
profile/agent rows are left untouched in the DB (they simply no longer dispatch —
|
|
3377
|
+
adapter gone); the `IMRoleTemplate.openclawConfig` column is kept as a dead column
|
|
3378
|
+
(no migration). Deleted the openclaw-specific tests
|
|
3379
|
+
(`openclaw-adapter-*`, `openclaw-skill-loader`, `context-render-openclaw`) and the
|
|
3380
|
+
`sa7-openclaw-gateway-verify` cookbook; trimmed the openclaw rows from the
|
|
3381
|
+
cross-adapter tests (multimodal / memory-tools / version-check / templates /
|
|
3382
|
+
skill-sync / workdir-materialize). Also dropped the `@prismer/openclaw-channel`
|
|
3383
|
+
plugin package and its references in the SDK build scripts.
|
|
3384
|
+
|
|
3385
|
+
### Fixed
|
|
3386
|
+
|
|
3387
|
+
- **Transient dispatch-precondition failures no longer hard-fail the run**
|
|
3388
|
+
(release202, HTTP 404 postmortem). A freshly-(re)connected daemon's FIRST
|
|
3389
|
+
cloud precondition fetch (`resolveProfile` → `GET /api/im/agent_profiles/:id`)
|
|
3390
|
+
can 404 during the warm-up window before its api-key-proxy identity is hot —
|
|
3391
|
+
the cloud stamps a valid `profileId`, but the owner-scoped lookup transiently
|
|
3392
|
+
misses. Previously that 404 was thrown straight past the 3× adapter-retry loop
|
|
3393
|
+
(which only wraps `adapter.dispatch()`, *after* `resolveProfile`) into the
|
|
3394
|
+
top-level catch, replying `{code:'adapter_dispatch_failed', message:'HTTP 404'}`
|
|
3395
|
+
→ terminal "Agent 失败 … HTTP 404" pill, recoverable only by the user
|
|
3396
|
+
resending. Now: (1) `resolveProfileResilient` retries TRANSIENT cloud failures
|
|
3397
|
+
(404 / 408 / 429 / 5xx / network) with backoff (~5s) before giving up;
|
|
3398
|
+
(2) the top-level catch classifies the final error via
|
|
3399
|
+
`isTransientPreconditionError` and replies with a distinct retryable code
|
|
3400
|
+
`dispatch_precondition_unavailable` (vs `adapter_dispatch_failed`) so the
|
|
3401
|
+
cloud re-queues instead of terminal-failing. 400/401/403 stay PERMANENT.
|
|
3402
|
+
|
|
3403
|
+
- **Upstream LLM failures no longer surface as fake-successful tasks**
|
|
3404
|
+
(release202/12, D1+D2). Hermes serializes an exhausted LLM call
|
|
3405
|
+
(`API call failed after N retries: …`) into `assistant.completed` content;
|
|
3406
|
+
the sessions SSE consumer now detects that signature (anchored, requires zero
|
|
3407
|
+
streamed deltas to avoid false-positives) and sets `SessionsSseResult.upstreamError`.
|
|
3408
|
+
`sessions-dispatcher` turns it into a FAILED `AdapterResult`
|
|
3409
|
+
(`code:'upstream_llm_error'`, carrying the HTTP status) instead of `ok:true`
|
|
3410
|
+
with the error string as output. The daemon retry loop (`dispatch.ts`) adds
|
|
3411
|
+
`isPermanentUpstreamError` so permanent causes skip the 3× retry and surface
|
|
3412
|
+
the real reason instead of `daemon_local_retry_exhausted`. Detection +
|
|
3413
|
+
classification key on the hermes message WORDING — `has no usable upstream
|
|
3414
|
+
source` (provider chain unconfigured), `Billing or credits exhausted:` (HTTP
|
|
3415
|
+
402 from the cloud balance gate), `HTTP <4xx>` — because hermes'
|
|
3416
|
+
`_summarize_provider_error` DROPS the JSON `error.type`, so a machine token
|
|
3417
|
+
can't be matched. Mirrored to the `/v1/runs` dispatcher so the flag-gated path
|
|
3418
|
+
can't reintroduce the bug. Pairs with the cloud-side
|
|
3419
|
+
`503 provider_chain_unconfigured` (release202/07 §5b) so a provider-chain
|
|
3420
|
+
misconfig reads as a clear failed task end-to-end.
|
|
3421
|
+
|
|
3422
|
+
### Added
|
|
3423
|
+
|
|
3424
|
+
- **`mode:'message-attach'` for `POST /local/deliver`** (release202/09 P5#3, 动作
|
|
3425
|
+
A2). Appends a freshly-uploaded asset to an ALREADY-SENT message. Body adds
|
|
3426
|
+
`messageId` (and requires `conversationId`); the daemon uploads with its own
|
|
3427
|
+
credential (same `deliverFile` upload path + agent-output-policy +
|
|
3428
|
+
magic-bytes as the other modes) then POSTs the resulting `assetId` to the
|
|
3429
|
+
cloud conversation-scoped attach route
|
|
3430
|
+
`POST /api/im/messages/:conversationId/:messageId/attach` (X-IM-Agent stamped
|
|
3431
|
+
so the cloud sender-check resolves the same agent that authored the message).
|
|
3432
|
+
Unlike `mode:'send'` it does not start a new message; unlike `mode:'attach'`
|
|
3433
|
+
it does not ride the reply. Wired from the in-container `cloud attach` SDK
|
|
3434
|
+
command. Complements `mode:'attach'` (reply does not exist yet) — A2 is for a
|
|
3435
|
+
reply that already exists.
|
|
3436
|
+
- **`mode:'task-attach'` for `POST /local/deliver`** (release202/09 P5#2, 动作
|
|
3437
|
+
③). The daemon uploads the file as a **task-bound** asset (`deliverFile`
|
|
3438
|
+
already stamps `sourceTaskId = taskId`), which the cloud `POST /assets`
|
|
3439
|
+
handler auto-rolls onto the kanban task card
|
|
3440
|
+
(`appendOutputAssetIdToTask` + `reemitTerminalDigestForAssetArrival`) plus the
|
|
3441
|
+
asset library. Unlike `mode:'attach'` it does **not** call
|
|
3442
|
+
`recordDeliveredAsset` (a kanban task may run without a chat reply — its
|
|
3443
|
+
products belong on the card, not a turn reply) and unlike `mode:'send'` it
|
|
3444
|
+
posts no message. No new cloud HTTP call is needed: the existing
|
|
3445
|
+
`sourceTaskId`-driven rollup is the kanban + library landing. Wired from the
|
|
3446
|
+
in-container `cloud task attach` SDK command.
|
|
3447
|
+
- **Explicit agent-driven file delivery** (release202/09 P2). New daemon
|
|
3448
|
+
local-server route `POST /local/deliver` (`local-server.ts` `onDeliver` +
|
|
3449
|
+
`daemon/asset/deliver.ts` `attachDeliver`): the in-container agent's
|
|
3450
|
+
`cloud deliver` / `cloud file send` proxy here so the daemon (which holds a
|
|
3451
|
+
usable IM credential the agent lacks) performs the upload + delivery. Body
|
|
3452
|
+
`{ taskId, path, mode:'attach'|'send', conversationId?, agentUsername? }`.
|
|
3453
|
+
`mode:'attach'` (动作 A) records the assetId onto `ArtifactsWatcher`'s
|
|
3454
|
+
existing `pendingByTask` so dispatch-end `flushPending` rides it on
|
|
3455
|
+
`reply.assetIds`; `mode:'send'` (动作 B) posts a standalone message with the
|
|
3456
|
+
asset attachment, stamped as the agent (`X-IM-Agent`). New public
|
|
3457
|
+
`ArtifactsWatcher.recordDeliveredAsset(taskId, assetId)` +
|
|
3458
|
+
`ArtifactsWatcher.deliverFile(...)`. `PRISMER_CONVERSATION_ID` is now
|
|
3459
|
+
injected into the agent env for 动作 B targeting.
|
|
3460
|
+
|
|
3461
|
+
### Changed
|
|
3462
|
+
|
|
3463
|
+
- **ArtifactsWatcher directory auto-scan is now flag-gated OFF by default**
|
|
3464
|
+
(release202/09 P2). New `ArtifactsWatcherOptions.autoScan` (default
|
|
3465
|
+
**false**): when off, the polling scan / `scanNow()` are no-ops (no implicit
|
|
3466
|
+
directory-magic), but `pendingByTask` / `flushPending` /
|
|
3467
|
+
`recordDeliveredAsset` / `upload` all still work. The legacy scan code is
|
|
3468
|
+
retained behind the flag and re-enablable via `PRISMER_ARTIFACTS_AUTOSCAN=1`.
|
|
3469
|
+
File delivery is now EXPLICIT (`cloud deliver` / `cloud file send`).
|
|
3470
|
+
|
|
3471
|
+
- **Hermes one-shot task-run dispatch via `/v1/runs`** (release202/08 Phase 1,
|
|
3472
|
+
flag-gated **default OFF**). New `HERMES_TASK_RUNS_DISPATCH` env flag. When ON
|
|
3473
|
+
and a dispatch is classified as a one-shot **run** (execution context
|
|
3474
|
+
`task-run` AND no triggering chat sender — pure kanban / scheduled fire /
|
|
3475
|
+
programmatic orchestration), the hermes adapter routes through the new
|
|
3476
|
+
`runs-dispatcher.ts` (`POST /v1/runs` → 202 + run_id, then `GET
|
|
3477
|
+
/v1/runs/{id}/events` SSE) instead of the stateful Sessions API. The run is
|
|
3478
|
+
**stateless** — no hermes session, no `local_run_sessions` mapping; its input
|
|
3479
|
+
is the bare task prompt (+ inline asset blocks) and `system_prompt` embeds a
|
|
3480
|
+
standalone `<execution_context type="task-run">` block (no SEED/THIN
|
|
3481
|
+
envelope, no participants/current_turn_sender). Multimodal image_url parts and
|
|
3482
|
+
the `/events` SSE parser (`consumeSessionsSse`) are reused verbatim. Chat
|
|
3483
|
+
turns (group/dm with a triggering sender) keep going through
|
|
3484
|
+
`dispatchViaSessions` unchanged. With the flag OFF the router is inert —
|
|
3485
|
+
`deriveDispatchKind` returns `'turn'` unconditionally, so 100% of traffic
|
|
3486
|
+
stays on Sessions (release201/25 §16.4 A3 behaviour, byte-for-byte).
|
|
3487
|
+
`adapters/hermes/{flag.ts,runs-dispatcher.ts,index.ts}`,
|
|
3488
|
+
`daemon/conversation-context.ts` (`renderExecutionContextXml`).
|
|
3489
|
+
|
|
3490
|
+
- **Role templates declare `taskDispatchScope`** (release202/08 §3.2 P3 —
|
|
3491
|
+
config-driven, role-agnostic task-dispatch permission). A role's template /
|
|
3492
|
+
agent-profile `config.taskDispatchScope` (`'any' | 'agents' | 'self' |
|
|
3493
|
+
'none'`) is the middle layer of cloud's three-layer dispatch resolver
|
|
3494
|
+
(`workspace.metadata.dispatchPolicy` > role/profile config >
|
|
3495
|
+
`DEFAULT_DISPATCH_POLICY`), so a NEW role gets its dispatch capability by
|
|
3496
|
+
declaring this field — the cloud resolver needs zero code changes. Declared:
|
|
3497
|
+
`ceo` → `any` (orchestrator, sibling of `taskAuthority`), `product-manager`
|
|
3498
|
+
→ `agents` (delegates to engineer/verifier, never a human peer), `engineer`
|
|
3499
|
+
/ `researcher` / `verifier` → `none` (executors, no peer delegation).
|
|
3500
|
+
`templates/roles/*.json`.
|
|
3501
|
+
|
|
3502
|
+
### Fixed
|
|
3503
|
+
|
|
3504
|
+
- **Model reasoning trace restored in agent messages on the hermes sessions
|
|
3505
|
+
stream** (regression from `2cdd2cad`, 2026-05-29). When dispatch migrated from
|
|
3506
|
+
`/v1/runs` (legacy `consumeSse`) to the sessions stream (`consumeSessionsSse`),
|
|
3507
|
+
the deleted `reasoning.available` handler was the only path feeding the model's
|
|
3508
|
+
thinking trace into `recorder.recordReasoningChunk(...)`, so reasoning stopped
|
|
3509
|
+
becoming `reasoning_chunk` steps and the expandable reasoning block vanished
|
|
3510
|
+
(tool-call steps still showed — separate handlers). Root cause of the shape
|
|
3511
|
+
mismatch: the sessions endpoint (`api_server.py:1585-1590 _tool_progress`)
|
|
3512
|
+
does **not** emit `reasoning.available`; it **remaps** reasoning into a
|
|
3513
|
+
`tool.progress` event with `tool_name: '_thinking'` and the text in `delta`
|
|
3514
|
+
(not `preview`), which the old `tool.progress` handler — reading only
|
|
3515
|
+
`preview` — silently dropped. Fix: `tool.progress` now reads `delta` first and
|
|
3516
|
+
routes reasoning through `recordReasoningChunk` + a `kind:'reasoning'`
|
|
3517
|
+
onProgress hint (no `lastProgress` bump — reasoning is not a progress unit for
|
|
3518
|
+
the reaper, mirroring the deleted handler). Also added a defensive top-level
|
|
3519
|
+
`reasoning.available` case (accepts `text`/`reasoning_content`/`delta`/
|
|
3520
|
+
`content`) for the `/v1/runs`-style direct event in case a hermes build
|
|
3521
|
+
surfaces it on the sessions stream. Guarded against empty strings. Covered by
|
|
3522
|
+
`test/reasoning-sse.test.ts`.
|
|
3523
|
+
- **Hermes adapter no longer reuses a stale/foreign gateway squatting on the
|
|
3524
|
+
port** (release202/07 robustness). `ensureService` decided reuse-vs-respawn
|
|
3525
|
+
with the UNAUTHENTICATED `/health` check, which an old-version (pre-sessions,
|
|
3526
|
+
404 on `/v1/capabilities`) or wrong-`API_SERVER_KEY` (401) gateway — e.g. an
|
|
3527
|
+
orphan we spawned in a PRIOR daemon session — answers `200` to. Reusing such a
|
|
3528
|
+
squatter made the capability gate later trip with the misleading "Hermes does
|
|
3529
|
+
not advertise session_chat_streaming", failing every dispatch after a daemon
|
|
3530
|
+
restart until the orphans were manually `pkill`ed. Now the reuse decision is
|
|
3531
|
+
AUTHENTICATED: a hermes counts as reusable only if it answers
|
|
3532
|
+
`GET /v1/capabilities` as ours; otherwise the adapter frees the port
|
|
3533
|
+
(`stopStaleHermesGateways`, matches `-p <profile>` / `API_SERVER_PORT`) and
|
|
3534
|
+
spawns a fresh gateway with our key — self-healing on restart. The capability
|
|
3535
|
+
probe result is reused as the pinned `HermesService.capabilities` (no extra
|
|
3536
|
+
fetch on the happy path). `adapters/hermes/index.ts` `ensureService`.
|
|
3537
|
+
|
|
3538
|
+
### Changed
|
|
3539
|
+
|
|
3540
|
+
- **Provider chain routing — `proxyProvider` widened from `'newapi'|'deepseek'`
|
|
3541
|
+
enum to any chain id** (release202/07). The codex adapter previously hardcoded
|
|
3542
|
+
the gateway base_url to `<PRISMER_BASE_URL>/api/v1` "regardless of
|
|
3543
|
+
proxyProvider" — silently dropping a `deepseek` selection so every Codex
|
|
3544
|
+
request landed on newapi (and 500'd for deepseek-only models). Now both the
|
|
3545
|
+
codex and hermes adapters resolve `base_url` by the configured chain:
|
|
3546
|
+
`newapi`/`default` → `/api/v1`; any other chain → `/api/v1/proxy/<chain>`
|
|
3547
|
+
(codex appends `/responses`, hermes `/chat/completions`). The cloud walks the
|
|
3548
|
+
chain (source1 → source2 → …) with per-source fallback.
|
|
3549
|
+
- `adapters/codex/index.ts` — `proxyProvider` schema `z.enum` → `z.string`;
|
|
3550
|
+
`resolveCodexPrismerProvider` honors the chain in base_url.
|
|
3551
|
+
- `adapters/hermes/index.ts` — same schema widening; `resolvePrismerProviderBaseUrl`
|
|
3552
|
+
generalized from the `deepseek`-only branch to any chain id.
|
|
3553
|
+
|
|
3554
|
+
- **Document-deliverable SOP — markdown files, not chat prose** (B-line,
|
|
3555
|
+
release201/3x). Codified the rule that document-type deliverables
|
|
3556
|
+
(research memos, PRDs, reports, reviews, summaries) MUST be written as
|
|
3557
|
+
markdown files into `${PRISMER_OUTBOX_DIR}/` (physically
|
|
3558
|
+
`${TASK_WORKDIR}/result/`) so the daemon outbox-watcher auto-registers
|
|
3559
|
+
them as task-bound IMAssets; the chat reply is a summary/teaser, not the
|
|
3560
|
+
deliverable itself.
|
|
3561
|
+
- `built-in-skills/tasks/SKILL.md` §产物输出 — added §文档型交付物 (= markdown
|
|
3562
|
+
文件) sub-section referencing the real `${PRISMER_OUTBOX_DIR}` /
|
|
3563
|
+
`result/` auto-archival wiring (`dispatch.ts` `appendArtifactsInstruction`,
|
|
3564
|
+
`PRISMER_ARTIFACTS_DIR` env). Filenames/structure are skill-defined
|
|
3565
|
+
(no hardcoded schema).
|
|
3566
|
+
- `templates/roles/{researcher,product-manager,engineer}.json`
|
|
3567
|
+
`systemPrompt` — replaced the old "upload as workspace file +
|
|
3568
|
+
prismer://file URI" convention with the markdown-to-`result/` SOP and a
|
|
3569
|
+
summary-vs-deliverable distinction; each points at the tasks skill SOP.
|
|
3570
|
+
- `templates/roles/verifier.json` `systemPrompt` + `operatingPrinciples` —
|
|
3571
|
+
extended the same SOP to the verifier: the full acceptance record
|
|
3572
|
+
(per-criterion method / params / observed result / repro steps) is now a
|
|
3573
|
+
markdown file in `${PRISMER_OUTBOX_DIR}/` (e.g. `verification-report.md`),
|
|
3574
|
+
auto-archived as a task-bound IMAsset. Binary evidence (screenshots /
|
|
3575
|
+
benchmark output / logs) still goes via `cloud task attach` +
|
|
3576
|
+
`evidenceRefs`; the `verify-criterion --note` stays a summary. The two
|
|
3577
|
+
paths are complementary.
|
|
3578
|
+
- Audit (cross-referenced docs): `templates/roles/ceo.json` left unchanged —
|
|
3579
|
+
it already routes all file deliverables through `office-artifacts` /
|
|
3580
|
+
`image-generate` / `canvas-design` / `web-artifacts-builder` skills (no
|
|
3581
|
+
legacy "upload as workspace file" anti-pattern), so it needs no SOP
|
|
3582
|
+
backfill. `templates/roles/skill-author.json` left unchanged — its
|
|
3583
|
+
deliverable is a structured `status=draft` IMSkill submitted via
|
|
3584
|
+
`cloud skill draft create`, not a chat-bound markdown document, so the
|
|
3585
|
+
`result/` SOP does not apply.
|
|
3586
|
+
|
|
3587
|
+
### Added
|
|
3588
|
+
|
|
3589
|
+
- **Adapter version probe with KNOWN_GOOD pinning** (Release 201 v2.0.7
|
|
3590
|
+
post-release P1). All four adapters (hermes / codex / claude-code /
|
|
3591
|
+
openclaw) now run `<binary> --version` on startup, parse the result,
|
|
3592
|
+
warn when the detected version drifts below the per-adapter
|
|
3593
|
+
`MIN_VERSION`, and warn when it diverges from `KNOWN_GOOD`. Pins are
|
|
3594
|
+
declared in the new `sdk/prismer-cloud/runtime/src/adapters/known-versions.ts`
|
|
3595
|
+
manifest:
|
|
3596
|
+
|
|
3597
|
+
- hermes: MIN=`0.0.0` (soft-pass), KNOWN_GOOD=`unknown` — TODO(v2.0.8):
|
|
3598
|
+
pin after cookbook run exercises a real hermes binary
|
|
3599
|
+
- codex: MIN=`0.0.0` (soft-pass), KNOWN_GOOD=`unknown` — TODO(v2.0.8):
|
|
3600
|
+
pin after cookbook run against `@openai/codex`
|
|
3601
|
+
- claude-code: MIN=`2.0.0` (Wave-4 flag set requires 2.x), KNOWN_GOOD=
|
|
3602
|
+
`unknown` — TODO(v2.0.8): pin knownGood after cookbook run
|
|
3603
|
+
- openclaw: MIN=`2026.4.0`, KNOWN_GOOD=`2026.4.0` — wrapper carries a
|
|
3604
|
+
2026.4.x stderr-bleed workaround, revisit once upstream fixes
|
|
3605
|
+
|
|
3606
|
+
Honesty note: three of the four pins are intentionally unpinned at
|
|
3607
|
+
the floor — this is the governance scaffold, not a claim that we've
|
|
3608
|
+
validated specific upstream revs. Bumping the pins is a v2.0.8 task
|
|
3609
|
+
blocked on real cookbook runs.
|
|
3610
|
+
|
|
3611
|
+
- New `sdk/prismer-cloud/runtime/src/adapters/version-check.ts` —
|
|
3612
|
+
`compareSemver`, `isVersionInRange`, `parseVersionFromStdout` shared
|
|
3613
|
+
helpers so the four adapters do not redefine their own semver math.
|
|
3614
|
+
|
|
3615
|
+
- `/healthz` `adapters[]` entries now carry `minVersion` + `knownGood`
|
|
3616
|
+
alongside the existing `name` / `ready` / `version` fields so cloud
|
|
3617
|
+
debug-pipeline can flag drift between detected and tested versions.
|
|
3618
|
+
|
|
3619
|
+
- `test/adapters-version-check.test.ts` — vitest regression covering
|
|
3620
|
+
compareSemver, range checks, pre-release stripping, parseVersionFromStdout,
|
|
3621
|
+
and the `ADAPTER_KNOWN_VERSIONS` manifest shape.
|
|
3622
|
+
|
|
5
3623
|
## v2.0.1 — 2026-05-21 — Skill multi-file manifest protocol (§A.7 Phase 1) + URL asset fetch
|
|
6
3624
|
|
|
7
3625
|
Coordinated v2.0.1 patch release. `/VERSION` → 2.0.1. Internal preview only;
|
|
@@ -325,3 +3943,16 @@ First published cut of `@prismer/runtime`. The runtime is the TS-only daemon tha
|
|
|
325
3943
|
## v1.8.x — Pre-publish scaffolding
|
|
326
3944
|
|
|
327
3945
|
The runtime tree was scaffolded across the `feat/refactoring` branch. There were no published cuts before v1.9.3; the package landed at v1.9.3 directly when its public API surface stabilized.
|
|
3946
|
+
|
|
3947
|
+
## 2.2.10 (2026-08-07)
|
|
3948
|
+
|
|
3949
|
+
- Fix Hermes "No LLM provider configured" on sandbox agents: the provider
|
|
3950
|
+
bootstrap wrote `model.provider: custom:prismer`, but hermes' custom-provider
|
|
3951
|
+
resolution matches `custom_providers[].name` verbatim — `custom:prismer`
|
|
3952
|
+
never matched the `prismer` entry, the gateway fell through with
|
|
3953
|
+
provider='custom' and no key, and every turn failed at AIAgent init.
|
|
3954
|
+
Exposed on 2.2.9: the dual-write made the gateway read the profile config
|
|
3955
|
+
(HERMES_HOME override) for the first time, surfacing the format mismatch.
|
|
3956
|
+
Write the bare provider name (`prismer`); verified in-pod that
|
|
3957
|
+
`_resolve_runtime_agent_kwargs` then resolves api_key + base_url correctly.
|
|
3958
|
+
## 2.2.9
|