@prismer/runtime 2.0.8 → 2.2.63
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 +4004 -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 +144 -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 +77 -38
- package/built-in-skills/agent-meta/SKILL.md +12 -0
- package/built-in-skills/assets/SKILL.md +9 -7
- package/built-in-skills/browser-use/SKILL.md +93 -0
- package/built-in-skills/canvas-design/SKILL.md +4 -3
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LICENSE-SOURCES.md +16 -0
- package/built-in-skills/claim-agent-ownership/SKILL.md +10 -5
- package/built-in-skills/claude-api/SKILL.md +2 -1
- package/built-in-skills/claude-api/python/claude-api/README.md +2 -2
- package/built-in-skills/claude-api/python/claude-api/tool-use.md +9 -5
- package/built-in-skills/claude-api/python/managed-agents/README.md +11 -1
- package/built-in-skills/claude-api/shared/error-codes.md +4 -4
- package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +4 -2
- package/built-in-skills/claude-api/shared/managed-agents-core.md +1 -2
- package/built-in-skills/claude-api/shared/managed-agents-events.md +41 -22
- package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +5 -3
- package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +1 -1
- package/built-in-skills/claude-api/shared/managed-agents-tools.md +2 -3
- package/built-in-skills/claude-api/shared/model-migration.md +3 -3
- package/built-in-skills/claude-api/shared/models.md +1 -1
- package/built-in-skills/claude-api/typescript/claude-api/batches.md +1 -1
- package/built-in-skills/claude-api/typescript/claude-api/files-api.md +3 -1
- package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +8 -2
- package/built-in-skills/claude-api/typescript/managed-agents/README.md +8 -2
- 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 +130 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/GUIDE.md +291 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE.superpowers +21 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/NOTICE.md +37 -0
- package/built-in-skills/codebase-design/references/sdlc-review/GUIDE.md +130 -0
- package/built-in-skills/codebase-design/references/sdlc-review/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/sdlc-review/NOTICE.md +17 -0
- package/built-in-skills/codebase-design/references/simplify-code/GUIDE.md +269 -0
- package/built-in-skills/codebase-design/references/simplify-code/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/simplify-code/NOTICE.md +37 -0
- package/built-in-skills/competitor-intelligence/LICENSE +21 -0
- package/built-in-skills/competitor-intelligence/NOTICE.md +6 -0
- package/built-in-skills/competitor-intelligence/SKILL.md +125 -0
- package/built-in-skills/competitor-intelligence/scripts/watch_state.py +160 -0
- package/built-in-skills/conversation-compaction/SKILL.md +117 -0
- package/built-in-skills/council-creator/SKILL.md +427 -0
- package/built-in-skills/diagnosing-bugs/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/SKILL.md +148 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/GUIDE.md +64 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/NOTICE.md +17 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/GUIDE.md +410 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE.superpowers +21 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/NOTICE.md +37 -0
- package/built-in-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/built-in-skills/doc-coauthoring/SKILL.md +7 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/GUIDE.md +647 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE +21 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE.hermes +21 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/PROVENANCE.md +7 -0
- package/built-in-skills/document-actions/LICENSE +21 -0
- package/built-in-skills/document-actions/NOTICE.md +21 -0
- package/built-in-skills/document-actions/SKILL.md +46 -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/evidence-citations/LICENSE +21 -0
- package/built-in-skills/evidence-citations/NOTICE.md +21 -0
- package/built-in-skills/evidence-citations/SKILL.md +46 -0
- package/built-in-skills/evidence-citations/references/citation-formats.md +68 -0
- package/built-in-skills/evidence-citations/references/grounding-rationale.md +64 -0
- package/built-in-skills/evidence-citations/scripts/sources.py +720 -0
- package/built-in-skills/frontend-design/SKILL.md +7 -0
- package/built-in-skills/frontend-design/references/claude-design/GUIDE.md +650 -0
- package/built-in-skills/frontend-design/references/claude-design/LICENSE +21 -0
- package/built-in-skills/frontend-design/references/claude-design/LICENSE.hermes +21 -0
- package/built-in-skills/frontend-design/references/claude-design/PROVENANCE.md +7 -0
- package/built-in-skills/human-approval/SKILL.md +17 -2
- package/built-in-skills/image-generate/SKILL.md +107 -301
- package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +310 -0
- package/built-in-skills/ingest/SKILL.md +13 -45
- package/built-in-skills/internal-comms/SKILL.md +1 -0
- package/built-in-skills/liteparse/SKILL.md +130 -110
- package/built-in-skills/mcp-builder/SKILL.md +1 -0
- package/built-in-skills/mcp-builder/reference/evaluation.md +11 -2
- package/built-in-skills/mcp-builder/reference/node_mcp_server.md +34 -5
- package/built-in-skills/mcp-builder/reference/python_mcp_server.md +6 -2
- package/built-in-skills/mcp-builder/scripts/connections.py +1 -1
- package/built-in-skills/mcp-builder/scripts/evaluation.py +52 -34
- package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -2
- package/built-in-skills/meeting-actions/LICENSE +21 -0
- package/built-in-skills/meeting-actions/NOTICE.md +21 -0
- package/built-in-skills/meeting-actions/SKILL.md +48 -0
- package/built-in-skills/memory/SKILL.md +444 -55
- package/built-in-skills/memory/references/llm-wiki/GUIDE.md +468 -0
- package/built-in-skills/memory/references/llm-wiki/LICENSE +21 -0
- package/built-in-skills/memory/references/llm-wiki/NOTICE.md +4 -0
- package/built-in-skills/memory/references/llm-wiki/scripts/lint_wiki.py +84 -0
- package/built-in-skills/memory/references/llm-wiki/scripts/requirements.txt +1 -0
- package/built-in-skills/memory/references/llm-wiki/templates/SCHEMA.md +19 -0
- package/built-in-skills/memory/references/llm-wiki/templates/index.md +9 -0
- package/built-in-skills/memory/references/llm-wiki/templates/log.md +4 -0
- package/built-in-skills/memory-dream/SKILL.md +332 -0
- package/built-in-skills/memory-import/SKILL.md +51 -0
- package/built-in-skills/office-artifacts/NOTICE.md +15 -0
- package/built-in-skills/office-artifacts/SKILL.md +37 -4
- package/built-in-skills/office-artifacts/references/RESOURCE-LIMITS.md +44 -0
- package/built-in-skills/office-artifacts/references/document-generation/GUIDE.md +105 -0
- package/built-in-skills/office-artifacts/references/docx/GUIDE.md +196 -0
- package/built-in-skills/office-artifacts/references/docx/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/docx/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/docx/references/revisions-and-comments.md +88 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_comments.py +292 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_common.py +93 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_create.py +177 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_edit.py +250 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_read.py +169 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_revisions.py +149 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_template.py +70 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_validate.py +155 -0
- package/built-in-skills/office-artifacts/references/docx/tests/test_docx_skill.py +525 -0
- package/built-in-skills/office-artifacts/references/pdf/GUIDE.md +125 -0
- package/built-in-skills/office-artifacts/references/pdf/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/pdf/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/pdf/references/forms.md +99 -0
- package/built-in-skills/office-artifacts/references/pdf/references/nano-pdf-editing.md +40 -0
- package/built-in-skills/office-artifacts/references/pdf/references/ocr-extraction.md +165 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/_raster.py +87 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/extract_marker.py +95 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/extract_pymupdf.py +107 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_create.py +153 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_fill_form.py +97 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_form_layout.py +168 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_make_form.py +145 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_merge.py +50 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_meta.py +123 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_page_image.py +101 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_read.py +153 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_secure.py +71 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_split.py +86 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_stamp.py +145 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_watermark.py +51 -0
- package/built-in-skills/office-artifacts/references/pdf/tests/test_pdf_skill.py +414 -0
- package/built-in-skills/office-artifacts/references/powerpoint/GUIDE.md +220 -0
- package/built-in-skills/office-artifacts/references/powerpoint/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/powerpoint/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_create.py +214 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_edit.py +442 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_from_template.py +88 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_read.py +131 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_render.py +104 -0
- package/built-in-skills/office-artifacts/references/powerpoint/tests/test_powerpoint_skill.py +478 -0
- package/built-in-skills/office-artifacts/references/xlsx/GUIDE.md +196 -0
- package/built-in-skills/office-artifacts/references/xlsx/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/xlsx/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/xlsx/references/restructuring.md +74 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/csv_to_xlsx.py +108 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_create.py +259 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_edit.py +269 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_read.py +160 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_recalc.py +111 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_restructure.py +418 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/built-in-skills/office-artifacts/references/xlsx/tests/test_xlsx_skill.py +542 -0
- package/built-in-skills/office-artifacts/tests/test_resource_regressions.py +272 -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 +258 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/GUIDE.md +148 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE +21 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE.hermes +21 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/PROVENANCE.md +7 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/templates/template.html +345 -0
- package/built-in-skills/pkf-writing/SKILL.md +258 -0
- package/built-in-skills/prismer-airtable/LICENSE +21 -0
- package/built-in-skills/prismer-airtable/NOTICE.md +11 -0
- package/built-in-skills/prismer-airtable/SKILL.md +248 -0
- package/built-in-skills/prismer-airtable/scripts/airtable_client.py +76 -0
- package/built-in-skills/prismer-apple-notes/LICENSE +21 -0
- package/built-in-skills/prismer-apple-notes/NOTICE.md +17 -0
- package/built-in-skills/prismer-apple-notes/SKILL.md +118 -0
- package/built-in-skills/prismer-apple-notes/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-apple-reminders/LICENSE +21 -0
- package/built-in-skills/prismer-apple-reminders/NOTICE.md +17 -0
- package/built-in-skills/prismer-apple-reminders/SKILL.md +155 -0
- package/built-in-skills/prismer-apple-reminders/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-ascii-video/LICENSE +21 -0
- package/built-in-skills/prismer-ascii-video/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-ascii-video/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-ascii-video/README.md +290 -0
- package/built-in-skills/prismer-ascii-video/SKILL.md +252 -0
- package/built-in-skills/prismer-ascii-video/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-ascii-video/references/architecture.md +807 -0
- package/built-in-skills/prismer-ascii-video/references/composition.md +900 -0
- package/built-in-skills/prismer-ascii-video/references/effects.md +1877 -0
- package/built-in-skills/prismer-ascii-video/references/inputs.md +693 -0
- package/built-in-skills/prismer-ascii-video/references/optimization.md +688 -0
- package/built-in-skills/prismer-ascii-video/references/scenes.md +1018 -0
- package/built-in-skills/prismer-ascii-video/references/shaders.md +1408 -0
- package/built-in-skills/prismer-ascii-video/references/troubleshooting.md +360 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE +21 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE.origin +21 -0
- package/built-in-skills/prismer-baoyu-infographic/PORT_NOTES.md +37 -0
- package/built-in-skills/prismer-baoyu-infographic/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-baoyu-infographic/SKILL.md +240 -0
- package/built-in-skills/prismer-baoyu-infographic/references/analysis-framework.md +182 -0
- package/built-in-skills/prismer-baoyu-infographic/references/base-prompt.md +43 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/bento-grid.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/binary-comparison.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/bridge.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/circular-flow.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/comic-strip.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/dashboard.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/dense-modules.md +72 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/funnel.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/hub-spoke.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/iceberg.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/isometric-map.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/jigsaw.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/linear-progression.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/periodic-table.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/story-mountain.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/tree-branching.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/venn-diagram.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/structured-content-template.md +244 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/aged-academia.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/bold-graphic.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/chalkboard.md +61 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/claymation.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/corporate-memphis.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/craft-handmade.md +44 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/ikea-manual.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/kawaii.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/knolling.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/lego-brick.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/morandi-journal.md +60 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/origami.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/pixel-art.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/pop-laboratory.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/subway-map.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/technical-schematic.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/ui-wireframe.md +29 -0
- package/built-in-skills/prismer-blocked-page-recovery/LICENSE +21 -0
- package/built-in-skills/prismer-blocked-page-recovery/NOTICE.md +17 -0
- package/built-in-skills/prismer-blocked-page-recovery/SKILL.md +158 -0
- package/built-in-skills/prismer-blocked-page-recovery/scripts/recover_page.py +311 -0
- package/built-in-skills/prismer-box/LICENSE +21 -0
- package/built-in-skills/prismer-box/NOTICE.md +11 -0
- package/built-in-skills/prismer-box/SKILL.md +147 -0
- package/built-in-skills/prismer-box/references/bulk-operations.md +43 -0
- package/built-in-skills/prismer-box/references/cli-guide.md +133 -0
- package/built-in-skills/prismer-box/references/content-workflows.md +86 -0
- package/built-in-skills/prismer-box/references/hubs.md +77 -0
- package/built-in-skills/prismer-box/references/oauth-setup.md +92 -0
- package/built-in-skills/prismer-box/references/rest-api.md +37 -0
- package/built-in-skills/prismer-box/references/sdk-development.md +85 -0
- package/built-in-skills/prismer-box/references/search-and-ai.md +147 -0
- package/built-in-skills/prismer-box/references/troubleshooting.md +33 -0
- package/built-in-skills/prismer-box/references/webhooks-and-events.md +43 -0
- package/built-in-skills/prismer-codebase-inspection/LICENSE +21 -0
- package/built-in-skills/prismer-codebase-inspection/NOTICE.md +17 -0
- package/built-in-skills/prismer-codebase-inspection/SKILL.md +141 -0
- package/built-in-skills/prismer-computer-use/LICENSE +21 -0
- package/built-in-skills/prismer-computer-use/NOTICE.md +17 -0
- package/built-in-skills/prismer-computer-use/SKILL.md +372 -0
- package/built-in-skills/prismer-design-md/LICENSE +21 -0
- package/built-in-skills/prismer-design-md/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-design-md/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-design-md/SKILL.md +227 -0
- package/built-in-skills/prismer-design-md/templates/starter.md +99 -0
- package/built-in-skills/prismer-email-inbox-triage/LICENSE +21 -0
- package/built-in-skills/prismer-email-inbox-triage/NOTICE.md +11 -0
- package/built-in-skills/prismer-email-inbox-triage/SKILL.md +116 -0
- package/built-in-skills/prismer-findmy/LICENSE +21 -0
- package/built-in-skills/prismer-findmy/NOTICE.md +17 -0
- package/built-in-skills/prismer-findmy/SKILL.md +150 -0
- package/built-in-skills/prismer-findmy/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-gif-search/LICENSE +21 -0
- package/built-in-skills/prismer-gif-search/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-gif-search/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-gif-search/SKILL.md +97 -0
- package/built-in-skills/prismer-gif-search/scripts/tenor.py +81 -0
- package/built-in-skills/prismer-github/LICENSE +21 -0
- package/built-in-skills/prismer-github/NOTICE.md +17 -0
- package/built-in-skills/prismer-github/SKILL.md +74 -0
- package/built-in-skills/prismer-github/references/auth.md +91 -0
- package/built-in-skills/prismer-github/references/ci-troubleshooting.md +193 -0
- package/built-in-skills/prismer-github/references/code-review.md +465 -0
- package/built-in-skills/prismer-github/references/conventional-commits.md +81 -0
- package/built-in-skills/prismer-github/references/github-api-cheatsheet.md +177 -0
- package/built-in-skills/prismer-github/references/issue-to-pr.md +74 -0
- package/built-in-skills/prismer-github/references/issues.md +336 -0
- package/built-in-skills/prismer-github/references/pr-workflow.md +346 -0
- package/built-in-skills/prismer-github/references/repo-management.md +484 -0
- package/built-in-skills/prismer-github/references/review-output-template.md +84 -0
- package/built-in-skills/prismer-github/scripts/ensure-gh.py +132 -0
- package/built-in-skills/prismer-github/scripts/gh-env.sh +25 -0
- package/built-in-skills/prismer-github/scripts/git-credential-token.py +68 -0
- package/built-in-skills/prismer-github/scripts/github-api.py +71 -0
- package/built-in-skills/prismer-github/scripts/github-context.py +28 -0
- package/built-in-skills/prismer-github/templates/bug-report.md +35 -0
- package/built-in-skills/prismer-github/templates/feature-request.md +31 -0
- package/built-in-skills/prismer-github/templates/pr-body-bugfix.md +35 -0
- package/built-in-skills/prismer-github/templates/pr-body-feature.md +33 -0
- package/built-in-skills/prismer-google-workspace/LICENSE +21 -0
- package/built-in-skills/prismer-google-workspace/NOTICE.md +11 -0
- package/built-in-skills/prismer-google-workspace/SKILL.md +369 -0
- package/built-in-skills/prismer-google-workspace/references/daily-brief.md +65 -0
- package/built-in-skills/prismer-google-workspace/references/gmail-search-syntax.md +63 -0
- package/built-in-skills/prismer-google-workspace/scripts/_hermes_home.py +17 -0
- package/built-in-skills/prismer-google-workspace/scripts/_private_state.py +39 -0
- package/built-in-skills/prismer-google-workspace/scripts/google_api.py +1339 -0
- package/built-in-skills/prismer-google-workspace/scripts/gws_bridge.py +121 -0
- package/built-in-skills/prismer-google-workspace/scripts/setup.py +563 -0
- package/built-in-skills/prismer-google-workspace/tests/test_productivity_regressions.py +214 -0
- package/built-in-skills/prismer-hermes-agent/LICENSE +21 -0
- package/built-in-skills/prismer-hermes-agent/NOTICE.md +17 -0
- package/built-in-skills/prismer-hermes-agent/SKILL.md +247 -0
- package/built-in-skills/prismer-hermes-agent/references/background-systems.md +108 -0
- package/built-in-skills/prismer-hermes-agent/references/cli-reference.md +158 -0
- package/built-in-skills/prismer-hermes-agent/references/configuration.md +96 -0
- package/built-in-skills/prismer-hermes-agent/references/contributor-guide.md +151 -0
- package/built-in-skills/prismer-hermes-agent/references/delegate-task-concurrency-diagnosis.md +47 -0
- package/built-in-skills/prismer-hermes-agent/references/desktop-plugins.md +215 -0
- package/built-in-skills/prismer-hermes-agent/references/native-mcp.md +351 -0
- package/built-in-skills/prismer-hermes-agent/references/petdex.md +82 -0
- package/built-in-skills/prismer-hermes-agent/references/portal-auth-for-third-party-apps.md +135 -0
- package/built-in-skills/prismer-hermes-agent/references/project-context-files.md +55 -0
- package/built-in-skills/prismer-hermes-agent/references/providers-and-models.md +81 -0
- package/built-in-skills/prismer-hermes-agent/references/security-privacy.md +73 -0
- package/built-in-skills/prismer-hermes-agent/references/slash-commands.md +119 -0
- package/built-in-skills/prismer-hermes-agent/references/themes.md +134 -0
- package/built-in-skills/prismer-hermes-agent/references/troubleshooting.md +87 -0
- package/built-in-skills/prismer-hermes-agent/references/tui-widgets.md +144 -0
- package/built-in-skills/prismer-hermes-agent/references/webhooks.md +219 -0
- package/built-in-skills/prismer-hermes-agent/references/windows-quirks.md +64 -0
- package/built-in-skills/prismer-hermes-agent/templates/clock.mjs +51 -0
- package/built-in-skills/prismer-hermes-agent/templates/plugin.js +97 -0
- package/built-in-skills/prismer-hermes-agent/templates/skin.yaml +49 -0
- package/built-in-skills/prismer-himalaya/LICENSE +21 -0
- package/built-in-skills/prismer-himalaya/NOTICE.md +11 -0
- package/built-in-skills/prismer-himalaya/SKILL.md +340 -0
- package/built-in-skills/prismer-himalaya/references/configuration.md +227 -0
- package/built-in-skills/prismer-himalaya/references/message-composition.md +206 -0
- package/built-in-skills/prismer-himalaya/scripts/compose_draft.py +48 -0
- package/built-in-skills/prismer-im-collab/SKILL.md +27 -7
- package/built-in-skills/prismer-imessage/LICENSE +21 -0
- package/built-in-skills/prismer-imessage/NOTICE.md +17 -0
- package/built-in-skills/prismer-imessage/SKILL.md +128 -0
- package/built-in-skills/prismer-imessage/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-manim-video/LICENSE +21 -0
- package/built-in-skills/prismer-manim-video/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-manim-video/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-manim-video/README.md +23 -0
- package/built-in-skills/prismer-manim-video/SKILL.md +279 -0
- package/built-in-skills/prismer-manim-video/references/animation-design-thinking.md +161 -0
- package/built-in-skills/prismer-manim-video/references/animations.md +282 -0
- package/built-in-skills/prismer-manim-video/references/camera-and-3d.md +135 -0
- package/built-in-skills/prismer-manim-video/references/decorations.md +202 -0
- package/built-in-skills/prismer-manim-video/references/equations.md +216 -0
- package/built-in-skills/prismer-manim-video/references/graphs-and-data.md +163 -0
- package/built-in-skills/prismer-manim-video/references/mobjects.md +333 -0
- package/built-in-skills/prismer-manim-video/references/paper-explainer.md +255 -0
- package/built-in-skills/prismer-manim-video/references/production-quality.md +190 -0
- package/built-in-skills/prismer-manim-video/references/rendering.md +187 -0
- package/built-in-skills/prismer-manim-video/references/scene-planning.md +118 -0
- package/built-in-skills/prismer-manim-video/references/troubleshooting.md +135 -0
- package/built-in-skills/prismer-manim-video/references/updaters-and-trackers.md +260 -0
- package/built-in-skills/prismer-manim-video/references/visual-design.md +124 -0
- package/built-in-skills/prismer-manim-video/scripts/setup.sh +15 -0
- package/built-in-skills/prismer-maps/LICENSE +21 -0
- package/built-in-skills/prismer-maps/NOTICE.md +11 -0
- package/built-in-skills/prismer-maps/SKILL.md +240 -0
- package/built-in-skills/prismer-maps/scripts/maps_client.py +1324 -0
- package/built-in-skills/prismer-node-inspect-debugger/LICENSE +21 -0
- package/built-in-skills/prismer-node-inspect-debugger/NOTICE.md +17 -0
- package/built-in-skills/prismer-node-inspect-debugger/SKILL.md +347 -0
- package/built-in-skills/prismer-notion/LICENSE +21 -0
- package/built-in-skills/prismer-notion/NOTICE.md +11 -0
- package/built-in-skills/prismer-notion/SKILL.md +495 -0
- package/built-in-skills/prismer-notion/references/block-types.md +112 -0
- package/built-in-skills/prismer-obsidian/LICENSE +21 -0
- package/built-in-skills/prismer-obsidian/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-obsidian/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-obsidian/SKILL.md +80 -0
- package/built-in-skills/prismer-obsidian/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-obsidian/scripts/write_note.py +84 -0
- package/built-in-skills/prismer-p5js/LICENSE +21 -0
- package/built-in-skills/prismer-p5js/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-p5js/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-p5js/README.md +64 -0
- package/built-in-skills/prismer-p5js/SKILL.md +574 -0
- package/built-in-skills/prismer-p5js/references/animation.md +439 -0
- package/built-in-skills/prismer-p5js/references/color-systems.md +355 -0
- package/built-in-skills/prismer-p5js/references/core-api.md +410 -0
- package/built-in-skills/prismer-p5js/references/export-pipeline.md +534 -0
- package/built-in-skills/prismer-p5js/references/interaction.md +398 -0
- package/built-in-skills/prismer-p5js/references/shapes-and-geometry.md +300 -0
- package/built-in-skills/prismer-p5js/references/troubleshooting.md +532 -0
- package/built-in-skills/prismer-p5js/references/typography.md +302 -0
- package/built-in-skills/prismer-p5js/references/visual-effects.md +905 -0
- package/built-in-skills/prismer-p5js/references/webgl-and-3d.md +423 -0
- package/built-in-skills/prismer-p5js/scripts/export-frames.js +91 -0
- package/built-in-skills/prismer-p5js/scripts/render.sh +113 -0
- package/built-in-skills/prismer-p5js/scripts/serve.sh +25 -0
- package/built-in-skills/prismer-p5js/scripts/setup.sh +87 -0
- package/built-in-skills/prismer-p5js/templates/viewer.html +401 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE +21 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE.origin +21 -0
- package/built-in-skills/prismer-popular-web-designs/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-popular-web-designs/SKILL.md +218 -0
- package/built-in-skills/prismer-popular-web-designs/templates/airbnb.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/airtable.md +102 -0
- package/built-in-skills/prismer-popular-web-designs/templates/apple.md +326 -0
- package/built-in-skills/prismer-popular-web-designs/templates/bmw.md +193 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cal.md +272 -0
- package/built-in-skills/prismer-popular-web-designs/templates/claude.md +325 -0
- package/built-in-skills/prismer-popular-web-designs/templates/clay.md +317 -0
- package/built-in-skills/prismer-popular-web-designs/templates/clickhouse.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cohere.md +279 -0
- package/built-in-skills/prismer-popular-web-designs/templates/coinbase.md +142 -0
- package/built-in-skills/prismer-popular-web-designs/templates/composio.md +320 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cursor.md +322 -0
- package/built-in-skills/prismer-popular-web-designs/templates/elevenlabs.md +278 -0
- package/built-in-skills/prismer-popular-web-designs/templates/expo.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/figma.md +233 -0
- package/built-in-skills/prismer-popular-web-designs/templates/framer.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/hashicorp.md +291 -0
- package/built-in-skills/prismer-popular-web-designs/templates/ibm.md +345 -0
- package/built-in-skills/prismer-popular-web-designs/templates/intercom.md +159 -0
- package/built-in-skills/prismer-popular-web-designs/templates/kraken.md +138 -0
- package/built-in-skills/prismer-popular-web-designs/templates/linear.app.md +380 -0
- package/built-in-skills/prismer-popular-web-designs/templates/lovable.md +311 -0
- package/built-in-skills/prismer-popular-web-designs/templates/minimax.md +270 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mintlify.md +339 -0
- package/built-in-skills/prismer-popular-web-designs/templates/miro.md +121 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mistral.ai.md +274 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mongodb.md +279 -0
- package/built-in-skills/prismer-popular-web-designs/templates/notion.md +322 -0
- package/built-in-skills/prismer-popular-web-designs/templates/nvidia.md +306 -0
- package/built-in-skills/prismer-popular-web-designs/templates/ollama.md +280 -0
- package/built-in-skills/prismer-popular-web-designs/templates/opencode.ai.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/pinterest.md +243 -0
- package/built-in-skills/prismer-popular-web-designs/templates/posthog.md +269 -0
- package/built-in-skills/prismer-popular-web-designs/templates/raycast.md +281 -0
- package/built-in-skills/prismer-popular-web-designs/templates/replicate.md +274 -0
- package/built-in-skills/prismer-popular-web-designs/templates/resend.md +316 -0
- package/built-in-skills/prismer-popular-web-designs/templates/revolut.md +198 -0
- package/built-in-skills/prismer-popular-web-designs/templates/runwayml.md +257 -0
- package/built-in-skills/prismer-popular-web-designs/templates/sanity.md +370 -0
- package/built-in-skills/prismer-popular-web-designs/templates/sentry.md +275 -0
- package/built-in-skills/prismer-popular-web-designs/templates/spacex.md +207 -0
- package/built-in-skills/prismer-popular-web-designs/templates/spotify.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/stripe.md +335 -0
- package/built-in-skills/prismer-popular-web-designs/templates/supabase.md +268 -0
- package/built-in-skills/prismer-popular-web-designs/templates/superhuman.md +265 -0
- package/built-in-skills/prismer-popular-web-designs/templates/together.ai.md +276 -0
- package/built-in-skills/prismer-popular-web-designs/templates/uber.md +308 -0
- package/built-in-skills/prismer-popular-web-designs/templates/vercel.md +323 -0
- package/built-in-skills/prismer-popular-web-designs/templates/voltagent.md +336 -0
- package/built-in-skills/prismer-popular-web-designs/templates/warp.md +266 -0
- package/built-in-skills/prismer-popular-web-designs/templates/webflow.md +105 -0
- package/built-in-skills/prismer-popular-web-designs/templates/wise.md +186 -0
- package/built-in-skills/prismer-popular-web-designs/templates/x.ai.md +270 -0
- package/built-in-skills/prismer-popular-web-designs/templates/zapier.md +341 -0
- package/built-in-skills/prismer-product-price-monitor/LICENSE +21 -0
- package/built-in-skills/prismer-product-price-monitor/NOTICE.md +11 -0
- package/built-in-skills/prismer-product-price-monitor/SKILL.md +97 -0
- package/built-in-skills/prismer-product-price-monitor/scripts/watch_state.py +116 -0
- package/built-in-skills/prismer-python-debugpy/LICENSE +21 -0
- package/built-in-skills/prismer-python-debugpy/NOTICE.md +17 -0
- package/built-in-skills/prismer-python-debugpy/SKILL.md +377 -0
- package/built-in-skills/prismer-python-debugpy/scripts/dap_client.py +68 -0
- package/built-in-skills/prismer-songsee/LICENSE +21 -0
- package/built-in-skills/prismer-songsee/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-songsee/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-songsee/SKILL.md +91 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE +21 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/SKILL.md +317 -0
- package/built-in-skills/prismer-spike/LICENSE +21 -0
- package/built-in-skills/prismer-spike/LICENSE.gsd +21 -0
- package/built-in-skills/prismer-spike/NOTICE.md +17 -0
- package/built-in-skills/prismer-spike/SKILL.md +212 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/LICENSE +21 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/NOTICE.md +11 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/SKILL.md +156 -0
- package/built-in-skills/prismer-xurl/LICENSE +21 -0
- package/built-in-skills/prismer-xurl/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-xurl/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-xurl/SKILL.md +444 -0
- package/built-in-skills/prismer-xurl/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-youtube-content/LICENSE +21 -0
- package/built-in-skills/prismer-youtube-content/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-youtube-content/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-youtube-content/SKILL.md +86 -0
- package/built-in-skills/prismer-youtube-content/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-youtube-content/references/output-formats.md +56 -0
- package/built-in-skills/prismer-youtube-content/scripts/fetch_transcript.py +146 -0
- package/built-in-skills/proactivity/SKILL.md +84 -0
- package/built-in-skills/remotion/SKILL.md +431 -0
- package/built-in-skills/research-arxiv/LICENSE +21 -0
- package/built-in-skills/research-arxiv/NOTICE.md +4 -0
- package/built-in-skills/research-arxiv/SKILL.md +263 -0
- package/built-in-skills/research-arxiv/scripts/search_arxiv.py +111 -0
- package/built-in-skills/role-builder/SKILL.md +209 -0
- package/built-in-skills/role-builder/scripts/author-role.mjs +350 -0
- package/built-in-skills/role-builder/scripts/ingest-role.mjs +121 -0
- package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +304 -0
- package/built-in-skills/role-builder/scripts/operation-harness.mjs +269 -0
- package/built-in-skills/skill-creator/LICENSE.prismer +21 -0
- package/built-in-skills/skill-creator/NOTICE.md +18 -0
- package/built-in-skills/skill-creator/SKILL.md +182 -421
- package/built-in-skills/skill-creator/eval-viewer/generate_review.py +20 -31
- package/built-in-skills/skill-creator/eval-viewer/viewer.html +35 -23
- package/built-in-skills/skill-creator/references/external-library-import.md +110 -0
- package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/GUIDE.md +220 -0
- package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/LICENSE +21 -0
- package/built-in-skills/skill-creator/references/skill-authoring/GUIDE.md +134 -0
- package/built-in-skills/{skill-authoring/skill.json → skill-creator/references/skill-authoring/draft-contract.json} +3 -3
- package/built-in-skills/skill-creator/references/skill-builder/GUIDE.md +167 -0
- package/built-in-skills/skill-creator/references/skill-builder/scripts/ingest.mjs +93 -0
- package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +3 -2
- package/built-in-skills/skill-creator/scripts/import-library.mjs +498 -0
- package/built-in-skills/skill-creator/scripts/improve_description.py +14 -16
- package/built-in-skills/skill-creator/scripts/package_skill.py +16 -9
- package/built-in-skills/skill-creator/scripts/quick_validate.py +22 -5
- package/built-in-skills/skill-creator/scripts/run_eval.py +63 -144
- package/built-in-skills/skill-creator/scripts/run_loop.py +24 -16
- package/built-in-skills/slack-gif-creator/SKILL.md +20 -0
- package/built-in-skills/slack-gif-creator/core/gif_builder.py +13 -2
- package/built-in-skills/slack-gif-creator/core/validators.py +17 -7
- package/built-in-skills/tasks/SKILL.md +39 -24
- package/built-in-skills/tdd/LICENSE +21 -0
- package/built-in-skills/tdd/SKILL.md +117 -0
- package/built-in-skills/tdd/mocking.md +59 -0
- package/built-in-skills/tdd/refactoring.md +10 -0
- package/built-in-skills/tdd/references/test-driven-development/GUIDE.md +356 -0
- package/built-in-skills/tdd/references/test-driven-development/LICENSE +21 -0
- package/built-in-skills/tdd/references/test-driven-development/LICENSE.superpowers +21 -0
- package/built-in-skills/tdd/references/test-driven-development/NOTICE.md +37 -0
- package/built-in-skills/tdd/tests.md +61 -0
- package/built-in-skills/team/SKILL.md +3 -2
- package/built-in-skills/web-artifacts-builder/SKILL.md +4 -3
- package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +9 -7
- package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +28 -55
- package/built-in-skills/webapp-qa/LICENSE +21 -0
- package/built-in-skills/webapp-qa/NOTICE.md +37 -0
- package/built-in-skills/webapp-qa/SKILL.md +41 -0
- package/built-in-skills/webapp-qa/references/dogfood/GUIDE.md +161 -0
- package/built-in-skills/webapp-qa/references/issue-taxonomy.md +109 -0
- package/built-in-skills/webapp-qa/templates/dogfood-report-template.md +86 -0
- package/built-in-skills/webapp-testing/SKILL.md +10 -2
- package/built-in-skills/webapp-testing/examples/console_logging.py +10 -4
- package/built-in-skills/webapp-testing/examples/element_discovery.py +11 -4
- package/built-in-skills/webapp-testing/examples/static_html_automation.py +14 -9
- package/built-in-skills/webapp-testing/scripts/with_server.py +34 -10
- package/built-in-skills/wechat-pay/SKILL.md +59 -0
- package/built-in-skills/weekly-review/LICENSE +21 -0
- package/built-in-skills/weekly-review/NOTICE.md +21 -0
- package/built-in-skills/weekly-review/SKILL.md +49 -0
- package/components/eaas-pi-components.manifest.json +37 -0
- package/dist/cli.cjs +75799 -20210
- package/dist/cli.js +75900 -20248
- package/dist/index.cjs +76159 -20082
- package/dist/index.d.cts +4766 -766
- package/dist/index.d.ts +4766 -766
- package/dist/index.js +76040 -19960
- package/package.json +42 -6
- package/plugins/memory/prismer/__init__.py +1215 -0
- package/plugins/memory/prismer/plugin.yaml +8 -0
- package/plugins/memory/prismer/tool-schemas.generated.json +257 -0
- package/plugins/tools/prismer-recall/__init__.py +282 -0
- package/plugins/tools/prismer-recall/plugin.yaml +15 -0
- package/built-in-skills/memory-curation/SKILL.md +0 -135
- package/built-in-skills/skill-authoring/SKILL.md +0 -124
- package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,3998 @@
|
|
|
1
1
|
# Changelog — @prismer/runtime
|
|
2
2
|
|
|
3
|
+
## 2.2.62 — 2026-09-23
|
|
4
|
+
|
|
5
|
+
- **Session recall + grant-aware recall policy**:daemon 新增
|
|
6
|
+
`recall-policy-provider` / `session-recall`(会话记忆召回并入
|
|
7
|
+
`[Memory Context]` 的 `[Session Recall]` 段,保留 `via:'grant:<id>'` /
|
|
8
|
+
`sourceWorkspaceId` 授权来源标注)+ fallback 集成测试。
|
|
9
|
+
- **Skill availability / loader / sync 收紧**:`skill-availability` 独立模块、
|
|
10
|
+
skill loader/source-resolution/sync 扩展与 sync rejection、slash 平台
|
|
11
|
+
toolsets 测试加固。
|
|
12
|
+
- **Hermes native skill projection**:`native-skill-projection`(Hermes 原生
|
|
13
|
+
skill 装载投影)+ 投影测试。
|
|
14
|
+
- **Turn protocol/runner 扩展** 与 pi components manifest
|
|
15
|
+
(`eaas-pi-components.manifest.json`)。
|
|
16
|
+
- 镜像/载荷对齐:Hermes v2026.9.21(S6 commentary 交付门),
|
|
17
|
+
`infra/sandbox-image` 与 desktop engine payload 同步(本包不进 image
|
|
18
|
+
fingerprint,随 OTA bundle 分发)。
|
|
19
|
+
|
|
20
|
+
## 2.2.13 — 2026-08-17
|
|
21
|
+
|
|
22
|
+
- ACS boot recovery fixes (merged via fix/acs-boot-recovery): manifest Pod
|
|
23
|
+
reachability base, Dockerfile Hermes install retry on China egress,
|
|
24
|
+
canonical millisecond ISO timestamps in the sandbox manager, runner empty
|
|
25
|
+
adoption storm guard, and dev-local stream + reclaim.
|
|
26
|
+
- Runtime version bumped 2.2.12 → 2.2.13 because daemon sources changed
|
|
27
|
+
since the 2.2.12 GA publish (immutable content-addressed releases).
|
|
28
|
+
|
|
3
29
|
## Unreleased
|
|
4
30
|
|
|
31
|
+
- Sandbox manager executes directly when already running as the target non-root
|
|
32
|
+
user, retaining sudo for cross-user launches. Restricted EaaS carriers use
|
|
33
|
+
image-prepared workspace, recipe and manager state directories without privilege escalation.
|
|
34
|
+
|
|
35
|
+
- Add a scoped `asset-ingest` maintenance envelope to the one-shot turn runner:
|
|
36
|
+
exact task/run/source binding, no chat history or tool grants, and bounded,
|
|
37
|
+
digest-checked semantic products. Advertise `maintenance.assetIngest: 1` in
|
|
38
|
+
turn capabilities. Cloud admission/egress and final installed-bundle acceptance
|
|
39
|
+
remain required; a synthetic engine test is not provider execution evidence.
|
|
40
|
+
|
|
41
|
+
- EaaS one-shot turns now seed validated conversation history into the real PI session, preserving principal images and assistant text without replaying historical tool calls. Previously the envelope history was ignored.
|
|
42
|
+
- Add `turn capabilities` for explicit Cloud compatibility checks, and enforce shared history/image count and decoded-byte limits. Old bundles must not silently accept history-aware turns.
|
|
43
|
+
- Local Runtime regression is recorded separately from signed-bundle deployment and real two-turn model acceptance; the latter remain required before release.
|
|
44
|
+
|
|
45
|
+
### EaaS PI core HITL guidance and tool policy
|
|
46
|
+
|
|
47
|
+
- Preserve the Cloud system prompt and add EaaS-only HITL `eaas-question`
|
|
48
|
+
guidance and ACL boundary instructions; prompt guidance is not authorization.
|
|
49
|
+
- Add a PI core execution gate for bound tools: deny takes precedence, an empty
|
|
50
|
+
allow list denies all tools, and an optional trusted-host `authorize` callback
|
|
51
|
+
permits execution only on `true`, failing closed on errors.
|
|
52
|
+
- Wire EaaS turn tool declarations into the runner's allow/deny policy, including
|
|
53
|
+
explicitly disabled tools. Absent declarations retain legacy default tools;
|
|
54
|
+
an explicit empty declaration list allows none.
|
|
55
|
+
- These declarations are a per-turn snapshot, not a dynamic Cloud ACL/HITL
|
|
56
|
+
bridge. Allowing bash does not enforce a cwd jail or make the environment
|
|
57
|
+
read-only when write/edit tools are denied.
|
|
58
|
+
|
|
59
|
+
### Workspace conversation/task overhaul — spec 11 closure
|
|
60
|
+
|
|
61
|
+
- **Runs dispatch durability**:daemon 派发链完成 spec 11 T3/T4 收口——comment
|
|
62
|
+
续跑、Phase A 判型闸、一次性 nonce、run 级 dispatch outbox 重投、10 次重试预算与
|
|
63
|
+
`dispatch-undelivered` 终态可见性,避免 run 已生成但 chat 侧静默丢派发。
|
|
64
|
+
- **Hermes/tool attribution honesty**:缺失 `detail.output` 的工具步骤不再渲染为空白,
|
|
65
|
+
统一显示 `[output not forwarded]`;有真实 output 的步骤保持原样。
|
|
66
|
+
- **Org collaboration soul carrier**:内置身份上下文新增 `org-collaboration` section,
|
|
67
|
+
`team-manager` 升级到 2.2.0、`personal-assistant` 升级到 1.1.0,并保持 catalog /
|
|
68
|
+
runtime role template / source role template 三处 byte-identical。
|
|
69
|
+
- **Task context for OKR execution**:dispatch context 新增 `[Active Key Results]`,
|
|
70
|
+
与 `[Active Goals]` / `[Active Objectives]` 同源注入,用于长线任务的 KR
|
|
71
|
+
baseline/target/current 验收依据。
|
|
72
|
+
- **Grant-aware memory recall**:runtime `memory_search` / `memory_load` 接受
|
|
73
|
+
`sourceWorkspaceId`,session recall 可将授权来源并入 `[Memory Context]` 的
|
|
74
|
+
`[Session Recall]` 段,并保留 `via:'grant:<id>'` / `sourceWorkspaceId` 标注。
|
|
75
|
+
|
|
76
|
+
### Hermes — S5 上下文连续性三件(docs/organization/specs/05 Task 1-3,daemon 侧)
|
|
77
|
+
|
|
78
|
+
- **轮换播种回读(§3.4-1a / Task 1)**:`sessions-dispatcher` 抽出
|
|
79
|
+
`fetchSessionTranscript`(`GET /api/sessions/{id}/messages`,保留
|
|
80
|
+
`X-Hermes-Session-Key` + 5s `AbortSignal.timeout`,任何失败 resolve `[]`),
|
|
81
|
+
由抢救捞回与轮换播种共用。轮换铸新 session 且存在 previousSessionId 时回读老
|
|
82
|
+
transcript 尾部(`RESEED_TAIL_ROWS=8`),并入 FULL seed:行序**原样透传**——
|
|
83
|
+
`order=latest` 是「从最新往回取页、按插入序返回」(v2026.9.14
|
|
84
|
+
`hermes_state_messages.py:779` + `:817-818`),读到的本就是时间序,不得再翻
|
|
85
|
+
(评审 N-1:此前多翻一次,超帽时反而丢最新、留最老,恰是 A1/A2)。单行截断
|
|
86
|
+
`RESEED_ROW_CHAR_CAP=1500`、总量 `RESEED_TOTAL_CHAR_CAP=6000`(超帽自最老行起丢)、
|
|
87
|
+
与 envelope `recent` 去重(normalize 后各取前 120 字符做**相等**比较——评审 I-1:
|
|
88
|
+
双向前缀匹配会让一条短行吞掉同头的长行,白丢真实交换)。
|
|
89
|
+
`context-render` 新增可选 `HermesRenderOptions.transcriptTail`,以
|
|
90
|
+
`sender='transcript_continuity'` / `senderRole='system'` 追加进 `priorMessages`
|
|
91
|
+
(composer 按 createdAt 排序,天然落在压缩段之后、recent 之前,无需手工
|
|
92
|
+
unshift);envelope 路径与 legacy `composeConversationContextXml` 路径均已透传。
|
|
93
|
+
首 turn(无映射)不回读,不额外发请求。
|
|
94
|
+
- **复用路径周期性对账(§3.4-1b / Task 2)**:新增 `cloud-io.ts` 窄 IO seam
|
|
95
|
+
(`setHermesCloudIO`/`getHermesCloudIO`,照 `setHermesSessionMapper` 先例,
|
|
96
|
+
runner.ts 启动接线、shutdown 摘除;未接线时全部降级为静默 no-op)。每个复用
|
|
97
|
+
turn 终态后 fire-and-forget 对账:`%5` turn 且距上次 ≥30min 双护栏限频,取
|
|
98
|
+
本会话最后一条**本 agent 发的** `metadata.kind='agent_reply'`(`senderId ===
|
|
99
|
+
agentImUserId`,评审 I-3:多 agent 会话里锚他 agent 的 turn 会报出本 transcript
|
|
100
|
+
从未有过的「错位」,白付轮换 + 误导通知)的 `taskId` 作 anchor,要求它出现
|
|
101
|
+
在 transcript 的某条 user row 中且其后存在非空 assistant row。错位 → 发一条
|
|
102
|
+
`context_continuity_reconciled` `system_event`(`session:anchor` 键 100 条 LRU
|
|
103
|
+
保证恰好一次)+ invalidate 映射。**并对账的补种链闭合**:invalidate 会 DELETE
|
|
104
|
+
映射行、使下一 turn 与「首 turn」不可区分,故错位时先把被弃 session 停靠进
|
|
105
|
+
`pendingReseedSources`(按 conversation+agent 稳定键),下一 dispatch 一次性消费
|
|
106
|
+
——补种回读与可见性事件因此都不断链。
|
|
107
|
+
- **抢救 anchor 校验(§3.4-2 / Task 3)**:`recoverReplyFromSessionTail` 捞回的
|
|
108
|
+
assistant 行须同时满足三条才采用:归属 anchor(anchorIdx 扫描已保证,显式化)、
|
|
109
|
+
anchorIdx 与捞回行之间无**其他** per-turn anchor 的 user row(防跨 turn 交叉
|
|
110
|
+
落盘)、捞回行 timestamp 不早于 anchor 行(唯一能抓「上一轮被杀 attempt 的答案
|
|
111
|
+
晚落盘在本轮 anchor 行之后」的判别——晚到答案生成于上一轮,timestamp 必然更早)。
|
|
112
|
+
任一不过 → 返回 null 并 `stderr` 记 `recovery rejected:`,交给既有 loud
|
|
113
|
+
`empty_reply` 失败路径(宁空失败,不冒充本轮回复);timestamp 缺失时降级为只查
|
|
114
|
+
前两条并在 stderr 记 `recovery_anchor_ts_missing`(可观测降级,不因缺证据拒绝)。
|
|
115
|
+
- 三条路径全部 fire-and-forget:失败 log/stderr 留痕后吞掉,绝不向主流程抛错、
|
|
116
|
+
不阻断派发(负控已固化)。零 DB 迁移、无 wire 协议变更、无
|
|
117
|
+
daemon↔cloud 版本协商需求(seam 未接线 / 老 cloud + 新 daemon 均安全降级)。
|
|
118
|
+
- 测试:新增 `sessions-dispatcher-transcript-reseed`(轮换播种正例 + 首 turn 不回读
|
|
119
|
+
+ 去重 + caps + 读失败负控 + **envelope 路径正例**——补上生产路径此前无正例的
|
|
120
|
+
覆盖缺口,删掉 :1270 的透传该用例即红、其余全绿)、`sessions-reconciliation`
|
|
121
|
+
(错位恰好一次 + 对齐零调用 + N/间隔门 + seam 未接线 + 云调用失败吞错)。
|
|
122
|
+
`sessions-dispatcher-empty-reply` 追加 recovery anchor 校验组(晚到答案拒绝 /
|
|
123
|
+
本轮晚落盘照常采用 / 异锚插入拒绝 / 无 timestamp 降级留痕)。
|
|
124
|
+
- 既有 immutable-release 规则下版本号 bump 与否由发版时判定。
|
|
125
|
+
|
|
126
|
+
### Hermes — S5 压缩可观测 / 轮换可见性 / skill-sync 错峰(docs/organization/specs/05 Task 5-9,daemon 侧)
|
|
127
|
+
|
|
128
|
+
- **压缩静默失败可观测(§3.4-3b / Task 5)**:`runBackgroundCompaction` 的四处**真
|
|
129
|
+
失败**——candidate GET 非 2xx / `compactSlice` 返回 `result.error` / projection 空
|
|
130
|
+
/ segment POST 非 2xx——除既有 `traceLog` 外,现在还会 bump
|
|
131
|
+
`MemoryStageCounters.compactionFailed`(新增计数位,随 `/healthz`
|
|
132
|
+
`memory.counters` 暴露)。三处**正常门**(`no_conversation_id` / 历史不足
|
|
133
|
+
`COMPACTION_MIN_HISTORY` / `%N` 轮询门)保持 log-only,不计入失败位——门不是失败。
|
|
134
|
+
有 run 归属时另经既有 run/task event 通道 fire-and-forget 上云
|
|
135
|
+
(`type=COMPACTION_FAILED`、`payload.stage`),上报失败只 log 不再造失败。
|
|
136
|
+
**落点(T1-2b R1 修正)**:首版打的是 `POST /api/im/tasks/:id/event`,而
|
|
137
|
+
`ctx.taskId` 实际是**本轮派发的 run id**(registry `canonicalTurnId` = 线上
|
|
138
|
+
run id)——该路由是 task 作用域、拿 id 查 `im_tasks`,对 run id 空间回
|
|
139
|
+
`400 RUN_ID_ON_TASK_ROUTE`,又被 best-effort 的 catch 吞掉 ⇒ durable 上报在产
|
|
140
|
+
是**静默 no-op**,只有本地计数位真的动。现改投 run 作用域
|
|
141
|
+
`POST /api/im/runs/:runId/events`(assignee 经 `checkRunReadAccess`),
|
|
142
|
+
**仅**在该请求回 404 时重试一次 task 作用域路由——id 空间本就是双义的
|
|
143
|
+
(release202/09 §3.2:chat 派发带 run id、kanban task 派发带 task id、legacy run
|
|
144
|
+
保留裸 cuid),404 是它唯一的信号;非 404(403 身份 / 5xx / 不可达)不重试。
|
|
145
|
+
- **轮换可见性 + FULL seed 诚实声明(§3.4-4a / §3.4-7 / Task 6+9)**:凡「铸新
|
|
146
|
+
session 且存在前一个 session」的轮换面(404 `session_not_found` 重入 / S10 空回复
|
|
147
|
+
轮换 / interrupt / 对账补种),fire-and-forget 发一条 `system_event`,文案统一为
|
|
148
|
+
诚实状态声明「上下文窗口已重建:更早的对话内容已按摘要保留,最近的交流已重新
|
|
149
|
+
载入。如有需要请提醒我补充关键背景。」,metadata 统一
|
|
150
|
+
`{kind:'context_rebuilt', reason, previousSessionId, reseededRows}`(
|
|
151
|
+
`reseededRows` 为 Task 1 实际回读注入行数,无回读诚实为 0)。此前轮换完全静默:
|
|
152
|
+
agent 丢了 transcript、cloud 按摘要重建,用户只看到「回复不再引用上文」。
|
|
153
|
+
seam 未接线(`setHermesCloudIO(null)`)→ 静默跳过,不向派发抛错。
|
|
154
|
+
- **skill-sync 杀 gateway 错峰(§3.4-4b / Task 6)**:上游 gateway route 表无
|
|
155
|
+
skill reload/rescan(catalog 只在 spawn 时扫),真热重载属上游改动。新增
|
|
156
|
+
`HermesService.busy`(`currentRunId !== undefined`,run 终态 `finally` 必清)与
|
|
157
|
+
`respawnHermesGatewayAfterSkillSync`:空闲照旧 atomic invalidate + 杀 gateway;
|
|
158
|
+
**有在飞 run 则推迟**——`markSkillDirty` 重新置脏位、交由下次 dispatch
|
|
159
|
+
(`consumeSkillDirty`)消费,本轮继续用现 gateway(新 skill 本轮不可见,优于杀在飞
|
|
160
|
+
run 并让该 profile 全部 session 集体失联)。无 `markSkillDirty` seam 时不静默丢
|
|
161
|
+
改动:stderr 告警后照旧杀(退回 S5 前行为)。`DispatchDeps` 新增
|
|
162
|
+
`markSkillDirty` / `peekServiceBusy`,runner 两处 dep bag 均已接线。
|
|
163
|
+
|
|
164
|
+
### Hermes — 空回复轮换阈值 1→2 + 首次空回复可见(spec 11 / T1-3,daemon 侧)
|
|
165
|
+
|
|
166
|
+
- `session-health`:`EMPTY_ROTATE_THRESHOLD` 默认 **1 → 2**——首次 `empty_reply`
|
|
167
|
+
不再轮换,连续第二次才在下一 turn 铸新 hermes session。2.2.9 的「一次即轮换」
|
|
168
|
+
建立在「hermes 只在可恢复但为空时才 EMPTY」这一假设上,生产事实相反:首次
|
|
169
|
+
EMPTY 常是上游瞬断(provider 流中断、网关冷启动),照旧语义立即轮换会丢掉一个
|
|
170
|
+
下一轮本可复用的健康 transcript——用户付一次完整 reseed(envelope FULL seed +
|
|
171
|
+
transcript 回读),agent 也丢掉它已有的上下文。
|
|
172
|
+
- env 覆盖 `HERMES_SESSION_EMPTY_ROTATE_THRESHOLD` 保留为运维逃生口(`1` = 2.2.9
|
|
173
|
+
语义,事故可回滚);`Math.max(1, …)` 下界保留(轮换只发生在 turn 起点,0/负数
|
|
174
|
+
无意义且会静默废掉守卫);非数字覆盖回退默认值而非 NaN(NaN 会让
|
|
175
|
+
`streak >= NaN` 恒 false,等于静默永久关掉轮换)。新增 `emptyRotateThreshold()`
|
|
176
|
+
导出,供调度器读取实际生效值,避免与 `shouldRotate` 的判据漂移。
|
|
177
|
+
- `sessions-dispatcher`:首次(未达阈值)空回复发一条 `empty_reply_observed`
|
|
178
|
+
`system_event`,其 `reason` 复用轮换事件(`context_rebuilt`)的
|
|
179
|
+
`empty_reply_streak` 枚举值,并附带 streak / 生效阈值 / sessionId——两次失败因此
|
|
180
|
+
读成一条故事:「仍在阈值下」→「达阈,窗口已重建」。文案与阈值解耦(不承诺
|
|
181
|
+
「下一轮即重建」:运维把阈值调到 ≥3 时那是假话)。云侧 IO 未接线或不可达时静默
|
|
182
|
+
跳过,不计为派发失败。
|
|
183
|
+
- 测试:新增 `sessions-dispatcher-empty-reply-threshold`(oracle 两次空回复 +
|
|
184
|
+
两条负控 + 边界);`session-health-persistence` 补阈值判据组(env=3 两次仍不轮换
|
|
185
|
+
= 真阈值而非「延迟一拍」的假实现、env=1 恢复 2.2.9 语义、非数字覆盖回退 2、
|
|
186
|
+
0 夹到 1);`sessions-dispatcher-single-flight-rotation` 按新裁决更新。
|
|
187
|
+
- 既有 immutable-release 规则下版本号 bump 与否由发版时判定。
|
|
188
|
+
|
|
189
|
+
### infra — Hermes pin v2026.8.3 → v2026.9.14(S1,docs/organization/specs/01)
|
|
190
|
+
|
|
191
|
+
- **infra 锁**:`engine-lock.json` hermes 块 ref/installerSha256/uv.lock sha256、
|
|
192
|
+
`build-inputs.lock.yaml` hermes ref/installerUrl/installerSha256 + verifiedAt。
|
|
193
|
+
- **镜像构建探针**:`Dockerfile.base` ARG 默认值随锁同步;探针串换为新 pin 的
|
|
194
|
+
`if not route and model:` 并追加语义锚 `route_source = "raw_request"`(防探针
|
|
195
|
+
弱化成空洞匹配);安装调用加 `--skip-computer-use`(新 install.sh 会 best-effort
|
|
196
|
+
从 unpinned `trycua/cua@main` 拉 cua-driver,不入 pinned-inputs 镜像)。
|
|
197
|
+
- **桌面 DMG payload**:hermes requirements 闭包按 v2026.9.14 `uv.lock` 重导出
|
|
198
|
+
(arm64:71 pins,cryptography 48.0.1→50.0.0,+firecrawl-anydoc/pillow-heif/
|
|
199
|
+
snowballstemmer,nemo-relay 0.6.0→0.8.3);`engine-payload.manifest.json` 的
|
|
200
|
+
`engineLockSha256` / `hermesRequirements.*.sha256` / `hermes-source` 重算。
|
|
201
|
+
⚠️ **x64 (Intel) 未能重导出**:tag 把 cryptography 钉在 50.0.0,上游自 49.0.0
|
|
202
|
+
起删除全部 Intel-macOS wheel,wheel-only 链路下无法成闭包;文件头已记录阻塞,
|
|
203
|
+
x64 当前不是 DMG 目标(`electron-builder.yml` arm64-only),需 owner 裁决。
|
|
204
|
+
- **测试**:新增 `test/sessions-sse-upgrade-compat.test.ts`(6 例)——keepalive 注释
|
|
205
|
+
帧不干扰正文、注释帧不喂 stall watchdog(负控)、未知/前瞻事件不进 output
|
|
206
|
+
(含 delta 兜底反例)、终态标志与 transcript 载荷无副作用、无 run.started 的
|
|
207
|
+
error 事件仍保留 run_id。
|
|
208
|
+
- **daemon 源码变更为注释级**(`sessions-dispatcher.ts` 两处 stale 说明更正:
|
|
209
|
+
sessions run 在 v2026.9.14 已注册 `_active_run_agents` + owner stamp,stall-abort
|
|
210
|
+
的 `/v1/runs/{id}/stop` 自新 pin 起真实生效)。既有 immutable-release 规则下
|
|
211
|
+
版本号 bump 与否由发版时判定。
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
### K 空腔闭合 — `prismer chat direct` 202 ACTION_DEFERRED 分支
|
|
216
|
+
|
|
217
|
+
- `chat direct` 收到 202 deferred(跨台联系人审批)时输出
|
|
218
|
+
「等待联系人审批(approvalId: …)——审批通过后重新发送」,不再打印
|
|
219
|
+
"sent"(此前消息 hold 在审批侧却被报成已发送;h-contact-system-refactor
|
|
220
|
+
§9-SDK)。
|
|
221
|
+
|
|
222
|
+
### Hermes — session-health 默认路径生效(spec 11 / T1-3b,daemon 侧)
|
|
223
|
+
|
|
224
|
+
- `session-health`:`HERMES_SESSION_HEALTH_FILE` 未设时 health 文件**默认落盘**于
|
|
225
|
+
`~/.prismer/hermes-session-health.json`。2.2.9 的
|
|
226
|
+
`process.env.HERMES_SESSION_HEALTH_FILE ?? null` 让紧随其后的 `=== undefined`
|
|
227
|
+
默认分支永不成立(`??` 已把 `undefined` 从类型里摘掉,且严格模式下也不报错):
|
|
228
|
+
env 未设时 `healthFile` 恒为 null,`persist()` / `load()` 双双 early return——
|
|
229
|
+
「重启不丢轮换信号」只在运维显式给 env 时才成立。阈值 1→2 之后后果更实:
|
|
230
|
+
streak 只活在进程内存,重启落在两次 EMPTY 之间即归零,污染会话可永久不轮换
|
|
231
|
+
(每轮只留一条可见行,无自愈)。语义收敛为 `undefined`(未设)→ 默认文件、
|
|
232
|
+
`''` → 关闭持久化、其余 → 显式路径。
|
|
233
|
+
- vitest `test/setup`:导入前把 env 钉到 per-file 临时路径。默认路径生效后,任何
|
|
234
|
+
import hermes dispatcher 的测试文件(实测 10 个里 7 个会写)都会把 fixture 写进
|
|
235
|
+
开发者真实的 `~/.prismer/hermes-session-health.json`——那正是 09 管理员消费的
|
|
236
|
+
观察资产;显式导出的 env(含空串 = 关闭)原样尊重。
|
|
237
|
+
- 测试:新增「无 env → 两次 EMPTY → `$HOME/.prismer/…` 出现且 streak=2 → 重载
|
|
238
|
+
(新 module 实例)后 `shouldRotate` 仍 true」(负控:显式 env 优先且默认位置不被
|
|
239
|
+
创建);既有显式路径用例逐字节不变。
|
|
240
|
+
- daemon 源码变更;版本号 bump 与否由发版时(immutable-release 规则)判定。
|
|
241
|
+
|
|
242
|
+
### Hermes — `prismer` 平台桥接客户端(daemon 侧传输,spec 11 / T4)
|
|
243
|
+
|
|
244
|
+
新增 `src/adapters/persistence/hermes/bridge-client.ts`:hermes gateway 的
|
|
245
|
+
`prismer` 自定义平台插件(`~/.hermes/profiles/<p>/plugins/prismer/adapter.py`)在
|
|
246
|
+
127.0.0.1 上以换行分隔 JSON 提供一个本地 TCP 桥;本模块是 daemon 侧的客户端传输层。
|
|
247
|
+
|
|
248
|
+
- 传输语义:`BridgeClient` 连 `127.0.0.1:<port>`,断线按 `reconnectDelayMs` 重连
|
|
249
|
+
(`close()` 是唯一的停止方式),`waitForConnected()` 超时抛显式错误;
|
|
250
|
+
`buildInboundFrame()` / `parseOutboundFrame()` 逐字段对齐插件读写契约;
|
|
251
|
+
`probeBridge()` 做 TCP 可达性探测(协议无 ping 帧,可达性即健康信号)。
|
|
252
|
+
- 失败可见性:桥进程不存在时 `sendInbound()` 返 `{ok:false,error}`、
|
|
253
|
+
`probeBridge()` 返 `{reachable:false,code:'ECONNREFUSED'|'ETIMEDOUT'}`、
|
|
254
|
+
超出上限的悬挂行与畸形出站行走 `onMalformed`——无静默丢弃、无 unhandled rejection。
|
|
255
|
+
- 如实透传的 gateway 侧事实(不在本层修补):突发连发时出站 `reply_to` 可能错锚;
|
|
256
|
+
无连接时 gateway 的 `send()` 返 `SendResult(success=false, error="no daemon bridge
|
|
257
|
+
connected")` 给 agent 而非上线(daemon 侧完全观测不到,连接态必须取自 `isConnected`);
|
|
258
|
+
首回合配对门回复按普通 `content` 透传;插件对 `t != "in"` 或空 `text` 的入站帧
|
|
259
|
+
静默丢弃——因此客户端在本地就拒绝空文本,不留「写出去但被吞掉」的窗口。
|
|
260
|
+
- 落地方式:模块为**未接线**的传输原语(谁在 dispatch 里用它不在本波范围);
|
|
261
|
+
为使其进入发布产物,`src/index.ts` 追加了对应 re-export(与 `WsClient` 同惯例)。
|
|
262
|
+
CHANGELOG 之外的版本文件零改动。
|
|
263
|
+
|
|
264
|
+
### EaaS — bash 解禁(会话级)+ turn 协议 v2 工具事件(events.jsonl / output.tools[])
|
|
265
|
+
|
|
266
|
+
设计:`docs/superpowers/specs/2026-09-21-eaas-bash-tool-observability-design.md`(§3.1 修1、
|
|
267
|
+
§3.2、§5 修5/修6)。runtime 侧半边;cloud 侧轮询器与事件类型在 PrismerCloud 仓。
|
|
268
|
+
|
|
269
|
+
- **pi-core 会话级 shell 开关 `allowShell`(缺省 false)** —— `PiAgentCoreClientOptions` /
|
|
270
|
+
session options 新增该字段(默认关闭)。true 时:① 工具面在 read/write/edit 之外追加
|
|
271
|
+
`bash`(`createBashTool()`);② `CwdJailedExecutionEnv.exec()` 由「恒返 `spawn_error`」
|
|
272
|
+
改为委托内层 env(异常仍映射回 `ExecutionError` 语义);③ `createTempDir` / `createTempFile`
|
|
273
|
+
解除恒拒,但在 `<cwd>/.eaas/tmp/` 内创建(mkdir -p,保留上游 prefix/suffix,仍走 jail
|
|
274
|
+
检查)——bash 长输出截断依赖 temp 文件,落点必须留在 jail 内。**缺省 false 是刻意的
|
|
275
|
+
爆炸半径控制**:daemon(dispatch / runner / skill-sync)与 engine registry
|
|
276
|
+
(`createPiCoreAdapter`)路径的工具面与拒绝面**逐字不变**(缺省时仍是「Tool bash not
|
|
277
|
+
found」+ exec 原文拒绝),桌面/本地 hosted agent 零影响。
|
|
278
|
+
- **工具事件(只记录不拦截)** —— `Agent` 构造接 `beforeToolCall` / `afterToolCall`:前者发
|
|
279
|
+
`tool_started`(name + argsSummary),后者发 `tool_finished`(resultSummary + isError +
|
|
280
|
+
durationMs)。两个 hook 恒返回 `undefined`(策略位留空——deny/HITL 后续可叠加,协议零
|
|
281
|
+
改动);sink 抛错被吞掉(否则会被上游换成错误工具结果 = 事件面污染 turn)。摘要清洗在
|
|
282
|
+
产生侧做一次(剥字面量出口 token + `Bearer …` / `sk-…` 形态),写侧再洗一次(防御纵深)。
|
|
283
|
+
- **turn 协议 v2**(`src/turn/protocol.ts`)—— `TURN_PROTOCOL_VERSION = 2`(唯一合法值,
|
|
284
|
+
不做兼容推断)。新增 `events.jsonl` 行 schema(`ToolEventV1`:`seq/at/kind/name/argsSummary?/
|
|
285
|
+
resultSummary?/isError?/durationMs?`)、常量 `TURN_EVENTS_FILENAME` /
|
|
286
|
+
`TURN_EVENTS_MAX_COUNT = 200` / `TURN_EVENTS_MAX_BYTES = 512KiB` /
|
|
287
|
+
`TOOL_SUMMARY_MAX_CHARS = 500`、`parseToolEventLine`、以及 `TurnResultV2.tools[]` 终态汇总
|
|
288
|
+
(`TurnResultV1` → `TurnResultV2`)。v1 `output.json` 被解析器**明确拒绝**(带 skew 说明),
|
|
289
|
+
v1 envelope / egress 同样拒绝——旧 bundle 收 v2 是可见失败(exit 70),不静默降级。
|
|
290
|
+
- **写侧(`src/turn/runner.ts`)** —— `createToolEventWriter`:**seq / at 由写侧赋值**
|
|
291
|
+
(1-based 单调,行序 ≡ seq 序,轮询器据此去重),一行一条追加、首行落盘即 0600,达上限
|
|
292
|
+
**停写**;终态 `tools[]` 在内存无条件折叠(修6:超限只影响流式粒度,不影响终态),孤儿
|
|
293
|
+
`tool_started`(取消/超时,修5)如实呈现(有 `startedAt` 无 `finishedAt`,不补写)。文件
|
|
294
|
+
懒创建(本轮无工具调用 ⇒ 不落 events.jsonl)。`runTurnFile` 把终态写进 `output.json.tools[]`;
|
|
295
|
+
EaaS turn 恒以 `allowShell: true` 建会话(`turnSessionClientOptions`)。
|
|
296
|
+
- 测试:`test/turn-protocol.test.ts`(v2 accept/reject、events 行 schema、摘要截断/剥凭据)、
|
|
297
|
+
`test/turn-events.test.ts`(seq 单调、上限、终态完整性、写失败不改 turn、turn 目录落点)、
|
|
298
|
+
`test/pi-core-shell-gate.test.ts`(缺省工具面恰好 read/write/edit、exec/temp 拒绝原文、
|
|
299
|
+
allowShell 下 bash 真跑与 jail 内 temp、hook 只记录不拦截)。三处均含故障注入负控(缺省
|
|
300
|
+
翻 true / seq 冻结 → 用例变红)。
|
|
301
|
+
- **无版本号 bump**:本波是 runtime 面新能力(需随下一次 bundle 重签重发才能进 pod),
|
|
302
|
+
版本对齐按发版轮统一做。
|
|
303
|
+
### S6/M1 Task 6 — 兼容矩阵固化 + 回滚开关(本包零代码改动)
|
|
304
|
+
|
|
305
|
+
- **本包无源码/测试改动**:Task 6 只做证明 + 开关固化 + 文档同步;唯一的测试补齿
|
|
306
|
+
落在云侧 `src/im/tests/acp-serial-merge-trigger.test.ts`(M0 合并 vanish-in-tx
|
|
307
|
+
用例补排队标记断言),不属本包。
|
|
308
|
+
- **四配对兼容矩阵逐条固化**(全部实跑绿;锚点表见 `docs/organization/specs/
|
|
309
|
+
06-milestone-narration-m1.md` Task 6):新 daemon × 旧 cloud(能力位缺席 → 单帧
|
|
310
|
+
I3)、旧 daemon × 新 cloud(帧无 `final` 键 → 走终结分支)、新 daemon × 新 cloud
|
|
311
|
+
且 relay 关(`HERMES_COMMENTARY_RELAY` off → 单帧)、上游门关(零 commentary →
|
|
312
|
+
零回调 + output 逐字节不变)。部署顺序任意——承重机制是**请求能力位**(旧 cloud
|
|
313
|
+
的请求永远不带 `interimReply`),不是帧字段的可选性;帧上 `seq/final` 对旧 cloud
|
|
314
|
+
是惰性 JSON 多余字段。
|
|
315
|
+
- **回滚开关**:`HERMES_COMMENTARY_RELAY=false|0|off`(本包
|
|
316
|
+
`src/adapters/persistence/hermes/flag.ts`,每 turn 动态读)→ 下一个 turn 回单帧;
|
|
317
|
+
对端 cloud 的 `FF_INTERIM_REPLY`(`src/im/ws/v19x-helpers.ts`,每次装配读)→
|
|
318
|
+
下一次派发回单帧。两者相互独立,任一关即回改动前字节行为;**无 DB 回滚需求**
|
|
319
|
+
(零迁移)。
|
|
320
|
+
- **无版本号 bump**:本轮无源码变更,版本对齐仍随 S6 发版轮统一做。
|
|
321
|
+
|
|
322
|
+
### S6/M1 Task 2 — daemon wire:`task.dispatch.reply` 多帧发送
|
|
323
|
+
|
|
324
|
+
- `src/types/im-events.ts` + 云侧镜像 `src/im/types/im-events.ts`(两份同步):
|
|
325
|
+
`TaskDispatchRequestPayload` 新增可选 `interimReply`(capability bit——只有
|
|
326
|
+
懂多帧的 cloud 才会带;legacy cloud 永远不带 = 它永远只收终结帧,这条不变式
|
|
327
|
+
由该字段承载而非由帧承载);`TaskDispatchReplyPayload` 新增可选
|
|
328
|
+
`seq`(turn 内 1-based 帧位)/ `final`(false = interim 旁白帧;true/缺省 =
|
|
329
|
+
终结帧)。
|
|
330
|
+
- `src/daemon/dispatch.ts`:双门制——`interimReply === true` **且**
|
|
331
|
+
`isHermesCommentaryRelayEnabled()`(env `HERMES_COMMENTARY_RELAY` 二道闸)
|
|
332
|
+
才把 Task 1 的 `TaskInput.onInterimReply` 接到发帧:每段旁白出 I1 帧
|
|
333
|
+
`{ ok: true, output: <段文本>, seq, final: false }`;终结帧在**所有** reply
|
|
334
|
+
出口(adapter_unhealthy / workdir_materialize_failed / 主出口)统一经
|
|
335
|
+
`markTerminalReply` 补标 I2 `{ seq: N+1, final: true }`(失败终结帧同样补
|
|
336
|
+
标——interim 帧永不失败是 I6,终结帧仍是唯一完成标记)。能力位缺省 / env
|
|
337
|
+
关 / 零旁白 turn:单帧且 wire 上**无 seq/final 键**(I3,与现状字节一致)。
|
|
338
|
+
runs-dispatcher / `tool.*` 降级路径 / shell 面一律未动。
|
|
339
|
+
- **deltas 回退残余窗口裁决**(Task 1 交接旗标):「上游 commentary 不进
|
|
340
|
+
delta 流」在 daemon 侧**不可保证**——已作为契约注释落在
|
|
341
|
+
`interimRelayArmed` 声明处;退化 turn(终结 output 与已发 interim 段拼接
|
|
342
|
+
完全一致 = 上游旁白漏进 output 路径)的 daemon 侧处置 = 主出口 grep 可见
|
|
343
|
+
的 `interim relay degenerate turn` stderr 标记 + 契约注释,帧计划不偏离
|
|
344
|
+
I1-I6(interim 帧逐段 fire-and-forget 已出站、不可撤回;cloud handler 只认
|
|
345
|
+
`final === false` / `seq`,不发明新 wire 语义;渲染侧去重归 cloud 层)。
|
|
346
|
+
- 新增导出纯函数 `buildInterimFrame(taskId, segment)` /
|
|
347
|
+
`markTerminalFrame(reply, lastInterimSeq)`。
|
|
348
|
+
- 新测试 `test/dispatch-interim-reply-frames.test.ts`(7 用例,mock WsClient
|
|
349
|
+
捕帧、不经真实 socket):多帧序 + 终结补标 + requestId 逐帧回显、能力位缺省
|
|
350
|
+
负控①(回调不挂载,legacy 恰 1 帧)、env 关负控②、零旁白负控③(I3 字节
|
|
351
|
+
兼容:`'seq' in frame === false`)、失败终结帧补标、退化签名观测、纯函数
|
|
352
|
+
shape。兄弟 dispatch 回归 8 文件 99 用例全绿,`tsc --noEmit` 干净。
|
|
353
|
+
- **fix round 1(评审 Important)**:I4 在瞬时重试下被打破——adapter 上报的
|
|
354
|
+
`seq` 是每条 SSE 流局部(Task 1 `commentarySeq` 每次 `consumeSessionsSse`
|
|
355
|
+
1-based 重置),而重试循环复用同一 `onInterimReply` 闭包,attempt 2 的旁白
|
|
356
|
+
会把 wire 打成 `[1, …, 1, terminal N]`(seq 重复 + 终结帧非最大)。修法 =
|
|
357
|
+
**daemon 侧单派 seq(`++interimSeq`),不信任 adapter 上报值**;新增重试
|
|
358
|
+
负控测试④(attempt1 旁白后瞬时失败 → attempt2 新流旁白 seq 重置,断言
|
|
359
|
+
daemon 帧序 `[1,2,3]` 严格递增、终结帧最大;红→绿,修前实测 wire
|
|
360
|
+
`[1,1,2]`)。评审 Minor #2 一并收:退化标记改走 `traceLog`(带
|
|
361
|
+
`[trace=…]` 前缀,与 dispatch 全程 trace 一致)。
|
|
362
|
+
- **无版本号 bump**:字段全部 optional-additive,单帧路径字节不变;版本对齐
|
|
363
|
+
随 S6 发版轮统一做。
|
|
364
|
+
|
|
365
|
+
### S6/M1 Task 1 — Hermes `assistant.commentary` 多段旁白回流(adapter SSE 消费段)
|
|
366
|
+
|
|
367
|
+
- `src/adapters/contract.ts`:`TaskInput` 新增可选 `onInterimReply(segment: { text, seq })`
|
|
368
|
+
—— turn 内每段非空旁白逐段回调(per-turn 1-based `seq`,流序保序);回调是
|
|
369
|
+
fire-and-forget,consumer 抛异常不断 SSE 循环;最终 `TaskResult.output` 语义不变
|
|
370
|
+
(仍= 最后一条 `assistant.completed`)。Task 2 的 daemon 消费面按此契约接。
|
|
371
|
+
- `src/adapters/persistence/hermes/sessions-sse.ts`:新增 `assistant.commentary` case
|
|
372
|
+
(hermes v2026.9.14)。实测上游契约(2026-09-21 本地 source 真跑):字段名是 `text`
|
|
373
|
+
(附 `message_id` / `already_streamed` / `session_id` / `run_id` / `seq` / `ts`),
|
|
374
|
+
brief 期 fixture 与旧 build 的 `content` 兜底——两者都收,`text` 优先(同文件
|
|
375
|
+
`reasoning.available` 多字段容忍先例)。旁白与 delta 累加、`finalContent` 保持
|
|
376
|
+
**完全不相交**:`already_streamed: true`(该段已作为 assistant.delta 流出)的段
|
|
377
|
+
只走回调,绝不并入最终 output——这就是去重语义。无「值得发否」二次过滤(owner
|
|
378
|
+
裁决:非空全量上抛,收窄后置)。`SessionsSseResult` 新增必填
|
|
379
|
+
`commentarySegments: string[]`(测试/诊断用;无旁白 turn 为空数组;approval
|
|
380
|
+
拆流早退路径同样携带)。`tool.*` / `message.started` 降级路径、
|
|
381
|
+
`!isAgentReply` 守卫、runs-dispatcher / message.interim 一律未动。
|
|
382
|
+
- `src/adapters/persistence/hermes/flag.ts`:新增 kill-switch
|
|
383
|
+
`HERMES_COMMENTARY_RELAY`(默认开,显式 `false|0|off` 才关)——与
|
|
384
|
+
`HERMES_TASK_RUNS_DISPATCH` 反极性是有意的:回流是新默认行为,env 仅作运维逃生门。
|
|
385
|
+
- 新测试 `test/sessions-sse-commentary.test.ts`(6 用例):多段顺序 + 1-based seq、
|
|
386
|
+
keepalive 注释帧/空白旁白零回调(负控①)、回调抛异常不断流(负控②)、实测上游
|
|
387
|
+
shape(text + already_streamed 不并入 output)、kill-switch、无旁白流回归
|
|
388
|
+
(负控③,commentarySegments 为空 + output 逐字节不变)。兄弟 SSE 回归
|
|
389
|
+
(upstream-error 9 / stall-watchdog 10 / first-event 3)与 dispatch 面 7 文件
|
|
390
|
+
109 用例全绿,`tsc --noEmit` 干净。
|
|
391
|
+
- **无版本号 bump**:`onInterimReply` 为可选新增、daemon 消费面属 Task 2,版本
|
|
392
|
+
对齐随 S6 发版轮统一做。
|
|
393
|
+
|
|
394
|
+
### memory211/10 R4 — W-1 digest「Verified this turn」段(harness receipt 派生)
|
|
395
|
+
|
|
396
|
+
- `daemon/memory/digest.ts`:`buildMemoryDigest` 新增可选 `verified.lines` 入参——
|
|
397
|
+
在 hub spine 之后渲染 `# Verified this turn` 段,内容**逐行 verbatim 来自
|
|
398
|
+
harness 持有的 receipt**(task 状态机当前态 / 上一 turn terminal receipt 摘要 /
|
|
399
|
+
已完成产物指针);builder 是纯渲染器,自己不派生任何事实。稳定性契约原样适用:
|
|
400
|
+
同 receipts ⇒ 字节一致、version(body 的 sha256 前 16)随之变化使缓存行失效;
|
|
401
|
+
**无 receipts ⇒ 整段不存在**(与 W-1 之前字节一致,不留空段)。段在极端守卫
|
|
402
|
+
(32K tokens)之内。新测试 `test/memory-digest-verified.test.ts`(5 用例)。
|
|
403
|
+
- **调用侧接线(digest provider 处传 receipts)与演化 origin 的 daemon 产线贯通**
|
|
404
|
+
属 runtime 发版轮工作(pack+promote 需 owner 授权);本波先把 builder 面与
|
|
405
|
+
契约测试落库。
|
|
406
|
+
- `daemon/memory/envelope.ts` 的 `MemoryPageUpsertEvent` 云侧解析面新增可选
|
|
407
|
+
`evolutionOrigin`(additive,不 bump schemaVersion,同 encrypted 先例)——
|
|
408
|
+
携带该字段的 upsert 在云侧改道为 proposal 候选(见 PrismerCloud 仓
|
|
409
|
+
memory-write.service.ts)。
|
|
410
|
+
|
|
411
|
+
### memory211/10 R1 — §2.1 演化产物 metadata 闸(daemon 侧镜像)
|
|
412
|
+
|
|
413
|
+
- `daemon/memory/write-gate.ts`:新增 `checkEvolutionMetadataGate(content)` +
|
|
414
|
+
导出值域常量 `MEMORY_ROLES`(07 §2.1 的三分角色)。校验页体 frontmatter
|
|
415
|
+
`extra.memory` 受控块(memoryRole / source 必填,trigger / sections 可选,
|
|
416
|
+
`sections[]` 按 anchor 索引 —— D-1 (a)),未知键拒绝。
|
|
417
|
+
- **本波不接线**(有意的,非遗漏):daemon 的演化面是 **proposal**(
|
|
418
|
+
`memory.proposal` outbox 车道),不是页;daemon 真正写页的那条腿是自动抽取
|
|
419
|
+
管线,而 write-gate.ts 的文件头已写明新增 422 绝不可落在那条腿(后台腿无法
|
|
420
|
+
修复 ⇒ 静默丢记忆 = D4 失效模式)。接线 `memory.proposal` 车道属 R2
|
|
421
|
+
「proposals 扩展」,待候选真的带上块之后。
|
|
422
|
+
- 与云侧 `src/im/services/memory-activation-probe.ts` 是**镜像**(两个包各有自己
|
|
423
|
+
的 tsconfig 与 staged pkf 拷贝,无法共享代码,先例 `src/im/services/
|
|
424
|
+
magic-bytes.ts`)。同源性由 `scripts/__tests__/
|
|
425
|
+
memory211-evolution-metadata-parity.test.ts` 强制:值域相等 + 逐 fixture
|
|
426
|
+
判定/字段级一致,单边改动即红。
|
|
427
|
+
- `daemon/dispatch.ts`:`MEMORY_CORE_DIRECTIVE` 增一行「演化产物过门 + verdict 回执」
|
|
428
|
+
(07 §5 维5 的义务)。理由:§2.1 闸现在会**拒**缺块的演化产物,若 brief 不提这
|
|
429
|
+
件事,被派发的腿会把 422 读成死路并把产物丢掉——正是本车道要杀的 no-fallback
|
|
430
|
+
失效模式。该行 210 字符,指令此前已在 1292/1300 顶格,故预算 1300 → **1600**
|
|
431
|
+
重钉(同 memory211/01 W5 轴H 的 1200→1300 先例:mandated content 才重钉,
|
|
432
|
+
非编辑性膨胀;理由写在 pkf-skill-delivery.test.ts 用例注释里)。
|
|
433
|
+
- 教学:`sdk/cloud/catalog/skills/memory/SKILL.md` 增第 5 条 PKF body invariant
|
|
434
|
+
(`extra.memory` 块、三分 memoryRole 的路由含义、source 的人类可陈述性、
|
|
435
|
+
`sections[]` 按 anchor、手写页豁免、被拒后按 verdict 指名字段修复重提)。
|
|
436
|
+
catalog 是单一来源,runtime 镜像由 `npm run prebuild` 生成(已重跑并核对
|
|
437
|
+
byte-identical,484 行 ≤ 520 预算)。
|
|
438
|
+
- **无版本号 bump**:本波不加行为到已发布的 daemon 面(纯新增导出、零调用点,
|
|
439
|
+
指令侧只是把既有闸的存在告知生产者),版本对齐随 memory211 发版轮统一做。
|
|
440
|
+
|
|
441
|
+
### memory211/08 A4-① — extract.done 观测上行(post-turn 抽取健康进云端活动面板)
|
|
442
|
+
|
|
443
|
+
- `daemon/memory/envelope.ts`:union 新增 `ExtractDoneEvent`(`eventType:
|
|
444
|
+
'extract.done'`,TOP-LEVEL ObservabilityCommon 字段;`eventFamily` 归
|
|
445
|
+
observability)。**Additive,不 bump schemaVersion**(同 traceId /
|
|
446
|
+
recall_fork 先例:老 daemon 永不发该字面量,旧 envelope 解析不变,
|
|
447
|
+
memory_outbox 无该形状旧行)。部署顺序:**cloud 先合、SDK 后发**——cloud
|
|
448
|
+
未加 OBSERVABILITY_TYPES 前,新事件会被判 unknown_event_type 进死信。
|
|
449
|
+
- `adapters/coding/shared/lifecycle/post-turn-worker.ts`:`PostTurnWorker`
|
|
450
|
+
新增第 4 个可选依赖闭包 `emitObservability`(缺省 no-op,永不断 Blocking
|
|
451
|
+
post-turn 车道)与 options.deviceId(extract.done 溯源);每个 outcome
|
|
452
|
+
迁移(applied / skipped / failed_retryable / failed_terminal)发一条
|
|
453
|
+
extract.done;idempotencyKey =
|
|
454
|
+
`obs:extract.done:<jobKey>:<attemptCount>:<outcome>`(attempt 入 key:cloud
|
|
455
|
+
幂等 hash 全 envelope,payload 可变则 key 必须变,否则 409)。
|
|
456
|
+
`PostTurnExtractionResult` 增可选诊断字段(skipReason/gatedOut/truncated/
|
|
457
|
+
latencyMs/promptTokens/completionTokens/errorStatus)。
|
|
458
|
+
- `daemon/memory/hook-server.ts`:`extractDurablePostTurn` 把 skip 原因与
|
|
459
|
+
抽取诊断透传给 worker(原先只进内存计数器)。
|
|
460
|
+
- `daemon/memory/outbox-worker.ts`:flush tick 后采样
|
|
461
|
+
`memoryOutboxHealthSnapshot()`(pending/deadLetter,最后样本)。
|
|
462
|
+
- `daemon/runner.ts`:worker 构造注入 emit 闭包(`runtime.resolve(ws).
|
|
463
|
+
outbox.enqueue`,non-blocking)+ 真实 daemon_id;/healthz 增 `memoryOutbox`
|
|
464
|
+
投影(零 I/O,首个 tick 前缺省)。
|
|
465
|
+
- 隐私:error 摘要取自 store lastError(`redactSensitiveText` 已跑、≤220
|
|
466
|
+
截断);`query`/对话正文永不入 envelope。
|
|
467
|
+
### MUST-1 — turn 的 LLM 出口改走 cloud turn-scoped 端点(`--key-file` → `--egress-file`,2026-09-18)
|
|
468
|
+
|
|
469
|
+
- **`prismer turn run` CLI 契约破坏性变更**:`--key-file <provider.key>` 移除,改为
|
|
470
|
+
`--egress-file <egress.json>`(`{protocolVersion, url, token, model, provider}`,
|
|
471
|
+
0600,**读完即删**)。pod 内从此**没有 provider key**:`token` 是 cloud 铸造的
|
|
472
|
+
turn 作用域短 TTL 凭据(绑定 envId + turnId,run 终态或到点即死),模型调用打
|
|
473
|
+
cloud 的 `/api/eaas-turn-egress/v1/chat/completions`(OpenAI chat-completions wire
|
|
474
|
+
含 SSE 不变),由 cloud 用它自己的 provider 链与计费调上游。
|
|
475
|
+
- **token 原样进 `Authorization: Bearer`**:pi-ai 时代「补 `sk-` 前缀」的 C-1 归一
|
|
476
|
+
逻辑不再适用于出口凭据——前缀会改变 cloud 出口面的 HMAC 验证输入。旧
|
|
477
|
+
`provider.key`(owner token 表)不再被接受(收到即 exit 70,不静默)。
|
|
478
|
+
- 协议面:`TurnEnvelopeV1` 删除 `providers[]`(链与有序 fallback 上移到 cloud 出口
|
|
479
|
+
侧——pod 内换源会重放已落盘工具副作用,cloud 侧换源不会);收到带 `providers[]`
|
|
480
|
+
的 envelope = cloud 早于本 bundle,明确拒绝(exit 70)而非静默忽略。新增
|
|
481
|
+
`TurnEgressV1` + `parseTurnEgress`;移除 `TurnProviderV1` / `TurnApiMode` /
|
|
482
|
+
`parseProviderKeys`。
|
|
483
|
+
- 结果 `providerSource` 恒为 `cloud-egress`(具体 provider 源在 cloud 侧解析后才
|
|
484
|
+
确定,runtime 不再冒认源 id);`PRISMER_PI_API` 固定 `openai-completions`(出口
|
|
485
|
+
端点 wire),`openai-responses` / `anthropic-messages` 分支保留给 daemon 路径。
|
|
486
|
+
- 测试:`test/turn-cli.test.ts`(假 **egress** 端点全链真进程跑 + 「无 `sk-` 前缀」
|
|
487
|
+
负控)、`test/turn-protocol.test.ts`(`TurnEgressV1` round-trip / 拒绝面 /
|
|
488
|
+
legacy providers[] 拒绝)。cloud 侧镜像:`src/tenant/__tests__/agent-turn-runtime.test.ts`。
|
|
489
|
+
|
|
490
|
+
### `prismer turn run`:环境内一次性 turn 进程(Gate B+ Task 7 协议 v1)
|
|
491
|
+
|
|
492
|
+
> 2026-09-18 起,本节的 `--key-file` / `providers[]` 语义已被上面的 MUST-1 取代;
|
|
493
|
+
> 以下保留为当时快照。
|
|
494
|
+
|
|
495
|
+
- 新增 CLI 子命令 `prismer turn run --input <input.json> --output <output.json>
|
|
496
|
+
--key-file <provider.key> --adapter pi-core [--deadline-ms N]`:cloud 的
|
|
497
|
+
`EaasSandboxPort` exec 包装器在租户容器内调用它跑**一轮** agent 对话。
|
|
498
|
+
- 协议 `TurnEnvelopeV1` / `TurnResultV1`(`src/turn/protocol.ts`):input.json
|
|
499
|
+
(0600,不含密钥)→ output.json;**stdout 不是数据通道**;exit code 契约
|
|
500
|
+
0 = 结果已写、70 = 基建失败(cloud 侧映射 `runtime_unavailable`)。
|
|
501
|
+
- 凭据只从 `--key-file`(0600,JSON map providerId → token)读,**读完即删**;
|
|
502
|
+
turn 窗口 = 凭据窗口。
|
|
503
|
+
- 有序 fallback:envelope 的 `providers[]` 按序试;某源在**未落工具副作用**前
|
|
504
|
+
失败才换下一源(避免重放副作用)。t7/t8 由引擎真上报(首个非空 delta = t8)。
|
|
505
|
+
- pi-core 引擎新增 `openai-completions` wire(`PRISMER_PI_API=openai-completions`):
|
|
506
|
+
EaaS turn 的 provider 源走的就是 gateway 形态的 `/v1/chat/completions` 端点,
|
|
507
|
+
换 wire 即静默行为漂移。原 `openai-responses` / `anthropic-messages` 选择逻辑
|
|
508
|
+
不变(daemon 路径零影响)。
|
|
509
|
+
- 多模态:envelope 的 image 块(path + dataUrl 双载)原样进 pi-core 的 content-part
|
|
510
|
+
(`{type:'image', data, mimeType}`),视觉能力门在 cloud 侧(curated 表是单一来源),
|
|
511
|
+
runtime 侧不复制模型能力表。
|
|
512
|
+
|
|
513
|
+
### vision 清单:deepseek-v4-flash-vision-exp 进 hermes VISION_CAPABLE_MODELS
|
|
514
|
+
|
|
515
|
+
- `adapters/persistence/hermes` 的 per-model vision allowlist 新增
|
|
516
|
+
`deepseek-v4-flash-vision-exp`(2026-09-10 owner 裁决,deepseek 默认模型统一切换)。
|
|
517
|
+
依据:官方 vision guide + 活体 `image_url` 块往返 HTTP 200(图片 token 计费入账)。
|
|
518
|
+
注意上游约束:图片仅 `user` 消息可携带;plain `deepseek-v4-flash` /
|
|
519
|
+
`deepseek-v4-pro` 的 no-vision 判定不变。与 `src/lib` `BUILTIN_VISION_MODELS`
|
|
520
|
+
的同步由 `model-defaults-chain.test.ts` parity 门保证。
|
|
521
|
+
|
|
522
|
+
### pkf-writing skill:全文评审门 + 生成器漂移同步
|
|
523
|
+
|
|
524
|
+
- `built-in-skills/pkf-writing/SKILL.md` 由生成器重新生成(与
|
|
525
|
+
`sdk/cloud/catalog/skills/pkf-writing/SKILL.md` 逐字节一致,258 行):
|
|
526
|
+
Report quality floor 升级为「构成底线 + 全文评审门」(plain table 不再计入
|
|
527
|
+
rich affordance;交付前 5 项内容形状→载体机械审查)。生成器模板此前落后于
|
|
528
|
+
线上文本,已一并同步。skill 行为变化,无 daemon 源码变化。
|
|
529
|
+
|
|
530
|
+
### ConfigDelivery env 直达 daemon process.env + 语义 config hash(test-504-storm 止血接线)
|
|
531
|
+
|
|
532
|
+
- **fix(config-bootstrap): `applyBundle` 把 bundle.env 全量应用进 daemon 自身
|
|
533
|
+
`process.env`**——此前只有 `PRISMER_API_KEY`(显式)与 `PRISMER_BASE_URL`
|
|
534
|
+
(providerBase)进 overlay,其余键只写 `~/.hermes/.env`(仅喂 hermes
|
|
535
|
+
python),daemon 侧 `extract.ts` / `resolveBarrierTimeoutMs` 读到的永远是
|
|
536
|
+
boot 时的 pod env——Nacos 调优键(`MEMORY_EXTRACT_MAX_TOKENS` /
|
|
537
|
+
`PRISMER_MEMORY_BARRIER_TIMEOUT_MS`)经 ConfigDelivery 下发后不生效。
|
|
538
|
+
- **fix(config-bootstrap): config.yaml 幂等比较改用语义 hash(剥离 `#` 注释
|
|
539
|
+
行)**——生成文件嵌有 `configVersion` 注释(曾有 `appliedAt` 时间戳),逐
|
|
540
|
+
字节 hash 在任何 bundle 变化下都不同,env-only 变化会被误判为 config 变化
|
|
541
|
+
而触发无谓的 gateway drain/kill(故障窗口里这是自伤)。语义相同 →
|
|
542
|
+
`applied:false` → 只更新 env 面、不重启 gateway;`appliedAt` 时间戳已删
|
|
543
|
+
(它同样击穿幂等检查)。备份仍只在真 config 变化时执行(env-only 不再
|
|
544
|
+
覆写回滚副本里的旧 .env)。
|
|
545
|
+
- **fix(memory): `PreReplyDurabilityBarrier` 每次 `run()` 重新解析
|
|
546
|
+
`PRISMER_MEMORY_BARRIER_TIMEOUT_MS`**——barrier 是 runner 里的长命单例,
|
|
547
|
+
构造期快照会把 boot 时的 120s 默认值钉死到进程死亡;ConfigDelivery 是
|
|
548
|
+
运行时热更新通道,预算必须绑到下一次 run 而不是下一次重启。
|
|
549
|
+
- cloud 侧配套:`src/im/api/runtime-bootstrap.ts` bundle.env 增设
|
|
550
|
+
`DAEMON_TUNING_ENV_ALLOWLIST`(显式白名单 + 纯整数守卫,防 NaN 毒化
|
|
551
|
+
extract max_tokens),负控测试
|
|
552
|
+
`src/im/tests/acp-runtime-bootstrap-tuning-env.test.ts`;daemon 负控测试
|
|
553
|
+
在 `test/config-bootstrap.test.ts`(env-only apply)与
|
|
554
|
+
`test/pre-reply-durability.test.ts`(预算热解析,修复前代码上红)。
|
|
555
|
+
|
|
556
|
+
### ConfigDelivery context_length 存活契约 — configurePrismerProvider 保留/继承 `models`(评审修正)
|
|
557
|
+
|
|
558
|
+
- **fix(hermes adapter): `applyBootstrap` 整项替换 prismer entry 时保留既有
|
|
559
|
+
`models` 键,profile doc 从 root doc 继承**——ConfigDelivery 写入 root
|
|
560
|
+
`~/.hermes/config.yaml` 的 `custom_providers[].models.<id>.context_length`
|
|
561
|
+
此前会在每次网关拉起时被不含 `models` 的新字面量整体替换剥掉(applyBundle
|
|
562
|
+
在 configVersion 不变时跳过重写,永不恢复),且 profile 网关(`hermes -p
|
|
563
|
+
<name>`,HERMES_HOME 被 profile 覆写、无 root 合并)读的 profile config 根本
|
|
564
|
+
拿不到该块——step-0 override 失效、嗅探风暴复活。修复:root 先应用并捕获其
|
|
565
|
+
`models`,profile 侧继承;本 doc 旧 entry 的 `models` 一律保留(operator
|
|
566
|
+
手写值不丢)。双侧写入对齐 commit 673beb3a0(C4)确立的「ConfigDelivery
|
|
567
|
+
YAML 与 configurePrismerProvider 逐键对齐」不变量。负控测试
|
|
568
|
+
`test/hermes-provider-context-length.test.ts`(3 条,前两条在修复前代码上红)。
|
|
569
|
+
|
|
570
|
+
### ConfigDelivery — hermes config.yaml 写入 per-model `context_length`(首响应嗅探风暴根治)
|
|
571
|
+
|
|
572
|
+
- **feat(config-bootstrap): `generateHermesConfigYaml` 输出
|
|
573
|
+
`custom_providers[].models.<id>.context_length`**——bundle 新增可选
|
|
574
|
+
`hermes.modelContextLengths`(model id → 权威 context window,cloud 侧取自
|
|
575
|
+
curated funnel),落到 Hermes 的 **step-0 config override**
|
|
576
|
+
(`hermes_cli/config.py:get_custom_provider_context_length`)。此前 Hermes
|
|
577
|
+
对自定义 base_url 每轮消息跑全量端点嗅探(`detect_local_server_type`×2 +
|
|
578
|
+
`/models` `/v1/models` `/v1/models/<id>` `/api/show` …≈20 发,对我们只服务
|
|
579
|
+
chat/completions 的 cloud proxy 全部 404)才能解析 context length,且无可
|
|
580
|
+
缓存权威值 → 每轮重复,首 token 前白烧秒级。字段缺省时 YAML 与旧版逐字节
|
|
581
|
+
一致(不产生空 `models:` 键)。
|
|
582
|
+
- `RuntimeConfigBundle.hermes.modelContextLengths?: Record<string, number>`
|
|
583
|
+
类型同步落 `sdk/prismer` / `sdk/cloud` / `sdk/aip/typescript` 三包
|
|
584
|
+
(additive optional,向后兼容;进 configVersion 内容 hash → daemon 下轮
|
|
585
|
+
poll 自动拉新,无需 OTA 即可让 cloud 先行生效;yaml 渲染变更本身随下次
|
|
586
|
+
runtime bundle 发布)。
|
|
587
|
+
|
|
588
|
+
### SDK 边界 Wave2 B2/B3 — daemon 离线改名落库 + CLI rename --handle
|
|
589
|
+
|
|
590
|
+
- **fix(daemon): `onAgentChanged` 补 local.db `agents` 表持久化**——改名
|
|
591
|
+
(PATCH /agents/:id)不 bump profile version ⇒ host.acked / syncProfileFromCloud
|
|
592
|
+
不会触发,旧实现改完只驻内存,重启后 declare/healthz/task-agentName 全回退旧名。
|
|
593
|
+
现在 `agent.changed` 的 displayName 直达 `UPDATE agents SET name`(dirty=0),
|
|
594
|
+
是离线窗口改名收敛的唯一落点;`AgentChangedPayload.fields` 同步补 `username`
|
|
595
|
+
字段声明(daemon 不持久化——本地表无 username 列)。
|
|
596
|
+
- **feat(cli): `prismer agent rename` 增 `--handle <new-username>`**——PATCH 体
|
|
597
|
+
`{ displayName, username? }`(handle 传时才带),与 PATCH /agents/:id 双字段
|
|
598
|
+
契约对齐;handle 在 CLI 侧先按 `/^[a-z][a-z0-9-]{2,30}$/` 校验(坏轴根本不出网)。
|
|
599
|
+
- **fix(cli): register 校验收窄到 `/^[a-z][a-z0-9-]{2,30}$/`**——旧轴
|
|
600
|
+
`[a-zA-Z0-9_-]`(大写/下划线,如 `Prod_Manager`)不再可新建(CLI 侧 400
|
|
601
|
+
之前拒绝;云端 register 端点不动,存量 handle 保留)。`slugifyUsername`
|
|
602
|
+
同步收窄:不再产出 `_`,数字开头派生名补 `agent-` 前缀保证字母开头。
|
|
603
|
+
|
|
604
|
+
### bugfix211 G2-R R-2 — runtime terminal-sessions 插件模块(workspace 终端)
|
|
605
|
+
|
|
606
|
+
- **feat(daemon): 新增 `daemon/terminal-sessions/` 插件模块**——daemon 协议
|
|
607
|
+
PTY(spec §T-1 唯一轨),wire 四方法 `terminal.open/write/resize/close` +
|
|
608
|
+
事件 `terminal.opened/data/exit/error`;会话卫生:per-daemon 并发上限 2、
|
|
609
|
+
空闲回收(`PRISMER_TERMINAL_IDLE_TIMEOUT_MS`,默认 10min、floor 30s,按
|
|
610
|
+
device-metrics interval 的 ConfigDelivery 模式)、ACL 逐字复用
|
|
611
|
+
shell-executor 的 `enabled`/`allowedWorkspaces`/cwd 语义。能力位
|
|
612
|
+
`runtime.terminal` 仅在 node-pty 真正加载到时宣告。
|
|
613
|
+
- **node-pty 分发形态(owner 裁决方案 A)**:只进 devDependencies,OTA
|
|
614
|
+
bundle 持续 JS-only(staging `npm ci --omit=dev --ignore-scripts` 不装
|
|
615
|
+
dev + `assertNoNativePayload` 对任何 `.node` die 兜底);pod 侧经 image
|
|
616
|
+
native ABI floor 供给(Dockerfile.base 源码编译 + install-builtin-runtime
|
|
617
|
+
`--native-module-root` 借用,循 better-sqlite3 先例)。`loadPty()` 两路
|
|
618
|
+
懒加载:常规 require(本机 dev/测试)→ floor 根
|
|
619
|
+
`/opt/prismer/native/node_modules` → null ⇒ 模块降级(不宣告能力位,
|
|
620
|
+
terminal.open 回类型化 `terminal_unavailable`,绝不伪造 PTY)。
|
|
621
|
+
- 测试:`test/terminal-sessions.test.ts` 30 例(真 PTY 生命周期 echo/exit
|
|
622
|
+
码/resize、上限/ACL/cwd 拒、idle reaper fake timers、负控:loadPty 强制
|
|
623
|
+
null ⇒ 降级、malformed 帧吞掉不进 dispatch、runner 未挂载 ⇒ 无能力位);
|
|
624
|
+
darwin spawn-helper 执行位兜底进 `test/setup/node-pty-exec-bit.ts`
|
|
625
|
+
(上游 microsoft/node-pty#850)。
|
|
626
|
+
- **feat(daemon): daemon 侧窗口输出帽(spec §T-5 follow-up 落地)**——per-
|
|
627
|
+
session 滚动窗字节预算(`PRISMER_TERMINAL_OUTPUT_BUDGET_BYTES` /
|
|
628
|
+
`PRISMER_TERMINAL_OUTPUT_BUDGET_WINDOW_MS`,默认 32MiB/10s,与 idle
|
|
629
|
+
timeout 同一条 ConfigDelivery 面)。洪流超预算 ⇒ kill 会话 +
|
|
630
|
+
`terminal.exit` 带 typed `reason=terminal_output_budget`(relay 透传、
|
|
631
|
+
前端独立横幅);kill 触发 onExit 二次广播已抑制。cloud relay 512KB/1s
|
|
632
|
+
保持浏览器侧收口,此帽是源端封顶——空闲回收在输出洪流下不再被续命。
|
|
633
|
+
测试新增 5 例(resolver 契约 + fake pty 越限 kill + 窗内正常 + 未装配
|
|
634
|
+
负控)。
|
|
635
|
+
- **fix(daemon): 输出帽系统评审三项修复**——① 窗口账本每次无条件排空(此前
|
|
636
|
+
length>64 才 filter,稀疏流下旧账不滚、累计误杀);② kill 后 onData 存活守卫
|
|
637
|
+
(孙进程持 slave fd 续 drain 时不再 touch/timer 重挂/重复 exit 帧);
|
|
638
|
+
③ env 显式 `0`/`off`/`disabled` 可关帽(此前任何 env 组合都落默认常开,
|
|
639
|
+
ConfigDelivery 无 off 面)。评审回归钉 3 例 + 语义测试更新。
|
|
640
|
+
|
|
641
|
+
### bugfix211 G3 — host.acked adopt/reclaim × rejection 分离(declare↔ack 自噬环)
|
|
642
|
+
|
|
643
|
+
- **fix(daemon): adoption 不再清掉同一轮 ack 刚拉黑的所有权黑名单。** 半成型
|
|
644
|
+
agent(有 profile 行、无 owner userId、无 binding)每轮 declare 同时命中
|
|
645
|
+
rejectedAgents("不是你的")与 adoptAgents("没人绑,来收养"):ownership-lock
|
|
646
|
+
删除+拉黑,syncHostAckAdoptions 又清黑名单重建,环实测 ~350ms/轮
|
|
647
|
+
(skills/ack+declare+me/agents+runtime 连环刷屏)。daemon 侧
|
|
648
|
+
syncHostAckAdoptions 增 rejectedThisRound 参数,本轮被拒 agent 跳过
|
|
649
|
+
reclaim/adopt(保留黑名单);cloud 侧在 ack 组装口做统一剔除(见主仓
|
|
650
|
+
handler.ts partitionAdoptableAgainstRejections)。
|
|
651
|
+
|
|
652
|
+
### bugfix211 G2 — ConfigDelivery 容器分类修正(workspace pod 被误判 ACS)
|
|
653
|
+
|
|
654
|
+
- **fix(daemon): 环境判据从镜像默认 `ALLOW_FAKE_API_KEY` 换成 env key 本身。**
|
|
655
|
+
dev 镜像把 `PRISMER_ALLOW_FAKE_API_KEY=true` 烧为默认(v200 时代 ACS 占位
|
|
656
|
+
key boot 的遗留),ACS checkpoint 与 workspace k8s pod 共享同一镜像,于是
|
|
657
|
+
持有真 `sk-prismer-live-*` 凭证的 workspace pod 被判成 "no env-injected
|
|
658
|
+
key" 的 ACS 世界:启动即打 "ACS sandbox detected",env-inference 兜底被
|
|
659
|
+
禁,B1 永久 401/403 时拒绝安全的最后手段而 Hermes 永不配置。新判据
|
|
660
|
+
`containerEnvIsKeyless`(runner.ts,三处消费点统一):container 且 env 无
|
|
661
|
+
`sk-prismer-live-` 形态 key 才是 keyless 沙箱;持真 key 一律走 ACK 分支。
|
|
662
|
+
配套 cloud 侧 pod-spec 显式注入 `PRISMER_ALLOW_FAKE_API_KEY=false`(覆盖
|
|
663
|
+
镜像 ENV;ACS pod 不受影响)。测试 `test/container-env-keyless.test.ts` 5 例。
|
|
664
|
+
|
|
665
|
+
### memory211 — pkf_reply_inline cross-turn delivery + office-artifacts interpreter
|
|
666
|
+
|
|
667
|
+
- **fix(pkf): `pkf_reply_inline` read roots now include the session dir.** In a
|
|
668
|
+
hermes LONG session the task dirs persist across turns under
|
|
669
|
+
`sessions/<sid>/tasks/<tid>/`, and the agent re-delivers a `.pkf` it authored
|
|
670
|
+
in a PREVIOUS turn's scratch; `bindPkfReplyInlineScope` computed `sessionDir`
|
|
671
|
+
but never added it to `allowedReadRoots`, so the new dispatch rejected that
|
|
672
|
+
path (`pkf_reply_inline_path_outside`) at tool time AND at terminal
|
|
673
|
+
re-validation. Rooting the session dir makes directory-prefix containment
|
|
674
|
+
cover every historical `tasks/*/scratch` under the session (referencing old
|
|
675
|
+
scratch is the natural shape, not an escape). Unbind stays taskId-keyed, so
|
|
676
|
+
the wider read set cannot outlive its own dispatch. Tests: red→green pair in
|
|
677
|
+
`pkf-reply-inline-tool.test.ts` — cross-turn delivery through `handleDispatch`
|
|
678
|
+
with the marker landing in THIS run's scratch, plus the same-session-bound
|
|
679
|
+
negative control (another session's file is still rejected).
|
|
680
|
+
- **docs(office-artifacts skill): the baked office libs live in the image venv
|
|
681
|
+
interpreter `/home/user/.venv/bin/python3`**, not the system
|
|
682
|
+
`/usr/bin/python3` (which has none of them). The Runtime baseline section now
|
|
683
|
+
says to run generator scripts with the venv interpreter and to re-probe there
|
|
684
|
+
before concluding a dependency is missing (agents probing system python were
|
|
685
|
+
reporting a false "missing deps"). Still no `pip install` on the fly. The
|
|
686
|
+
authoritative copy is `sdk/cloud/catalog/skills/office-artifacts/SKILL.md`
|
|
687
|
+
(`sdk/prismer/built-in-skills/` is the gitignored build-time copy, refreshed
|
|
688
|
+
in this checkout to stay byte-identical); the catalog source-contract
|
|
689
|
+
skills-manifest hash was re-pinned accordingly
|
|
690
|
+
(`catalog-source-contract.test.ts`).
|
|
691
|
+
|
|
692
|
+
### memory211 external review — P1 (chunk lane cap boundary)
|
|
693
|
+
|
|
694
|
+
- **fix(P1, security, asset raw text): the daemon `memory_search` chunk lane
|
|
695
|
+
was fail-OPEN for asset ACLs.** `runWorkspaceSearch` post-filtered hits with
|
|
696
|
+
`store.loadById(r.pageId)`, but a T3 chunk hit's `pageId` mirrors an ASSET id
|
|
697
|
+
and `memory_pages` never has that row — every chunk hit fell through the
|
|
698
|
+
"no page row" pass and was KEPT, so a scoped sub-agent cap on a shared daemon
|
|
699
|
+
could read any materialized asset's raw body (the hook recall path fails
|
|
700
|
+
CLOSED on the same shape). Chunk hits are now judged by the new
|
|
701
|
+
`canCapReadAsset(cap, asset)` boundary predicate (acl-predicate.ts) against
|
|
702
|
+
the asset's `visibility` + `ownerImUserId`, mirrored onto the daemon's own
|
|
703
|
+
asset projection: `asset_metadata_index` gains both columns (local schema
|
|
704
|
+
v17) fed by the SAME `/assets/index` DTO it already mirrors — no endpoint or
|
|
705
|
+
wire change — and the runner wires `resolveAssetAcl` (a synchronous local
|
|
706
|
+
read, no I/O in the search hot path). Strictly narrower than the cloud asset
|
|
707
|
+
ACL by design: `workspace` or own-asset grants, everything else DENIES
|
|
708
|
+
(`user` / `task:*` / `quarantined` / `aclJson` grants stay cloud-side). An
|
|
709
|
+
asset with no verdict (never synced, pre-v17 mirror row, older cloud) is
|
|
710
|
+
DENIED — fail-closed — so the lane degrades to wiki-only instead of leaking.
|
|
711
|
+
Tests: `memory-search-asset-cap.test.ts` (real RPC surface; the system cap
|
|
712
|
+
sees the same chunk the scoped cap is denied, proving the fixture) +
|
|
713
|
+
`canCapReadAsset` matrix in `memory-acl-predicate.test.ts` + the projection
|
|
714
|
+
round-trip in `asset-metadata-index.test.ts`; mutation (removing the
|
|
715
|
+
chunk-lane boundary) turns 2/4 red.
|
|
716
|
+
|
|
717
|
+
### memory211 review round 2 — P3 + P5
|
|
718
|
+
|
|
719
|
+
- **fix(P3, recall ranking): `mergeChunkHits` compared the COMPOSITE score
|
|
720
|
+
instead of the LEXICAL evidence it documented.** Its comment promised that
|
|
721
|
+
workspace priors never bury the upload, but `existing.score` already folds
|
|
722
|
+
recency (+0.2) / supersede (−0.8) / stale (−0.5) — so a freshly-touched wiki
|
|
723
|
+
page outranked a chunk that carried the query term far more strongly (the
|
|
724
|
+
exact D9 shape). The merge now reads `components.matchScore` (the
|
|
725
|
+
pre-adjustment BM25 relevance), the same basis the cloud `mergeRawTier`
|
|
726
|
+
compares on. `MemorySearchResult` gains the additive optional
|
|
727
|
+
`components: { matchScore }` decomposition (cloud parity), populated on both
|
|
728
|
+
the wiki FTS leg and the chunk leg.
|
|
729
|
+
- **fix(P5, prompt doctrine): the dispatched agent prompt still taught the
|
|
730
|
+
retired ">1M sharding" trigger.** `MEMORY_CORE_DIRECTIVE` (injected into
|
|
731
|
+
every dispatch) plus five comment sites (extract.ts ×3, runner.ts,
|
|
732
|
+
hook-server.ts) predate §6.9 裁决 4's 64K-character re-threshold, teaching an
|
|
733
|
+
agent to shard a source the gate now admits whole up to 64K characters. All
|
|
734
|
+
six now carry the 64K-character口径; `dispatch.test.ts` and
|
|
735
|
+
`memory-skill-degrammar.test.ts` pin it (and pin that "1M" never returns).
|
|
736
|
+
- **fix(P6, deliverable gate): an image-only deliverable's page was gated out
|
|
737
|
+
wholesale.** The pairing side declares a deliverable source on ANY
|
|
738
|
+
`prismer://asset/<id>` occurrence — including the `<figure><img src>` form the
|
|
739
|
+
extraction prompt itself teaches for IMAGE/CHART assets — while G9 recognized
|
|
740
|
+
only `<a rel="derived-from">`. The gate now accepts the inline image pointer
|
|
741
|
+
as the same materializable jump pointer (an image cannot be distilled into
|
|
742
|
+
prose; its embed IS the jump), and the 422 message teaches both forms. An
|
|
743
|
+
`<img>`/`<a>` pointing at a DIFFERENT asset, or an untyped `<a>`, still fails.
|
|
744
|
+
|
|
745
|
+
- **fix(recall): the T3 chunk lane collapsed an upload's chunks to ONE hit**
|
|
746
|
+
(found as the W4 span-mint suite's order-dependent red). The lane deduped its
|
|
747
|
+
FTS candidates by `path`, but every chunk of an asset shares the same
|
|
748
|
+
`asset:<id>#<hash>` path — so an 11-chunk upload recalled exactly one chunk,
|
|
749
|
+
whichever the (near-tied) bm25 ordering surfaced first, and mint-即升层 was
|
|
750
|
+
invisible whenever the cited chunk lost that coin flip. Deduping is now by
|
|
751
|
+
chunk identity (`assetId#ordinal`), and bm25-equal chunks tie-break by ordinal
|
|
752
|
+
so the window cannot reshuffle between recalls on an unchanged store.
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
### memory211/01 §6.11 — W7 behavioural-closure wave (read-side telemetry)
|
|
756
|
+
|
|
757
|
+
- **Read-side `[memory-trace]` pipeline (D11-5)** — search / load / browse
|
|
758
|
+
(place-context) / the search miss-lane's navigation now emit the same
|
|
759
|
+
`[memory-trace] stage=…` lines the write lane has logged since memory203/18
|
|
760
|
+
R8.2 (13 write sites, 0 read sites before W7). Lines carry the query COUNT,
|
|
761
|
+
`navigation_used`, the tier histogram, result count, duration and a traceId
|
|
762
|
+
(`?traceId=` honoured, else minted `rd_…`). The query TEXT never enters a
|
|
763
|
+
read trace. Full-volume local stderr, no sampling.
|
|
764
|
+
- **CLI recall telemetry (D11-1)** — `prismer memory recall/search` enqueues one
|
|
765
|
+
`recall_pull` (metadata `via: 'cli'`) into the daemon outbox via the existing
|
|
766
|
+
`/local/memory/observability/emit` pass-through, attributed to the presented
|
|
767
|
+
cap's `sub` claim (no subject ⇒ no event, never a fabricated actor). Best-effort:
|
|
768
|
+
a telemetry failure never changes the command's exit code. NOTE: a scoped-cap
|
|
769
|
+
CLI run now produces both a daemon tool-channel row and this `via:'cli'` row for
|
|
770
|
+
the same intent — consumers filter on `via`.
|
|
771
|
+
- **CLI batch faces (D11-2)** — `prismer memory search --queries '["a","b"]'`
|
|
772
|
+
(array passed through; the daemon owns the ≤8 bound and its `truncated` flag)
|
|
773
|
+
and `prismer memory load-batch <paths…>` (≤10 point loads, per-path verdict,
|
|
774
|
+
exit 1 only on a total miss). New `daemonGet` keeps a load 404 as a real
|
|
775
|
+
per-path verdict instead of `tryDaemon`'s route-probing 404-continue. Contract:
|
|
776
|
+
`maxItems` now survives into the regenerated plugin schema artifact; the
|
|
777
|
+
memory SKILL CLI appendix teaches both faces.
|
|
778
|
+
- **Tool-sequence ring + turn metrics (D11-4)** — new
|
|
779
|
+
`daemon/memory/tool-sequence.ts`: per-workspace ring (cap 200, in-process)
|
|
780
|
+
recording one entry per search/load/browse/write the daemon serves. PRIVACY
|
|
781
|
+
BOUNDARY: verb + page path + duration + timestamp + the `navigation` /
|
|
782
|
+
query-COUNT flags only; `ALLOWED_ENTRY_KEYS` is a closed set enforced by test
|
|
783
|
+
and the query text is unrecoverable. The dispatch finally block reads the
|
|
784
|
+
turn's slice and emits `turn.navigation_used` (browse, or a load after a
|
|
785
|
+
miss-lane search — repeated searches read 0) and `turn.shortcuts_taken`
|
|
786
|
+
(loads with no preceding browse), only when the turn made a memory call.
|
|
787
|
+
|
|
788
|
+
### memory211/01 §6.9 — W6 (owner 终裁执行)
|
|
789
|
+
|
|
790
|
+
- memory211/01 §6.9 裁决 2 (W6) — **digest budget cap replaced by the 32K extreme
|
|
791
|
+
guard** (`MEMORY_DIGEST_EXTREME_GUARD_TOKENS`, Nacos/env key
|
|
792
|
+
`PRISMER_MEMORY_DIGEST_BUDGET_TOKENS` kept as the knob name). Full injection is
|
|
793
|
+
the default; the guard is the only cut and it marks itself. The INDEX-TOC is no
|
|
794
|
+
longer pre-budgeted to a share of the guard — that share silently
|
|
795
|
+
skeletonized the map of a workspace whose digest was still far below the
|
|
796
|
+
ceiling (covered by a red/green test in `memory-w3-upload-pipeline`).
|
|
797
|
+
|
|
798
|
+
- memory211/01 §6.9 裁决 1 (W6 fix round 1) — **the daemon miss navigation now
|
|
799
|
+
respects the per-cap read boundary**. `runWorkspaceSearch` filtered hits
|
|
800
|
+
through `canCapReadPage` but passed `navigation.startPoints` straight through,
|
|
801
|
+
so a hub/INDEX the actor cannot read still surfaced as path + title +
|
|
802
|
+
childrenCount. Start points are now filtered by the same predicate (an
|
|
803
|
+
unreadable entry is dropped; if none survive the navigation is omitted) —
|
|
804
|
+
matching the cloud side, which already applies `allowedPageIds` + the per-row
|
|
805
|
+
read verdict.
|
|
806
|
+
|
|
807
|
+
- memory211/01 §6.9 裁决 5 ①② (W6) — the ingest bookkeeping gaps:
|
|
808
|
+
① **ingest terminal jump** (`reconcileWorkspaceIngestTasks` /
|
|
809
|
+
`reconcileIngestTasks`): a completed ingest task flips its asset to
|
|
810
|
+
`ingested`, a failed/cancelled one requeues it as `pending` with the attempt
|
|
811
|
+
recorded on the asset row, and the third failure parks it `failed` (cap 3,
|
|
812
|
+
mirroring the dream merge leg). The scheduler's ingest drain runs the
|
|
813
|
+
reconciliation first. Fix round 1: a LIVE (non-terminal) task outranks a stale
|
|
814
|
+
terminal one, so a gen1 failure no longer gets re-applied over a gen2 that is
|
|
815
|
+
still running — that misjudgement burned the retry budget twice, surfaced
|
|
816
|
+
gen1's error as if fresh, and could park `failed` an asset whose current
|
|
817
|
+
generation was fine (a false stall, since the drain never sweeps `failed`).
|
|
818
|
+
② **`prismer memory ingest`** — the workspace upload-ingest operational switch
|
|
819
|
+
(owner/admin, cloud `/api/im/memory/ingest-settings`, merge-safe: it patches
|
|
820
|
+
one metadata key instead of replacing the object).
|
|
821
|
+
|
|
822
|
+
- memory211/01 §6.9 裁决 5 ③ (W6) — **daemon-first search scaffold removed** rather than wired: `setDaemonRpcSender`
|
|
823
|
+
had no responder (nothing in `src/im/ws/` ever registered presence or answered a
|
|
824
|
+
`memory.search` RPC), so `FF_MEMORY_SEARCH_DAEMON_FIRST` was a flag whose ON path
|
|
825
|
+
was unreachable in production. Cloud is the single authoritative recall path;
|
|
826
|
+
`SearchHit` collapses back to the cloud payload shape. Wiring daemon-first recall
|
|
827
|
+
would be a new designed seam (WS route + trace + parity tests), not a resurrected
|
|
828
|
+
stub.
|
|
829
|
+
|
|
830
|
+
- memory211/01 §6.9 裁决 4 (W6) — **sharding threshold 1M → 64K characters**.
|
|
831
|
+
The daemon deliverable gate's constant is renamed
|
|
832
|
+
`SHARDING_THRESHOLD_BYTES` → `SHARDING_THRESHOLD_CHARS` (`64 * 1024`) because
|
|
833
|
+
the ruling's 口径 is characters, not bytes (a CJK source is ~3 bytes per
|
|
834
|
+
char). The gate now applies the ceiling to the PAGE BODY in characters
|
|
835
|
+
(每页 ≤64K, 422 `sharding_required`); the cloud pipeline plans the source half
|
|
836
|
+
from the exact chunk character count.
|
|
837
|
+
|
|
838
|
+
- memory211/01 §6.9 裁决 3 (W6) — **memory/SKILL.md regenerated for the new loop**
|
|
839
|
+
(three-stage recall protocol: structure route → semantic search → navigation
|
|
840
|
+
miss-fallback; batch `queries[]` usage; the wiki/asset/raw tier trust bands;
|
|
841
|
+
copy + reference writing rules; 64K-char sharding). The skill now teaches
|
|
842
|
+
"startPoints are NOT answers — walk them". The prompt-size receipt budget is
|
|
843
|
+
rebased 360 → 460 lines for the mandated content (owner ruling: rebuild without
|
|
844
|
+
historical baggage, request budget rather than drop content). The plugin tool
|
|
845
|
+
schema artifact was regenerated from the TS spec (memory_search description now
|
|
846
|
+
teaches the navigation contract).
|
|
847
|
+
|
|
848
|
+
- memory211/01 §6.9 裁决 2 (W6) — **digest budget cap removed**: the W3-era
|
|
849
|
+
~2K-token routine cap on the turn-start digest is GONE. `buildMemoryDigest`
|
|
850
|
+
now injects the FULL map (INDEX-TOC + every hub summary); the only remaining
|
|
851
|
+
bound is the extreme guard `MEMORY_DIGEST_EXTREME_GUARD_TOKENS = 32_000`
|
|
852
|
+
(env/Nacos key `PRISMER_MEMORY_DIGEST_BUDGET_TOKENS` keeps its name — it now
|
|
853
|
+
tunes the guard, not a routine cap), above which the body is cut with the
|
|
854
|
+
existing observable `digest truncated` marker. Determinism and content-hash
|
|
855
|
+
versioning are unchanged.
|
|
856
|
+
- memory211/01 §6.9 裁决 1 (W6) — **miss-path navigation redesign**: on a text
|
|
857
|
+
miss `memory_search` no longer returns the structural lane dressed up as a
|
|
858
|
+
ranked hit list (a miss carries no rank evidence — the old band was a random
|
|
859
|
+
order). The response now carries `navigation: {reason:'text-miss',
|
|
860
|
+
startPoints:[{path,title,pageType,childrenCount,why:'structural-entry'}],
|
|
861
|
+
guidance}` (INDEX + hubs, children first) on the daemon response, the TS
|
|
862
|
+
adapter types (`MemoryNavigation`) and — via the plugin's JSON passthrough —
|
|
863
|
+
the tool result. `hybrid()` keeps its shape for existing callers; use
|
|
864
|
+
`hybridWithNavigation()` for the payload. The W1a dynamic miss band
|
|
865
|
+
(`GRAPH_MISS_PROMOTED_BAND` / `applyMissBands`) is removed: graph hits now
|
|
866
|
+
exist only as a supplement to real FTS evidence and always land in the sink
|
|
867
|
+
band. Hits with lexical evidence (FTS pages, T3 chunks) are unchanged.
|
|
868
|
+
- memory211/01 v3.3 W5 (轴H 单源契约 + 轴G section curate 动词):
|
|
869
|
+
- **Single schema source (D1 extinction)**: the five memory tool schemas in
|
|
870
|
+
`plugins/memory/prismer/__init__.py` are no longer hand-written — they are
|
|
871
|
+
generated from the frozen TS spec (`src/adapters/memory-tools.ts`) into
|
|
872
|
+
`tool-schemas.generated.json` and LOADED at runtime. Regenerate with
|
|
873
|
+
`npx tsx scripts/memory211/generate-memory-tool-contract.ts`; the
|
|
874
|
+
three-surface contract test
|
|
875
|
+
(`scripts/__tests__/memory211-tool-contract.test.ts` on the cloud repo)
|
|
876
|
+
fails on any divergence between tool schema, plugin handler, CLI flags and
|
|
877
|
+
the SKILL.md parameter table. The placement parameter is now camelCase
|
|
878
|
+
`parentHubPath` on EVERY surface (was `parent_hub_path` on the Hermes
|
|
879
|
+
plugin while the skill taught `parentHubPath`).
|
|
880
|
+
- Plugin forwards the batch `queries` + `pageType` memory_search params
|
|
881
|
+
(W1a leftover) and the `visibility` write param.
|
|
882
|
+
- Daemon RPC: `/local/memory/curate` routes the new `section_merge` /
|
|
883
|
+
`section_supersede` / `rewire` ops to cloud; new `/local/memory/delete`
|
|
884
|
+
(cloud-adjudicated soft delete + local mirror invalidation) and
|
|
885
|
+
`/local/memory/sync` (alias of flush).
|
|
886
|
+
- CLI: `prismer memory delete|sync` now hit the real daemon routes (they
|
|
887
|
+
previously probed only legacy paths no daemon exposes), `memory write
|
|
888
|
+
--title`, `memory curate promote-to-hub --child-paths`, and three new
|
|
889
|
+
subcommands `curate section-merge|section-supersede|rewire`.
|
|
890
|
+
|
|
891
|
+
- memory211/01 v3.3 W4 (轴F 溯源闭合), daemon runtime side — **lazy span mint +
|
|
892
|
+
mint 即升层**:
|
|
893
|
+
- Local store schema V5 → V6: `asset_chunks` gains `sid` (additive column,
|
|
894
|
+
pragma-guarded ALTER). NULL = the chunk was never cited by a distilled page
|
|
895
|
+
= still T3 `raw`; non-NULL = cited + minted = T2 `asset`.
|
|
896
|
+
- New `daemon/memory/span-mint.ts`: after an agent-authored write
|
|
897
|
+
(`memory_write`) or a post-turn extracted page commits, the content is
|
|
898
|
+
scanned for asset provenance pointers (`prismer://workspace/<ws>/asset/<sha256>`
|
|
899
|
+
scoped, `prismer://asset/<id>` bare, `asset:<id>#<hash>` token — each with an
|
|
900
|
+
optional `#chunk-<ordinal>` span anchor). Every cited chunk of the asset's
|
|
901
|
+
CURRENT revision gets a DETERMINISTIC span sid (`sp-<16hex>`, derived from
|
|
902
|
+
assetId + contentHash + ordinal + text hash — byte-identical to the cloud
|
|
903
|
+
twin `src/im/services/memory-span-sid.ts#mintChunkSid`, so both sides
|
|
904
|
+
converge on one identity), the local mirror row is promoted, and the mint
|
|
905
|
+
rides the SAME idempotent `asset.chunk.upsert` channel with the `sid`
|
|
906
|
+
attached so the cloud authoritative row promotes too. The mint is a derived
|
|
907
|
+
projection: it never throws into the write path and is a one-way latch
|
|
908
|
+
(a sid-less replay never un-mints).
|
|
909
|
+
- Local recall honours the promotion: a chunk hit whose row carries a sid
|
|
910
|
+
surfaces as `tier:'asset'` with `spanSid`, and no longer pays the
|
|
911
|
+
`RAW_TRUST_DISCOUNT` (spec 轴C: T2 sits above T3).
|
|
912
|
+
|
|
913
|
+
- memory211/01 v3.3 W3 (upload ingestion pipeline + turn-start warmup), daemon
|
|
914
|
+
runtime side:
|
|
915
|
+
- **T3 raw-chunk mirror (轴D, F2 裁决)** — local store schema V4 → V5 adds
|
|
916
|
+
`asset_chunks` + `asset_chunks_fts` (additive migration, no rebuild). A
|
|
917
|
+
materialized upload is chunked locally (deterministic ~2K-token chunks with
|
|
918
|
+
10% overlap; byte-identical contract with the cloud chunker) and recalled as
|
|
919
|
+
`tier:'raw'` hits, so offline recall now includes uploads, not only wiki
|
|
920
|
+
pages. The same rows ride the existing memory outbox to the cloud
|
|
921
|
+
authoritative `im_asset_chunks` via the new `asset.chunk.upsert` event
|
|
922
|
+
(idempotent per `workspaceId:assetId:contentHash:ordinal`).
|
|
923
|
+
- **pdf ingestion** — pdf uploads go through the bundled liteparse `lit`
|
|
924
|
+
(digital fast path `--no-ocr`, OCR fallback, 60s kill). A missing/failed
|
|
925
|
+
parser SKIPS loudly; it never throws into the `asset.materialize` ack.
|
|
926
|
+
- **Stable memory digest (轴E)** — new `daemon/memory/digest.ts` builds the
|
|
927
|
+
turn-start digest (INDEX-TOC + hub one-liners): deterministic (path-sorted,
|
|
928
|
+
no timestamps/volatile fields), sha256 versioned, bounded to ~2K tokens
|
|
929
|
+
(`PRISMER_MEMORY_DIGEST_BUDGET_TOKENS`), with an observable truncation
|
|
930
|
+
marker. `FF_MEMORY_INDEX_INJECT_ENABLED` now defaults ON (explicit
|
|
931
|
+
`false|0|off` to disable).
|
|
932
|
+
- **Hermes assembly (轴E)** — the adapter injects the digest at the TAIL of
|
|
933
|
+
the per-turn `instructions` (i.e. the tail of the composed system message)
|
|
934
|
+
so the rarely-changing blocks above it keep their prompt-cache prefix; the
|
|
935
|
+
block is byte-stable for a given digest version.
|
|
936
|
+
- **Miss-lane anchors (轴D 追加项 D①)** — when the text leg misses entirely,
|
|
937
|
+
the daemon miss lane now RETURNS the INDEX/hub anchors as `via:'graph'`
|
|
938
|
+
hits (they were seeds only) and a routing term (childrenCount) keeps a hub
|
|
939
|
+
above its own leaves in the elevated band.
|
|
940
|
+
- **Review fixes** — the deliverable gate no longer strips
|
|
941
|
+
`contentHash`/`sizeBytes` from a declared source (the G10 sharding trigger
|
|
942
|
+
was unreachable from extraction), both extraction legs report
|
|
943
|
+
`extractionGatedOut`/`shardingRequired`, and the dead `distillOverBudget`
|
|
944
|
+
counter is gone.
|
|
945
|
+
|
|
946
|
+
- memory211/01 v3.3 W2 (内容模型 + 写入门 + CJK 分词) — the extraction-thinning
|
|
947
|
+
reversal, on the daemon runtime side:
|
|
948
|
+
- **CJK recall fixed (W2 #7, W0 baseline finding)** — the daemon FTS index
|
|
949
|
+
gains a `cjk` bigram projection column (`memory_fts.cjk`, store schema
|
|
950
|
+
V4 with a drop/recreate/re-index migration) and the query side offers the
|
|
951
|
+
query's own CJK bigrams as FTS alternatives. Before: FTS5's unicode61
|
|
952
|
+
tokenizer kept a whole CJK run as ONE token, so a 2-char Chinese term inside
|
|
953
|
+
a longer run could never match — a pure-Chinese query was a guaranteed zero.
|
|
954
|
+
ASCII queries are byte-for-byte unchanged.
|
|
955
|
+
- **G10 redefined in place (轴A / §0.1)** — the anti-copy budget
|
|
956
|
+
(`min(32KiB, 10% × source)` → 422 `distill_over_budget`) is GONE; copy +
|
|
957
|
+
reference means a distilled page may carry the source's near-full content.
|
|
958
|
+
The only budget left is the sharding trigger: a source over 1,000,000 bytes
|
|
959
|
+
is refused with 422 `sharding_required` (the W5 sharding pipeline will own
|
|
960
|
+
the actual splitting). G9 (derived-from pointer) is unchanged.
|
|
961
|
+
- **one gate, two surfaces (轴H / D3)** — the deliverable gate moved to
|
|
962
|
+
`daemon/memory/deliverable-gate.ts` and the MAIN extraction path now runs
|
|
963
|
+
it: a page that declares a deliverable source but references none of them is
|
|
964
|
+
dropped loudly (trace + `extractionGatedOut` counter) instead of written.
|
|
965
|
+
- **write gates ②③ (轴H)** — `memory_write` now rejects a NEW PKF page with no
|
|
966
|
+
frontmatter description (422 `description_required`) and a body that fails
|
|
967
|
+
the bundled `validatePkf` structure audit (422 `pkf_invalid`, warnings never
|
|
968
|
+
reject). Markdown (legacy input) is exempt from both, edits of existing
|
|
969
|
+
pages are exempt from the description gate, and the PKF gate runs AFTER the
|
|
970
|
+
bare-asset upgrade so the taught `prismer://asset/<id>` pointer form is not
|
|
971
|
+
re-flagged. The placement gate additionally honours an existing child-of
|
|
972
|
+
graph edge (轴H ①).
|
|
973
|
+
- **extraction prompt re-cast (轴A)** — the "pointers not copies" doctrine is
|
|
974
|
+
replaced by copy + reference, with two acceptance rules the model must
|
|
975
|
+
self-check: LEXICON COVERAGE (proper nouns / codes / 中英对照 terms survive
|
|
976
|
+
verbatim) and SECTION EDGES (typed `<a rel>` links anchored at `#section`,
|
|
977
|
+
which the cloud materializer lands as `im_memory_links.sourceSection` /
|
|
978
|
+
`targetSection`). `MAX_PAGES_PER_TURN` and the 32768 extract budget are
|
|
979
|
+
unchanged, and the recorded `lexicon_coverage` metric
|
|
980
|
+
(`extractLexicon` / `coverage`) is the W2 acceptance measure — recorded,
|
|
981
|
+
never gating.
|
|
982
|
+
- memory search surface re-cast (memory211/01 §3 轴C W1a) — the recall payload
|
|
983
|
+
now carries what an agent needs to decide 直读 vs 多跳 without a second call,
|
|
984
|
+
and recall no longer dies when the text leg does:
|
|
985
|
+
- **hop-decision payload**: every `memory_search` hit adds `pagePath`
|
|
986
|
+
(mirror of the frozen `path`), `hubPath` (reverse `child-of` placement,
|
|
987
|
+
null for a root/INDEX page or a dangling edge), `version`,
|
|
988
|
+
`sectionAnchor`/`sectionPreview`, `tier: 'wiki'` (the T1-only enum is
|
|
989
|
+
pinned now; T2/T3 land with the W3 ingestion pipeline),
|
|
990
|
+
`inboundLinkCount`, `childrenCount` and `outboundPreview[]`. All of it is
|
|
991
|
+
aggregated IN PLACE from the existing `memory_links` mirror
|
|
992
|
+
(`MemoryStore.pageGraphContext`) — no new table, no new sync channel.
|
|
993
|
+
Legacy fields (`path`/`title`/`snippet`/`score`/`tokenCount`) are
|
|
994
|
+
untouched, so an un-upgraded consumer keeps working.
|
|
995
|
+
- **dynamic graph band** (spec open ruling point 3, W1 experiment): the old
|
|
996
|
+
constant "every graph hit one unit below the weakest FTS hit" is replaced.
|
|
997
|
+
When the text leg has hits, graph hits stay the supplementary band (the
|
|
998
|
+
old invariant still holds and is still tested). When text misses
|
|
999
|
+
ENTIRELY — the memory211 §1 paraphrase failure, where the answering page
|
|
1000
|
+
shares zero tokens with the query — the graph leg becomes the recall lane:
|
|
1001
|
+
it seeds from INDEX + hubs and scores a structurally-anchored hit
|
|
1002
|
+
(≥1 inbound link or a resolvable hub) in the normal 0-1 band instead of
|
|
1003
|
+
the sub-zero decay band. `graph: false` keeps the legacy pure-FTS path.
|
|
1004
|
+
- **batch recall**: `GET /local/memory/search` accepts `queries` (JSON
|
|
1005
|
+
array, ≤8; over-limit is sliced with `truncated: true`) and returns
|
|
1006
|
+
`resultsByQuery: [{query, results}…]` plus the legacy `query`/`results`
|
|
1007
|
+
fields, which stay the FIRST query's so an old consumer sees today's
|
|
1008
|
+
shape. One `recall_pull` event is emitted per query (a batch carries N
|
|
1009
|
+
recall intents, not one). The single-`q` form is a batch of one.
|
|
1010
|
+
- **tree-shaped browse**: every hub in `GET /local/memory/place-context`
|
|
1011
|
+
(and in the `memory_browse` payload) now carries
|
|
1012
|
+
`children: [{path, title}]` from the local `child-of` edges; `nearest` is
|
|
1013
|
+
deliberately unchanged (graph-distance ordering is deferred to W1b).
|
|
1014
|
+
- **load 离线 links**: when the cloud does NOT answer `GET
|
|
1015
|
+
/memory/pages/:id/links` (unreachable, non-2xx, or never wired), the load
|
|
1016
|
+
response now falls back to the LOCAL link mirror instead of silently
|
|
1017
|
+
omitting `links`, assembled in the same `{outbound, backlinks}` envelope.
|
|
1018
|
+
An authoritative cloud answer is never overridden.
|
|
1019
|
+
- Covered by `test/memory-search-payload.test.ts` (payload fields + 升带,
|
|
1020
|
+
with the re-band and the offline-links fallback each carrying a mutation
|
|
1021
|
+
negative control), `test/memory-search-batch.test.ts`,
|
|
1022
|
+
`test/memory-browse-children.test.ts` and
|
|
1023
|
+
`test/memory-load-links-offline.test.ts`.
|
|
1024
|
+
- metric outbox replay (B-P1a): the per-agent `metrics.jsonl` offline outbox
|
|
1025
|
+
(written by `daemonMetricEmit` whenever cloud is unreachable) finally has a
|
|
1026
|
+
reader. `MetricOutboxReplayWorker` (new `daemon/metric-outbox-replay.ts`,
|
|
1027
|
+
wired into the runner lifecycle) drains it into `POST /api/im/metrics/batch`
|
|
1028
|
+
— the same endpoint the online path uses, with the same per-event field
|
|
1029
|
+
shape (`eventType` splits at the first dot into `namespace.name`; value /
|
|
1030
|
+
dims / ts pass through as serialised; the entry's top-level workspace /
|
|
1031
|
+
project / task mirrors repair dims lost in the payload). Delivery is
|
|
1032
|
+
**at-least-once**: the file is frozen with `renameSync` BEFORE it is read and
|
|
1033
|
+
unlinked only after every ≤500-event batch chunk came back HTTP-ok, so a
|
|
1034
|
+
crash anywhere replays the file (additive metrics tolerate the duplicate).
|
|
1035
|
+
Freezing before reading (rather than truncating in place) is what makes a
|
|
1036
|
+
line appended by a concurrent emit during the cloud round-trip survive — it
|
|
1037
|
+
lands in a fresh live file and drains next tick. Bad-JSON lines and non
|
|
1038
|
+
`metric.event` kinds are counted, logged and dropped (they would retry
|
|
1039
|
+
forever otherwise); cloud-side per-event rejections are likewise logged and
|
|
1040
|
+
dropped, HTTP-level failures retain the whole file with 5s→10min backoff and
|
|
1041
|
+
a warn→error escalation after 5 failed ticks. Triggered on daemon startup and
|
|
1042
|
+
every 60s (timer unref'd); no notify hook on the write path — offline metrics
|
|
1043
|
+
are additive and a ≤60s lag is acceptable. Covered by
|
|
1044
|
+
`test/metric-outbox-replay.test.ts` (batch content, retention + retry,
|
|
1045
|
+
empty/missing no-op, chunking, backoff escalation, and a mutation negative
|
|
1046
|
+
control proving a mid-flight append is not lost under truncate).
|
|
1047
|
+
- turn metrics (B-P0, agent performance pipeline): the daemon now emits eight
|
|
1048
|
+
turn-level metric events from the dispatch `finally` block alongside the
|
|
1049
|
+
existing `agent.dispatch` / `skill.invoked` batch — `turn.count`,
|
|
1050
|
+
`turn.tokens_input`, `turn.tokens_output`, `turn.tokens_cache_read`,
|
|
1051
|
+
`turn.tokens_cache_write`, `turn.duration_ms`, `turn.first_event_ms`,
|
|
1052
|
+
`turn.tool_calls`. Dims: required `workspaceId` + `agentId`; optional
|
|
1053
|
+
`conversationId`, `taskId`, `model`, `provider`, `status` (`ok`|`error`).
|
|
1054
|
+
Usage comes from the TERMINAL attempt's hermes usage only — retry inflation
|
|
1055
|
+
stays in `agent.dispatch`. Token rows are emitted per-field only when the
|
|
1056
|
+
terminal attempt reported them. `turn.first_event_ms` is new instrumentation
|
|
1057
|
+
on the hermes sessions lane: `consumeSessionsSse` captures the first
|
|
1058
|
+
stall-guard activation (gateway acks excluded) and derives it against the
|
|
1059
|
+
dispatcher's `startedAt`; `TaskResult.metrics` carries the new optional
|
|
1060
|
+
`firstEventMs`. Emit failures stay non-fatal (observability path). Covered
|
|
1061
|
+
by `test/daemon-metric-emit.test.ts` + `test/sessions-sse-first-event.test.ts`.
|
|
1062
|
+
- config bootstrap (B1): 403 `SETUP_PIN_PENDING` (cloud: the workspace setup
|
|
1063
|
+
runner has provisioned the pod but has not persisted the canonical API-key
|
|
1064
|
+
pin yet) now backs off with the existing curve instead of terminal-stopping,
|
|
1065
|
+
bounded by `SETUP_PIN_RETRY_WINDOW_MS` (5 min) after which it degrades to the
|
|
1066
|
+
ordinary terminal 403. `BootstrapFetchResult` carries the cloud `error.code`
|
|
1067
|
+
and `BootstrapState.pinPendingSince` holds the window anchor — cleared by
|
|
1068
|
+
`resetBootstrapStopped` AND on every successful fetch (per-episode, so a
|
|
1069
|
+
later absent-pin episode gets a fresh window). Fixes fresh workspaces coming
|
|
1070
|
+
up memoryless when the
|
|
1071
|
+
pod daemon's first B1 fetch raced the pin persist (2026-09-02 ws-72d282f9).
|
|
1072
|
+
Covered by `test/config-bootstrap.test.ts`.
|
|
1073
|
+
- memory write: write-time bare-asset-URI upgrade (pkf v1.1 §4.7). The
|
|
1074
|
+
memory skill / dispatch guidance / extraction prompt teach the bare
|
|
1075
|
+
`prismer://asset/<id>` pointer form (G9), but v1.1 strict validation
|
|
1076
|
+
rejects it on read-back (`bare-asset-uri`, compat-read only) — the
|
|
1077
|
+
canonical authoring form is `prismer://workspace/<wsId>/asset/<contentHash>`.
|
|
1078
|
+
`POST /local/memory/write` now deterministically rewrites every bare
|
|
1079
|
+
href/src pointer AFTER the G9 gate and BEFORE the page lands (store + outbox
|
|
1080
|
+
up-sync both carry the upgraded form; rel / link text / quote style /
|
|
1081
|
+
attribute order survive). assetId → contentHash resolves from the workspace
|
|
1082
|
+
AssetMetadataIndex (one throttled delta pull forced on a miss); unresolvable
|
|
1083
|
+
assets degrade to the workspace-scoped assetId form
|
|
1084
|
+
`prismer://workspace/<ws>/asset/<assetId>` (still v1.1-valid) — the write is
|
|
1085
|
+
NEVER blocked. Counters: `memory.counters.bareUriUpgraded` /
|
|
1086
|
+
`bareUriScopedDegrade` + `[memory-trace] stage=bare_uri_upgrade`. Covered by
|
|
1087
|
+
`test/memory-write-bare-uri-upgrade.test.ts`.
|
|
1088
|
+
|
|
1089
|
+
- daemon local gateway (desktop205 alignment round, 2026-08-31):
|
|
1090
|
+
- `PRISMER_LOCAL_GATEWAY` is now injected by the desktop embedded fork —
|
|
1091
|
+
the local data plane (SWR reads, SSE mirror, outbox writes) activates on
|
|
1092
|
+
desktop instead of being structurally unreachable.
|
|
1093
|
+
- user-Bearer acceptance: the gateway now serves the renderer's USER session
|
|
1094
|
+
JWT / sk-prismer key in addition to the daemon key. Introspected against
|
|
1095
|
+
the cloud (`/api/im/me`) and BOUND TO THE DAEMON OWNER — any other valid
|
|
1096
|
+
account token is refused (review hardening: previously any valid token
|
|
1097
|
+
could read the owner's SWR cache from any same-machine page via the
|
|
1098
|
+
permissive CORS origin). Cache is keyed by token SHA-256 digest with
|
|
1099
|
+
positive/negative TTLs; the raw bearer is never retained.
|
|
1100
|
+
- CORS: Allow-Headers now includes X-IM-Workspace/X-Request-Id/
|
|
1101
|
+
X-Idempotency-Key and Expose-Headers includes X-Data-Stale (the desktop
|
|
1102
|
+
offline indicator can now light on gateway-served responses).
|
|
1103
|
+
- SWR revalidation for /api/im/conversations is scoped to the daemon's
|
|
1104
|
+
declared workspace (`workspaceId()` dep); passthrough forwards the
|
|
1105
|
+
caller's X-IM-Workspace/X-Request-Id so multi-workspace identity survives
|
|
1106
|
+
the loopback hop.
|
|
1107
|
+
|
|
1108
|
+
- hermes adapter: `stopHermesGatewayForProfile` is now a logged no-op when the
|
|
1109
|
+
profile config fails `HermesProfileConfigSchema` (e.g. ACS sandbox fixtures
|
|
1110
|
+
carry no `apiKey`). Previously the uncaught ZodError escaped the
|
|
1111
|
+
`ServicePool.invalidate` disposer in `Runner.rebindHermesMemoryCapabilities`,
|
|
1112
|
+
crashed the daemon (exit 1), and after two retries the bootstrapper
|
|
1113
|
+
blacklisted the whole runtime bundle — sandbox healthz stuck at 503
|
|
1114
|
+
(reproduced 2026-08-30 by the dev-local fxr acceptance fixture). Covered by
|
|
1115
|
+
`test/hermes-stop-gateway-noop.test.ts`.
|
|
1116
|
+
- sandbox-manager: removed the orphaned `resolve_daemon_id` wrapper (its only
|
|
1117
|
+
caller switched to `resolve_daemon_id_for_boot` in d74e00074); the dead
|
|
1118
|
+
symbol failed the dev-local rust-manager clippy gate (`-D warnings`).
|
|
1119
|
+
- Memory post-turn extraction no longer mints pages from agent
|
|
1120
|
+
self-introductions (kickoff / "你们都能干什么" turns): a deterministic
|
|
1121
|
+
`agent_self_intro` skip in `shouldSkipExtraction` (narrow shape match on the
|
|
1122
|
+
reply head; an explicit user retention contract still passes) plus an
|
|
1123
|
+
`AGENT SELF-DESCRIPTIONS ARE NOT DURABLE` rule in the extraction prompt.
|
|
1124
|
+
2026-08-29 test incident: one role page per self-intro reply in a 3-agent
|
|
1125
|
+
kickoff (see docs/product210/03 附录 A-4). Tests:
|
|
1126
|
+
`sdk/prismer/test/memory-extract-self-intro.test.ts`.
|
|
1127
|
+
- Filesystem-checkpoint restores now rebind every cached Hermes gateway after
|
|
1128
|
+
the per-boot Cloud Memory authority snapshot first becomes available. The
|
|
1129
|
+
gateway is atomically invalidated and prewarmed with a freshly minted
|
|
1130
|
+
`PRISMER_MEMORY_CAP`, including when the Runtime config bytes are unchanged.
|
|
1131
|
+
Explicit Memory GET tools also preserve structured daemon 401/403 responses
|
|
1132
|
+
instead of misreporting them as empty data or `daemon_unreachable`. Managed
|
|
1133
|
+
Hermes plugins now stage and directory-swap upgrades, so 0444 files restored
|
|
1134
|
+
from a Checkpoint no longer turn a valid Runtime update into EACCES.
|
|
1135
|
+
|
|
1136
|
+
- Replayed Hermes profile preparation now treats an already byte-identical
|
|
1137
|
+
`prismer-recall` plugin as a no-write success. Restored/hardened profile
|
|
1138
|
+
trees no longer emit false `EACCES` install warnings on repeated
|
|
1139
|
+
`host.acked`, while changed plugin bytes still fail visibly if unwritable.
|
|
1140
|
+
|
|
1141
|
+
- ACS workspace adoption now overlaps independent agents with a sliding,
|
|
1142
|
+
bounded three-agent worker pool after the initial 0/0 `host.declare`;
|
|
1143
|
+
profiles belonging to one agent remain ordered, failures remain isolated,
|
|
1144
|
+
and the daemon emits one aggregate adoption timing before the final roster
|
|
1145
|
+
declaration.
|
|
1146
|
+
|
|
1147
|
+
- Aligned Runtime package metadata with monorepo version `2.2.39` for a new
|
|
1148
|
+
immutable sandbox image containing the workspace-neutral template marker and
|
|
1149
|
+
clone identity behavior; Runtime OTA publication remains a separate chain.
|
|
1150
|
+
|
|
1151
|
+
- Promoted the canonical `remotion` skill into the common Agent baseline and
|
|
1152
|
+
calibrated its sandbox workflow against current Remotion guidance. Rendered
|
|
1153
|
+
media now has an explicit artifacts-dir → `cloud deliver` receipt contract,
|
|
1154
|
+
plus version/composition, async-asset, deterministic-media, writable-cache,
|
|
1155
|
+
browser-compatibility, adjacent-frame, and probe gates.
|
|
1156
|
+
|
|
1157
|
+
- ACS environment-template sources now initialize the full adapter floor without
|
|
1158
|
+
opening Cloud transport or binding a workspace. Template clones discard the
|
|
1159
|
+
source daemon identity and resume normal bootstrap before Cloud dispatch.
|
|
1160
|
+
- Objective dispatch prompts now preserve the task/objective mirror fields used
|
|
1161
|
+
by Cloud OKR flows, so hosted runtime turns keep objective context attached
|
|
1162
|
+
through dispatch and post-turn durability.
|
|
1163
|
+
- Sandbox runtime readiness now requires the daemon health identity to match the
|
|
1164
|
+
claimed workspace and daemon id before Cloud accepts an ACS dialback as ready.
|
|
1165
|
+
|
|
1166
|
+
- Image generation delivery is now single-path and run-aware. The bundled
|
|
1167
|
+
`image-generate` helper performs model discovery/generation and invokes
|
|
1168
|
+
`cloud deliver` once. Runtime marks that file snapshot handled so the
|
|
1169
|
+
dispatch-final scan makes no second upload; scan-only and offline-replay
|
|
1170
|
+
fallbacks use the same unbound `/runs/<id>` scope for chat runs. The result
|
|
1171
|
+
is one upload path, one user-visible root IMAsset (plus normal internal
|
|
1172
|
+
preview derivatives), and one reply attachment.
|
|
1173
|
+
- Explicit-delivery snapshot de-dup is scoped to the cloud dispatch and cleared
|
|
1174
|
+
at teardown instead of living for the daemon process lifetime. This also
|
|
1175
|
+
bridges Hermes-local run ids to cloud dispatch ids, preventing both
|
|
1176
|
+
same-turn double uploads and later-dispatch false skips. A local :3000 live
|
|
1177
|
+
acceptance now verifies one user-visible Asset, one attachment, and no JSON
|
|
1178
|
+
in the message body through the real helper/CLI/daemon path.
|
|
1179
|
+
|
|
1180
|
+
- SS-01 bundle parser parity with Cloud: YAML `|` / `>` block-scalar
|
|
1181
|
+
descriptions are supported, and the 50-unit description floor is
|
|
1182
|
+
language-aware so concise CJK metadata does not require filler.
|
|
1183
|
+
|
|
1184
|
+
- Closed the Runtime PKF/Memory/Dream contract: bundled `memory` and
|
|
1185
|
+
`memory-dream` now mirror the carrier transition and authoritative-candidate
|
|
1186
|
+
rules; `parentHubPath` is consistent across schemas/prompts/errors; curate
|
|
1187
|
+
schema exposes `conflicts` and `oversized`; and `rebuild_index` is described
|
|
1188
|
+
truthfully as hub-TOC maintenance with graph-derived INDEX Contents. Daemon
|
|
1189
|
+
section addressing now reads complete PKF `<section>` wrappers so section
|
|
1190
|
+
edits cannot consume neighbouring tags.
|
|
1191
|
+
- Long/structured replies now default to validated inline PKF across hosted
|
|
1192
|
+
adapters without asking the user to choose a format. Runtime passes the
|
|
1193
|
+
canonical inline source—not only its short Markdown projection—into post-turn
|
|
1194
|
+
durability, so lasting conclusions can be distilled into searchable Memory.
|
|
1195
|
+
- Automatic Memory extraction now rejects unclassified leaves, verifies model
|
|
1196
|
+
placement against the browse snapshot, promotes unmatched new topics to hubs,
|
|
1197
|
+
and emits knowledge-profile PKF with semantic sections and real descriptions.
|
|
1198
|
+
The direct write RPC enforces placement by default.
|
|
1199
|
+
- Removed the obsolete daemon Dream trigger path. `FF_MEMORY_DREAM_ENABLED`
|
|
1200
|
+
cannot revive the retired page-dream scheduler; Cloud scheduling followed by
|
|
1201
|
+
the appointed orchestrator is the sole automatic authority.
|
|
1202
|
+
|
|
1203
|
+
- Headless SERP + cache regression (runtime210/01, 2026-08-23): agent-first
|
|
1204
|
+
local web search/read for in-pod agents using the sandbox image's existing
|
|
1205
|
+
Playwright+Chromium, with results flowing back into the cloud
|
|
1206
|
+
`im_context_cache` (decentralized data regression).
|
|
1207
|
+
- Engine chain (`daemon/web/headless-serp.ts`): Bing (chromium, with
|
|
1208
|
+
`bing.com/ck/a` base64url redirect decoding), Google (chromium,
|
|
1209
|
+
`FF_WEB_HEADLESS_SERP_GOOGLE` opt-in; consent/challenge detection), DDG
|
|
1210
|
+
(plain HTTP with `uddg=` decoding + anomaly detection). Coalescing
|
|
1211
|
+
single-flight + 2s interval rail; engine-chain fallthrough; SSRF gate
|
|
1212
|
+
(http(s)-only, public-address-only resolution, subresource/redirect
|
|
1213
|
+
interception, final-URL recheck; TUN fake-IP range blocked by default,
|
|
1214
|
+
explicit `PRISMER_HEADLESS_ALLOW_FAKE_IP=1` opt-in for dev machines).
|
|
1215
|
+
- Deposit-dedup journal (`daemon/web/seen-journal.ts`) so agents never
|
|
1216
|
+
overwrite each other's cache entries (ContextCacheService.deposit is a
|
|
1217
|
+
blind upsert).
|
|
1218
|
+
- `daemon/web/rpc.ts`: `FF_WEB_HEADLESS_SERP` routing — **default
|
|
1219
|
+
`fallback`** (cloud Exa+Serper keeps relevance; headless rescues only when
|
|
1220
|
+
cloud fails, relevance not promised). Explicit `off` / `local-first` still
|
|
1221
|
+
available. Local responses are
|
|
1222
|
+
load-API-shaped (`success/requestId/mode/results/summary/cost/
|
|
1223
|
+
processingTime`) plus `degraded`/`providerAttempts`, so provider-shell
|
|
1224
|
+
tools and payload bounding are untouched. Reads/search hits deposit back
|
|
1225
|
+
via the existing free-tier `POST /api/context/save`. New
|
|
1226
|
+
`GET /local/web/doctor` health probe.
|
|
1227
|
+
- Real-scenario validation (2026-08-23): C6 corpus 20/20 on Bing, fetch of
|
|
1228
|
+
real pages (~2s for a 57KB Wikipedia article), Google consent + DDG
|
|
1229
|
+
anomaly challenge detection verified live. Expression-matrix finding:
|
|
1230
|
+
search operators (site:/quotes/boolean) are ignored on cookieless
|
|
1231
|
+
automated traffic — engine-side behavior, not a parser defect.
|
|
1232
|
+
- Added the embedded `pi-core` runtime adapter floor: the SDK now pins
|
|
1233
|
+
`@earendil-works/pi-agent-core@0.84.2` and `@earendil-works/pi-ai@0.84.2`,
|
|
1234
|
+
registers `pi-core` as the canonical adapter/provider identity, injects
|
|
1235
|
+
Prismer gateway routes through `PRISMER_PI_*`, and validates jailed cwd
|
|
1236
|
+
read/write/edit tool execution. Daemon/desktop/K8s runtime bundle gates now
|
|
1237
|
+
share the `runtime-required-entries.json` manifest so Pi Core dependencies
|
|
1238
|
+
cannot drift across packers and loaders.
|
|
1239
|
+
|
|
1240
|
+
- Hosted Hermes turns now bind each native `run_*` row to its exact provider
|
|
1241
|
+
`api_*` session and capture bounded post-response model/provider evidence via
|
|
1242
|
+
the signed `prismer-recall` lifecycle bridge. Post-turn Memory resolution
|
|
1243
|
+
excludes synthetic session-cache rows, rejects unknown or identity-mismatched
|
|
1244
|
+
evidence, and no longer falls into ambiguous-agent / configured-model
|
|
1245
|
+
fallbacks for a real sessions-API turn.
|
|
1246
|
+
|
|
1247
|
+
- Closed the hosted PKF execution gap found by a real Hermes task: the shipped
|
|
1248
|
+
Python provider now registers `pkf_validate` / `pkf_outline` / `pkf_search` /
|
|
1249
|
+
`pkf_read` and forwards them to bounded `/local/pkf/*` Runtime routes backed
|
|
1250
|
+
by the staged `@prismer/pkf-core`. Native tool names are no longer presented
|
|
1251
|
+
as shell binaries. Hermes startup also installs a direct managed `cloud`
|
|
1252
|
+
symlink to the signed bundle's `@prismer/sdk/dist/cli.js`, including a safe
|
|
1253
|
+
`~/.local/bin` alias for login-shell PATH resets.
|
|
1254
|
+
|
|
1255
|
+
- Agent-output policy now accepts `.pkf` as
|
|
1256
|
+
`application/vnd.prismer.pkf+html` with the 5 MiB source budget, so a
|
|
1257
|
+
dispatch-final forced artifact scan can upload PKF deliverables. Expensive
|
|
1258
|
+
`host.acked` catch-up is connection-epoch/workspace idempotent: every ack
|
|
1259
|
+
still handles governance/profile deltas, while transport (retry-on-failure),
|
|
1260
|
+
Memory and Asset catch-up no longer repeat on each 30-second heartbeat.
|
|
1261
|
+
|
|
1262
|
+
- Runtime built-in delivery now ships canonical `pkf-svg` (with historical
|
|
1263
|
+
`pkf-visual` resolved through its metadata alias), installs both `pkf-svg`
|
|
1264
|
+
and `pkf-writing` as common coding skills, and verifies byte-identical
|
|
1265
|
+
catalog → bundled-fallback mirroring. The skill preserves the current PKF
|
|
1266
|
+
v1.1 production boundary: Mermaid/d3, not inline SVG authoring.
|
|
1267
|
+
|
|
1268
|
+
- `pkf-writing` now defines message-inline, Library `.pkf` Asset, and Memory
|
|
1269
|
+
Page carriers with a unique Runtime extraction sentinel contract plus
|
|
1270
|
+
canonical validation/persist/readback/Markdown-projection receipts.
|
|
1271
|
+
|
|
1272
|
+
- `pkf-writing` skill acceptance moved to the single-file contract
|
|
1273
|
+
(product209/19 WP2): SKILL.md < 180 lines with the references/ directory
|
|
1274
|
+
gone; the acceptance gates now carry explicit negative controls (over-budget
|
|
1275
|
+
content, invented commands, references/ import, bare asset URI all go red);
|
|
1276
|
+
the trigger matrix covers the Chinese recall intent ("recall 我的记忆" →
|
|
1277
|
+
memory). The prebuild built-in-skills mirror follows the catalog.
|
|
1278
|
+
|
|
1279
|
+
- Hermes gateway health check accepts `degraded` (product209/18): hermes
|
|
1280
|
+
0.20.0 reports top-level status `degraded` under disk pressure (>= 90%)
|
|
1281
|
+
while gateway + api_server are fully working; the old `status === 'ok'`
|
|
1282
|
+
gate bricked dispatch (30s x 3 retries + kill-respawn storm). The
|
|
1283
|
+
evaluator now requires gateway_state=running + api_server connected and
|
|
1284
|
+
accepts ok|degraded; the spawn stamp is written before the health wait so
|
|
1285
|
+
a slow/degraded gateway can't be misread as an un-witnessed orphan.
|
|
1286
|
+
|
|
1287
|
+
- Attachment blocks carry executable addresses (product209/18 Part A):
|
|
1288
|
+
non-text non-vision attachments now emit `prismer://workspace/<ws>/asset/
|
|
1289
|
+
<hash>` (stable reference) plus `path=file://<localPath>` for adapters
|
|
1290
|
+
with real filesystem tools (hermes gated by toolsetScope terminal/file;
|
|
1291
|
+
claude-code/codex/opencode). Tool-less adapters keep the legacy reminder
|
|
1292
|
+
and never see a path.
|
|
1293
|
+
|
|
1294
|
+
- Asset materialization push (product209/18 Part B): new WS frames
|
|
1295
|
+
`asset.materialize.request` (cloud → daemon, requestId=assetId) and
|
|
1296
|
+
`asset.materialize.reply` (daemon → cloud). The daemon materializes
|
|
1297
|
+
uploaded asset bytes into the local asset-cache on demand so the upload
|
|
1298
|
+
progress bar completes only when bytes are in the pod; failures reply
|
|
1299
|
+
with ok:false and cloud records no state (frontend polls and degrades).
|
|
1300
|
+
|
|
1301
|
+
- Canonical `pkf-writing` skill (product209/15 PKF-C1): slim SKILL.md + 6
|
|
1302
|
+
progressive-disclosure references (core format / semantic HTML / media-data-
|
|
1303
|
+
files / math / harness / validation-projection-export). Capability-based lane
|
|
1304
|
+
choice (structured tools vs native filesystem lane); resolved readback
|
|
1305
|
+
required before completion. Installed on coding agents via the common
|
|
1306
|
+
allowlist; description routes recall/curation to `memory`.
|
|
1307
|
+
|
|
1308
|
+
- PKF filesystem service (product209/15 PKF-H5): durable `pkf_checkouts`
|
|
1309
|
+
registry (daemon SQLite, idempotent boot migration) + four PATH-ONLY
|
|
1310
|
+
lifecycle specs. These names are not advertised to Hermes until a shipped
|
|
1311
|
+
provider handler can supply the task-root context. Ordinary UTF-8 files in
|
|
1312
|
+
the task root; symlink/path
|
|
1313
|
+
escape/device rejection; CRLF/BOM churn detected (explicit flag required);
|
|
1314
|
+
final file bytes commit through the Cloud carrier CAS
|
|
1315
|
+
(`POST /api/im/pkf/commit`); unlink never triggers a remote delete.
|
|
1316
|
+
|
|
1317
|
+
- SQLite V3 + snapshot-fenced full reconcile (product209/16 MA-2, Task 12):
|
|
1318
|
+
`memory.db` migrates 2 → 3 in ONE transaction — `memory_replica_state`
|
|
1319
|
+
(per-workspace cursor / accessVersion / replicaSubjectHash /
|
|
1320
|
+
contentHighWatermark / reconciling|ready|suspended|stale / lease) plus
|
|
1321
|
+
`memory_pages.sourceKind` and `replicaActorIdsJson`; the old
|
|
1322
|
+
`memory_inbox_cursor` is migrated read-only (accessVersion=0, suspended —
|
|
1323
|
+
never an authorization input), an in-flight `reconciling` flips to
|
|
1324
|
+
`suspended` on restart, and a newer-schema db fails closed with a typed
|
|
1325
|
+
`MemorySchemaIncompatibleError` (never a downgrade write). The runtime
|
|
1326
|
+
consumes the Task 11 manifest/content ports: first manifest page pins
|
|
1327
|
+
epoch/subject/high-watermark, content comes ONLY via
|
|
1328
|
+
`POST /memory/sync/content` (snapshotToken + sourceRevisionId +
|
|
1329
|
+
transport/content hashes), encrypted-pkf verifies transportHash → decrypts
|
|
1330
|
+
→ verifies plaintext contentHash, legacy-html lands via the compat text
|
|
1331
|
+
path (never the local PKF write/outbox path), replicated rows carry the
|
|
1332
|
+
sorted exact actor set, and the ACL-shrink diff (complete set vs local
|
|
1333
|
+
controlled rows) deletes page/version/content/link/FTS — heads, deletes,
|
|
1334
|
+
tombstones and cursor/epoch/hash/lease/ready commit atomically, followed
|
|
1335
|
+
by a catch-up pass for content created mid-reconcile. Agent recall fails
|
|
1336
|
+
closed (typed `MemoryReplicaNotReadyError`) unless the replica is ready
|
|
1337
|
+
AND the live registered authority snapshot still matches the pinned
|
|
1338
|
+
epoch/subject hash AND neither the snapshot nor the local lease expired;
|
|
1339
|
+
`memory.invalidate` suspends the replica first. The boundary ACL predicate
|
|
1340
|
+
now checks `replicaActorIdsJson` membership (cap `sub` ∈ exact set,
|
|
1341
|
+
empty set denies all, tampered JSON fails closed) BEFORE coarse
|
|
1342
|
+
visibility/principal rules. Legacy daemon sync receiving 426
|
|
1343
|
+
`MEMORY_RUNTIME_UPGRADE_REQUIRED` stops typed (no fallback) and surfaces
|
|
1344
|
+
`SyncResult.upgradeRequired { minRuntimeVersion,
|
|
1345
|
+
requiredRuntimeCapabilities }`; strict workspaces are routed to the V3
|
|
1346
|
+
port automatically. `MIN_STRICT_RUNTIME_VERSION = '2.2.12'` (first build
|
|
1347
|
+
with SQLite V3, same source as the Cloud rollout constant).
|
|
1348
|
+
|
|
1349
|
+
- Memory authority snapshot + cap v2 (product209/16 MA-1B, Task 10): the
|
|
1350
|
+
daemon registers the Cloud-delivered `memoryAuthority` snapshot bundle
|
|
1351
|
+
(optional field on the RuntimeConfigBundle, 60m lease) and mints cap v2
|
|
1352
|
+
ONLY from a valid snapshot — server-derived actor claims (principal /
|
|
1353
|
+
taskIds / councilIds / verbs), 15m TTL clamped to
|
|
1354
|
+
`snapshot.validUntil`. No valid snapshot (never fetched / expired /
|
|
1355
|
+
tampered / epoch regression) → NO cap is injected and agent Memory RPC
|
|
1356
|
+
fails closed. v1 tokens still verify for one compat release cycle (the v1
|
|
1357
|
+
minter is retained only as the compat encoder; production mint sites
|
|
1358
|
+
migrated to `mintCapV2`). The boundary ACL predicate now adjudicates
|
|
1359
|
+
actor/principal/task claims: a deputy cap reads its bound member's
|
|
1360
|
+
`human:*` pages (strict principal-id equality, never as `sub`, never
|
|
1361
|
+
widened to other agents/members). `agent.host.declare` now carries
|
|
1362
|
+
`runtimeCapabilities` (the three §8.2 v1 capabilities) and the daemon
|
|
1363
|
+
handles the `memory.authority.invalidate` owned-channel frame (drop
|
|
1364
|
+
snapshot + forced bootstrap refresh).
|
|
1365
|
+
|
|
1366
|
+
- Memory RPC write/outbox atomicity (product209/16 MA-0S, M-OUTBOX-001):
|
|
1367
|
+
`POST /local/memory/write` now commits the page aggregate
|
|
1368
|
+
(page/version/content/FTS) and its outbox events (page upsert + placement
|
|
1369
|
+
link) in ONE SQLite transaction, reusing the post-turn
|
|
1370
|
+
extracted-page-applicator pattern. An outbox enqueue failure rolls the whole
|
|
1371
|
+
aggregate back and the RPC returns 500 instead of the old success that
|
|
1372
|
+
stranded the page local-only. Success responses now also carry
|
|
1373
|
+
`localVersion` and `outboxEventId`; the idempotency key stays derived from
|
|
1374
|
+
the logical write identity (page id + parentVersion + contentHash), so
|
|
1375
|
+
retries with a stable caller-supplied page id keep the same key.
|
|
1376
|
+
|
|
1377
|
+
- Native bounded query tools `pkf_outline` / `pkf_search` / `pkf_read`
|
|
1378
|
+
(product209/15 PKF-H2): in-process over the bundled core — 32 KiB output
|
|
1379
|
+
budget, signed paged cursors, 16/64 KiB read bounds, no includeSource
|
|
1380
|
+
escape, no regex query surface. Offline; same receipts as Cloud.
|
|
1381
|
+
|
|
1382
|
+
- Native `pkf_validate` tool (product209/15 PKF-D3): in-process PKF validation
|
|
1383
|
+
over the bundled pkf-core — no shell-out, no daemon RPC, offline by
|
|
1384
|
+
construction. Registered across the Hermes / Claude Code / Codex tool
|
|
1385
|
+
surfaces; same golden-fixture results as Cloud.
|
|
1386
|
+
|
|
1387
|
+
- PKF core staging (product209/15 PKF-D2): the runtime now stages a
|
|
1388
|
+
hash-verified `@prismer/pkf-core` build into its local node_modules during
|
|
1389
|
+
`prebuild` (`sdk/build/stage-pkf-core.cjs`), so offline parsing/validation
|
|
1390
|
+
runs from the bundled core with no cloud network and no monorepo root source
|
|
1391
|
+
(`test/pkf-core-staged.test.ts`). The staged copy never lands in the
|
|
1392
|
+
published tarball; the standalone pack gate verifies both tarballs unpack
|
|
1393
|
+
and run without the repo root.
|
|
1394
|
+
|
|
1395
|
+
- The bundled Remotion catalog now ships one self-contained canonical
|
|
1396
|
+
`remotion` skill instead of twelve duplicate top-level skills plus a set of
|
|
1397
|
+
short topic references. Runtime fallback resolves historical slugs through
|
|
1398
|
+
canonical metadata aliases, and installed-skill delivery collapses multiple
|
|
1399
|
+
legacy edges to one prompt section while preserving the selected historical
|
|
1400
|
+
skill ID for sync acknowledgements.
|
|
1401
|
+
- Hermes session creation and every chat turn now lock the configured `model`
|
|
1402
|
+
(`require_model_lock: true`) while deliberately omitting `provider`. The
|
|
1403
|
+
dedicated per-profile gateway resolves its authoritative named provider
|
|
1404
|
+
before session creation; model-only locks let pre-lock sessions self-heal
|
|
1405
|
+
after an OTA without Hermes v2026.8.3's named-provider identity mismatch.
|
|
1406
|
+
- Runtime OTA bundles now carry the in-tree Cloud CLI/SDK and its bundled AIP
|
|
1407
|
+
package. The frozen sandbox image no longer supplies these JavaScript product
|
|
1408
|
+
packages; the signed boot bundle is their single delivery path. The resident
|
|
1409
|
+
Rust manager now starts health before bundle resolution, retries when a fresh
|
|
1410
|
+
Pod has no bundle, prepends the bundle CLI path, and is the sole owner of
|
|
1411
|
+
Runtime child respawn.
|
|
1412
|
+
- Signed Runtime manifests now record exact Runtime, Cloud SDK, and AIP package
|
|
1413
|
+
provenance and reject native `.node` payloads. Packing also requires the
|
|
1414
|
+
signing private key to match either the explicit rotated daemon public key or
|
|
1415
|
+
the bootstrapper's built-in public key, preventing bundles that self-verify
|
|
1416
|
+
during packing but cannot verify inside a Pod.
|
|
1417
|
+
- Sandbox Pod probes now separate manager liveness (`:7890/healthz`) from
|
|
1418
|
+
Runtime readiness (`:7890/readyz`). A fresh Pod waiting for a signed bundle
|
|
1419
|
+
remains manager-alive but NotReady; Runtime child failure no longer makes
|
|
1420
|
+
kubelet kill the manager that owns recovery.
|
|
1421
|
+
- Bundle pointers and boot markers are atomically written as `0640 user:user`,
|
|
1422
|
+
so both the Runtime owner and the group-member resident manager can read
|
|
1423
|
+
rollback state even under a strict `0077` launcher umask.
|
|
1424
|
+
- Sandbox Manager S2 (WP-E r4): Parameterize `execute_disk_cleanup` slow-ms switch
|
|
1425
|
+
as a function parameter (previously read from process-global env). Eliminates
|
|
1426
|
+
non-deterministic test races under parallel `cargo test` — unit tests now pass
|
|
1427
|
+
slow_ms directly. Production path reads env once in `execute_action`. No
|
|
1428
|
+
protocol/behavior change.
|
|
1429
|
+
- Sandbox Manager S2 (WP-E, product209/09 S2): OTA settle logic (strike/rollback/
|
|
1430
|
+
blacklist) migrated from TS (`ota-check.ts settlePreviousBoot`) into the Rust
|
|
1431
|
+
manager (`ota.rs`). File formats unchanged — boot-attempt.json, pointer files,
|
|
1432
|
+
blacklist.json match TS `bundle-store.ts` byte-for-byte. Manager settles
|
|
1433
|
+
unconfirmed boot markers BEFORE OTA resolve; `SANDBOX_MANAGER_PRESENT` env
|
|
1434
|
+
disables TS-side settle to prevent double-settle. Config backup point:
|
|
1435
|
+
`config-bootstrap.ts applyBundle` now writes backup to
|
|
1436
|
+
`~/.prismer/config-backup/` (atomic temporary+rename) before applying new
|
|
1437
|
+
Hermes config. Rescue actions (cmd channel + inotify polling): `rollback_config`
|
|
1438
|
+
(restore from backup → validate → restart), `ota_rollback` (current ← previous),
|
|
1439
|
+
`restart_daemon`, `diag_bundle` (tar.gz of config+logs+resources),
|
|
1440
|
+
`disk_cleanup`. Command idempotency via command-id result cache. Failure
|
|
1441
|
+
classification: FATAL (config syntax, OTA failure, DB corruption) → Fail-slow
|
|
1442
|
+
(diag pack + stay alive); TRANSIENT → Fail-fast (immediate restart, max 3
|
|
1443
|
+
consecutive → Fail-slow). Design: docs/product209/09-resident-sandbox-manager.md
|
|
1444
|
+
§3.3, §3.5, §3.6, §4, §6.
|
|
1445
|
+
- Observability (WP-D, product209/07 §3.7.4): daemon /healthz `config` segment
|
|
1446
|
+
(`configVersion` / `lastApplyAt` / `lastApplyError` / `pending`) derived from
|
|
1447
|
+
bootstrapStates via spread discipline — absent when no ConfigDelivery state,
|
|
1448
|
+
keeping CLI/K8s healthz shape unchanged. `runtime.incident` WS emission on
|
|
1449
|
+
bootstrap errors (401/403 stop, 404/500/network backoff) for cloud-side
|
|
1450
|
+
audit-row consumption. `LocalServerState.config` interface added.
|
|
1451
|
+
- Sandbox Manager S1 (09): Resident sandbox-manager — Rust static binary
|
|
1452
|
+
(`sdk/prismer/src/daemon/sandbox-manager/`) that supervises the daemon as a
|
|
1453
|
+
child process under tini. Entrypoint init logic (config.toml first-write,
|
|
1454
|
+
fake key fallback, static binding validation, OTA resolve) migrated from
|
|
1455
|
+
`daemon-entrypoint.sh` into the Rust binary. Healthz HTTP endpoint on port
|
|
1456
|
+
7890 (K8s probes target the manager, not daemon). Daemon heartbeat file
|
|
1457
|
+
(`~/.prismer/daemon-heartbeat`) written every 15s by the new
|
|
1458
|
+
`DaemonHeartbeat` class (`daemon/daemon-heartbeat.ts`) for manager-side
|
|
1459
|
+
liveness monitoring. Design: docs/product209/09-resident-sandbox-manager.md.
|
|
1460
|
+
- ConfigDelivery P1: RuntimeConfigBundle types (`RuntimeConfigBundle`,
|
|
1461
|
+
`HermesProviderConfig`, `HermesBundleConfig`, `BootstrapRequest`,
|
|
1462
|
+
`BootstrapResponse`, `ConfigApplyState`) and daemon-side bootstrap module
|
|
1463
|
+
(`fetchBootstrapBundle`, `applyBundle`, `isValidBundle`,
|
|
1464
|
+
`createBootstrapState`, `resetBootstrapStopped`, `computeBootstrapErrorAction`,
|
|
1465
|
+
`toApplyState`) for fetching workspace-level runtime config from
|
|
1466
|
+
`GET /api/im/runtime/bootstrap` (B1 endpoint). Design:
|
|
1467
|
+
docs/product209/07-config-delivery-runtime-bootstrap.md §3.3–3.4.
|
|
1468
|
+
- Adoption path in runner.ts now calls `triggerBootstrap()` (cloud B1 fetch)
|
|
1469
|
+
instead of direct `reprovisionHermesProfiles()` when `FF_CONFIG_DELIVERY` is
|
|
1470
|
+
ON (default). The cloud is the single source of truth for provider + model
|
|
1471
|
+
+ key assembly. §3.6: explicit OFF falls back to 2.2.8 env-inference path.
|
|
1472
|
+
- Coding Agent Line (08 P2/P3): CodingSessionControls UI component for
|
|
1473
|
+
workspace coding agent session lifecycle — adapter selection (claude-code /
|
|
1474
|
+
codex / opencode), forced-mode display, new-session / continue-session
|
|
1475
|
+
actions. Reuses `POST /api/conversations/direct` (zero new API). Session
|
|
1476
|
+
lifecycle tests for code-agent-driver resume semantics (same-key cache hit,
|
|
1477
|
+
restore-after-restart resumeSession, different-conversation creates new
|
|
1478
|
+
session), TRAP 1 single-flight guard (duplicate taskId replay), and
|
|
1479
|
+
autonomous launch on resume. Design:
|
|
1480
|
+
docs/product209/08-coding-agent-line.md §4–5.
|
|
1481
|
+
- Backoff schedule for bootstrap fetch: 5s → 10s → 30s → 60s cap
|
|
1482
|
+
(`BOOTSTRAP_BACKOFF_MS`). Error semantics: 404/500 → backoff, 401/403 → stop
|
|
1483
|
+
retries; 401 stopped state reset by key adoption event (§3.4).
|
|
1484
|
+
- Memory cap v1 fail-closed (spec16 §8.1 MA-0S, M-CAP-001): the daemon memory
|
|
1485
|
+
RPC no longer has an enforce-off branch — `PRISMER_MEMORY_CAP_ENFORCE` is
|
|
1486
|
+
deleted and every `/local/memory/*` call must carry a valid
|
|
1487
|
+
`x-prismer-memory-cap` (missing → 401 `memory_cap_required`; expired /
|
|
1488
|
+
tampered / forged → 401 `memory_cap_invalid`; a cross-workspace request is
|
|
1489
|
+
a cap-layer ws mismatch → 401 `memory_cap_invalid` per §13.4 — the old 403
|
|
1490
|
+
`memory_ws_scope_violation` is retired). The `prismer memory` CLI and the
|
|
1491
|
+
shared adapter tool client auto-carry the cap from `$PRISMER_MEMORY_CAP`.
|
|
1492
|
+
The acting identity is always the verified cap subject — body/query actor
|
|
1493
|
+
fields are never trusted — and a signed wildcard-scope cap under a
|
|
1494
|
+
non-system subject (or a cap whose own ws claim disagrees with its scope)
|
|
1495
|
+
is rejected at verify. Deny logs carry only a sha256 prefix of the
|
|
1496
|
+
presented token plus a reasonCode. Daemon-internal maintenance (outbox
|
|
1497
|
+
flush, WS invalidate, extraction) keeps writing the store in-process
|
|
1498
|
+
(system channel), never through the agent RPC gate.
|
|
1499
|
+
|
|
1500
|
+
### memory211/03 §7 B1 — memory_browse recency signal(`updatedAt` + `hubsByRecent[]`)
|
|
1501
|
+
|
|
1502
|
+
- **feat(daemon):** `memory_browse` 结果 hub 行新增 `updatedAt`(epoch ms——hub 行自身
|
|
1503
|
+
与其 child-of 后代的最新更新时间 spread;hub 行在叶节点写入时不会被 re-touch,故按
|
|
1504
|
+
spread 算)与 `hubsByRecent[]`(同集 hub 按 `updatedAt` DESC 排序的 recency 信号)。
|
|
1505
|
+
Additive:`hubs[]` 保持既有结构序,`index`/`nearest` 不变;无新工具、无 schema 变更
|
|
1506
|
+
(`/local/memory/place-context` 同 helper,两处不可漂移)。
|
|
1507
|
+
|
|
1508
|
+
### memory211/03 §7.2 B4 — R5 recall-methodology turn metrics
|
|
1509
|
+
|
|
1510
|
+
- **feat(daemon):** dispatch 度量随既有 `turn.navigation_used` / `turn.shortcuts_taken`
|
|
1511
|
+
追加三枚 `turn.*`:`first_round_hybrid`(第一轮即 batch search ≥2 且同轮 browse)、
|
|
1512
|
+
`first_round_direct_read`(第一轮即 load 且无前导 browse——direct-recall shortcut)、
|
|
1513
|
+
`tool_rounds`(批内按 `R5_ROUND_GAP_MS` 计工具轮数)。分析式归并 daemon-side 的
|
|
1514
|
+
`tool-sequence.ts` ring;与既有两枚同纪律:turn 无 memory 调用 ⇒ 无行(不伪造 0)。
|
|
1515
|
+
|
|
1516
|
+
## 2.2.10 (2026-08-07)
|
|
1517
|
+
|
|
1518
|
+
- Fix Hermes "No LLM provider configured" on sandbox agents: the provider
|
|
1519
|
+
bootstrap wrote `model.provider: custom:prismer`, but hermes' custom-provider
|
|
1520
|
+
resolution matches `custom_providers[].name` verbatim — `custom:prismer`
|
|
1521
|
+
never matched the `prismer` entry, the gateway fell through with
|
|
1522
|
+
provider='custom' and no key, and every turn failed at AIAgent init.
|
|
1523
|
+
Exposed on 2.2.9: the dual-write made the gateway read the profile config
|
|
1524
|
+
(HERMES_HOME override) for the first time, surfacing the format mismatch.
|
|
1525
|
+
Write the bare provider name (`prismer`); verified in-pod that
|
|
1526
|
+
`_resolve_runtime_agent_kwargs` then resolves api_key + base_url correctly.
|
|
1527
|
+
|
|
1528
|
+
## 2.2.9 (2026-08-07)
|
|
1529
|
+
|
|
1530
|
+
- Fix Hermes sessions EMPTY reply after a process-level interrupt (sandbox
|
|
1531
|
+
agent observed live on 2026-08-07): the S10 pollution-rotation state was
|
|
1532
|
+
process-memory only, so a daemon restart (OTA kill1 / re-adoption) wiped
|
|
1533
|
+
"this session is polluted" and the next turn reused the polluted hermes
|
|
1534
|
+
transcript → `empty_reply`. Three changes:
|
|
1535
|
+
- `session-health` interrupted/empty-streak state now persists to
|
|
1536
|
+
`~/.prismer/hermes-session-health.json` (env `HERMES_SESSION_HEALTH_FILE`
|
|
1537
|
+
to override, `setSessionHealthFile(null)` to disable) and reloads on boot —
|
|
1538
|
+
a daemon restart keeps the rotation signal.
|
|
1539
|
+
- The dispatcher catch marks the session interrupted on CONNECTION-level
|
|
1540
|
+
breaks too (gateway killed by adoption re-provision, network drop), not
|
|
1541
|
+
only `task_cancelled` / `upstream stall` — sessions-sse never throws on
|
|
1542
|
+
application errors, so a thrown error is always an interrupted turn.
|
|
1543
|
+
- EMPTY rotation threshold defaults to 1 (`HERMES_SESSION_EMPTY_ROTATE_THRESHOLD`
|
|
1544
|
+
to raise) — one empty reply rotates the session instead of burning a
|
|
1545
|
+
second user-visible failed turn.
|
|
1546
|
+
Design: docs/product209/07 §2 (plan 2 closure).
|
|
1547
|
+
|
|
1548
|
+
## 2.2.8 (2026-08-06)
|
|
1549
|
+
|
|
1550
|
+
- Hermes provider bootstrap written to the ROOT `~/.hermes/config.yaml` +
|
|
1551
|
+
`~/.hermes/.env`, not the per-profile dir — Hermes' provider resolution
|
|
1552
|
+
(runtime_provider / AIAgent) only reads the root config, so every sandbox
|
|
1553
|
+
agent failed with "No LLM provider configured" (2026-08-06). Profile dir kept
|
|
1554
|
+
for the gateway `-p` flag.
|
|
1555
|
+
- Re-provision Hermes profiles after the WS handshake adopts the real
|
|
1556
|
+
per-workspace API key: ACS sandboxes boot with the entrypoint placeholder key
|
|
1557
|
+
and any gateway spawned before adoption holds it; on adoption re-run
|
|
1558
|
+
`prepareProfile` (rewrites the root config with the adopted key) and kill the
|
|
1559
|
+
gateways so the next dispatch respawns with the real env/config. No-op for
|
|
1560
|
+
k8s/local (real key from boot, byte-identical rewrite).
|
|
1561
|
+
|
|
1562
|
+
## 2.2.7 (2026-08-05)
|
|
1563
|
+
|
|
1564
|
+
- Fix ACS handshake key adoption reaching the HTTP layer: `CloudClient` froze
|
|
1565
|
+
the boot-time placeholder key in its opts at construction, so after the
|
|
1566
|
+
handshake adopted the real per-workspace key every HTTP call still sent the
|
|
1567
|
+
placeholder and 401'd ("API key not found or revoked"). Profile sync then
|
|
1568
|
+
failed, declare stayed 0/0 and the setup parked at `agents_bound`. New
|
|
1569
|
+
`CloudClient.setApiKey()` + the adoption block now re-points the live client
|
|
1570
|
+
and `PRISMER_API_KEY` env. No-op for desktop/local daemons (real key from
|
|
1571
|
+
boot).
|
|
1572
|
+
|
|
1573
|
+
## 2.2.6 (2026-08-05)
|
|
1574
|
+
|
|
1575
|
+
- Adopt the cloud-delivered daemonId on the ACS binding handshake
|
|
1576
|
+
(`authenticated` ack extra.daemonId), exactly like the existing apiKey
|
|
1577
|
+
adoption: the sandbox boots with a derived `container:<hostname>` id that
|
|
1578
|
+
never matches the `im_containers` row's canonical UUID, so `declare` would
|
|
1579
|
+
otherwise fail the setup's daemonHasSignal lookup and `daemon_connected`
|
|
1580
|
+
would park forever. Only the ACS binding path sends daemonId, so
|
|
1581
|
+
desktop/local daemons are unaffected.
|
|
1582
|
+
|
|
1583
|
+
## 2.2.5 (2026-08-02)
|
|
1584
|
+
|
|
1585
|
+
- Established `@prismer/runtime` as the Runtime host under `sdk/prismer`, with
|
|
1586
|
+
Cloud SDK dependencies removed from its control-plane boundary.
|
|
1587
|
+
- Replaced external coding-agent Marketplace plugin lifecycle control with
|
|
1588
|
+
Runtime-owned Claude adapter hooks and a durable provider-independent
|
|
1589
|
+
post-turn job ledger.
|
|
1590
|
+
- Added remote-first skill catalog resolution with verified cache and bundled
|
|
1591
|
+
fallback, including resolver state and post-turn counters in `/healthz`.
|
|
1592
|
+
- Retired Go/Rust SDK builds and four-segment hotfix releases; active packages
|
|
1593
|
+
now use the shared `X.Y.Z` release version and npm/PyPI release matrix.
|
|
1594
|
+
|
|
1595
|
+
### Added — D1/D2 裁决落地:两个新终态 code + `daemon.declare.retry` 解 park
|
|
1596
|
+
|
|
1597
|
+
cloud 侧本轮实施了 product206/13 的两条用户裁决(D1「显式归属,一次性选择」/ D2「凭证指纹
|
|
1598
|
+
现在收窄」),daemon 侧要认它们的拒绝,并且要能被**解 park**。
|
|
1599
|
+
|
|
1600
|
+
- **`declare-guard.ts` 分类表新增两个终态 code**(判定逻辑与阶梯一行未动,只是表多了两行):
|
|
1601
|
+
- `WORKSPACE_DAEMON_MISMATCH`(D1)—— 这个 workspace 的**本机槽位**显式归另一台设备,
|
|
1602
|
+
而归属**刻意不自动漂移**。归为终态正是裁决的要点:把它当可重试等于把「等对方掉线就
|
|
1603
|
+
抢过来」这条隐式规则从后门放回来。
|
|
1604
|
+
- `DAEMON_CREDENTIAL_MISMATCH`(D2)—— daemonId 属于本账号,但这把 API key 不是认领时那把。
|
|
1605
|
+
daemon 只有一把 key,重试永远拿同一个答案。
|
|
1606
|
+
- 两条都带用户能照做的 hint(`prismer status` / `/healthz.declareBlocked` 逐字透出)。
|
|
1607
|
+
cloud 同时发 `retryable: false`,所以**即使是不认识这两个 code 的老 daemon** 也会按终态
|
|
1608
|
+
处理(wire hint 压过本地表,R5 既有契约)。
|
|
1609
|
+
- **新增入站帧 `daemon.declare.retry`(cloud → daemon)** —— 用户在云端做完补救动作
|
|
1610
|
+
(批量重认领 / 改派 workspace 槽位)后,cloud 主动叫 daemon 再试一次。**它是这两条出路
|
|
1611
|
+
端到端能用的那一半**:R5 的终态 park 是有界探测(+5min/+10min 之后彻底停发),没有这个
|
|
1612
|
+
推送,一个 20 分钟后才被修好的 daemon 只能靠重启进程恢复 —— 而「重启 daemon」正是 R5
|
|
1613
|
+
出入1 拒绝交付的答案。实现上复用 `onExplicitDeclare()` 这个既有的解 park 缝
|
|
1614
|
+
(`POST /v1/workspace` 走的同一条),**不新增状态机**。帧按 `daemonIds` 过滤(rooms 按
|
|
1615
|
+
人的 IMUser id 寻址,同账号所有 daemon 都会收到),不点名自己就忽略。纯加法:不认识这个
|
|
1616
|
+
type 的旧 daemon 落进 `handleIncoming` 的静默 default。
|
|
1617
|
+
|
|
1618
|
+
### Added — R5:declare 被 cloud 拒绝时有出路(分类 + 退避 + 用户可见),local-first 不变
|
|
1619
|
+
|
|
1620
|
+
`agent.host.declare` 被拒时,daemon 过去**把拒绝丢在地上**:`runner.ts` 的 `ws.on('message')`
|
|
1621
|
+
只对三个 `AUTH_*` code 有动作,其余 error 帧写完 stderr 就落进 `handleIncoming()` 的
|
|
1622
|
+
switch 被 default 吞掉;同时 30s 心跳**无条件**重发同一份注定失败的 declare。用户看到的是
|
|
1623
|
+
「设备连上了、但一个 agent 都不托管」,没有任何面能说出原因。cloud 侧的 R4(daemonId 认领
|
|
1624
|
+
鉴权)落地后这条从体验缺陷变成**必现路径**:认领冲突就会走到这里。
|
|
1625
|
+
|
|
1626
|
+
- **新增 `daemon/declare-guard.ts`** —— 分类表 + 退避阶梯 + 阻塞态,一个模块决定这三件事:
|
|
1627
|
+
- **终态**(`DAEMON_ID_CLAIMED` / `DAEMON_FORGOTTEN` / `NO_USER`):重试永远不会成功,
|
|
1628
|
+
只有人能解。**不再无限重发** —— 拒后只留 2 次有界探测(10×/20× tick,默认 5min/10min)
|
|
1629
|
+
然后彻底停发。留探测而不是归零,是因为 forgotten / 认领释放这类终态常常几分钟内被人修好,
|
|
1630
|
+
daemon 应该自己发现,不该要求用户重启。
|
|
1631
|
+
- **可重试**(`WORKSPACE_ACTIVE_DEVICE_BUSY` / `DAEMON_IDENTITY_UNAVAILABLE` /
|
|
1632
|
+
`NO_IMUSER_LINKED` / `SHADOW_JOIN_FAILED` / `INTERNAL`):继续重试但**退避**
|
|
1633
|
+
2×/4×/8×/10× tick(默认 60s→120s→240s→300s 封顶),不再恒 30s 打。
|
|
1634
|
+
- **认证类**(`AUTH_FAILED` / `AUTH_REQUIRED` / `auth_invalid`):**行为不变**,仍是整机停机。
|
|
1635
|
+
凭证坏了退避没有意义 —— 这条显式确认,不再隐含在「反正都被 default 吞掉」里。
|
|
1636
|
+
- **非 declare 通道的 error**(`WITHDRAW_DAEMON_MISMATCH` / `UNKNOWN_EVENT`):完全不碰
|
|
1637
|
+
declare 状态机(wire 没有可信的 per-frame 关联 id,这张排除表就是关联)。
|
|
1638
|
+
- **未知 code 一律降级为可重试**:新 cloud 发一个老 daemon 没见过的 code,绝不能把设备
|
|
1639
|
+
变成永久沉默。
|
|
1640
|
+
- **wire hint 压过本地表**:cloud 可在 error 帧带可选的 `retryable: false | {afterMs}`
|
|
1641
|
+
(`ServerEvents.error` 第 4 参,本包外配套)。双向可选、各自兜底,**不引入
|
|
1642
|
+
protocolVersion 协商**。
|
|
1643
|
+
- **`/healthz` 新增 `declareBlocked`**(`LocalServerState`)—— `{code, message, retryable,
|
|
1644
|
+
since, attempts, nextRetryAt, hint}`。这是「**连不上**」(`wsConnected:false`)与
|
|
1645
|
+
「**连上了但被拒**」(`wsConnected:true` + `declareBlocked`)唯一可区分的地方。字段用
|
|
1646
|
+
spread 发出:没被拒的 daemon 的 healthz 形状与 R5 之前逐字节一致。
|
|
1647
|
+
- **`prismer status` 打印拒绝原因**(新增导出的纯函数 `formatDeclareBlocked`)——「设备未被
|
|
1648
|
+
cloud 接受: <code>」+ cloud 原话 + 怎么办 + 下次尝试时间(park 时明说「不再自动尝试」)。
|
|
1649
|
+
同一改动里 `readDaemonStatus()` 改为认 `PRISMER_DAEMON_PORT`(原来硬编码 3210,导致
|
|
1650
|
+
desktop / agent-rt pod / 集成 rig 上 `prismer status` 一律误报「daemon 没在跑」)。
|
|
1651
|
+
- **不变量(每条都是 j48 的一道门)**:① local-first —— 被拒**不停机**,本地 run 照跑照写产物;
|
|
1652
|
+
② 离线 ≠ 被拒 —— socket 断只走 `ws-client` 自己的重连退避,**绝不**置 `declareBlocked`;
|
|
1653
|
+
③ 正常重连不吃退避 —— 新 `authenticated` 重置阶梯(重连是高频路径);④ 显式用户意图
|
|
1654
|
+
(装 agent / 切 workspace)解 park,这也是「我修好了,再试一次」的入口。
|
|
1655
|
+
- **可调**:`PRISMER_DECLARE_INTERVAL_MS`(默认 30_000,钳 1s–300s)—— 心跳 declare 周期,
|
|
1656
|
+
整条阶梯以它为单位。生产默认不变,只给运维 / 集成测试压缩墙钟用。
|
|
1657
|
+
- 测试:`test/declare-guard.test.ts` 16 条(分类表 + 阶梯算术 + 四条不变量,注入时钟)。
|
|
1658
|
+
贯通门 `scripts/test203/journeys/j48-declare-rejection-backoff.ts`:**真 daemon 进程**
|
|
1659
|
+
(从源码起)+ 逐帧记录的 stub cloud,6 条断言全取副作用(wire 上的 declare 帧数与间隔 /
|
|
1660
|
+
`/healthz` / `prismer status` 输出 / 磁盘产物)。负控 `--tamper` 用**源码级自动注入**
|
|
1661
|
+
(把 R5 从一份 src 拷贝里拆掉再起 daemon):实测终态 3 帧 → 30 帧、退避间隔 6/8/16s → 恒 2s、
|
|
1662
|
+
`declareBlocked` 消失、CLI 无输出,四处如期翻红;local-first 与离线两条在 tamper 下仍绿
|
|
1663
|
+
(证明它们不是靠 R5 假绿)。
|
|
1664
|
+
|
|
1665
|
+
### Fixed — 桌面本地 provider 选择落不了库:`proxyProvider` 新增 `local:<profileId>` 命名空间
|
|
1666
|
+
|
|
1667
|
+
`AgentProfile.config.proxyProvider` 一个命名空间装两类值:云端 provider chain(云端全知)
|
|
1668
|
+
与**桌面本地 provider profile**(只存在于用户机器的 `~/.prismer/config.toml`
|
|
1669
|
+
`[[providers]]` + keychain,**云端结构性不可知**)。cloud 侧的写入校验只放行它认得的 id,
|
|
1670
|
+
于是桌面用户从三个入口(`ProxyProviderSelect` / `ModelPicker` / composer 模型切换器)选中
|
|
1671
|
+
本地 provider 时,每一次写入都 400 `invalid_proxy_provider` —— 这条漂移全仓无任何 doc 或
|
|
1672
|
+
代码记录过,实测复现(`PATCH /api/im/agent_profiles/<id>` config.proxyProvider='qwen' → 400)。
|
|
1673
|
+
|
|
1674
|
+
修法是**加命名空间**而不是放松校验:`local:<profileId>` 自描述,cloud 零知识放行;裸的未知
|
|
1675
|
+
链 id 照旧被拒(release202/12 C2 的防拼错能力一点没丢)。
|
|
1676
|
+
|
|
1677
|
+
- **`resolveLocalProvider()` 认两种形态**(`adapters/shared/local-provider.ts`)——
|
|
1678
|
+
`local:<id>` strip 前缀后按 `profile.id` 匹配;**裸 id 照旧匹配**(前缀出现之前写下的
|
|
1679
|
+
profile、手写 config.toml、既有测试全部不受影响)。新增导出 `LOCAL_PROVIDER_PREFIX` /
|
|
1680
|
+
`isLocalProviderSelector()` / `localProviderProfileId()`(cloud 侧的镜像副本在
|
|
1681
|
+
`src/lib/llm/local-provider-ref.ts`,**前缀字符串必须同步**)。
|
|
1682
|
+
`newapi` / `default` 的保留短路只对**裸** selector 生效 —— 显式 `local:newapi` 指的是用户
|
|
1683
|
+
正好这么命名的本地 profile,前缀说得毫不含糊。
|
|
1684
|
+
- **未解析的 `local:` 不再被当成云端链走**(hermes `resolvePrismerProviderBaseUrl` +
|
|
1685
|
+
codex `resolveCodexPrismerProvider`)。profile 不在这台机器上 / key 未配 / 类型与该
|
|
1686
|
+
adapter 的 wire 不兼容时,旧逻辑会拼出 `/api/v1/proxy/local%3Aqwen` —— 每一发都 404。
|
|
1687
|
+
现在落回聚合器 `/api/v1`,与「没选」同一个降级面。**真的云端链 id 仍走
|
|
1688
|
+
`/api/v1/proxy/<chain>`**(这条闸严格限定在 `local:` 命名空间内)。
|
|
1689
|
+
- 测试:`test/local-provider.test.ts` 新增 `local:` 命名空间 6 条 + codex 路由 2 条;
|
|
1690
|
+
新增 `test/hermes-local-provider-selector.test.ts` 5 条 —— oracle 取 hermes 真正跑的字节
|
|
1691
|
+
(`<HERMES_HOME>/profiles/<p>/config.yaml` 的 `custom_providers[].base_url` + `api_mode`),
|
|
1692
|
+
不是返回值。负控:拿掉 strip → 7 条红(实测)。
|
|
1693
|
+
贯通门 `scripts/test203/journeys/j44-local-provider-selector.ts`(真 endpoint + DB oracle)
|
|
1694
|
+
正向 PASS;`--tamper`(发裸 id = 修复前的前端)如期红,复现的正是那条 400。
|
|
1695
|
+
|
|
1696
|
+
⚠️ 本条需要 cloud 端配套(不在本包):`src/im/api/agent-profiles.ts::validateProxyProvider`
|
|
1697
|
+
放行 `local:` 前缀,三个前端选择器发带前缀的值。缺任一层用户仍撞 400。
|
|
1698
|
+
|
|
1699
|
+
### Added — F1-b:运行中的 daemon 可以改 workspace(`POST /v1/workspace` + `Runner.setDeclaredWorkspace()`)
|
|
1700
|
+
|
|
1701
|
+
用户在 daemon **已经在跑**的时候新建 / 切换 / 删除 workspace,daemon 与桌面此前**零感知**:
|
|
1702
|
+
`config.toml` 没有 workspace 字段,`PRISMER_WORKSPACE_ID` 在 spawn 时就冻死,
|
|
1703
|
+
`GET /workspaces/sync` 有 client 方法但**零调用方**(死代码)。daemon 会一直服务它启动时
|
|
1704
|
+
的那个 workspace,直到整个 app 重启。
|
|
1705
|
+
|
|
1706
|
+
- **`Runner.setDeclaredWorkspace(workspaceId)`**(`daemon/runner.ts`)—— 新增
|
|
1707
|
+
`workspaceOverride` 字段,优先级**高于** spawn 冻结的 `PRISMER_WORKSPACE_ID`(原
|
|
1708
|
+
`sendDeclare()` 的取值链 `env || this.workspaceId` 里,env 永远赢,所以只改
|
|
1709
|
+
`this.workspaceId` 是够不到的)。改完立即 `sendDeclare()` 重新声明;socket 断开时
|
|
1710
|
+
只记录意图,下一次 `authenticated` 时带上。**幂等**:同 id ⇒ `changed:false` 且不发帧。
|
|
1711
|
+
- **`POST /v1/workspace { workspaceId }`**(`daemon/local-server.ts`)—— 桌面主进程
|
|
1712
|
+
用它把用户当前的 workspace 推给在跑的 daemon。未接 `onSetWorkspace` 的 embedder 答
|
|
1713
|
+
**501**(而不是假装成功)。空串 / 非字符串 / 坏 JSON 一律 400 且不触达 runner。
|
|
1714
|
+
|
|
1715
|
+
**选 redeclare 而不是「切换即重启 daemon」**:重启会杀掉在飞 run;而 `agent.host.declare`
|
|
1716
|
+
在 cloud 侧本来就是 refresh-on-redeclare("the latest declare wins",shadow 全拆重建、
|
|
1717
|
+
binding 走 `handleHostDeclare` 对同一 daemon 返回 `refreshed`)。**实测**二次 declare 既
|
|
1718
|
+
不报 409 也不报 `DAEMON_FORGOTTEN`,cloud 正常回 `host.acked`。
|
|
1719
|
+
|
|
1720
|
+
⚠️ **本条需要配套的 cloud 端修复才生效**(`src/im/ws/handler.ts`,不在本包):cloud 的
|
|
1721
|
+
workspace 解析链把 `im_containers` 行排在 `payload.workspaceId` 之前,而 local daemon 的
|
|
1722
|
+
那行正是 handler 自己上次 declare 时写的 —— 于是 daemon 被**永久钉死**在第一次 declare 的
|
|
1723
|
+
workspace 上。实测二次 declare 带新 workspaceId,**同一条 socket 上**和**整条连接重连之后**
|
|
1724
|
+
cloud 都照样 ack 回旧的,静默无错。(这也是为什么「重启 daemon」那条退路同样修不好。)
|
|
1725
|
+
|
|
1726
|
+
- 测试:`test/local-server-set-workspace.test.ts`(5 条,真 HTTP,非 mock 路由)绿。
|
|
1727
|
+
真栈门:`scripts/test203/journeys/j43-daemon-workspace-redeclare.ts` —— 真 WS declare +
|
|
1728
|
+
`im_containers` DB oracle。正向 PASS;负控 `--tamper`(撤掉 redeclare)必红,实测红。
|
|
1729
|
+
|
|
1730
|
+
### Fixed — desktop205 R1-e:资产投递链的三条残留(`pendingByTask` 泄漏 / auto-scan 去重 / `bindSourceTask` 判别)
|
|
1731
|
+
|
|
1732
|
+
- **`bindSourceTask` 不再靠 id 形状猜**(`daemon/asset/deliver.ts`)。旧判据
|
|
1733
|
+
`taskId.length <= 30 && !/^(session:|run[:_])/` 是**已经在错**而不只是脆弱:
|
|
1734
|
+
`IMTask.id` 与 `IMTaskRun.id` 都是 `@default(cuid()) @db.VarChar(30)`,形状不可分;
|
|
1735
|
+
cloud 对聊天 dispatch 下发的 `payload.taskId` 就是 `IMTaskRun.id`,经
|
|
1736
|
+
`PRISMER_RUN_ID`(spawn 适配器)或从 `<execution_context><run_id>` 抄出的 `--run-id`
|
|
1737
|
+
(hermes)原样走到这里时,形状闸答"看板卡"并 stamp `sourceTaskId` = 一个 `im_tasks` 里
|
|
1738
|
+
不存在的 id(装得下 VarChar(30),所以不 500,只是静默错绑 + 落进 `/tasks/<runId>`)。
|
|
1739
|
+
改为按判别式取真值:auto-resolve ⇒ run · 本机在飞 dispatch 的 `payload.kind`(新增
|
|
1740
|
+
`ArtifactsWatcher.activeTaskKind()`,由 dispatch.ts 用 `dispatchKind` 立键)⇒ 权威 ·
|
|
1741
|
+
`mode:'task-attach'` ⇒ 调用方显式声明 · 都问不到才回退形状闸**并打警告行**。
|
|
1742
|
+
- **合成会话行不再往 `pendingByTask` 立无人抽干的桶**(`daemon/asset/deliver.ts`)。
|
|
1743
|
+
auto-resolve 落在 `task_id` 为 NULL 的行(`HermesSessionMapper` 的 `session:<id>` 合成行)
|
|
1744
|
+
时没有可搭载的 dispatch,`flushPending` 的两处生产调用都按 cloud dispatch id 取键,
|
|
1745
|
+
写进去的桶永远没人读、随 daemon 生命周期单调增长。现在不 record,`mode:'attach'` 的响应
|
|
1746
|
+
改带 `ridesReply: false` + 说明(资产仍上传并锚在 `/runs/<runId>`)。
|
|
1747
|
+
- **auto-scan 上传方补上与 `recordDeliveredAsset` 同口径的 assetId 去重**
|
|
1748
|
+
(`daemon/artifacts-watcher.ts`)。cloud `POST /assets` 按
|
|
1749
|
+
`(workspaceId, contentHash[, sourceTaskId])` 去重并返回既有行,于是同一份文件走
|
|
1750
|
+
「写进 artifactsDir + 显式 `cloud deliver --mode attach`」两条路时同一个 assetId 会被裸
|
|
1751
|
+
`push` 两次,在 `reply.assetIds` 里出现两次。
|
|
1752
|
+
- 测试:`test/desktop205-run-artifact-folder.test.ts` 新增 R1-e 7 条(每条正控配反向负控;
|
|
1753
|
+
fake cloud 复刻真 dedup 规则)。逐条注入故障验红:A `expected 1 to be +0`、
|
|
1754
|
+
B `expected [ 'ast_1', 'ast_1' ] to deeply equal [ 'ast_1' ]`、
|
|
1755
|
+
C `expected 'cmq5r8t1v0002wxyz' to be null`。
|
|
1756
|
+
|
|
1757
|
+
### Tests — `prismer banner` 的欢迎词行现在被 T0 钉住(apc/04 §5 样例 2)
|
|
1758
|
+
|
|
1759
|
+
`prismer banner`(非 `--compact`)在 banner 之前输出一行固定欢迎词
|
|
1760
|
+
`Welcome to Prismer Cloud`(`src/cli/commands/banner.ts:13`,2026-07-26 由并行线落盘)。
|
|
1761
|
+
这一行是**桌面本地 feed OTA 环的可观测 oracle**,但此前全仓没有任何测试断言它——
|
|
1762
|
+
「测试选层绿」对这条改动是空的,apc/04 §5 强制的负控(欢迎词打错字 ⇒ T0 断言红)
|
|
1763
|
+
**根本红不起来**。
|
|
1764
|
+
|
|
1765
|
+
新增 `test/banner-welcome.test.ts`(真 `buildBannerCommand()` + `parseAsync`,
|
|
1766
|
+
不 mock 命令本体):非 compact 必须写出该行,`--compact` 必须不写。
|
|
1767
|
+
|
|
1768
|
+
- commit `b3821c22`(首轮 2 条断言)+ `a3478605`(同一 task 的第二轮,锚定「欢迎词必须是 stdout
|
|
1769
|
+
第一行」并补 `--compact` 非空断言,共 6 条)
|
|
1770
|
+
(task `cms3dwas3034qxze2p6t857ch`,进开发审批 `cms3dwva3034xxze2md8n54yj`,
|
|
1771
|
+
发版审批 `cms3e9y7b03dlxze2c9fg2fva`)
|
|
1772
|
+
- 绿:`apc test --tier=T0 --json` exit 0 —— T0(root) 56/56 · T0(runtime) 191/191 · T0(desktop) 13/13
|
|
1773
|
+
- 负控红:同一命令在欢迎词打错字后 exit 1 —— T0(runtime) 190/191,
|
|
1774
|
+
`failedNames = ["test/banner-welcome.test.ts (vitest)"]`;红触发 H2 acceptance hook
|
|
1775
|
+
自动 redispatch(`im_tasks.metadata.acceptanceRedispatchCount = 1`)
|
|
1776
|
+
- OTA 触达(本机 daemon channel local feed):bundle `2.2.4-ota.1785166882`
|
|
1777
|
+
|
|
1778
|
+
### BREAKING — built-in role template `--from-template ceo` renamed to `--from-template team-manager`
|
|
1779
|
+
|
|
1780
|
+
`sdk/prismer-cloud/runtime/src/templates/roles/ceo.json`'s top-level `templateName` field changed
|
|
1781
|
+
from `"ceo"` to `"team-manager"` (CEO→Team Manager internal naming pass, no compat alias kept —
|
|
1782
|
+
scripts hardcoding `prismer profile create --from-template ceo` will now fail with "Unknown
|
|
1783
|
+
template"; re-run with `--from-template team-manager`). This only renames the CLI-facing template
|
|
1784
|
+
lookup key surfaced by `prismer profile templates` / `getRoleTemplate()` / `listRoleTemplates()`.
|
|
1785
|
+
The role's internal routing identifier `roleTemplate.slug` stays `"ceo"` — unrelated code paths
|
|
1786
|
+
(`profile-defaults.ts`, `orchestrator-lock.ts`, `workspace-orchestrator.service.ts`, …) that branch
|
|
1787
|
+
on `slug === 'ceo'` are unaffected.
|
|
1788
|
+
|
|
1789
|
+
### Added — `gh-claim-readback`:第一个"他证"型判据(apc/17 §3 相 1 · W1.1 / W1.2)
|
|
1790
|
+
|
|
1791
|
+
`structured-criteria.ts` 既有 6 个 checker 的 ground truth **全部是被判进程自己能写的东西**
|
|
1792
|
+
(它跑的那个文件系统、它能改写的 git 对象库、它自己建的 task 行)。apc/12 §0.10 记的实证:
|
|
1793
|
+
一棵从没参与本仓库任何发版的独立代码树跑出的 `ota-promote` 产物,与本仓库产物**逐字节相同** ——
|
|
1794
|
+
本地产物不携带运行身份。
|
|
1795
|
+
|
|
1796
|
+
新 checker 读的是 **GitHub 服务端签发、且绑死在一个仓库上的 id**:PR number / Actions `run_id` /
|
|
1797
|
+
check-run `conclusion` / branch protection。声明词表 `GH-REPO:` / `PR:` / `RUN:` / `SHA:` /
|
|
1798
|
+
`CHECK: <name> = <conclusion>` / `PROTECTION:`。
|
|
1799
|
+
|
|
1800
|
+
- **跨绑定比对**:回读对象的 `base.repo.full_name` / `repository.full_name` 必须等于声明的
|
|
1801
|
+
`GH-REPO` —— 一个真实但属于**另一个仓库**的 id 是 §0.10 的 free-ride 形态,即使传输层肯服务
|
|
1802
|
+
它也死在这一比。`repo` arg(skill.json 钉死)另挡"换成自己的 fork"
|
|
1803
|
+
- **fail-closed 三态**(§7 边界 3):无 `APC_GH_TOKEN` / 不可达 / 非 2xx **一律红**,不降级为
|
|
1804
|
+
`env_blocked`。branch protection 的 403 是只读边界在如实作答,仍然是红
|
|
1805
|
+
- **`headShaMustEqualLocal`(W1.2)**:服务端 head sha 必须同时等于声明的 `SHA:` 与本地
|
|
1806
|
+
`git rev-parse HEAD` —— §0.10 列为"待建"的 run-scoped 运行标识的第一个真实现(三方必须同名,
|
|
1807
|
+
agent 只控其二)
|
|
1808
|
+
- **判据侧不 spawn `gh` 二进制**(§3 相 1 ⚠️):`gh` 对"没有 run"答 exit 0,而 doc12 §0.9 的
|
|
1809
|
+
同一形状已复发 5 次。走 REST + 强制字段比对,从源头不引入那个退出码语义家族。`apiBase` 只是
|
|
1810
|
+
skill.json arg、**故意不给 env 覆盖**:判据回读的端点不该由被判 agent 所处的环境来指定
|
|
1811
|
+
- 测试:**传输不 mock** —— GitHub 响应形态的 fixture server 跑在**独立进程**里
|
|
1812
|
+
(`syncHttpGetJson` 是同步阻塞的,同进程 server 必死锁),checker 走真 HTTP。四条负控
|
|
1813
|
+
(编造 id / 跨仓库真 id / 无 token / 非 2xx)+ 变异证明:摘掉判定后这 11 条负控全部转绿
|
|
1814
|
+
|
|
1815
|
+
### Security — coding agent spawn env: 凭据 denylist 反转为 allowlist(apc/17 §8.1 W2.1′)
|
|
1816
|
+
|
|
1817
|
+
`filterCredentialBaseEnv` 之前问的是「这个名字是不是**已知的坏**」。拿本仓库 `.env.local` 的
|
|
1818
|
+
57 个真实变量名实测:**拦 21 / 透传 36**,透传里包括一把**活的** SMTP 口令(`SMTP_PASS` —
|
|
1819
|
+
`_PASS` 匹配不上 `_PASSWORD`)、`REDIS_URL`(URL 形态携带 `user:pass@`)、`SMS_ACCOUNT`、
|
|
1820
|
+
`SMTP_USER`、`K8S_CLUSTER_URL`、`AUTH_URL`。补前缀只是同一形态的又一次枚举。
|
|
1821
|
+
|
|
1822
|
+
- **反转**:新 `isAllowlistedSpawnEnvKey()` 是唯一的门 —— 只有**显式列出**的键能从 daemon 自身
|
|
1823
|
+
`process.env` 继承进 coding agent。原来的名字形态规则降级为**第二道门**,防前缀族
|
|
1824
|
+
(`NODE_AUTH_TOKEN` 这类)夹带。判别式:一个谁都没枚举过的
|
|
1825
|
+
`FUTURE_VENDOR_CREDENTIAL` 现在**默认被拦**(denylist 下必漏)
|
|
1826
|
+
- 大小写归一化到 UPPER:一次同时吃下 Windows 的 `Path`/`ProgramFiles`/`SystemRoot` 与小写
|
|
1827
|
+
`http_proxy`/`no_proxy`,不需要重复条目
|
|
1828
|
+
- **`claude-code-cli` / `codex-cli`(runner.ts 的 D21 fallback adapter)接进同一个漏斗** ——
|
|
1829
|
+
这两条之前用裸 `{...process.env}` spawn,driver 那条修好了它们还开着,等于没修
|
|
1830
|
+
- overlay 语义不变:`launchEnv` / `taskEnv` / `runtimeSettings.env` / `config.envVars` 在过滤
|
|
1831
|
+
**之后**合入,永远不被剥 —— 那是 code agent 唯一的鉴权通路(网关注入)
|
|
1832
|
+
- 逃生门不变:`PRISMER_CC_NO_CRED_FILTER=1`,且只从 `baseEnv` 读(overlay 关不掉)
|
|
1833
|
+
- ⚠️ **未纳入**:hermes gateway(`{...process.env}` 逐字继承,**零过滤**)、
|
|
1834
|
+
`daemon/shell-executor.ts`、`runner.ts::runShellCommand` —— 三条都不走本漏斗,见报告
|
|
1835
|
+
|
|
1836
|
+
### Fixed — task 产物根本不过 outbox(desktop205 O14)
|
|
1837
|
+
|
|
1838
|
+
W8 把断云契约修好了,但 `docs/desktop205/03-acceptance.md` §3.3 的「产物 = 落本地 + 进 outbox /
|
|
1839
|
+
复联 = 补传成功」**只对 drop-folder 成立**。task 产物走的是完全另一条路:
|
|
1840
|
+
`ArtifactsWatcher.deliverFile` / `upload()` 里的裸 `uploader.uploadAsset(...)` —— 不入队,失败
|
|
1841
|
+
直接抛给调用方,文件留在 task workdir,**复联永不补传**(auto-scan 那条的重试还是纯内存的,
|
|
1842
|
+
进程一死就没了)。`asset/origin/agent-gen.ts` 这个适配器早就在,但从没接线。
|
|
1843
|
+
|
|
1844
|
+
- **接线**:task 产物在**瞬时**上传失败(`isTransientUploadError`,W8 已有的分类器)时进
|
|
1845
|
+
`OriginOutbox`,由**同一个** `UploadRunner` tick 补传 —— 与 drop-folder 同一个队列、同一份
|
|
1846
|
+
持久化重试保证、同一个 tray/healthz 积压计数(W13)
|
|
1847
|
+
- **语义选择:在线路径不变,只在「云不可达」时转异步**。不是"一律入队立即返回"——
|
|
1848
|
+
`send` / `message-attach` 两个 mode 结构上需要用 assetId 再打一次 cloud,一律入队会让这两个
|
|
1849
|
+
功能彻底不可用;而永久拒绝(4xx / policy / MIME)继续抛回调用方,agent 必须知道自己的文件被
|
|
1850
|
+
拒收,把它排进一个注定 dead-letter 的队列只会把可行动的错误藏起来
|
|
1851
|
+
- **返回**:`attach` / `task-attach`(交付方式就是上传本身)⇒ **202** `{ok:true, queued:true,
|
|
1852
|
+
outboxId}`;`send` / `message-attach` ⇒ 字节照样入队(产物不丢)但**报 502**,因为消息动作
|
|
1853
|
+
确实没发生,说成功是 agent 会照着行动的谎。`ArtifactsWatcher.deliverFile` 返回类型改为
|
|
1854
|
+
`{status:'uploaded'|'queued'}` 判别联合(唯一调用方是 `asset/deliver.ts`)
|
|
1855
|
+
- ⚠️ **`AgentGenAdapter.fetch` 原来读 `detail.bytes`(内存 Buffer),结构上无法过 outbox**
|
|
1856
|
+
—— outbox 存的是 `payloadJson = JSON.stringify(detail)`,Buffer round-trip 回来是
|
|
1857
|
+
`{type:'Buffer',data:[…]}` 而不是 Buffer(outbox 文件头也明写"不存字节")。task 产物因此
|
|
1858
|
+
按 **path** 入队、drain 时从磁盘重读(与 drop-folder 同构);`observedAt` 取 **mtime**,
|
|
1859
|
+
同一份未修改文件重复交付收敛成一行
|
|
1860
|
+
- `SourceHints` 补 `assetKind` / `sourceTaskId` / `adapter` 三个透传位,否则补传出来的资产会
|
|
1861
|
+
是 `kind=file` 且**不绑看板卡** —— 那是"补传成功"的假象
|
|
1862
|
+
- `test/desktop205-task-artifact-outbox.test.ts`(真 http server + 真 socket + 真 SQLite +
|
|
1863
|
+
真文件系统,零 mock;断云 = 真关端口)。负控四条,全部实测转红:不复联就断言补传 ⇒ 红 ·
|
|
1864
|
+
永久 4xx 仍必须 dead-letter(把分类器改成永不判死 ⇒ 红)· 摘掉入队那一步 ⇒ 正控 6/8 转红 ·
|
|
1865
|
+
同步路径遇 4xx 不得入队。另加 daemon 重启后仍补传(证明持久化不是内存重试)
|
|
1866
|
+
|
|
1867
|
+
### Fixed — 「归属换人」被当成「服务器确认它没了」(desktop205 O15 残留)
|
|
1868
|
+
|
|
1869
|
+
收紧到「404 且是我方信封」之后仍有一条通道能通过确认:cloud 的
|
|
1870
|
+
`GET /agent_profiles/:id` 的 where 带 `workspace: { ownerImUserId }`,所以 **profile 还在、
|
|
1871
|
+
只是 workspace 换了主人**(转移 / 换账号)时也回我方信封的 404 ⇒ 本地 profile + agent 行照删。
|
|
1872
|
+
「不是你的」是权限变化,不是资源消失。
|
|
1873
|
+
|
|
1874
|
+
- **cloud 侧**(`src/im/api/agent-profiles.ts` GET `/:id`):miss 分支再查一次「不带 owner 约束」
|
|
1875
|
+
的同 id,回 `{ok:false, error:{code:'forbidden'|'not_found', message:'Profile not found'}}`。
|
|
1876
|
+
判别位放 `error.code`,**HTTP 状态与 message 文本两个分支完全一致**(沿用 `workdirs.ts` 的既有
|
|
1877
|
+
范式;403 会把「这个 id 存在」抬到最易探测的那层)。额外查询只在 miss 分支
|
|
1878
|
+
- **daemon 侧**:`confirmProfileGoneOnCloud` 的判据由「信封对」升级为
|
|
1879
|
+
**`error.code === 'not_found'`** —— 正向凭据。`forbidden` / 无 code 的老信封 / HTML / `{}` /
|
|
1880
|
+
其它状态码 / 网络失败一律不删
|
|
1881
|
+
- **版本漂移是刻意的保守方向**:老 cloud 回裸字符串(无 code)⇒ 不确认 ⇒ 保留行。代价有界
|
|
1882
|
+
(脏 profile 留到 cloud 升级;`host.acked` 的 tombstone 路径 `computeProfilesToDelete`
|
|
1883
|
+
**本来就没有 owner 过滤**,仍会清真正删掉的 profile),反方向(误删活 agent 的行)不可恢复
|
|
1884
|
+
- 负控实测:把判据改回「只要信封对」⇒ 归属换人那条**当场删光两张表的行**
|
|
1885
|
+
(`profiles:0, agents:0`),2 条转红;cloud 侧 `src/im/tests/acp-agent-profile-ownership-404.test.ts`
|
|
1886
|
+
在改路由前 4/7 红(两种 404 字节完全同形)
|
|
1887
|
+
|
|
1888
|
+
### Fixed — 删本地 profile 行用弱判据(desktop205 O15)
|
|
1889
|
+
|
|
1890
|
+
`syncProfileFromCloud` 只要 `CloudError.status === 404` 就删掉本地 profile + agent 行。真断云
|
|
1891
|
+
给的是 `status:0`,所以当时是安全的 —— 但「404」≠「服务器确认这个 profile 没了」:captive
|
|
1892
|
+
portal、反代误路由、滚动发布期的 404 都会命中,而这是**破坏性操作**。
|
|
1893
|
+
|
|
1894
|
+
- `CloudError.code` 帮不上忙:`CloudClient.request` 在 body 不是我们 `{error:{code}}` 对象时会
|
|
1895
|
+
**合成** `code:'not_found'`,nginx 的 HTML 404 与真 404 在 CloudError 上完全同形
|
|
1896
|
+
- 改为要求一次**正向确认** `confirmProfileGoneOnCloud()`:用 `fetchRaw` 重读同一路由(保留了
|
|
1897
|
+
`request` 丢掉的原始 body),必须是 404 **且** body 是我们自己的 JSON 错误信封
|
|
1898
|
+
(`{ok:false,…}`,cloud 实回 `{ok:false, error:'Profile not found'}`)。HTML / 非 JSON /
|
|
1899
|
+
`{}` / 其它状态码 / 网络失败一律**不删**。只在 404 分支多打一次请求,正常路径不加
|
|
1900
|
+
- `test/desktop205-profile-delete-warrant.test.ts`(真 http server 分别回我方信封 / nginx HTML /
|
|
1901
|
+
关端口,真 CloudClient,真 local.db;oracle 是两张表的行数)。把判据改回裸 404 ⇒ HTML 404 与
|
|
1902
|
+
`{}` 404 **当场删光本地行**,实测转红
|
|
1903
|
+
|
|
1904
|
+
### Fixed — 断云会永久毁掉产物投递(desktop205 W8,两个真 bug)
|
|
1905
|
+
|
|
1906
|
+
`docs/desktop205/03-acceptance.md` §3.3 的断云契约要求「产物 = 落本地 + 进 outbox」且
|
|
1907
|
+
「复联 = outbox 补传成功」。实测(`test/desktop205-offline-outbox.test.ts`,真 http server +
|
|
1908
|
+
真 socket + 真 SQLite + 真文件系统,零 mock)两条都不成立,成因是 `UploadRunner` 里两个叠加的
|
|
1909
|
+
缺陷:
|
|
1910
|
+
|
|
1911
|
+
- **一次 `drainOnce` 烧光全部 attempts**。`recordFailure` 把非终态行直接放回 `'pending'`,
|
|
1912
|
+
而 `drainOnce` 的 `while(true)` 会在同一轮里**立刻重新 claim 这同一行** ⇒ `maxAttempts=3`
|
|
1913
|
+
的三次重试全部发生在同一次 drain、彼此之间没有任何时间间隔。配合 runner.ts 每 1 秒一次的
|
|
1914
|
+
drop-folder tick,**一次 1 秒的网络抖动就足以把一个产物判死**。
|
|
1915
|
+
修:一次 drain 对同一行最多消耗一次尝试(重复 claim 即释放并跳出)。
|
|
1916
|
+
- **ECONNREFUSED 被算进 dead-letter 预算**。cloud 不可达属于"稍后重试",不是"这些字节
|
|
1917
|
+
永远不会被接受";旧行为把断云变成**永久数据丢失**(文件被移进 `upload-failed/`,复联后
|
|
1918
|
+
再也不会补传)。修:新增 `isTransientUploadError()`(socket 级错误码 / `fetch failed` /
|
|
1919
|
+
超时 / 408·429·5xx),瞬时失败**永不** dead-letter;4xx 等永久拒绝仍照旧判死。
|
|
1920
|
+
|
|
1921
|
+
负控(防"把 dead-letter 整个拆了"骗绿):永久性 HTTP 400 仍必须在 maxAttempts 次后
|
|
1922
|
+
dead-letter 并把文件移进 `upload-failed/`;以及"不复联就断言复联结果必须红"。
|
|
1923
|
+
|
|
1924
|
+
### Added — daemon 侧上报 tray 可观测性数据(desktop205 W13)
|
|
1925
|
+
|
|
1926
|
+
2026-07-26 裁决把桌面 tray 定位成**普通用户唯一的可观测面**(daemon runtime 是服务器形态,
|
|
1927
|
+
`prismer` CLI 对桌面 daemon 结构性失明)。daemon 补上它此前从不上报的两项:
|
|
1928
|
+
|
|
1929
|
+
- **`/healthz.assetOutbox`** `{pending, uploaded, deadLetter, sampledAt}` —— 产物待传/失败积压。
|
|
1930
|
+
那次清理撞见 2 在飞 + 4 dead-letter,用户完全无从知晓。
|
|
1931
|
+
⚠️ **计数由 producer 侧采样**:healthz 是纯内存零 I/O 快照而 tray 定时轮询它,
|
|
1932
|
+
handler 里做 `COUNT(*)` 会把每次轮询变贵。采样点是 drop-folder tick(本就每秒开这个 db),
|
|
1933
|
+
复用既有的 `pendingCount/uploadedCount/deadLetterCount`,新增的只是组合器
|
|
1934
|
+
`snapshotOriginOutboxCounts`
|
|
1935
|
+
- **`/tasks/running.tasks`** `[{taskId, agentName?, kind?, scopeLabel?, startedAt}]` —— 在跑的是谁、
|
|
1936
|
+
归属哪个 workspace、跑了多久。`scopeLabel` 是 cloud 预渲染的 `identityContext.scope`
|
|
1937
|
+
**原样透传**(不解析)。**不含任务名**:wire 上根本没有 title,只有 `prompt`,而 prompt 是
|
|
1938
|
+
用户内容,不该画进菜单栏
|
|
1939
|
+
- 两者都走既有 **spread 范式**:runner 没接对应子系统 ⇒ 字段整个缺席,
|
|
1940
|
+
**CLI / K8s 的 healthz 与 `/tasks/running` 形状逐字节不变**;且「没数据」与「数据是 0」
|
|
1941
|
+
在 wire 上就是可区分的(消费端不得 `?? 0`)
|
|
1942
|
+
- `test/tray-outbox-healthz.test.ts`:真 SQLite outbox → 真 HTTP /healthz 断言积压数;
|
|
1943
|
+
负控三条(数据源置反 / 停止采样 / state 不带字段),并断言**反复轮询 healthz 不会改变读数**
|
|
1944
|
+
(证明零 I/O 没被破坏)
|
|
1945
|
+
|
|
1946
|
+
### Fixed — held-out deny 规则不再落共享 settings.json(desktop205 W4)
|
|
1947
|
+
|
|
1948
|
+
`ensureApcHeldOutDeny` 把 deny 规则 read-merge-write 进 **per-daemon** 的
|
|
1949
|
+
`~/.prismer/claude-config/.claude/settings.json`,合并是 `new Set([...existing, ...rules])`
|
|
1950
|
+
**只增不删** ⇒ 一次 APC run 置位后,同机此后所有 coding agent(含无关的用户 agent)
|
|
1951
|
+
永久继承 held-out deny,且规则藏在 hermetic 目录里用户无从得知。
|
|
1952
|
+
|
|
1953
|
+
- `ensureApcHeldOutDeny`(有文件副作用)**删除**,换成纯函数
|
|
1954
|
+
`apcHeldOutDenySettings()` / `apcHeldOutSettingsArg()`,零文件系统副作用
|
|
1955
|
+
- 规则改由 **per-run** 的 Agent SDK `Options.settings` 承载(= CC 的 `--settings` flag,
|
|
1956
|
+
CC 自己落到临时文件再读)。共享 settings.json 全程不被写
|
|
1957
|
+
- 置位点从 `buildClaudeSpawnEnv`(env builder 不该有文件副作用)挪到 `buildOptions`
|
|
1958
|
+
- ⚠️ 选 `Options.settings` 而非 `extraArgs.settings`:两者编译成**同一个** `--settings`
|
|
1959
|
+
flag,而 SDK 让 `Options.settings` 覆盖 extraArgs 项(`sdk.mjs`:
|
|
1960
|
+
`if (this.options.settings) k9.settings = this.options.settings`)。`buildFastModeOptions`
|
|
1961
|
+
已经在 fast-mode 机型上设 `Options.settings`,走 extraArgs 会在那些 run 上**静默丢掉**边界
|
|
1962
|
+
- ~~已知残留:老机器上**已被污染**的 `settings.json` 不会自己消失,需一次性清理~~
|
|
1963
|
+
→ 已由下面的 W4 迁移收掉
|
|
1964
|
+
|
|
1965
|
+
### Fixed — held-out deny 的**开关**也变成 per-dispatch(desktop205 W5 / F7)
|
|
1966
|
+
|
|
1967
|
+
W4 只消除了「污染永久化」,开关本身仍是 daemon 全局:`buildOptions` 调
|
|
1968
|
+
`apcHeldOutDenySettings()` **不传参**,读的是 daemon 的 `process.env`。⇒ 想开这个边界,
|
|
1969
|
+
只能给整台机器上所有 coding agent 一起开 —— 正是 W4 刚刚拆掉的爆炸半径又从正门走了回来。
|
|
1970
|
+
|
|
1971
|
+
- `buildOptions` 改为 `apcHeldOutDenySettings(sdkEnv)`:判据取**这次 spawn 真正拿到的
|
|
1972
|
+
env**。`buildClaudeSpawnEnv` 已经把 dispatch 的 per-task env(`extra.claude.env` →
|
|
1973
|
+
`taskEnv`,以及承载 cloud `metadata.skillConfigEnv` / `roleParamsEnv` 的
|
|
1974
|
+
`launchContext.env`)叠在 daemon env 之上,所以 per-task `APC_HELDOUT_DENY=1`
|
|
1975
|
+
只窄化这一次 run,同 daemon 并发的其他 coding agent 不受影响
|
|
1976
|
+
- `test/apc-heldout-per-task.test.ts`:oracle 是**注入 queryFactory 捕获的真 SDK
|
|
1977
|
+
`Options`**(真 `ClaudeAgentSession` 跑真 `buildOptions`,探针不复刻合并逻辑)。
|
|
1978
|
+
负控 = 同一 client(同 daemon、`process.env` 全程不置位)两个 session,只有带标记的那个
|
|
1979
|
+
拿到规则;另一条负控 = per-task `'0'` 压过 daemon 全局 `'1'`
|
|
1980
|
+
- ⚠️ **仍未接线**:cloud / daemon 两侧都**不存在**「这次 run 是 APC coding-scope」这个判据
|
|
1981
|
+
(`workdir.sourceRef` 从不与 repo 身份或 held-out 列表比对),所以通道就绪、置位方待定。
|
|
1982
|
+
⚠️ 定位是 **hint 不是安全边界**——`Bash` 结构上就在 CC 的 file-permission check 之外
|
|
1983
|
+
|
|
1984
|
+
### Fixed — 一次性清理被 pre-W4 代码污染的 settings.json(desktop205 W4 迁移)
|
|
1985
|
+
|
|
1986
|
+
- 新增 `pruneApcHeldOutDenyFromSettings(configDir)`:从 `permissions.deny` 里移除**恰好等于**
|
|
1987
|
+
旧写入方生成过的规则(含已废弃的 `Write(...)` / `MultiEdit(...)` / `NotebookEdit(...)`
|
|
1988
|
+
三种拼法——那正是老机器上的大头),用**精确字符串相等**而非前缀/模式匹配,用户自建规则
|
|
1989
|
+
逐字保留;deny 变空则删该键,文件里其他内容一律不动。非抛出:文件缺失 / 不可解析 /
|
|
1990
|
+
不可写都是返回值,不是异常
|
|
1991
|
+
- 落点是 `ensureClaudeConfigIsolation`:它本就拥有 hermetic 目录、在任何 CC spawn 前必跑、
|
|
1992
|
+
已是 per-daemon-process 缓存(= 一次性迁移想要的节奏),且只在隔离开启时触发——污染只可能
|
|
1993
|
+
在那个前提下产生。失败被吞,清理不阻断 dispatch
|
|
1994
|
+
- `test/apc-heldout-deny-migration.test.ts`:oracle 是 settings.json 的**字节**。
|
|
1995
|
+
最吃重的负控是「用户自建规则必须存活」,含形似但不在列表里的 `Edit(scripts/test203)`
|
|
1996
|
+
(held-out 路径的前缀)、`Edit(scripts/test203/journeys/j11-…)`(held-out 目录**内部**)等;
|
|
1997
|
+
把实现换成子串匹配即变红
|
|
1998
|
+
- 已知边界:若将来从 `APC_HELD_OUT_PATHS` **删除**某路径,磁盘上它的旧规则将不再可清理,
|
|
1999
|
+
需要单独补一条
|
|
2000
|
+
|
|
2001
|
+
### Fixed — deny 规则表按 CC 2.1.220 实际语法换代(desktop205 W3)
|
|
2002
|
+
|
|
2003
|
+
`APC_DENY_TOOLS` 从 `["Write","Edit","MultiEdit","NotebookEdit"]` 收敛为 `["Edit"]`。
|
|
2004
|
+
CC 2.1.220 实跑证实:`Edit(path)` 是**唯一**参与 file permission check 的路径匹配器,
|
|
2005
|
+
且它覆盖全部 file-editing 工具;`Write(p)` / `NotebookEdit(p)` 不参与,`MultiEdit`
|
|
2006
|
+
已不是已知工具。**语义不减弱**,去掉的是每个 held-out 路径 6 条死规则(每次 spawn 54 行
|
|
2007
|
+
stderr 告警)。
|
|
2008
|
+
|
|
2009
|
+
- 新增 `test/apc-heldout-cc-syntax.test.ts`(desktop205 G1):拿**实际发出的** `--settings`
|
|
2010
|
+
payload 起一次真 `claude`(临时 HOME、无凭证、无网络——CC 在鉴权前就校验规则),
|
|
2011
|
+
断言 permission-rule 告警 **0 条**;负控塞一条 `MultiEdit(path)` 必须出告警。
|
|
2012
|
+
CC 二进制不可得时写 evidence 文件并 **loud skip**,不静默通过
|
|
2013
|
+
- 旧 drift-guard 只比对**路径列表**、不校验规则语法,所以这次 CC 改语法我们完全不知道;
|
|
2014
|
+
G1 把「CC 升级悄悄废掉我们的规则」变成可检出
|
|
2015
|
+
|
|
2016
|
+
### Fixed — APC skill 源随包分发(desktop205 D8)
|
|
2017
|
+
|
|
2018
|
+
`sdk/apc/skills/` 此前**只存在于 repo working tree**。`resolveApcSkillsRoot()` 从运行
|
|
2019
|
+
模块向上walk,因此在任何非 repo 宿主(OTA bundle / npm 装的 daemon / 桌面
|
|
2020
|
+
`resources/runtime`)都返回 null ⇒ `installPlatformApcSkills()` 返回 null ⇒ **什么都不装**。
|
|
2021
|
+
这是藏在平台门背后的第二道静默门,与 2026-07-19「bundle 漏 plugins/ ⇒ 记忆层全黑」
|
|
2022
|
+
**同一失败模式**(打包清单与真实依赖各自演化),修法沿用那次的范式。
|
|
2023
|
+
|
|
2024
|
+
- prebuild hook 现镜像 **两个**源进包根:`built-in-skills/` + 新增 `apc/skills/`
|
|
2025
|
+
- `package.json` `files` 增加 `apc`(npm tarball 已实测含全部 35 个文件)
|
|
2026
|
+
- 两个 OTA 打包器(K8s `build-daemon-runtime-bundle.ts` / 桌面 `build-daemon-bundle.cjs`)
|
|
2027
|
+
stage `apc/` 并在各自 REQUIRED_ENTRIES 里校验
|
|
2028
|
+
- `BUNDLE_REQUIRED_ENTRIES` 与桌面 `REQUIRED_ENTRIES_BY_KIND.daemon` **同步**加
|
|
2029
|
+
`apc/skills`——缺 skill 的 bundle 判为不可用,而不是静默降级
|
|
2030
|
+
- 桌面 `assemble-runtime.sh`(built-in 兜底层,被 `resolveDaemonBundlePath()`
|
|
2031
|
+
**不经校验**直接返回)补 stage `apc/` **与 `plugins/`**。后者是本次挖出的既有缺口:
|
|
2032
|
+
2026-07-19 那次断言「floor 总是 memory-capable」只对 K8s 镜像成立,桌面这层手工
|
|
2033
|
+
组装的 floor 从来没有 `plugins/`
|
|
2034
|
+
- 新增 `test/apc-skill-distribution.test.ts`:仓外临时目录里 esbuild 打真模块 → 断言
|
|
2035
|
+
`resolveApcSkillsRoot()` 真返回存在路径(正控);删掉 `apc/skills` 后断言解析为 null
|
|
2036
|
+
**且** `isValidBundleDir()` 判红(负控);四处清单再分叉即红(drift 门)
|
|
2037
|
+
|
|
2038
|
+
### Fixed — launchd boot shim 是第三份 REQUIRED_ENTRIES 且早就漂了(desktop205 O11)
|
|
2039
|
+
|
|
2040
|
+
`apps/desktop/electron/daemon-boot.cjs` 的注释自称与 `bundle-manager.ts`
|
|
2041
|
+
"same contract … keep them in sync",实际只要 `dist/cli.js` + `package.json`——
|
|
2042
|
+
2026-07-19 加的 `plugins/` 和 2026-07-26 加的 `apc/skills` 都没跟上。
|
|
2043
|
+
⇒ **launchd 会启动一个 host 进程判为无效的 bundle**,而「缺 plugins 的 bundle 被启动」
|
|
2044
|
+
正是那次记忆层全黑事故的形状。
|
|
2045
|
+
|
|
2046
|
+
- shim 的 **bundle** 判据收紧到与另两份逐字节一致(4 项)
|
|
2047
|
+
- **兜底层判据刻意更弱**(只要 `dist/cli.js` + `package.json`):floor 是用户与
|
|
2048
|
+
「这台机器没有 daemon」之间的最后一道,因为缺 `plugins/` 就拒绝启动它 = 把「降级的
|
|
2049
|
+
daemon」变成「没有 daemon」,还会被 KeepAlive 无限重放。这与 host 侧完全一致——
|
|
2050
|
+
`resolveDaemonBundlePath()` 校验 staged/current **bundle**,然后**不校验**地返回
|
|
2051
|
+
builtin。保持 floor 有能力是打包步骤的职责(`assemble-runtime.sh`,已有断言)
|
|
2052
|
+
- 不从共享模块 require 清单:该文件**只 import node builtin** 是它的立身之本
|
|
2053
|
+
(生成物 = 又一个能在「绝不许起不来」的路径上丢失的东西)。改用 drift 断言机械化
|
|
2054
|
+
- `apc-skill-distribution.test.ts` 的 drift 门从四处扩到**五处**,并把「floor 判据必须是
|
|
2055
|
+
bundle 判据的真子集」也钉住,让这条不对称保持是**决定**而不是退化成漂移
|
|
2056
|
+
- `apps/desktop/electron/__tests__/boot-shim.test.ts`:oracle 仍是 marker 文件
|
|
2057
|
+
(**哪个 cli.js 真的跑了**)。新增负控——缺 `plugins/` 的 bundle 与缺 `apc/skills` 的
|
|
2058
|
+
bundle 都必须被拒且回落到 floor;配套反向负控:完整 bundle 仍能启动(证明门不是无条件拒绝)、
|
|
2059
|
+
缺 `plugins/` 的 **floor** 仍能启动、缺 `dist/cli.js` 的 floor 仍 exit 1
|
|
2060
|
+
|
|
2061
|
+
### Added — skill 验收判据引擎:新增 `type:"structured"` 与 6 个 checker(补记)
|
|
2062
|
+
|
|
2063
|
+
> **补记说明**:这批改动分 4 个 commit 落地(`4f17c311` / `689ea719` / `e82db478` /
|
|
2064
|
+
> `c3d91481`)**均未更新本文件**,违反 CLAUDE.md「改 `sdk/*` 任何包必须更对应
|
|
2065
|
+
> CHANGELOG」。由 apc/12 的 code-review 验收跑在审 `c3d91481` 时作为 **convention
|
|
2066
|
+
> 段 major finding** 抓出——这正是该 skill 该抓的东西,故在此补齐而非静默略过。
|
|
2067
|
+
|
|
2068
|
+
`bundle` 的 `matchAcceptanceCriteria` 新增 `type:"structured"` 判据类型(`substring`
|
|
2069
|
+
/ `regex` 两条既有路径**逐字节不变**;未知 checker fail-closed)。动机:原判据全部
|
|
2070
|
+
读 agent 的**报告文本**,因此原则上无法区分「真跑」与「编造」——实证一份零检索、
|
|
2071
|
+
路径行号瞎写的纯编造报告可拿满分。
|
|
2072
|
+
|
|
2073
|
+
**层 2(复核报告关于「文件」的声称)**
|
|
2074
|
+
|
|
2075
|
+
- `cited-evidence` — 每个 `path:line` 读回磁盘(存在/行范围/非空行/锚定命中/行数下限)
|
|
2076
|
+
- `dimension-coverage` — 每维独占 keyed 行,答案须是可复核引用或 `N/A — 理由`;
|
|
2077
|
+
可选 `requiredVerdicts` 钉某维 verdict;可选 `anchorToKey` 要求引用落在**声明该维的那一行**
|
|
2078
|
+
- `doc-sync-obligations` — 从 delta 自行重算义务集与 gap 集再比对
|
|
2079
|
+
|
|
2080
|
+
**层 3(复核关于「运行时副作用」的声称)**
|
|
2081
|
+
|
|
2082
|
+
- `json-claim` — 重解析报告内 fenced JSON:钉 schema/形态、散文数值 ⇄ 产物字段、
|
|
2083
|
+
派生判定须由退出码推出、每层算术、`baselineRecompute`、`freshArtifacts`(产物 mtime
|
|
2084
|
+
与自带 timestamp 同期)、`filesExist`、`fileEquals`
|
|
2085
|
+
- `declared-id-readback` — `TASK:/ASSET:/CRITERION:/META:` 声明的 id **回读真 API**
|
|
2086
|
+
(含存储 JS 类型,`--set` 的数字化在此被逮)
|
|
2087
|
+
- `git-claim-readback` — 只读 git plumbing:commit 文件集恰好等于声明、branch tip、
|
|
2088
|
+
push 落点须为本地 bare 替身、被拒 prod tag 不在其上、冲突仓 `MERGE_HEAD` 仍在
|
|
2089
|
+
|
|
2090
|
+
I/O 走同步子进程(HTTP 经 `execFileSync(node -e fetch)`,token 由 env 传不进 argv;
|
|
2091
|
+
git 全只读),**`matchAcceptanceCriteria` 签名未变**,两个 CLI 的 `normalizeCriterion`
|
|
2092
|
+
同步保留 `checker`/`args`(否则结构化条目会降级成 substring 而恒红)。
|
|
2093
|
+
|
|
2094
|
+
### Fixed — `dimension-coverage` 的 `anchorToKey` 配错不再静默 fail-open
|
|
2095
|
+
|
|
2096
|
+
`anchorToKey` 此前经 `argRecord` 取值,对任何非 plain-object 返回 `undefined` ⇒
|
|
2097
|
+
`true` / `[{…}]` / `"minAnchored:1"` / 键名手滑,**都会静默退回加严前的行为并报满绿**。
|
|
2098
|
+
而该 arg 存在的唯一理由就是堵「label 承诺大于实现」那个洞 ⇒ 静默跳过使这道守卫沦为
|
|
2099
|
+
装饰。现改为显式 `checker misconfigured` 判红,与本文件既有惯例一致(`dimensions` /
|
|
2100
|
+
`requiredVerdicts` / `deltaFiles` / `json-claim` args / `allowed.*` 早已 fail-closed)。
|
|
2101
|
+
**缺省不置位时行为仍逐字节不变**,不追溯打红已按旧语义验收的判据。
|
|
2102
|
+
|
|
2103
|
+
### Fixed — `CITATION_RE` 缺 `%` 导致引用真实文件被判编造
|
|
2104
|
+
|
|
2105
|
+
路径字符类与 lookbehind 排除集均不含 `%`,而仓库有 19 个真实的 `%5F` 路径文件
|
|
2106
|
+
(admin debug endpoint)⇒ 被解析成 `5Fadmin/...` 判 FAKE。对 `cited-evidence`
|
|
2107
|
+
(`bad.length > 0 ⇒ fail`)是**无条件红**——**这类「如实判红」比「编造判绿」更隐蔽**,
|
|
2108
|
+
因为它看起来像判据很严。已补 `%` 并加回归;存在性检查未放宽。
|
|
2109
|
+
|
|
2110
|
+
### Fixed — APC skill 只投平台 workspace,源改工作树(判据修正)
|
|
2111
|
+
|
|
2112
|
+
上一版把「谁能拿 APC skill」判据挂在 **cloud 的逐 agent 安装记录**上。用户随后给出平台
|
|
2113
|
+
workspace 的正式定义,两条都推翻了它:
|
|
2114
|
+
|
|
2115
|
+
1. **APC 范围的 skill 只在唯一的平台 workspace 安装**,只用来做 prismercloud 工程本身,
|
|
2116
|
+
**不需要任何发布动作**。⇒ cloud catalog 不该出现在内容路径上(它当时只有 17 个里的 7 个,
|
|
2117
|
+
且比工作树旧几天)。
|
|
2118
|
+
2. **平台 workspace = 平台管理员账号绑定的 workspace**。⇒ 判据是 workspace 的 owner,不是
|
|
2119
|
+
agent 的安装行。旧判据选错了对象:真机上被投中的 agent 属于一个**非平台** workspace。
|
|
2120
|
+
|
|
2121
|
+
**新判据**(`isPlatformWorkspace`,daemon 侧):
|
|
2122
|
+
|
|
2123
|
+
- `GET /api/im/workspaces/:id` → `ownerImUserId`;`GET /api/im/users/lookup?identifier=<email>`
|
|
2124
|
+
→ 平台管理员的 imUserId;相等即平台 workspace。两个都是既有 endpoint,**cloud 侧零改动**。
|
|
2125
|
+
- 管理员白名单读 `ADMIN_EMAILS`(与 cloud `admin-rbac.ts` 同一个 env、同一套 lowercase 归一)。
|
|
2126
|
+
- **不问 cloud「我是不是 admin」**:`isSandboxAdmin()` 在 `NODE_ENV !== 'production'` 对任何人
|
|
2127
|
+
短路返回 true,拿它当高危投递的门在 dev 等于没门(apc/14 A3 的教训)。这里是
|
|
2128
|
+
`isAdminEmailStrict` 的 daemon 侧镜像。
|
|
2129
|
+
- **fail-closed 覆盖每一种 unknown**:白名单未配置/为空、profile 没有 workspaceId、workspace
|
|
2130
|
+
读不到(非成员 404 / 网络错 / 报文异常)—— 一律判 false,且 false 不只是「不装」,而是把
|
|
2131
|
+
entitled class **reconcile 成空**(非平台机器上残留的 APC 目录与新装同样是泄漏)。
|
|
2132
|
+
- verdict 有缓存:正判 30min、负判 60s(`/users/lookup` 是 30/min 限流;正判长 TTL 让已证实的
|
|
2133
|
+
平台机器在云端短暂不可达时不掉工具链)。
|
|
2134
|
+
|
|
2135
|
+
**源改工作树**:`resolveApcSkillsRoot()` 从模块位置向上找 `sdk/apc/skills/`(dist 3 跳、src 7
|
|
2136
|
+
跳),`installPlatformApcSkills()` 直接 cpSync。永远是最新版、无需发布、cloud 退出内容路径只
|
|
2137
|
+
留授权路径。npm 安装态找不到该目录 → null → 不投(只有平台 checkout 才有源)。
|
|
2138
|
+
|
|
2139
|
+
**保留未动**:`persistence → hermes only` 的门逐字节未动;`.prismer-managed` /
|
|
2140
|
+
`.prismer-entitled` 两个互不相交 managed class 与各自 reconciler 的归属护栏;
|
|
2141
|
+
`resolveCodingCwdSkillsDir()` 的 claude-code-only 目标解析。
|
|
2142
|
+
|
|
2143
|
+
测试 `test/coding-skill-entitlement.test.ts` 18 → **33 例**,四处判据变异全部真红(平台门恒真
|
|
2144
|
+
→ 6 红;白名单空时静默兜底 → 2 红;去掉 persistence 拒绝 → 2 红;去掉外来目录护栏 → 2 红)。
|
|
2145
|
+
真机(活 daemon 未停未重启,走真 endpoint):平台 workspace 的 coding agent 拿到 **17/17**
|
|
2146
|
+
且含工作树独有的「输出契约」节;非平台 workspace 的 agent **0 个**且旧的 7 个被 evict,12 个
|
|
2147
|
+
`.prismer-managed` 与用户的 `sdk-release` 完好;`ADMIN_EMAILS` 未配置时对**真平台 workspace**
|
|
2148
|
+
也是 0 且目录不创建。
|
|
2149
|
+
|
|
2150
|
+
### Fixed — structured acceptance checkers: three contract↔checker misalignments that reddened truthful reports (apc/12)
|
|
2151
|
+
|
|
2152
|
+
端到端真跑(真 daemon → claude-code → 真回帖)证实结构化 checker 的机制是对的(编造报告
|
|
2153
|
+
`18 citations / 0 verified on disk` 判红,同一份报告下三条旧 regex 判据仍全 PASS),但同时暴露
|
|
2154
|
+
**三条会把 100% 如实的报告判红**的缺陷。「无论对错都会红」与「无论对错都会绿」是同一类 bug。
|
|
2155
|
+
|
|
2156
|
+
- **`CHANGE-POINT` 正则拒绝整行反引号包裹**:前导字符类 `^[\s>*\-|]*` 不含反引号,而
|
|
2157
|
+
`impact-trace` sample prompt 自己给的范例就是整行包裹的 —— 照抄范例 → `no CHANGE-POINT
|
|
2158
|
+
declaration found`。前导类补反引号。
|
|
2159
|
+
- **`rg -c` 的 `path:COUNT` 被当行号复核**:契约明令跑 `rg -c` 对账,其输出正是 `path:数字`。
|
|
2160
|
+
新增 `parseClaimCitations()`:整行只有一个裸 `path:数字` 的行(即 `rg -c` 输出)不采为
|
|
2161
|
+
citation;`parseCitations()` 保持字面语义不变(调用方会传单行片段给它,容差不能落在那里)。
|
|
2162
|
+
契约侧同步要求计数写成 `count=N`、不得内联进句子——内联即恢复为声明并照常复核。
|
|
2163
|
+
- **缩写路径判 FAKE 且文案误导**:`verifyCitation` 的失败文案改为点名契约规则
|
|
2164
|
+
(REPO-ROOT-RELATIVE / `rg -c` 计数不是行号),契约侧显式写明必须 repo-root-relative。
|
|
2165
|
+
|
|
2166
|
+
### Added — `dimension-coverage` 支持钉死 verdict(`args.requiredVerdicts`)
|
|
2167
|
+
|
|
2168
|
+
`{ "<dimension>": "covered"|"gap"|"n/a" }` 要求该维度**自己那一行**以指定 verdict 开头
|
|
2169
|
+
(中文 `已覆盖`/`缺口`/`不适用` 同义)。没有该 arg 时行为逐字节不变;有该 arg 但行上无
|
|
2170
|
+
verdict token、或 verdict 不符、或 `requiredVerdicts` 指向 `dimensions` 之外的键 → fail-closed。
|
|
2171
|
+
这是 `design-review` 五维审计的承重 oracle:没有它,「五维全标 covered + 正文某处出现 gap 一词」
|
|
2172
|
+
是一次照样打绿的橡皮图章。
|
|
2173
|
+
|
|
2174
|
+
测试 `test/bundle-structured-criteria.test.ts` 25 → 36 例(新增 11,每条正控配负控,含
|
|
2175
|
+
「照契约范例逐字产出的报告必须绿」一组)。
|
|
2176
|
+
|
|
2177
|
+
### Added — APC held-out write DENY at dispatch (apc/14 D6,双重约束的 dispatch 侧)
|
|
2178
|
+
|
|
2179
|
+
docs/apc/02 §2 R4 的"双重约束"要求 held-out(`baseline.json` / journeys / contract gates /
|
|
2180
|
+
视觉基线)**物理上打不开写**。review 侧的 diff 门(`scripts/test203/run.ts --review-diff`,
|
|
2181
|
+
不依赖 agent 诚实)已成;本条补上 dispatch 侧的 deny。
|
|
2182
|
+
|
|
2183
|
+
隔离 CC 的 hermetic `settings.json`(`~/.prismer/claude-config/.claude/settings.json`,
|
|
2184
|
+
即 CC 的 `user` settings source)种入 `permissions.deny` 规则——`Write/Edit/MultiEdit/NotebookEdit`
|
|
2185
|
+
命中 held-out 路径即被 CC 二进制**硬拒**(deny 是硬边界,即使 `bypassPermissions` 模式
|
|
2186
|
+
`canUseTool` 谓词根本不被调用也生效,故落点选 settings.json 而非谓词)。
|
|
2187
|
+
|
|
2188
|
+
- **门**:`APC_HELDOUT_DENY=1`(dispatch 侧显式开,默认 OFF——普通 coding agent 写权限不受影响)。
|
|
2189
|
+
且只在 config 隔离开启(默认 ON)时生效,绝不写用户真实 `~/.claude`。
|
|
2190
|
+
- **单一真相源**:`config-isolation.ts::APC_HELD_OUT_PATHS` **镜像**
|
|
2191
|
+
`scripts/test203/heldout-guard.ts::HELD_OUT_PATHS`(runtime 包 `rootDir=./src` 无法跨包
|
|
2192
|
+
import,故镜像 + drift-guard 测试读源文件断言两份集合相等——漂移即红)。
|
|
2193
|
+
- **不误伤**:只拒 held-out 写;普通源文件(含 `scripts/test203/run.ts` 本身)、读操作不受限。
|
|
2194
|
+
- 落点 `config-isolation.ts`(`isApcHeldOutDenyEnabled` / `apcHeldOutDenyRules` /
|
|
2195
|
+
`ensureApcHeldOutDeny`,read-merge-write 幂等,不丢无关 settings),种入点在
|
|
2196
|
+
`agent.ts::buildClaudeSpawnEnv`。契约测试 `test/apc-heldout-deny.test.ts`(8 例,每正控配负控,
|
|
2197
|
+
三处变异各自转红)。端到端(真 CC 进程试写 baseline 被拒)本轮未跑(无活 daemon)。
|
|
2198
|
+
|
|
2199
|
+
### Fixed — daemon churn 放大成请求风暴(APC Root B:sync/ack 无 backoff + host.acked 全量再同步)
|
|
2200
|
+
|
|
2201
|
+
gap A(下条)用有界 undici dispatcher 封住了请求风暴的**爆炸半径**,但**没治源头**。源头
|
|
2202
|
+
是一个 churn 循环(`ownership rejected → adopt → re-declare`)让 `host.acked` 在短窗内**连发多次**,
|
|
2203
|
+
而每次 `host.acked` 都会 (a) 触发一遍**全 workspace** 的 cloud→local memory 再同步,(b) skill-sync
|
|
2204
|
+
路径**重发每个 skill 的 ack**——**攻击之间无 backoff、快速触发不合并**。打到慢 cloud 上就是自我放大
|
|
2205
|
+
(实测 `skill sync ack failed` ×85 / `Request aborted/timeout` ×107)。
|
|
2206
|
+
|
|
2207
|
+
**修**(新模块 `src/daemon/churn-guard.ts`,两个纯 + 可注入时钟/RNG 的原语):
|
|
2208
|
+
|
|
2209
|
+
- **`ExponentialBackoff`** — 连续失败 → 下次尝试延迟 `base·factor^n`(封顶 + ±抖动),成功即重置。
|
|
2210
|
+
用于 skill-sync **ack 门**(`skill-sync.ts::ackSkillSync`):按 `(agent,slug)` 记账,一个刚失败的
|
|
2211
|
+
ack 在其指数退避窗口内**跳过重发**(下个自然触发在窗口过后重试,不丢 ack、只是不再猛砸)。base 2s /
|
|
2212
|
+
cap 60s。env kill-switch `PRISMER_DAEMON_ACK_BACKOFF=off`。
|
|
2213
|
+
- **`CoalescingRunner`** — 把短窗内的 N 次 `trigger()` **合并成一次** trailing run,失败后按 backoff
|
|
2214
|
+
拉开重试间距。`memory/runner-wiring.ts::syncMemoryFromCloud` **改为 debounce**:churn 期一串
|
|
2215
|
+
`host.acked` 只落**一次**全 workspace fan-out(workspace 并集);整体不可达时按指数退避重试而非
|
|
2216
|
+
每 tick 再发火。默认 1s 窗口(非延迟敏感,见 cloud-dispatcher.ts 注释),env
|
|
2217
|
+
`PRISMER_DAEMON_MEMORY_SYNC_DEBOUNCE_MS=0` 可关。
|
|
2218
|
+
|
|
2219
|
+
只改 retry/trigger 的**节奏**,不掩盖竞态、不放松任何断言。单测断真行为(间隔指数增长、N 次触发只跑 1 次、
|
|
2220
|
+
失败按 backoff 拉开),每条修复配一条"原本会抓到它"的变异用例(拆 backoff 成恒定间隔 / 拆 debounce /
|
|
2221
|
+
拆 ack 门 → 分别红 4/4/1 条,已实测)。**诚实边界**:本轮只证 backoff/debounce 的**逻辑**有牙;真活
|
|
2222
|
+
daemon 下"连接不再涨"的前后对照未跑(有界 dispatcher 已封半径,主会话可随后验)。孤儿 council daemon 的
|
|
2223
|
+
**根治 = 孤儿 conversation soft-delete cascade**,属 `project_orphan_conversations_survive_workspace_soft_delete`
|
|
2224
|
+
深域,不在本次。
|
|
2225
|
+
|
|
2226
|
+
### Fixed — daemon cloud fetch 连接池无上限,churn 会撑爆慢 cloud(APC gap C)
|
|
2227
|
+
|
|
2228
|
+
daemon 的 cloud HTTP 客户端(`auth.ts CloudClient`)用 Node 内置 `fetch`,即 undici
|
|
2229
|
+
**默认** global Agent——对单一 origin 的连接数**无上限**。任何请求风暴(如 adopt/ownership
|
|
2230
|
+
churn 在每次 `host.acked` 重放 memory-sync + transport-probe + skill-sync-ack、且无 backoff)
|
|
2231
|
+
打到一个**慢** cloud(本机 `npm run dev`,HTTP/1.1)时,undici 每个并发请求开一条**新** socket
|
|
2232
|
+
而非复用 ⇒ 连接爆涨(实测单个 daemon 攥住 **345 条**空闲 ESTABLISHED socket 到 :3000),
|
|
2233
|
+
把单线程 dev server 的 event loop 饿死(连静态路由都从 0.005s 涨到 24s),并形成恶性循环
|
|
2234
|
+
(慢 → 30s 超时 → 重试 → 更多 socket → 更慢)。删掉该 daemon,server 连接 711→5、延迟 24s→5ms,
|
|
2235
|
+
把病因隔离到 daemon 的无界连接池。
|
|
2236
|
+
|
|
2237
|
+
**修**:daemon 前台启动时(`daemon.ts::runForeground`,任何 cloud 请求/SSE 之前)安装一个
|
|
2238
|
+
**有界** undici global dispatcher(新模块 `src/daemon/cloud-dispatcher.ts`):`connections` 上限
|
|
2239
|
+
(默认 24,`PRISMER_DAEMON_MAX_CLOUD_CONNECTIONS` 可调)+ keep-alive 复用。再大的风暴也只能开
|
|
2240
|
+
≤N 条 socket,超额请求在 undici 内排队而不是拿新 socket 砸服务器——结构性封住爆炸半径。
|
|
2241
|
+
实测 200 并发请求打慢服务器:修前 200 socket,修后 24 socket。WS(走 `ws` 包)与 asset 下载
|
|
2242
|
+
(自带 per-request dispatcher)不受影响。**注**:这只封上限,不治真正的驱动(churn 循环 +
|
|
2243
|
+
skill-sync-ack 无 backoff),后者是另一个更深的 bug。
|
|
2244
|
+
|
|
2245
|
+
### Fixed — daemon git RPC / workdir jail:三个对抗评审坐实的真缺陷
|
|
2246
|
+
|
|
2247
|
+
评审用真 git 复现,全部已修 + 补回归用例(`test/git-rpc.test.ts` 26 例,
|
|
2248
|
+
`test/workdir-materialize.test.ts` 25 例,全部带负控)。
|
|
2249
|
+
|
|
2250
|
+
1. **非冲突的 git 失败被误判成 `conflict`**(`git-rpc.ts`)。旧实现对 git 的**错误文本**
|
|
2251
|
+
做子串匹配(`/conflict|automatic merge failed|unmerged files/i`),而 git 会把调用方
|
|
2252
|
+
给的 ref 名回显进错误里 ⇒ `branch fix/conflict-handling`(分支已存在)和
|
|
2253
|
+
`merge feat/conflict-x`(ref 不存在)都被判成 `conflict` 且 `files=[]`,一路透到 CLI 打印
|
|
2254
|
+
"Merge conflict — NOT auto-resolved. Escalate to a human. Conflicted files (0)"——
|
|
2255
|
+
把拼写错误渲染成需要人类介入的冲突。**现在判据取副作用**:git 自己的未合并索引
|
|
2256
|
+
(`git diff --diff-filter=U`)或在场的 `MERGE_HEAD`,与错误文本无关。真冲突的
|
|
2257
|
+
`files` 因此恒非空。
|
|
2258
|
+
2. **jail root 可由 payload 抬高**(`git-rpc.ts::runGitExecRequest`)。`workspaceId` 是
|
|
2259
|
+
拼进 `workspacesDir` 的**路径段**,而它只校验了 `typeof === 'string'`;`path.join`
|
|
2260
|
+
会归一化 `..` ⇒ `workspaceId:'ws_A/..'` 把 jail root 抬到 workspaces 目录,
|
|
2261
|
+
实测在**另一个 workspace 的 repo 里建出了分支**。现在用 `isSafeSegment` 校验段。
|
|
2262
|
+
(cloud 侧 member 门此前挡住了 HTTP 可达性,所以这是纵深防御层的洞——而
|
|
2263
|
+
daemon jail 存在的全部意义正是"cloud 错了也不塌"。)
|
|
2264
|
+
3. **jail 是词法的,symlink 可越狱**(`git-rpc.ts` + `workdir-materialize.ts`)。包含判定
|
|
2265
|
+
走 `path.resolve`,不解 symlink;而能在 workdir 里种 symlink 的正是这道 jail 针对的
|
|
2266
|
+
coding agent 本人。实测 jail 内 `ln -s <外部 repo> link` + `cwd:'link'` ⇒ 外部 repo
|
|
2267
|
+
真被写了分支。现在包含判定走 **realpath**(新模块 `src/daemon/path-jail.ts`),
|
|
2268
|
+
且返回解析后的真实路径(不再从 symlink 穿过去执行)。
|
|
2269
|
+
|
|
2270
|
+
**新增内部模块** `src/daemon/path-jail.ts`:`isSafeSegment` / `realpathBestEffort` /
|
|
2271
|
+
`resolveWithinJail`。安全谓词只此一份——重复实现必然漂移。
|
|
2272
|
+
|
|
2273
|
+
**顺带修的既有债(与上面 3 条分开记账)**:`runner.ts` 的
|
|
2274
|
+
`agent.fs.list` / `agent.fs.read` / `agent.fs.write` / `agent.workdir.materialize`
|
|
2275
|
+
用同样的方式拼 jail root,同样没校验段,现在四处都加了同一个守卫;
|
|
2276
|
+
`resolveWorkdirCwd` 的词法 jail 同步改成 realpath 判定。
|
|
2277
|
+
|
|
2278
|
+
**行为变化**:`resolveGitCwd` / `resolveWorkdirCwd` 返回的 cwd 现在是 realpath。生产
|
|
2279
|
+
路径(`~/.prismer/workspaces/...`)无 symlink,解析前后一致;仅测试夹具里的
|
|
2280
|
+
`os.tmpdir()`(macOS `/var` → `/private/var`)会看出差别。
|
|
2281
|
+
|
|
2282
|
+
### Added — daemon git RPC 接线 + 硬化(APC P0-5 / apc/05 §1 A2)
|
|
2283
|
+
|
|
2284
|
+
`src/daemon/git-rpc.ts` 此前**已存在但零调用方**(只在 `index.ts` export)。本次接线成
|
|
2285
|
+
`agent.git.exec` reverse-RPC(`runner.ts` 新增 case,回 `agent.git.reply`,镜像
|
|
2286
|
+
`agent.workdir.materialize` 的形态),并补四项硬化:
|
|
2287
|
+
|
|
2288
|
+
1. **cwd jail** —— 新增 `resolveGitCwd(root, cwd)`(镜像 `resolveWorkdirCwd`)。
|
|
2289
|
+
`GitRpcRequest.root` 现为**必填**(fail-closed);root 由 daemon 自己从
|
|
2290
|
+
`workspaces/<workspaceId>` 算,不接受 payload 指定。
|
|
2291
|
+
2. **remote allowlist** —— push 的 remote 必须是白名单里的**纯名字**(默认 `['origin']`,
|
|
2292
|
+
`PRISMER_GIT_REMOTE_ALLOWLIST` 可覆盖)。URL 形态与 option 形态
|
|
2293
|
+
(`--receive-pack=…`,可在对端执行命令)在进 argv 前就被拒。
|
|
2294
|
+
3. **commit / merge 回 sha** —— `GitRpcResult.sha`,供 task↔commit provenance。
|
|
2295
|
+
4. **冲突返回文件清单** —— `GitRpcError.files`(`git diff --name-only --diff-filter=U`),
|
|
2296
|
+
worktree 保持现场(不 `merge --abort`、不 reset)。
|
|
2297
|
+
|
|
2298
|
+
其它:错误 detail 现同时读 stderr+stdout(git 把 CONFLICT 写 stdout,旧的 `||` 链在
|
|
2299
|
+
stderr 非空时会丢掉冲突文本);git 子进程加 120s 超时(旧实现对不可达 remote 会永久挂)。
|
|
2300
|
+
|
|
2301
|
+
**破坏性(内部 API)**:`gitRpc()` 的 `root` 必填。此前零调用方,无外部影响。
|
|
2302
|
+
|
|
2303
|
+
### Fixed — drain_respawn no longer hangs on stop()(product205 OTA)
|
|
2304
|
+
|
|
2305
|
+
`armDrainRespawnWatcher` 不再 `await this.stop()`——`stop()` 里的
|
|
2306
|
+
`await servicePool.shutdown()` / `await localServer.stop()` 可能无限卡住
|
|
2307
|
+
(adapter 子进程不退出 / HTTP 连接不关闭),导致 `process.exit(0)` 永远
|
|
2308
|
+
到不了,daemon 不退出,kubelet 不重启,OTA 中断,用户必须手动重建 pod。
|
|
2309
|
+
|
|
2310
|
+
修法:drain_respawn 只做同步清理(关 WS + abort in-flight + stop SSE)
|
|
2311
|
+
然后直接 `process.exit(0)`。K8s/desktop supervisor 重启进程,OS 回收
|
|
2312
|
+
所有资源。
|
|
2313
|
+
|
|
2314
|
+
### Fixed — OTA bundle 纯净化(product205 OTA 空腔修复)
|
|
2315
|
+
|
|
2316
|
+
OTA bundle 构建 6 个空腔全部修复:
|
|
2317
|
+
1. `127.0.0.1` artifactUrl 不可达 → `host.docker.internal`
|
|
2318
|
+
2. `redis-cli` 不存在 → 用 `ioredis` 清 apply blob
|
|
2319
|
+
3. 版本黑名单 → 发新版本绕过
|
|
2320
|
+
4. macOS native 二进制(`fsevents.node` / `rollup.darwin-arm64.node`)→ 删除
|
|
2321
|
+
5. `built-in-skills/` 缺失(`readSkillText` throw)→ 补进 bundle
|
|
2322
|
+
6. `better-sqlite3` 目录残留导致 `linkBuiltinNativeDeps` 跳过 → 整个目录删除
|
|
2323
|
+
|
|
2324
|
+
### Added — action governance runtime P1(product205 M6)
|
|
2325
|
+
|
|
2326
|
+
daemon-side runtime enforcement seam reopens(docs/product205/03 §3.4):
|
|
2327
|
+
|
|
2328
|
+
- `HERMES_YOLO_MODE` 从硬编码 `'true'` 改为 `runtimeApprovalMode` 驱动(team=gated
|
|
2329
|
+
→ YOLO off → dangerous-command 桥激活;personal=auto)
|
|
2330
|
+
- `--dangerously-skip-permissions`(claude-code)改为 `runtimeApprovalMode` 条件化
|
|
2331
|
+
- Hermes `approval.request` SSE 事件捕获 §3.5 bundle(`buildApprovalBundleFromHermesPayload`
|
|
2332
|
+
+ `classifyActionClass`),透传到 `SessionsSseResult.approvalBundle`
|
|
2333
|
+
- bundle 传播:sessions-sse → sessions-dispatcher → runs-dispatcher → AdapterResult.metadata
|
|
2334
|
+
- `dispatch.ts` `finalError` 附带 `approvalBundle`(runtime-hook 源)随 `task.dispatch.reply`
|
|
2335
|
+
上报 cloud(code=awaiting_human_approval)
|
|
2336
|
+
- `turn.ask_human`:`task.dispatch.reply` error code=awaiting_clarification + clarifyBundle
|
|
2337
|
+
|
|
2338
|
+
### Added — persona seatScope(product205)
|
|
2339
|
+
|
|
2340
|
+
`CreateAgentForWorkspaceInput` gains `seatScope`('workspace' | 'council')。
|
|
2341
|
+
Council convene 创建 persona agent 时标 `seatScope='council'`——不占 workspace
|
|
2342
|
+
席位、不出现在 agent roster。需配合 cloud 侧迁移 523。
|
|
2343
|
+
|
|
2344
|
+
### Added — general-assistant role template(product205 M4)
|
|
2345
|
+
|
|
2346
|
+
平台默认 deputy role 模板(通用助理),member first mile accept-provision 用。
|
|
2347
|
+
|
|
2348
|
+
|
|
2349
|
+
### Fixed — persona role: cloud MCP allowlist(product205 M0 · AC-C6)
|
|
2350
|
+
|
|
2351
|
+
persona role 模板此前 `mcpServers: []`——persona-agent 在 cloud MCP 面无限制
|
|
2352
|
+
(可调 `prismer.task.create`/spend),与动作治理能力黑名单冲突(strip 后暴露为
|
|
2353
|
+
load-bearing)。补 discussion-only `toolsAllowlist`(memory read/recall ·
|
|
2354
|
+
agent/message send · asset 只读;task/skill/publish/spend 面按 allowlist 语义省略即拒)。
|
|
2355
|
+
oracle:`council-m2` 正测 `config.mcpAllowlist` 转绿 + 参数化负控(persona 拒
|
|
2356
|
+
`task.create`、orchestrator 同工具放行对照)。
|
|
2357
|
+
|
|
2358
|
+
|
|
2359
|
+
### Added — role/council-scoped memory writes(product204 E4)
|
|
2360
|
+
|
|
2361
|
+
打通 role/council 可见性从 tool → daemon store → outbox envelope → cloud 的全链
|
|
2362
|
+
路(cloud 侧 `POST /api/im/memory/pages` 早已 honor `body.visibility`,缺口全在
|
|
2363
|
+
daemon/tool 侧):
|
|
2364
|
+
|
|
2365
|
+
- `MemoryVisibility` 联合新增 `{ kind:'role'; slug }` / `{ kind:'council'; id }`;
|
|
2366
|
+
所有 switch/consumer(store 写入+hydrate、cloud-sync `parseCloudVisibility`、
|
|
2367
|
+
boundary `acl-predicate`、rpc `visibilityToString`)穷尽处理新 kind。
|
|
2368
|
+
- `rpc.ts`:`visibilityToString` emit `role:<slug>` / `council:<id>`(cloud
|
|
2369
|
+
memory-acl 期望的确切串形态);新增 `parseVisibilityString` 把 memory_write
|
|
2370
|
+
工具传来的 `role:<slug>`/`council:<id>`/`agent:<id>`/`workspace` 串解析回联合,
|
|
2371
|
+
缺失/未知安全降级为 `workspace`(D3 保守默认)。
|
|
2372
|
+
- `memory-tools.ts`:`MemoryWriteToolInput` + `MEMORY_WRITE_INPUT_SCHEMA` 增可选
|
|
2373
|
+
`visibility?: string`(`required` 仍为 `['path','content']`)。
|
|
2374
|
+
- boundary ACL:role/council 作为粗粒度共享 scope 在 daemon 边界按 workspace-可见
|
|
2375
|
+
处理,真正的成员治理留 cloud 超集。
|
|
2376
|
+
- 新增回归 `test/memory-visibility-scope.test.ts`:断言 outbox envelope 串
|
|
2377
|
+
`council:<id>`/`role:<slug>` 精确落地 + 负控(缺失→workspace、未知→workspace)。
|
|
2378
|
+
|
|
2379
|
+
### Fixed — 代码评审整改(feature/release203 批次)
|
|
2380
|
+
|
|
2381
|
+
- **hermes gateway 每轮杀重启风暴**:`writeHermesGatewayStamp` 写盘失败(profile
|
|
2382
|
+
dir 不可写)时,`hermesGatewayConfigDrifted` 把本进程刚 spawn、只是没记下的
|
|
2383
|
+
gateway 当成无见证孤儿 → 每条消息 kill+respawn、清空会话上下文。spawn 时同时在
|
|
2384
|
+
内存里见证(键用 stamp 绝对路径,生产复用/测试隔离两不误),写盘失败也不再 churn。
|
|
2385
|
+
补回归负控 `test/hermes-gateway-stamp.test.ts`(抽掉内存兜底即翻红)。
|
|
2386
|
+
- **`cloud role test` 把瞬时网络错误当「skill 不存在」**:只有 404 才是真缺失;
|
|
2387
|
+
status 0 / 5xx / timeout 归为 `unreachable`(exit 2 + 「transient, retry; do NOT
|
|
2388
|
+
rebuild」),不再误导 agent 去重建已存在的 skill。
|
|
2389
|
+
|
|
2390
|
+
### Added — `prismer skill|role` 发布后治理七动词(product204/21 M-P W3)
|
|
2391
|
+
|
|
2392
|
+
发布之前有 `publish`,发布之后此前**什么都没有**。补齐镜像动作,skill 与 role
|
|
2393
|
+
两个命名空间同构(blueprint 无 CLI 命名空间,仅 HTTP + Studio):
|
|
2394
|
+
|
|
2395
|
+
- **`delist <slug> [--reason]`** / **`relist <slug> [--changelog]`** — 退出/回到
|
|
2396
|
+
Marketplace 的**可见性单轴翻转**。存量使用者零影响(skill 已装 agent 继续
|
|
2397
|
+
同步内容、role 继续投影)。relist 不是新端点,是重跑该资产自己的 publish
|
|
2398
|
+
端点——publish 的门(license / SS-02 / takedown 锁)因此只有一份实现。
|
|
2399
|
+
- **`deprecate <slug> --reason <r> [--successor <slug>]`** / **`undeprecate`** —
|
|
2400
|
+
软信号 + 替代品指针;**不拦截**新安装/新 apply,只让消费者看见理由与后继。
|
|
2401
|
+
- **`archive <slug> [--confirm]`** — 破坏性退役。**有活跃消费者时服务端 409**,
|
|
2402
|
+
CLI 把消费者计数打成人话并提示 `--confirm`("3 agents are still using this
|
|
2403
|
+
skill — archiving unbinds them")。public role 直接 archive → 提示先 delist。
|
|
2404
|
+
- **`transfer <slug> --to <imUserId>`** + **`transfer-accept`** / **`transfer-abort`** —
|
|
2405
|
+
两段式所有权转移;offer 阶段 owner 不变,受让人 accept 才落。
|
|
2406
|
+
- **`published`** — 我的发布清单表格:slug / 上架状态(listed·delisted·deprecated·
|
|
2407
|
+
taken_down·archived)/ **消费者数**(= archive 的爆炸半径)/ installs / 在途转让。
|
|
2408
|
+
|
|
2409
|
+
**错误码人话化**(新 `cli/publish-lifecycle.ts`,与 `@prismer/sdk` 的 `cloud`
|
|
2410
|
+
CLI **共享同一张表**,两包不允许漂移):`not_owner` / `has_active_consumers` /
|
|
2411
|
+
`taken_down` / `changelog_required` / `successor_not_listed` / `transfer_pending` /
|
|
2412
|
+
`not_transfer_target` / `must_delist_first` … 一律译成"原因 + 出路"的整句,
|
|
2413
|
+
不再抛裸 HTTP 码。
|
|
2414
|
+
|
|
2415
|
+
> **承重语义**(SKILL.md 与文案都必须传达):skill 安装与 role→agent 是**活引用,
|
|
2416
|
+
> 不是快照**——改已发布件的内容会自动传播到全部使用者,故内容改动强制带
|
|
2417
|
+
> `--changelog`;**下架不影响存量使用者,归档才解绑**。
|
|
2418
|
+
|
|
2419
|
+
同步更新 built-in skill `skill-builder` / `role-builder` 的 SKILL.md
|
|
2420
|
+
(新增「发布后管理」节)与 `docs/api/publish-lifecycle.md`。
|
|
2421
|
+
|
|
2422
|
+
### Added — `agent.host.declare` 上报 `bundleVersion`(product204/08 §2.3 step 4,M9 收口)
|
|
2423
|
+
|
|
2424
|
+
- declare payload 新增 additive 字段 `bundleVersion`:**实际在跑的 runtime 版本**
|
|
2425
|
+
(boot-time OTA 换入的 bundle 即 bundle 版本;builtin boot 即镜像/npm 版本——
|
|
2426
|
+
进程 exec 的是 bundle 的 cli.js,自身 package.json 就是真相)。cloud 侧落
|
|
2427
|
+
agent card `metadata.bundleVersion`,runtime-skew 探针 fleet 分布随之从
|
|
2428
|
+
`im_agent_bindings.daemonVersion` 升级为真实运行版本(daemonVersion 保留为
|
|
2429
|
+
legacy fallback)。老 daemon 不带该字段 → cloud 行为不变。
|
|
2430
|
+
|
|
2431
|
+
### Fixed — role-builder `ingest-role.mjs` 默认公域回归(M9 收口,随 bundle 分发)
|
|
2432
|
+
|
|
2433
|
+
- `built-in-skills/role-builder/scripts/ingest-role.mjs` 默认路径回归为 admin
|
|
2434
|
+
公域 catalog(违反 product204/16 §2.3「CLI 默认私域」;根因=M5 的改动落在
|
|
2435
|
+
**gitignored 的 runtime 拷贝**、被 runtime 重建用 canonical 旧版覆盖——
|
|
2436
|
+
lost-uncommitted-change,j25 A4 回归抓到)。修复落 **canonical**
|
|
2437
|
+
`sdk/prismer-cloud/built-in-skills/`:默认 `/mine`(private),公域须显式
|
|
2438
|
+
`--publish`(alias `--admin-catalog`),`--mine` 保留为 no-op 兼容。
|
|
2439
|
+
|
|
2440
|
+
### Fixed — 无 config 环境任何 `prismer` 子命令崩溃(M9 收口)
|
|
2441
|
+
|
|
2442
|
+
- `cli/commands/task.ts` 的 `mkTaskWaitAdapter` 与 `cli/commands/workspace.ts`
|
|
2443
|
+
的 `mkMemberAdapter` 在 **program 构建期** 急切 `mkCloud()`(→ `loadConfig`),
|
|
2444
|
+
导致没有 `~/.prismer/config.toml` 的主机上 `prismer --help` / `prismer ota
|
|
2445
|
+
status` / 一切子命令直接崩 "Config not found"。CloudClient 改为**首次调用时
|
|
2446
|
+
惰性创建**,与 daemon 轮询等 config 的设计对齐——OTA resolver(`prismer ota
|
|
2447
|
+
resolve`)必须在 pre-setup 环境可用(08 §2.3 entrypoint 钩子)。
|
|
2448
|
+
|
|
2449
|
+
### Added — boot-time runtime-bundle OTA 消费链(product204/08 §2.3,M9-β)
|
|
2450
|
+
|
|
2451
|
+
- **`daemon/ota/`(新模块)**:K8s/CLI 侧 bundle OTA 状态机——
|
|
2452
|
+
检查(manifest 3s+重试,候选序 `PRISMER_BUNDLE_MANIFEST_URL` env →
|
|
2453
|
+
`/api/runtime/update/manifest` → `/api/desktop/update/manifest`,两种
|
|
2454
|
+
payload 形态均容忍)→ 下载 → **sha256/sha512 + Ed25519 双验**(签 zip 原始
|
|
2455
|
+
字节;公钥 `DAEMON_BUNDLE_PUBKEY` env 可覆盖、内置常量与桌面同 keypair;
|
|
2456
|
+
验签失败=显式拒绝+incident 回传,**绝不落盘执行**)→ 系统 `unzip` 解包到
|
|
2457
|
+
`~/.prismer/bundle/<version>/` → 解包后 `package.json` 版本≠manifest 版本
|
|
2458
|
+
即拒(双版本源 drift 门禁)→ 原生依赖(better-sqlite3)解包时从镜像内置
|
|
2459
|
+
runtime **symlink 借入**(ESM 不查 NODE_PATH——桌面 bundle-manager 已
|
|
2460
|
+
code-verified 的教训,08 §2.3 "NODE_PATH 借用"的语义落点)→ 原子指针切换
|
|
2461
|
+
(`previous` = 回滚位)。
|
|
2462
|
+
- **回滚 + 熔断**:boot-attempt marker 跨容器原地重启存活(emptyDir 语义,
|
|
2463
|
+
**无 PVC**);上次 boot 未确认 → 计 strike + `boot_failed`/`rolled_back`
|
|
2464
|
+
回传 + 回退 previous;**连续 2 strikes → 本地拉黑 + 删目录(防复活)**,
|
|
2465
|
+
停留 previous/镜像 builtin(镜像永固兜底,任何失败路径都不 brick)。
|
|
2466
|
+
- **incident 回传**:走既有 `POST /api/desktop/update/report`(migration 499
|
|
2467
|
+
词表,`component='daemon'`)——`applied`/`boot_ok`(熔断统计分母)+
|
|
2468
|
+
`verify_failed`/`boot_failed`/`rolled_back`/`blacklisted`。⚠️ 该路由现有
|
|
2469
|
+
`DAEMON_ID_RE` 只认桌面形态 id,K8s `container:<pod>` 形态会被
|
|
2470
|
+
logged-then-dropped——server 侧放宽挂 α/收口波。
|
|
2471
|
+
- **`prismer ota (resolve|status)`(新 CLI verb)**:`resolve --exec-path`
|
|
2472
|
+
是 K8s entrypoint 钩子(stdout 只打 bundle cli.js 路径,builtin=空输出,
|
|
2473
|
+
日志全走 stderr);`PRISMER_BUNDLE_OTA=0` 本地跳过(server 侧 kill switch
|
|
2474
|
+
= manifest decision=none)。
|
|
2475
|
+
- **boot 确认**:`daemon start` 成功后 10s 稳定窗(`scheduleBundleBootConfirm`)
|
|
2476
|
+
清 marker + 清 strike 记录 + 回传 `boot_ok`;窗口内崩溃 ⇒ marker 存活 ⇒
|
|
2477
|
+
下次 boot 自动计 strike 回滚。
|
|
2478
|
+
- 测试:`test/ota-bundle.test.ts`(13 用例:真 keypair/真 zip/真本地 HTTP
|
|
2479
|
+
server 全链 + 篡改负控 + 回滚/拉黑注入 + 回传 payload 断言)。
|
|
2480
|
+
|
|
2481
|
+
### Removed — `PRISMER_UNIFIED_WS` 逃生口(product204/18 Wave 3,挂 M9 收割)
|
|
2482
|
+
|
|
2483
|
+
- `daemon/runner.ts` 不再读 `PRISMER_UNIFIED_WS`:unified `WS /ws/realtime`
|
|
2484
|
+
是唯一 realtime 路径,legacy `SseSubscriber`(`/api/im/sync/stream`)不再
|
|
2485
|
+
被 runner 实例化(模块保留,仅测试引用)。设 `PRISMER_UNIFIED_WS=0` 的部署
|
|
2486
|
+
自本版起为 no-op。
|
|
2487
|
+
|
|
2488
|
+
### Added — per-agent skill config env injection(product204/09 §2.3 Phase C)
|
|
2489
|
+
|
|
2490
|
+
- **`adapters/prismer-env.ts::skillConfigEnvFromMetadata`**:读取 cloud 派发时
|
|
2491
|
+
解析好的 `task.metadata.skillConfigEnv`(KEY→value,UPPER_SNAKE、禁
|
|
2492
|
+
`PRISMER_` 前缀——cloud 入库门禁保证),`applyPrismerScopeEnv` 尾部以
|
|
2493
|
+
**覆盖语义**并入子进程 env(三级解析 agent 覆盖/role 默认 高于 daemon 全局
|
|
2494
|
+
env;缺省 = 不注入 = global env 兜底)。coding 路径(claude-code / codex /
|
|
2495
|
+
provider-proxy-env)零改动即消费。
|
|
2496
|
+
- **hermes adapter(seam b)**:`dispatch()` 把 `skillConfigEnv` 循环
|
|
2497
|
+
`writeEnvValue` 进 per-profile `.env`(gateway 长驻进程不走 spawn env;
|
|
2498
|
+
per-profile 文件 ⇒ 同 daemon 多 agent 隔离是结构性的)。
|
|
2499
|
+
- **`daemon/skill-loader.ts::parseSkillConfig`**:SKILL.md frontmatter `config:`
|
|
2500
|
+
声明的 daemon 侧宽松镜像解析(诊断用;值永远来自 dispatch metadata,不做本地
|
|
2501
|
+
解析注入)。`LoadedSkill` 增 `config` 字段。
|
|
2502
|
+
|
|
2503
|
+
### Added — identityContext named sections(product204/07 Phase C · 分段注册制 D6)
|
|
2504
|
+
|
|
2505
|
+
- **Wire type** `TaskDispatchRequestPayload.identityContext.sections?: IdentitySection[]`
|
|
2506
|
+
(`{ id, owner, order, content }`,additive;旧 daemon 忽略该字段,旧 cloud 不发
|
|
2507
|
+
——双向 version-skew 安全)。
|
|
2508
|
+
- **`daemon/dispatch.ts::renderIdentityLines`** 扩展:校验(content string +
|
|
2509
|
+
order finite number)→ 按 `order` 升序稳定排序 → trim → 滤空,作为
|
|
2510
|
+
`sections: string[]` 与 identity/user/scope 一起进 `metadata.identityContext`;
|
|
2511
|
+
`hasIdentity` 判定包含 sections。
|
|
2512
|
+
- **三个 adapter 消费面**(coding `code-agent-driver` / `claude-code` legacy CLI /
|
|
2513
|
+
hermes instructions slot):sections 以 `\n\n` 连接在既有 identity 三行**之后**、
|
|
2514
|
+
同一 native identity slot。hermes 侧只进 per-turn `instructions`,**不写 SOUL.md**
|
|
2515
|
+
(D6 红线:动态 envelope 注入,不落任何持久文件)。
|
|
2516
|
+
- 首个注入段:cloud 侧 `platform-directives`(order 20,仅 active orchestrator
|
|
2517
|
+
dispatch 携带);order 30/40(config-directives / per-turn-voice)为 09/03 预留槽。
|
|
2518
|
+
|
|
2519
|
+
### Changed — `role create` defaults to the PRIVATE domain(product204/16 §2.2 M5)
|
|
2520
|
+
|
|
2521
|
+
**Behavioral flip, no version bump.** 创作默认私域,进公域是显式动作:
|
|
2522
|
+
|
|
2523
|
+
- **`prismer/cloud role create`** 默认改打 `POST /api/im/role-templates/mine`
|
|
2524
|
+
(owner-scoped private,任何已认证 key 可用,落 Studio `/mine`)。旧默认
|
|
2525
|
+
(admin 公域 catalog 直写)收敛为显式 `--admin-catalog`(非 admin 403)。
|
|
2526
|
+
`--mine` 保留为向后兼容 no-op。
|
|
2527
|
+
- 新 `--publish`:create 后自动 `POST /:slug/publish`(走 RO-11/RO-6 publish
|
|
2528
|
+
gate),一步进公域 Marketplace。
|
|
2529
|
+
- **`role-builder/scripts/ingest-role.mjs`** 同步反转默认(头注释与代码此前
|
|
2530
|
+
自相矛盾——注释声称默认 `--mine`、代码默认公域;现在代码与文档一致),同样
|
|
2531
|
+
新增 `--publish` / `--admin-catalog`。role-builder `SKILL.md` 口径同步。
|
|
2532
|
+
|
|
2533
|
+
### Added — `skill publish` + `role export`(product204/16 §2.2(c) / §2.5)
|
|
2534
|
+
|
|
2535
|
+
- **`skill publish <slugOrId> [--license --changelog]`** — 显式 owner 动作,把
|
|
2536
|
+
skill 从私域(`publishScope='workspace'`,create 默认)翻到公域 Marketplace
|
|
2537
|
+
(`POST /api/im/skills/:id/publish-template { scope:'marketplace' }`)。注意:
|
|
2538
|
+
marketplace 搜索面自本版起只见 `publishScope='marketplace'`——不 publish 的
|
|
2539
|
+
skill 他人搜不到。
|
|
2540
|
+
- **`role export <agentImUserId> [--out <dir>] [--ingest]`** — 把活 agent 的人格
|
|
2541
|
+
晶化为 role bundle(`GET /api/im/agents/:id/role-bundle`,SOUL 取 RAW
|
|
2542
|
+
operatingPrinciples,不含运行时注入子句,round-trip 不堆叠);`--ingest` 直接
|
|
2543
|
+
`POST /role-templates/mine` 落私有 role。
|
|
2544
|
+
|
|
2545
|
+
### Added — daemon declares its device identity(desktop204 D204-3)
|
|
2546
|
+
|
|
2547
|
+
`agent.host.declare` 现在恒带 `daemonLabel` + `daemonKind`。此前 daemon 只发
|
|
2548
|
+
`daemonId / daemonVersion / platform`,cloud 只能靠 `daemonId.startsWith('daemon-')`
|
|
2549
|
+
猜 kind、拿 daemonId 后缀当 label——对任何不遵守该前缀约定的 id 都是错的,且永远
|
|
2550
|
+
surface 不出用户自己起的设备名。
|
|
2551
|
+
|
|
2552
|
+
- 新 `src/daemon/device-identity.ts`:
|
|
2553
|
+
- `resolveDaemonKind()` — `PRISMER_DEVICE_KIND` > `KUBERNETES_SERVICE_HOST`(⇒ `k8s`)> `local`
|
|
2554
|
+
- `resolveDaemonLabel()` — `PRISMER_DAEMON_LABEL` > config.toml `daemon_label` > OS hostname > daemonId(永不为空)
|
|
2555
|
+
- config.toml 新增可选 `daemon_label`(用户自定义设备名)。
|
|
2556
|
+
- `device-dir.ts` 的 `inferDeviceKind()` 收敛到同一个 resolver —— `device.json` 与
|
|
2557
|
+
declare 线上的 kind 不可能再互相打架。
|
|
2558
|
+
- cloud 侧字段 / DB 列早已存在(`im_agent_bindings.boundDaemonKind/Label`),**无需迁移**;
|
|
2559
|
+
cloud 的猜测逻辑保留为旧 daemon 的 fallback。
|
|
2560
|
+
|
|
2561
|
+
### Added — `agent.host.withdraw` on intentional shutdown(desktop204 D204-4)
|
|
2562
|
+
|
|
2563
|
+
daemon 现在会在**主动关闭**时(SIGTERM/SIGINT ⇐ ⌘Q / `prismer daemon stop` / pod
|
|
2564
|
+
terminate)**drain 完成后、WS 关闭前**发 `agent.host.withdraw`。cloud 侧 handler /
|
|
2565
|
+
service / 错误码此前是**写好的死代码——没人发过这个事件**,于是 ⌘Q 后 cloud 要等满
|
|
2566
|
+
**3 分钟**心跳陈旧才把 agent 标离线,这 3 分钟内派给它的任务全是黑洞。
|
|
2567
|
+
|
|
2568
|
+
- `Runner.stop(opts?: { withdraw?: { reason, timeoutMs? } })` —— 只有**显式传 intent**
|
|
2569
|
+
才发(崩溃 / auth-failed / version-skew respawn 不发:同一个 daemonId 马上会回来,
|
|
2570
|
+
binding 不该被标成可回收)。
|
|
2571
|
+
- `inflightDrained` 取 stop 时是否还有在跑的 run(有 ⇒ `false`,cloud 知道该 requeue)。
|
|
2572
|
+
- 新 `WsClient.sendAndFlush(msg, timeoutMs)` —— 等帧真正写进 socket 再返回,
|
|
2573
|
+
避免 `close()` + `process.exit()` 把 withdraw 一起蒸发掉。
|
|
2574
|
+
**best-effort 是硬约束**:socket 不 OPEN / flush 超时 → 立刻 resolve `false`,
|
|
2575
|
+
绝不抛、绝不阻塞退出(离线 ⌘Q 最多多花 1.5s)。
|
|
2576
|
+
|
|
2577
|
+
## 2.0.9
|
|
2578
|
+
|
|
2579
|
+
### Fixed — sandbox daemon 镜像 stale-tag 陷阱(线上 daemon 落后 cloud)
|
|
2580
|
+
|
|
2581
|
+
镜像 tag 从 `/VERSION` 派生(`daemon-v$(cat VERSION)`),pod 按 **tag** 引用镜像
|
|
2582
|
+
(`getDaemonImage()` 返回 `canonical`,`digest` 仅供诊断),且 test/prod 是
|
|
2583
|
+
`imagePullPolicy: IfNotPresent`。
|
|
2584
|
+
|
|
2585
|
+
于是当一次发版没有 bump `/VERSION` 时:CI 的 `build_sandbox_image` 确实重新构建
|
|
2586
|
+
并 push 了新镜像,但**tag 名字不变** → kubelet 看到节点已缓存该 tag,永远复用旧
|
|
2587
|
+
层。cloud 前进、daemon 原地不动 —— test 环境因此用 07-07 的 daemon 跑
|
|
2588
|
+
release203/28 的 cloud(会话压缩 + systemHidden + memory 抽取/召回均不匹配)。
|
|
2589
|
+
|
|
2590
|
+
本次 bump `/VERSION` → `2.0.9` 使 tag 变为 `daemon-v2.0.9`,强制 kubelet 拉取新
|
|
2591
|
+
镜像。约束已写入 `infra/sandbox-image/image-pin.yaml` 顶部注释:**任何触及
|
|
2592
|
+
`sdk/prismer-cloud/runtime/` 的发版必须 bump `/VERSION`。**
|
|
2593
|
+
|
|
2594
|
+
## Unreleased
|
|
2595
|
+
|
|
2596
|
+
### Fixed — release203/10: skill 下发闭环三环修复(workspace203 体系化 e2e 全绿)
|
|
2597
|
+
|
|
2598
|
+
skill 从"安装"到"运行中 agent 真能用"的完整链路修复。根因:hermes gateway 的
|
|
2599
|
+
`HermesSkillLoader` 在 spawn 时从 `profile.config.skillsDir` 固定 skillsRoot;
|
|
2600
|
+
若启动时未注入 device dir,则读 profile dir,而 skill-sync 写 device dir → 错位。
|
|
2601
|
+
|
|
2602
|
+
- **目录错位** (`daemon/skill-sync.ts` + `daemon/runner.ts`): 新增纯函数
|
|
2603
|
+
`withPerAgentSkillsDir(profile, paths, daemonId)`;`syncProfileFromCloud` 的
|
|
2604
|
+
prewarm 用它注入 device skillsDir,使 gateway 从启动即读 skill-sync 写入的
|
|
2605
|
+
`devices/<did>/agents/<aid>/skills/`。此前 prewarm 用未注入的 profile 启动 →
|
|
2606
|
+
读 profile dir → 已安装 skill 对 agent 不可见。
|
|
2607
|
+
- **活进程缓存不刷新** (`daemon/dispatch.ts` + `daemon/runner.ts`): dispatch 的
|
|
2608
|
+
skill-sync `synced>0` 时经新增的 `DispatchDeps.dropService` drop 缓存的
|
|
2609
|
+
hermes 服务,`ensureService` 以含新 skill 的 device dir 重启。运行中 agent
|
|
2610
|
+
安装 skill 后**当次 dispatch** 即可用(此前需服务重启才可见)。revision-diff
|
|
2611
|
+
保证稳态 `synced==0` 不再重启,无 churn。
|
|
2612
|
+
- **首次冷启动根治** (`daemon/runner.ts`): `syncProfileFromCloud` 在 prewarm
|
|
2613
|
+
前,当确实注入了 device skillsDir,先 `syncInstalledSkillsForDispatch` 铺
|
|
2614
|
+
skill 到 device dir,再 `servicePool.drop` 旧服务,使 gateway 以 warmProfile
|
|
2615
|
+
(device-dir loader)重建、其 skill loader 首次扫描即读完整 skill 集。
|
|
2616
|
+
blueprint 实例化 / bind 的 agent **首次 invoke** 即命中,免冷启动窗口。
|
|
2617
|
+
|
|
2618
|
+
端到端验证:`docs/workspace203/20-systematic-test-execution.md` —— workspace203
|
|
2619
|
+
体系化 flow(前置1-4 + workspace1-2)S0-S7 真机全绿,全程 DeepSeek flash。
|
|
2620
|
+
|
|
2621
|
+
### Changed — memory203/20 W-B: extraction prompt v2 (richness-balanced) + `web_load` prismer:// + oversized advisory passthrough
|
|
2622
|
+
|
|
2623
|
+
- **Extraction prompt v2** (`daemon/memory/extract.ts`, ruling 0705-3/4
|
|
2624
|
+
"record, never limit"): removed the "emit minimal valid PKF" self-framing
|
|
2625
|
+
and the `BUDGET: … prefer 1-3 DENSE pages / concise` compression language
|
|
2626
|
+
(doc 19 §3.4 ①② — the structural scarcity that flattened pages to prose).
|
|
2627
|
+
New posture: richness-balanced PKF — frontmatter script with a **REQUIRED
|
|
2628
|
+
one-sentence `description`** (feeds the cloud description column, doc 20
|
|
2629
|
+
§1.1), `<h2 id>` sections, full typed-link vocabulary incl.
|
|
2630
|
+
`supports`/`contradicts`, the FULL `<prismer-data>` view set
|
|
2631
|
+
(`table|bar|line|scatter|area|heatmap` — "never flatten a source table into
|
|
2632
|
+
prose"), and asset POINTERS (short description +
|
|
2633
|
+
`<a rel="derived-from" href="prismer://asset/<id>">`, never a body copy —
|
|
2634
|
+
doc 20 §1.4 asset 零镜像). The ONLY token rules are anti-waste: ANTI-REPEAT
|
|
2635
|
+
(same-topic page in the recall context ⇒ placement MUST be extend/attach,
|
|
2636
|
+
never a near-duplicate new page) + circular-recall discipline (pages already
|
|
2637
|
+
in the provided context are not new sources to re-query). Prompt exported
|
|
2638
|
+
(`EXTRACTION_SYSTEM_PROMPT`) so the guidance lane (W-C skills) teaches
|
|
2639
|
+
consistently.
|
|
2640
|
+
- **`MEMORY_EXTRACT_MAX_TOKENS` default 2048 → 8192** (record-not-limit):
|
|
2641
|
+
usage is recorded, not constrained — gateway `usage.input_tokens`/
|
|
2642
|
+
`output_tokens` now flow onto `ExtractTurnResult.promptTokens`/
|
|
2643
|
+
`completionTokens` and into the `llm_response` stage line
|
|
2644
|
+
(`pages=N, chars=M[, tokens=in:X/out:Y][, truncated]`, exported
|
|
2645
|
+
`formatLlmResponseDetail` in `hook-server.ts`). Truncation salvage stays as
|
|
2646
|
+
the pure fallback. Paired: `MEMORY_EXTRACT_TIMEOUT_MS` default 60s → 120s
|
|
2647
|
+
(measured: 4096 tokens took 29–44s; the timeout must outlast the 8192
|
|
2648
|
+
ceiling or the abort-drops-memory bug returns).
|
|
2649
|
+
- **Attached-asset ids reach extraction** (additive contract): the provider
|
|
2650
|
+
shell's `sync_turn` now parses `<attached_assets><asset id="…"/></…>` out of
|
|
2651
|
+
the turn text and stamps `extra.attached_asset_ids` on the `post_llm_call`
|
|
2652
|
+
body; hook-server threads it into `ExtractInput.attachedAssetIds`, and
|
|
2653
|
+
`extract.ts` falls back to parsing the XML itself
|
|
2654
|
+
(`parseAttachedAssetIdsFromTurn`) for older shells. The user prompt lists
|
|
2655
|
+
the real `prismer://asset/<id>` pointer URIs so pages reference assets
|
|
2656
|
+
instead of mirroring them.
|
|
2657
|
+
- **`web_load` accepts `prismer://` URIs** (doc 20 §2.2 / 19 B9): scheme
|
|
2658
|
+
allowlist widened from http(s)-only to http(s) | `prismer://` in BOTH the
|
|
2659
|
+
provider shell (`plugins/memory/prismer/__init__.py`) and the daemon route
|
|
2660
|
+
(`daemon/web/rpc.ts`); the daemon still forwards to the cloud Load API
|
|
2661
|
+
unchanged (it natively resolves `prismer://<owner>/asset/<sha>` and
|
|
2662
|
+
`.../file/...`). Tool description retargeted: load web pages OR workspace
|
|
2663
|
+
assets/files — use it instead of re-reading raw sources memory already
|
|
2664
|
+
points at. Garbage schemes (file://, ftp://, …) still 400 `invalid_url`.
|
|
2665
|
+
Spec sync in `adapters/web-tools.ts`.
|
|
2666
|
+
- **`oversized` candidates kind** (doc 20 §1.2 hub-size ADVISORY,
|
|
2667
|
+
record-not-limit): added to the daemon `HEALTH_KINDS` whitelist
|
|
2668
|
+
(`daemon/memory/rpc.ts` → forwards `GET /api/im/memory/health/oversized`),
|
|
2669
|
+
the Python `memory_curate` `kind` enum, and the shared
|
|
2670
|
+
`MemoryCurateInput.kind` spec (`adapters/memory-tools.ts`, which also picks
|
|
2671
|
+
up the previously-drifted `conflicts` value). Advisory only — the cloud
|
|
2672
|
+
lane measures and suggests splits; nothing is machine-enforced.
|
|
2673
|
+
|
|
2674
|
+
### Changed — memory203/20 W-C: guidance rewrite (four-truths) — memory + memory-dream skills, MEMORY_CORE_DIRECTIVE
|
|
2675
|
+
|
|
2676
|
+
- **Teach only what W-A/W-B landed** (the initiative's lesson: never teach what
|
|
2677
|
+
the platform can't do). All guidance now consistent with
|
|
2678
|
+
`EXTRACTION_SYSTEM_PROMPT` v2 (`daemon/memory/extract.ts`): same rel
|
|
2679
|
+
vocabulary (`supports`/`contradicts`/`related`/`derived-from`/`references`/
|
|
2680
|
+
`cites` + structural `child-of`), same `<prismer-data>` view set
|
|
2681
|
+
(`csv|json` × `table|bar|line|scatter|area|heatmap`), same asset-POINTER
|
|
2682
|
+
form (short description + `<a rel="derived-from" href="prismer://asset/<id>">`,
|
|
2683
|
+
never a body copy), same anti-waste rules (no duplicate extraction, no
|
|
2684
|
+
circular recall — the ONLY token rules, 0705 record-not-limit rulings).
|
|
2685
|
+
- **`built-in-skills/memory/SKILL.md`** (canonical + runtime mirror): dual-
|
|
2686
|
+
section ownership section replaces the self-contradictory "hub needs a body"
|
|
2687
|
+
vs "never hand-edit INDEX/hub" doctrine (doc 19 D11) — `#overview` prose is
|
|
2688
|
+
agent-owned and rebuild-proof (W-A `rewriteTocSection`, test-verified),
|
|
2689
|
+
`#toc` is machine-owned; frontmatter `description` REQUIRED (one-sentence
|
|
2690
|
+
self-summary feeding TOC entries/chips) with the dual-track note (body prose
|
|
2691
|
+
carries inter-entity logic); de-hedged append flow — recall/browse shows an
|
|
2692
|
+
existing page on the topic ⇒ `memory_write op="append-section"` /
|
|
2693
|
+
`"rewrite-section"` is a MUST (the "may not be available / fallback" hedging
|
|
2694
|
+
is gone — the ops are live in daemon rpc + Python tool schema); richness
|
|
2695
|
+
palette with three worked PKF examples (prismer-data page, asset-pointer
|
|
2696
|
+
page incl. `<img src="prismer://asset/…">` media, supports/contradicts pair
|
|
2697
|
+
with `#section` anchors) — media/section-granularity teachings lifted from
|
|
2698
|
+
the deprecated memory-curation skill (doc 19 D12 迁移, that skill stays
|
|
2699
|
+
deprecated); size discipline (INDEX/hub lean as taught discipline, enforced
|
|
2700
|
+
by nothing; `kind="oversized"` split suggestions; ordinary pages unlimited,
|
|
2701
|
+
richness encouraged); consolidated anti-patterns (re-reading raw files
|
|
2702
|
+
memory already distills, circular recall, duplicate extraction, copying
|
|
2703
|
+
asset bodies, hand-editing `#toc`).
|
|
2704
|
+
- **`built-in-skills/memory-dream/SKILL.md`** (v2 → v3, canonical + runtime
|
|
2705
|
+
mirror): convergence loop gains STEP 1c (review `kind="oversized"` hub-split
|
|
2706
|
+
advisories — suggestions, never enforcement) and STEP 6 (MAINTAIN the
|
|
2707
|
+
`#overview` prose on INDEX/hubs via `op="rewrite-section"` — the
|
|
2708
|
+
orchestrator's editorial duty; the rebuild only regenerates `#toc` with
|
|
2709
|
+
per-entry descriptions); candidates envelope documents the `oversized`
|
|
2710
|
+
surface; "rebuild_index is the ONLY INDEX write" reworded to the dual-
|
|
2711
|
+
section truth; section-op hedging removed from the enact/fold steps.
|
|
2712
|
+
- **`MEMORY_CORE_DIRECTIVE`** (`daemon/dispatch.ts`): "NEVER write or
|
|
2713
|
+
hand-edit INDEX.pkf (machine-generated)" replaced with the dual-section
|
|
2714
|
+
truth ("`#toc` machine-owned — never hand-edit; `#overview` prose IS yours
|
|
2715
|
+
to write and maintain"); adds the richness line (description-required +
|
|
2716
|
+
rich blocks + asset pointers + work-from-memory-not-raw-files + anti-waste);
|
|
2717
|
+
extend decisions name the live section ops; orchestrator loop gains
|
|
2718
|
+
overview maintenance + the conflicts/oversized candidates kinds.
|
|
2719
|
+
|
|
2720
|
+
### Added — release203 web-capability fix: `workspace_web_search`/`web_load` tools + terminal toolset restore
|
|
2721
|
+
|
|
2722
|
+
- **`workspace_web_search` / `web_load` provider tools** (`plugins/memory/prismer/__init__.py`):
|
|
2723
|
+
workspace-CONTEXT tools (not memory ops) registered through the provider
|
|
2724
|
+
shell's `get_tool_schemas()` seam (7 tools total now). Handlers POST the
|
|
2725
|
+
new daemon routes; descriptions steer agents away from scripting HTTP via
|
|
2726
|
+
`execute_code`+subprocess (the observed fallback: 197 execute_code vs 0
|
|
2727
|
+
web calls in 14 days — Hermes' native web toolset is schema-dropped in pods
|
|
2728
|
+
because no upstream search backend is configured, and per the user ruling
|
|
2729
|
+
we ARE the search backend: no third-party keys/packages enter the pod).
|
|
2730
|
+
`web_load` validates http(s)-only shell-side before any daemon call.
|
|
2731
|
+
Name note: the search tool CANNOT be `web_search` — Hermes v0.17 rejects
|
|
2732
|
+
provider tools shadowing reserved CORE tool names even when the core tool
|
|
2733
|
+
is check_fn-dropped ("Core tools always win", live-hit 2026-07-03); the
|
|
2734
|
+
event-stream mapper (`tool-call-mapper.ts`) maps `workspace_web_search`
|
|
2735
|
+
onto the first-class `web_search` search row (and `web_load` onto the
|
|
2736
|
+
`fetch` row) so the timeline surface is unchanged.
|
|
2737
|
+
- **Daemon `/local/web/search` + `/local/web/load` routes** (`daemon/web/rpc.ts`,
|
|
2738
|
+
new `attachWeb` hook in `local-server.ts`, wired in `runner.ts`): FORWARD to
|
|
2739
|
+
the cloud Load API `POST /api/context/load` (`{input: query|url|urls}` —
|
|
2740
|
+
search + cache + compress + deposit, Exa server-side) with the daemon's own
|
|
2741
|
+
`Authorization: Bearer <sk-prismer>` (same credential lane as extract.ts;
|
|
2742
|
+
the Load API's `apiGuard` accepts sk-prismer Bearer directly, no JWT
|
|
2743
|
+
exchange). Forward timeout 120s default (live-measured: cold 3-result query
|
|
2744
|
+
= 59s), env-tunable `PRISMER_WEB_LOAD_TIMEOUT_MS`; the daemon also sends
|
|
2745
|
+
`x-forwarded-proto` matching its cloud baseUrl scheme (the Load API
|
|
2746
|
+
self-fetches `/api/search` at `x-forwarded-proto || https`, which 500s
|
|
2747
|
+
against a plain-HTTP dev/pod cloud). Search passes `search.topK = limit`
|
|
2748
|
+
alongside `return.topK` — the route's default searched set is 15 and it
|
|
2749
|
+
compresses EVERY uncached hit before ranking (59–118s cold for a 3-result
|
|
2750
|
+
ask). Every text field in the passthrough bounded to ~8k chars with an
|
|
2751
|
+
explicit truncation marker; `[web-tool]`-prefixed structured logs (trace
|
|
2752
|
+
id, query/url count, status, ms).
|
|
2753
|
+
- **Explicit `platform_toolsets.api_server` pin** (`adapters/persistence/hermes/index.ts`
|
|
2754
|
+
`configurePrismerProvider`, exported `HERMES_API_SERVER_PLATFORM_TOOLSETS`):
|
|
2755
|
+
bypasses the hermes v0.17.0 subset-inference bug that silently dropped the
|
|
2756
|
+
`terminal` toolset. The pinned list is the `hermes-api-server` composite
|
|
2757
|
+
reverse-mapped to configurable toolset keys minus default-off (verified
|
|
2758
|
+
against the hermes-agent reference `hermes_cli/tools_config.py`); explicit
|
|
2759
|
+
membership flips hermes to deterministic direct resolution. Union-merged
|
|
2760
|
+
with operator entries, idempotent; per-role `agent.disabled_toolsets`
|
|
2761
|
+
(toolsetScope deny) still applies LAST hermes-side, so governance wins.
|
|
2762
|
+
- **`WEB_TOOL_DIRECTIVE`** (`daemon/dispatch.ts`): hermes-only teaching line on
|
|
2763
|
+
the same `composedSystemPrompt` seam as `MEMORY_CORE_DIRECTIVE` ("Web
|
|
2764
|
+
research → `web_search`/`web_load` (workspace-billed, cached). CLI →
|
|
2765
|
+
`terminal`. Do NOT script HTTP or subprocess for these.").
|
|
2766
|
+
- **Frozen spec** `adapters/web-tools.ts` (sibling of `memory-tools.ts`):
|
|
2767
|
+
`WebSearchInput/Output`, `WebLoadInput/Output` + naming rationale
|
|
2768
|
+
(`web_search` matches the event-stream mapper's `SEARCH_TOOLS` so calls
|
|
2769
|
+
render as first-class search rows).
|
|
2770
|
+
- Tests: `test/web-rpc.test.ts` (forward+auth header+bounding+non-http
|
|
2771
|
+
reject+degrade), `test/web-provider-shell.test.ts` (real python3 exec of the
|
|
2772
|
+
shell: 7-tool registration + shell-side invalid_url negatives),
|
|
2773
|
+
`test/hermes-platform-toolsets.test.ts` (pin content, operator merge,
|
|
2774
|
+
idempotency, deny coexistence).
|
|
2775
|
+
|
|
2776
|
+
### Added — memory203/18 §11.4 residual #3: conflict-lifecycle review surface (`kind=conflicts`)
|
|
2777
|
+
|
|
2778
|
+
- **`GET /local/memory/health?kind=conflicts`** (`daemon/memory/rpc.ts`
|
|
2779
|
+
`HEALTH_KINDS`): passthrough to the new cloud
|
|
2780
|
+
`GET /api/im/memory/health/conflicts` — live remote-conflict pages, each
|
|
2781
|
+
carrying `metadata.latestTwoVersionSummaries` (`{version, changeSummary,
|
|
2782
|
+
authoredBy, createdAt}` from the version DAG) + `metadata.currentVersion`,
|
|
2783
|
+
so the orchestrator can judge which LWW side owns the head without loading
|
|
2784
|
+
full contents. `kind=all` now returns four surfaces
|
|
2785
|
+
(`{orphans, duplicates, stale, conflicts}`).
|
|
2786
|
+
- **`memory_curate` python tool** (`plugins/memory/prismer/__init__.py`):
|
|
2787
|
+
`candidates` `kind` enum gains `"conflicts"`. Cloud-side, conflict is now a
|
|
2788
|
+
STATE, not a brand — a curation touch or clean (non-conflicting) rewrite of
|
|
2789
|
+
a conflict-flagged page restores its `sourceKind` to `daemon-sync`, so
|
|
2790
|
+
reviewed pages drop off this surface; the `memory-dream` skill's convergence
|
|
2791
|
+
loop gained a STEP 1b teaching the review flow.
|
|
2792
|
+
|
|
2793
|
+
### Fixed — memory203/18 W5 (final-round5 P0): two-phase stall watchdog + silent-empty-reply guard
|
|
2794
|
+
|
|
2795
|
+
- **Two-phase upstream-stall watchdog** (`adapters/persistence/hermes/sessions-sse.ts`):
|
|
2796
|
+
the W4 single 120s threshold killed HEALTHY long-context turns — after
|
|
2797
|
+
round-3 bulk ingestion the recall probes carried ~120k promptTokens and the
|
|
2798
|
+
upstream legitimately took >120s to the FIRST token (3/3 probes
|
|
2799
|
+
watchdog-aborted; the 15s dispatch heartbeats already protected those turns
|
|
2800
|
+
from the 300s reaper). Now the guard runs two phases: before the first
|
|
2801
|
+
MODEL-activity event it uses `PRISMER_UPSTREAM_FIRST_EVENT_MS` (default
|
|
2802
|
+
270 000 ms — under the 300s reaper, generous for long-context first tokens);
|
|
2803
|
+
after it, the original inter-event `PRISMER_UPSTREAM_STALL_MS` (default
|
|
2804
|
+
120 000 ms). Both env-tunable, resolved per call. ⚠️ hermes enqueues
|
|
2805
|
+
`run.started`/`message.started` BEFORE the LLM call (api_server.py
|
|
2806
|
+
`_run_and_signal`), so those acks re-arm but do NOT end the first-event
|
|
2807
|
+
phase — gating on the literally-first frame would have made the fix inert.
|
|
2808
|
+
Abort messages name the phase (`no first event for Ns` vs `no events for
|
|
2809
|
+
Ns`); `dispatch.ts` `isLimiterClassError`/`retryReasonToken` match both
|
|
2810
|
+
wordings (requeue lane + `reason=stall` unchanged). Clarify-disarm and
|
|
2811
|
+
keepalive-comment semantics unchanged. Covers both hermes lanes (shared
|
|
2812
|
+
`consumeSessionsSse`).
|
|
2813
|
+
- **Silent-empty-reply guard + transcript-tail recovery**
|
|
2814
|
+
(`adapters/persistence/hermes/sessions-dispatcher.ts`): after a stall-abort,
|
|
2815
|
+
hermes keeps generating server-side (`_run_agent` runs `run_conversation` in
|
|
2816
|
+
a thread executor the disconnect-cancel cannot preempt) and flushes the turn
|
|
2817
|
+
into the session store on completion; the daemon retry re-sent the same
|
|
2818
|
+
prompt into the SAME session and the retried stream could complete with
|
|
2819
|
+
EMPTY content — the adapter forwarded ok=true output:'' → cloud marked the
|
|
2820
|
+
run completed and the DM message-post gate (`output || attachments`)
|
|
2821
|
+
silently skipped the post (run=completed, error=NULL, DM empty, credits
|
|
2822
|
+
burned; the killed attempt's answer later leaked into the next turn's
|
|
2823
|
+
reply). Now an empty-output completion (approval/clarify suspensions
|
|
2824
|
+
excluded) is never a silent success: the dispatcher first tries to RECOVER
|
|
2825
|
+
the reply from `GET /api/sessions/{id}/messages` (last non-empty assistant
|
|
2826
|
+
row after the first user row carrying this dispatch's id — the id is
|
|
2827
|
+
stamped in the `<execution_context>` XML; 2 bounded polls), surfacing it
|
|
2828
|
+
with `metadata.hermes.replyRecovered='session_transcript_tail'`; otherwise
|
|
2829
|
+
it fails LOUDLY with `empty_reply`, which `daemon/dispatch.ts` treats as
|
|
2830
|
+
terminal (no 3× re-burn of a ~120k-token prompt into the polluted session)
|
|
2831
|
+
so the cloud posts a visible "Agent failed" system event.
|
|
2832
|
+
- **Best-effort upstream cancel on stall-abort** (same file): fires
|
|
2833
|
+
`POST /v1/runs/{runId}/stop` fire-and-forget before the retry. Known
|
|
2834
|
+
ineffective on current hermes — the sessions chat/stream handler never
|
|
2835
|
+
registers its run in `_active_run_agents` (only the /v1/runs path does), so
|
|
2836
|
+
this 404s today; kept as the correct semantic signal for the day hermes
|
|
2837
|
+
registers sessions runs. A clean per-turn abort therefore CANNOT be done
|
|
2838
|
+
from the adapter yet — the guard above is the real mitigation.
|
|
2839
|
+
|
|
2840
|
+
### Changed — memory203/18 W4 runtime tail: upstream-stall watchdog + CLI gateway-detection fix
|
|
2841
|
+
|
|
2842
|
+
- **In-flight upstream-stall watchdog** (`adapters/persistence/hermes/sessions-sse.ts`
|
|
2843
|
+
`createStallGuard` + `resolveUpstreamStallMs`): the W2-gate's dominant residual
|
|
2844
|
+
failure mode was a SILENT upstream LLM hang — no error, no SSE events — so the
|
|
2845
|
+
adapter emitted no progress, the 300s daemon reaper killed the run
|
|
2846
|
+
(`daemon_task_timeout`), the cloud requeued, the same stall repeated until the
|
|
2847
|
+
requeue cap (4) exhausted → 3/18 permanent burst failures + starved recall
|
|
2848
|
+
probes. The W3 retry-loop progress frames couldn't cover this: the call never
|
|
2849
|
+
RETURNED to enter the retry loop. Now, when an in-flight sessions SSE stream
|
|
2850
|
+
produces NO event frames for `PRISMER_UPSTREAM_STALL_MS` (default 120 000 ms,
|
|
2851
|
+
env-tunable, read per-call), the guard cancels the stream and throws
|
|
2852
|
+
`upstream stall: no events for Ns (…)` — a plain (non-AbortError) error that
|
|
2853
|
+
`categorizeDispatchError` maps to a retryable `adapter_dispatch_failed`.
|
|
2854
|
+
Covers BOTH hermes lanes (sessions-dispatcher + runs-dispatcher share
|
|
2855
|
+
`consumeSessionsSse`). The reaper window is untouched (last-resort backstop);
|
|
2856
|
+
`clarify.request` disarms the guard while a human answers (re-armed by the
|
|
2857
|
+
next event frame).
|
|
2858
|
+
- **Stall-class rides the limiter/requeue lane** (`daemon/dispatch.ts`):
|
|
2859
|
+
`isLimiterClassError` now matches `upstream stall: no events for` — the retry
|
|
2860
|
+
loop uses the jittered limiter backoff with `retrying(attempt=N, reason=stall)`
|
|
2861
|
+
progress frames (new `retryReasonToken` token), and on exhaustion the failure
|
|
2862
|
+
lands `dispatch_precondition_unavailable` so the cloud's transient-requeue
|
|
2863
|
+
channel re-delivers instead of a permanent red failure. Generic `HTTP 504`
|
|
2864
|
+
wording remains terminal (unchanged).
|
|
2865
|
+
- **`prismer memory list` gateway-detection fix** (`cli/commands/memory.ts`):
|
|
2866
|
+
`list` never fell back to `$PRISMER_WORKSPACE_ID` (unlike search/read/recall/
|
|
2867
|
+
write), so a bare `prismer memory list` in an agent pod skipped the modern
|
|
2868
|
+
`/local/memory/list` route entirely, probed only the legacy 404 paths, and
|
|
2869
|
+
reported `memory_gateway_unavailable` against a live daemon RPC (W2-gate
|
|
2870
|
+
item 3 FAIL, R7 CLI projection). Now `list` honours the env workspace;
|
|
2871
|
+
explicit `--workspace-id` still wins. Verified inside the live agent-rt pod
|
|
2872
|
+
(patched CLI bundle, direct node invocation): `memory list --page-type hub`
|
|
2873
|
+
returns the workspace hubs with exit 0.
|
|
2874
|
+
Known residual: `memory delete` / `memory sync` still only probe legacy
|
|
2875
|
+
routes (no modern `/local/memory/*` equivalents wired) — unchanged.
|
|
2876
|
+
|
|
2877
|
+
### Changed — memory203/18 W3 lane: failure self-heal (R3.1–R3.3) + event-stream fidelity (R5.2)
|
|
2878
|
+
|
|
2879
|
+
- **R3.1 — limiter-exhaustion now self-heals instead of terminal-failing**
|
|
2880
|
+
(`daemon/dispatch.ts`): when the local retry loop (3 attempts) exhausts and the
|
|
2881
|
+
LAST error is limiter-class (HTTP 429 queue-full / RPM "Rate limit exceeded",
|
|
2882
|
+
504 "slot not available before deadline"), the synthesised failure carries
|
|
2883
|
+
`dispatch_precondition_unavailable` (original message preserved) instead of
|
|
2884
|
+
`daemon_local_retry_exhausted` — the cloud's EXISTING transient-requeue channel
|
|
2885
|
+
(`requeueTransientRun`, cap 4) re-delivers the run once the queue drains.
|
|
2886
|
+
Non-limiter exhaustion (e.g. HTTP 500 ×3) still lands
|
|
2887
|
+
`daemon_local_retry_exhausted` terminal. Cloud side (same change set,
|
|
2888
|
+
`src/im/ws/handler.ts` + `task.service.ts::isTransientRequeueErrorCode`): the
|
|
2889
|
+
requeue branch also accepts reaper `daemon_task_timeout` for CHAT runs
|
|
2890
|
+
(`run.source !== 'task'`), mirroring the kanban-task retry policy that chat
|
|
2891
|
+
runs never had — same bounded counter.
|
|
2892
|
+
- **R3.2 — limiter-aware backoff** (`daemon/dispatch.ts`): limiter-class retry
|
|
2893
|
+
waits honor an explicit Retry-After hint in the message (`Retry-After: n` /
|
|
2894
|
+
`Retry in ns`, clamped 60s) and otherwise use jittered windows [3–6s, 8–15s]
|
|
2895
|
+
instead of the fixed [1s, 3s] that burnt all 3 attempts in ~4s. Attempts stay
|
|
2896
|
+
at 3 (the requeue channel owns the long tail). `PRISMER_DISPATCH_RETRY_BACKOFF_MS`
|
|
2897
|
+
(comma list) overrides all classes for ops/testing.
|
|
2898
|
+
- **R3.3 — progress during retry** (`daemon/dispatch.ts`): before each backoff
|
|
2899
|
+
the dispatcher emits `task.dispatch.progress` with
|
|
2900
|
+
`retrying(attempt=N, reason=<status>)` (+ structured `detail.kind='retry'`),
|
|
2901
|
+
resetting the runner reaper's inactivity window so the 300s reaper no longer
|
|
2902
|
+
kills runs that are mid-retry, and giving the UI something to show.
|
|
2903
|
+
- **R5.2 — event-stream CLI extraction** (`adapters/persistence/hermes/tool-call-mapper.ts`):
|
|
2904
|
+
`execute_code` wrapping a CLI in python (`subprocess.run(['prismer',…])`,
|
|
2905
|
+
`subprocess.*` string form, `os.system(…)`, `!cmd` lines) now maps
|
|
2906
|
+
`detail.command` to the reconstructed CLI (multiple calls join with ` && `,
|
|
2907
|
+
capped 400 chars) with `detail.commandSource='argv'`; the FULL python source
|
|
2908
|
+
rides additively on `detail.script` (capped/spilled by the step recorder like
|
|
2909
|
+
`output`). Dynamic argv (variables/f-string elements) is never guessed — no
|
|
2910
|
+
match keeps the previous whole-source behaviour. The workspace timeline's
|
|
2911
|
+
collapsed row now renders `` $ prismer memory search … `` instead of
|
|
2912
|
+
`import subprocess (+9 行)` (`src/app/workspace/components/agent-message/adapter.ts`).
|
|
2913
|
+
- Additive type fields: `ToolCallDetail.shell.{script,commandSource}`
|
|
2914
|
+
(`adapters/coding/shared/agent-sdk-types.ts` + UI mirror in
|
|
2915
|
+
`agent-message/types.ts`); step recorder caps `shell.script` like `shell.output`.
|
|
2916
|
+
|
|
2917
|
+
### Changed — memory203/18 W2 guidance lane: teach the primitives that now exist (§2.3/R6.5/R4.1/R4.2)
|
|
2918
|
+
|
|
2919
|
+
- **`built-in-skills/memory/SKILL.md` rewritten tool-centric** (native `memory_*` tools primary;
|
|
2920
|
+
short `prismer memory` CLI appendix for code agents): browse-first write flow
|
|
2921
|
+
(`memory_browse` → extend / attach via `parent_hub_path` / new-hub-then-leaf → verify via
|
|
2922
|
+
`memory_search`), prefix-free path convention ("use paths exactly as returned by
|
|
2923
|
+
browse/search"), structural edges over prose (`child-of` direction taught explicitly: edge
|
|
2924
|
+
points FROM child TO hub — the W1-gate found 8 hand-written inverted links), INDEX.pkf is
|
|
2925
|
+
machine-generated and never hand-written, never whole-page-rewrite another agent's page.
|
|
2926
|
+
Section ops (`op="append-section"` / `"rewrite-section"`) are taught with an explicit
|
|
2927
|
+
landing note — they ship in the same daemon image (R6.4, parallel lane).
|
|
2928
|
+
- **`built-in-skills/memory-dream/SKILL.md` rewritten around the tight convergence loop**:
|
|
2929
|
+
`candidates` → `memory_browse` → cluster (your LLM) → `promote_to_hub` **WITH `childPaths[]`**
|
|
2930
|
+
(no hollow promotes, no hand-written re-anchor links — the old "no move-anchor op" §Re-anchor
|
|
2931
|
+
workaround is deleted) → `rebuild_index` once per batch → `candidates` again to verify →
|
|
2932
|
+
REPORT the structural delta in the reply. (W1-gate: CEO ran promote without childPaths because
|
|
2933
|
+
the param was undocumented, and hand-wrote 8 inverted child-of hrefs.)
|
|
2934
|
+
- **`built-in-skills/memory-curation/SKILL.md`** (deprecated/reference): placement guidance
|
|
2935
|
+
aligned to browse-first + `parent_hub_path`; removed "anti-orphan is discipline only, the
|
|
2936
|
+
platform won't enforce it" apologetics and the `memory/`-prefixed path convention.
|
|
2937
|
+
- **`MEMORY_CORE_DIRECTIVE` updated** (`daemon/dispatch.ts`): names all five tools (adds
|
|
2938
|
+
`memory_browse`), hardened RECALL rule ("when answering anything about workspace knowledge you
|
|
2939
|
+
did not just read, run `memory_search` FIRST; attached files are raw sources — check memory for
|
|
2940
|
+
distilled knowledge before re-reading them"), browse-first write flow, and the orchestrator
|
|
2941
|
+
convergence loop with `childPaths` + verify + report duty.
|
|
2942
|
+
- **R4.2 — builtin `memory` skill now installs into every hermes profile**
|
|
2943
|
+
(`adapters/persistence/hermes/index.ts`): `installPrismerImSkill` generalized to
|
|
2944
|
+
`installBuiltInHermesSkill(profileName, slug)` (per-slug content cache); both install sites
|
|
2945
|
+
(ensureService + per-dispatch) land `prismer-im-collab` + `memory`, and `memory-dream` for
|
|
2946
|
+
profiles with `taskAuthority: 'orchestrator'` (first consumer of that config bit).
|
|
2947
|
+
- **R4.1 — orchestrator prompt no longer routes file questions straight to raw sources**
|
|
2948
|
+
(`src/lib/role-runtime-policy/prompt-contract.ts`, cloud side): "For uploaded files, first
|
|
2949
|
+
`memory_search` for existing distilled knowledge; use `assets`/`ingest` when memory misses."
|
|
2950
|
+
- **R4.3 — `<execution_context>` dirs annotated** (`daemon/conversation-context.ts`): a
|
|
2951
|
+
`<dirs_note>` leaf marks artifacts/scratch dirs as per-run working space, pointing prior-fact
|
|
2952
|
+
questions at `memory_search` (the W1-gate read-file-over-recall bypass).
|
|
2953
|
+
|
|
2954
|
+
### Added — memory203/18 W2 daemon lane: path/URI normalization + section write + placement guard + 0-yield root fix
|
|
2955
|
+
|
|
2956
|
+
- **P0 path/URI namespace normalization** (`daemon/memory/store.ts` → `rpc.ts handleWrite`,
|
|
2957
|
+
`hook-server.ts writeExtractedPage`, `cloud-sync.ts fetchPageFromCloudByPath`): ONE shared
|
|
2958
|
+
normalizer — `normalizeMemoryPath` (strips leading `/` + `memory/` segments) +
|
|
2959
|
+
`memoryPathToUri(workspaceId, path)` — used by every link/URI composition site, so a page path
|
|
2960
|
+
(or `parentHubPath`) that already starts with `memory/` no longer produces
|
|
2961
|
+
`prismer://…/memory/memory/…` (the W1-gate: 3/3 agent child-of `link.upsert` events silently
|
|
2962
|
+
dropped cloud-side, 0 rows). `memory.link.upsert` envelopes additionally carry plain
|
|
2963
|
+
`sourcePath` / `targetPath` (normalized; OPTIONAL + additive on `envelope.ts`, no schemaVersion
|
|
2964
|
+
bump) — the cloud lane prefers these over URI parsing, and trace path-mode matches on them.
|
|
2965
|
+
Local store lookups are variant-tolerant via `store.loadByAnyPath`
|
|
2966
|
+
(`memoryPathLookupVariants`: as-given, ±`memory/`, ±`.pkf`); a repeat write under the OTHER
|
|
2967
|
+
path convention extends the EXISTING page (existing row's path wins) instead of forking a
|
|
2968
|
+
near-duplicate.
|
|
2969
|
+
- **R1.4 write-time 回源 (write hygiene)** (`rpc.ts handleWrite` op=replace, `cloud-sync.ts`,
|
|
2970
|
+
`store.ts`, `types.ts`): when the target page is absent locally or still `local-only`, the
|
|
2971
|
+
daemon first pulls the cloud head via the SAME single-page回源 `handleLoad` uses
|
|
2972
|
+
(`fetchPageFromCloudByPath`); `materialisePage` now passes the cloud `version` and
|
|
2973
|
+
`store.write` adopts `max(localExisting+1, version)` — so the write's outbox `parentVersion`
|
|
2974
|
+
continues the cloud head (cloud v4 + empty subset → parentVersion=4, not 0; dissolves the
|
|
2975
|
+
W1-gate's cross-agent full-page-rewrite `remote-conflict base v4 vs head v1` class). Fail-open:
|
|
2976
|
+
offline → proceed exactly as before (a local-first write never blocks on the cloud).
|
|
2977
|
+
- **R6.4 section-level write** (`rpc.ts handleWrite`/`handleSectionWrite`, `__init__.py`,
|
|
2978
|
+
`adapters/memory-tools.ts`, `cli/commands/memory.ts --op/--section`): `memory_write` gains
|
|
2979
|
+
`op` (`replace`|`append-section`|`rewrite-section`, default replace) + `section` (heading id,
|
|
2980
|
+
required for section ops). Section ops FORWARD to the cloud section verbs
|
|
2981
|
+
(`POST /api/im/memory/pages/:id/sections/append|rewrite`, actor-override header like curate),
|
|
2982
|
+
then refresh the local subset from the cloud response (adopting the cloud version,
|
|
2983
|
+
syncStatus→`acked`) — and deliberately emit **NO** `memory.page.upsert` outbox event (the
|
|
2984
|
+
cloud write is authoritative; an upsert on top would double-apply the edit). No cloud wired →
|
|
2985
|
+
explicit 503 `section_write_requires_cloud` (never a silent no-op); unreachable cloud → 5xx
|
|
2986
|
+
passthrough with the local page untouched.
|
|
2987
|
+
- **R6.3 placement guard (ramp, gated LAST per §9.3)** (`rpc.ts handleWrite`): a NEW un-anchored
|
|
2988
|
+
leaf (no existing page at path, not `pageType=hub`, no `parentHubPath`, no `rel="child-of"`
|
|
2989
|
+
content link) is gated by `PRISMER_MEMORY_PLACEMENT_ENFORCE` = `off` | `warn` (DEFAULT) |
|
|
2990
|
+
`enforce`. warn → `[memory-trace] stage=placement_warn` + `placementWarn` counter, write
|
|
2991
|
+
proceeds; enforce → 422 `{code:'placement_required', hubs:[{path,title,snippet}], message}`
|
|
2992
|
+
(hub candidates from the SAME `assemblePlaceContext` browse assembly). The Python tool's
|
|
2993
|
+
memory_write POST is now status-preserving so the structured rejection reaches the model
|
|
2994
|
+
verbatim (was: swallowed into `daemon_unreachable`).
|
|
2995
|
+
- **Extraction 0-yield root fix + terminal states** (`extract.ts`, `hook-server.ts`): live-
|
|
2996
|
+
diagnosed 2026-07-03 on the W1-gate pod (manual post_llm_call + direct gateway replay of the
|
|
2997
|
+
real gate turn): kimi's multi-page JSON stochastically exceeds `max_tokens=2048`
|
|
2998
|
+
(`stop_reason=max_tokens`, observed 996/1924/2048-cut across 3 identical trials), and the
|
|
2999
|
+
truncated payload JSON.parse-failed into a SILENT `[]` — the gate's
|
|
3000
|
+
`llm_called 3/3 → extracted 0/3`. Fix: (a) string-aware salvage parser recovers every
|
|
3001
|
+
COMPLETE page object from a truncated `{"pages":[…` array (the REAL truncated gate-shape
|
|
3002
|
+
payload now yields 3 pages instead of 0); (b) `stop_reason` surfaced —
|
|
3003
|
+
`stage=llm_truncated(max_tokens)` + `truncated` flag on the result; (c) terminal stages
|
|
3004
|
+
`stage=llm_response(pages=N, chars=M[, truncated])` and `stage=skipped(reason=no_pages)` +
|
|
3005
|
+
`extractedEmpty` counter close the llm_called-then-silence gap; (d) a BUDGET line in the
|
|
3006
|
+
extraction prompt biases toward fewer, denser pages. `/healthz` `memory.counters` is now
|
|
3007
|
+
`{received, skipped, extracted, writeFailed, extractedEmpty, placementWarn}`.
|
|
3008
|
+
- **R9.2 extraction deferred retry (doc 18 §9.4)** (`hook-server.ts`, `extract.ts errorStatus`):
|
|
3009
|
+
under burst the in-pod extraction call is starved by the workspace concurrency limiter
|
|
3010
|
+
(429 / 504 slot-deadline / status=0 cloud_unreachable) and the turn's knowledge — from exactly
|
|
3011
|
+
the busiest ingestion window — was dropped forever. Limiter-class failures now land on a
|
|
3012
|
+
bounded in-memory deferred queue (max 32, drop-oldest + `stage=deferred_dropped`); a lazy,
|
|
3013
|
+
jittered ~60s pump (`MEMORY_EXTRACT_RETRY_INTERVAL_MS`, unref'd, self-stopping) drains ONE
|
|
3014
|
+
entry per tick (serial — deliberately gentle on the limiter), max 3 total attempts, then
|
|
3015
|
+
`stage=extraction_abandoned` + counter. A successful retry runs the normal written/synced path
|
|
3016
|
+
with the ORIGINAL traceId (the trace shows the gap AND the recovery); each retry rebuilds a
|
|
3017
|
+
FRESH place-context. Non-limiter failures keep the writeFailed path (never queued). Counters:
|
|
3018
|
+
`memory.counters` gains `{deferred, deferredRetried, deferredAbandoned}` (deferred counts
|
|
3019
|
+
entries, not tries). Stage logs: `stage=deferred(reason=limiter, attempt=N)` /
|
|
3020
|
+
`deferred_retry(attempt=N)`. KNOWN BOUND (deliberate): the queue is in-memory only — a pod
|
|
3021
|
+
restart loses pending entries (no persistence by design; this module has no daemon shutdown
|
|
3022
|
+
hook to log the residual size).
|
|
3023
|
+
- Tests: `test/memory-write-w2.test.ts` (20, negative controls for every mode),
|
|
3024
|
+
`test/memory-extract-salvage.test.ts` (4, incl. the real-payload shape),
|
|
3025
|
+
`test/memory-extract-deferred.test.ts` (9: defer on 429/504/0, non-limiter + 0-page negative
|
|
3026
|
+
controls, traceId-preserving recovery, 3-attempt abandonment, hard-failure-on-retry, 32-cap
|
|
3027
|
+
drop-oldest), `test/memory-stage-counters.test.ts` (+1 extractedEmpty terminal state,
|
|
3028
|
+
counters shape ×9).
|
|
3029
|
+
|
|
3030
|
+
### Added — memory203/18 W0+W1 daemon lane: write-time placement + browse + trace observability
|
|
3031
|
+
|
|
3032
|
+
- **R1.1 `memory_write` structural placement** (`plugins/memory/prismer/__init__.py`,
|
|
3033
|
+
`daemon/memory/rpc.ts handleWrite`): optional `parent_hub_path` + `relation`
|
|
3034
|
+
(`child-of`|`related`, default child-of) on the tool schema, forwarded as
|
|
3035
|
+
`parentHubPath`/`relation` to `POST /local/memory/write`. When present the daemon enqueues a
|
|
3036
|
+
`memory.link.upsert` GRAPH event (page→hub) mirroring the auto-extract leg — the edge that
|
|
3037
|
+
actually nests the page cloud-side. A locally-missing hub still emits (warn-not-reject; the
|
|
3038
|
+
fail-closed guardrail is R6.3/W2, gated on browse being live). Response additively echoes
|
|
3039
|
+
`link: { targetPath, relation }`.
|
|
3040
|
+
- **R1.3 `memory_curate` promote_to_hub `childPaths[]`** (`__init__.py`, `rpc.ts handleCurate`,
|
|
3041
|
+
`adapters/memory-tools.ts`): pass-through to the cloud POST body so the promoted hub can attach
|
|
3042
|
+
children in the same transaction (cloud lane consumes; older clouds ignore the extra field).
|
|
3043
|
+
- **R6.1 hub snippet wiring** (`daemon/memory/hook-server.ts`): the extraction recall context now
|
|
3044
|
+
carries WHAT each hub is about — `description`, else first ~200 chars of content, else `''`.
|
|
3045
|
+
(The old call site mapped hubs without a snippet, so the extract model was blind to hub topics
|
|
3046
|
+
and rationally fell back to `placement=new` → orphan leaves.)
|
|
3047
|
+
- **R6.2 `memory_browse` / `GET /local/memory/place-context`** (`rpc.ts`, `__init__.py`,
|
|
3048
|
+
`adapters/memory-tools.ts`): write-time structure view `{ index, hubs[], nearest[] }` (each
|
|
3049
|
+
`{path,title,pageType,snippet}`), assembled by the SHARED `assemblePlaceContext` helper the
|
|
3050
|
+
extraction leg uses — browse and extraction cannot drift. `q=` adds nearest pages via the same
|
|
3051
|
+
local hybrid search; F5 cap predicate applies (private pages hidden). `memory_load` additively
|
|
3052
|
+
returns the page's outbound `links` (best-effort cloud `GET /pages/:id/links`; omitted offline).
|
|
3053
|
+
FROZEN spec (`adapters/memory-tools.ts`) now lists `memory_write` + `memory_browse` (R6.5 partial).
|
|
3054
|
+
- **R8.1 traceId 贯穿** (`__init__.py` → `hook-server.ts` → `extract.ts` → `envelope.ts` → `rpc.ts`):
|
|
3055
|
+
`sync_turn` stamps `extra.trace_id` (session id + random suffix); the daemon threads it through
|
|
3056
|
+
extraction into the `memory.page.upsert` / `memory.link.upsert` outbox envelopes (optional
|
|
3057
|
+
`traceId` added to `MemoryEventCommon` — additive, no schemaVersion bump). `handleWrite` accepts
|
|
3058
|
+
an optional `traceId` (else mints `wr_<10hex>`).
|
|
3059
|
+
- **R8.2 stage counters + standardized stage logs** (`hook-server.ts`, `extract.ts`,
|
|
3060
|
+
`daemon/local-server.ts`): module-level `{received, skipped, extracted, writeFailed}` counters
|
|
3061
|
+
surfaced on `/healthz` as `memory.counters`; every pipeline stage logs one grep-able line —
|
|
3062
|
+
`[memory-trace] stage=received|skipped(reason=…)|llm_called|llm_failed(status=…)|written(paths=…)|synced traceId=…`.
|
|
3063
|
+
- **R9.1 sync_turn delivery hardening** (`__init__.py`): the bare `except: pass` around the
|
|
3064
|
+
post_llm_call POST is replaced with at-least-once delivery — one retry after ~1s; terminal
|
|
3065
|
+
outcome always lands as ONE structured stderr line (`[memory-trace] sync_turn delivered …` /
|
|
3066
|
+
`… delivery FAILED err=<class>: <msg>`). Still fire-and-forget: never raises into the turn.
|
|
3067
|
+
- Tests: `test/memory-place-context.test.ts` (7), `test/memory-write-placement.test.ts` (8),
|
|
3068
|
+
`test/memory-stage-counters.test.ts` (5) — each new behavior with a negative control.
|
|
3069
|
+
|
|
3070
|
+
### Fixed — memory extraction dropped under real docs (generation outran the 30s client timeout)
|
|
3071
|
+
|
|
3072
|
+
- **Why:** test-verified (2026-07-02, memory203 systematic doc-ingest) — a full-doc extraction
|
|
3073
|
+
prompt with `max_tokens: 4096` on `us-kimi-k2.6` takes **29–44s** to generate. The daemon's
|
|
3074
|
+
extraction gateway call was bounded at **30s**, so under real documents the call aborted
|
|
3075
|
+
mid-flight (`status=0 cloud_unreachable "Request aborted/timeout"`), the 2-retry budget was
|
|
3076
|
+
exhausted, and the memory was silently dropped — 6 docs yielded ~1 page, 0 hubs. (The cloud
|
|
3077
|
+
proxy completed the work at ~29s, but the daemon had already given up.) A control run with the
|
|
3078
|
+
workspace concurrency limiter OFF reproduced identically → the limiter was **exonerated**; the
|
|
3079
|
+
cause was purely extraction generation-time vs client-timeout.
|
|
3080
|
+
- **What changed** (`daemon/memory/extract.ts`): `max_tokens` 4096 → **2048** (fits 6 concise
|
|
3081
|
+
pages at ~300 tok each while keeping generation well under the bound) and
|
|
3082
|
+
`GATEWAY_TIMEOUT_MS` 30s → **60s** (margin over the measured worst case). Both are now
|
|
3083
|
+
env-overridable — `MEMORY_EXTRACT_MAX_TOKENS`, `MEMORY_EXTRACT_TIMEOUT_MS` — so ops can tune
|
|
3084
|
+
without a daemon rebuild.
|
|
3085
|
+
|
|
3086
|
+
### Changed — memory skills/seeds repointed from `cloud memory` → `prismer memory` (post-2822f36f CLI separation)
|
|
3087
|
+
|
|
3088
|
+
- **Why:** commit `2822f36f` dropped the erroneous `cloud` bin from @prismer/runtime (it
|
|
3089
|
+
collided with @prismer/sdk's `cloud` bin → EEXIST on global install). The two CLIs are now
|
|
3090
|
+
cleanly separated: `cloud` = @prismer/sdk (cloud-side HTTP, incl. the memory203/13 decision-D
|
|
3091
|
+
**retired** memory HTTP impl), `prismer` = @prismer/runtime (this canonical daemon CLI). In
|
|
3092
|
+
the agent-rt image both tarballs install, so in-pod `cloud memory` would now resolve to the
|
|
3093
|
+
RETIRED sdk HTTP path — the daemon-backed memory CLI is `prismer memory`.
|
|
3094
|
+
- **What changed:** repointed every agent-facing `cloud memory …` reference to `prismer memory …`
|
|
3095
|
+
across the memory built-in skills, seeds, and adapter/CLI docs:
|
|
3096
|
+
`built-in-skills/memory/SKILL.md`, `built-in-skills/memory-dream/SKILL.md`,
|
|
3097
|
+
`src/im/data/memory-seed/{index.ts,sdk-intro.md}` (memory/recall moved to the `prismer` family;
|
|
3098
|
+
task/send/asset/load/parse/deliver/okr/pay stay `cloud`), the claude-code/codex
|
|
3099
|
+
`adapters/coding/*/memory-tools.ts` doc comments, this package's `cli/commands/memory.ts`
|
|
3100
|
+
header, and the desktop copy `apps/desktop/resources/runtime/built-in-skills/memory/SKILL.md`
|
|
3101
|
+
(whose stale `cloud memory extract/consolidate/compact` were aligned to the canonical set —
|
|
3102
|
+
`extract` → direct `prismer memory write` + automatic background review; `consolidate` →
|
|
3103
|
+
`prismer memory curate`; `compact` dropped, no canonical equivalent). `prismer memory` covers
|
|
3104
|
+
recall/search/read/list/write/delete/curate. No behavior change to the daemon `/local/memory/*`
|
|
3105
|
+
RPC; this is a CLI-name correction so in-pod agents hit the daemon, not the retired sdk HTTP.
|
|
3106
|
+
|
|
3107
|
+
### Clarified — code-agent memory path is the `prismer memory` CLI, NOT a native tool surface (memory203/09 §① gap G, resolved to §10 decision)
|
|
3108
|
+
|
|
3109
|
+
- **Context:** doc memory203/09 §① flagged 🔴 "CC/codex adapter 工具未接 dispatch
|
|
3110
|
+
(`memory-tools.ts:10`)" — i.e. the per-adapter memory tool schemas were never wired into
|
|
3111
|
+
the running code agent. Investigation shows the gap is **narrower than the 🔴 implies and
|
|
3112
|
+
already resolved by design**: doc memory203/10 §56 decides code agents
|
|
3113
|
+
(claude-code/codex/opencode) have no Hermes-style programmatic provider seam (they run as
|
|
3114
|
+
filesystem CLI subprocesses), so their memory path is the **`prismer memory` CLI + the
|
|
3115
|
+
`memory` built-in skill**, which hits the SAME daemon `/local/memory/*` RPC as the Hermes
|
|
3116
|
+
tool impls. That CLI is fully implemented (`src/cli/commands/memory.ts`:
|
|
3117
|
+
recall/read/write/list/curate, registered in `cli/index.ts`) and the `memory` skill
|
|
3118
|
+
(`scope: common`) is seeded into every coding workdir's `.claude/skills/` via
|
|
3119
|
+
`CODING_COMMON_ALLOWLIST` (`adapters/coding/shared/coding-skill-set.ts`), with the
|
|
3120
|
+
AGENTS.md/CLAUDE.md preamble pointing the agent at `prismer memory`. So recall/read/write/
|
|
3121
|
+
curate are available to code agents out of the box — through the CLI, not a tool schema.
|
|
3122
|
+
- **What changed:** corrected the now-misleading "adapter integration owner work / NOT shipped
|
|
3123
|
+
in phase-0" header comments in `adapters/coding/claude-code/memory-tools.ts` and
|
|
3124
|
+
`adapters/coding/codex/memory-tools.ts` to record the §56 decision (CLI is the path; the
|
|
3125
|
+
native tool surface is intentionally Hermes-only; these schemas are a consumer-less FORMAT
|
|
3126
|
+
FREEZER kept for parity / a possible future programmatic seam — do NOT force-wire them).
|
|
3127
|
+
- **No behavior change** — comment-only; the runtime memory path for code agents already works
|
|
3128
|
+
via the CLI + skill.
|
|
3129
|
+
|
|
3130
|
+
### Added — Dream CONVERGENCE: orchestrator can now READ candidates + a concrete convergence oneshot (memory203/13 §P4 + §0.5)
|
|
3131
|
+
|
|
3132
|
+
- **Why:** the write path produces well-placed anchored leaves, but at SCALE the wiki
|
|
3133
|
+
degrades into a **FLAT STAR** (`INDEX → many leaves directly`), not a navigable
|
|
3134
|
+
hierarchy. The curation WRITE verbs (promote-to-hub / supersede / rebuild-index)
|
|
3135
|
+
existed, but the orchestrator had **no way to READ what to converge** — orphan leaves,
|
|
3136
|
+
near-duplicate clusters, stale pages all lived cloud-side with no daemon passthrough.
|
|
3137
|
+
The "brain" (orchestrator's own LLM) had hands but no eyes; it could not DECIDE clusters.
|
|
3138
|
+
- **Daemon READ passthrough (`daemon/memory/rpc.ts`):** new `GET /local/memory/health?workspaceId=&kind=&limit=`
|
|
3139
|
+
forwards to the cloud candidate surfaces `GET /api/im/memory/health/{orphans|duplicates|stale}`
|
|
3140
|
+
and returns `{ ok, candidates: { orphans, duplicates, stale } }` (each `{ items, total }`).
|
|
3141
|
+
`kind=all` (default) fetches all three. Same passthrough shape as `handleCurate` — ws cap
|
|
3142
|
+
gated daemon-side, ACL gated cloud-side, **ZERO cloud LLM** (it only scans the page graph).
|
|
3143
|
+
Offline (`cloud` unwired) → 200 degraded empty, honouring the same "降级不中断" contract.
|
|
3144
|
+
Forwards the verified acting agent in `X-Prismer-Memory-Actor` so the workspace ACL
|
|
3145
|
+
projection is correct.
|
|
3146
|
+
- **CLI (`cli/commands/memory.ts`):** new `cloud memory curate candidates [--kind orphans|duplicates|stale|all] [--limit N]`
|
|
3147
|
+
— the READ half. GET to the daemon health route. Read-only (any agent), unlike the three
|
|
3148
|
+
orchestrator-gated write verbs.
|
|
3149
|
+
- **Tool (`adapters/memory-tools.ts`):** `memory_curate` gains a `candidates` op (additive to
|
|
3150
|
+
the existing `promote_to_hub` / `supersede` / `rebuild_index`) with `kind` + `limit` inputs;
|
|
3151
|
+
it branches to the daemon's GET health route and returns the `candidates` payload. Read-only,
|
|
3152
|
+
so it is offered to every agent (the LLM clusters; the cloud does not).
|
|
3153
|
+
- **`built-in-skills/memory-dream/SKILL.md`:** new **"## Convergence flow (oneshot)"** — STEP 1
|
|
3154
|
+
READ candidates → STEP 2 DECIDE clusters (the agent's LLM) → STEP 3 per-cluster `promote-to-hub`
|
|
3155
|
+
a representative + re-anchor members under it (typed `<a rel="parent" prismer://…>` write) →
|
|
3156
|
+
STEP 4 `rebuild-index` so INDEX becomes a TOC of hubs. One worked example (8 scattered
|
|
3157
|
+
`project/helios-*` orphan leaves → `project/helios` hub → re-anchor → rebuild). Fixed the
|
|
3158
|
+
stale `cloud memory curate run-dream` reference (that retired cloud-LLM command no longer
|
|
3159
|
+
exists — replaced by `candidates` + the write verbs).
|
|
3160
|
+
- **Re-anchor note (flagged, not built here):** there is no single "move-anchor" verb. Re-anchor
|
|
3161
|
+
is `promote-to-hub` (mints the hub) + a `cloud memory write` adding the member→hub `rel="parent"`
|
|
3162
|
+
link; `rebuild-index` then drops hub-parented leaves out of the top-level INDEX automatically.
|
|
3163
|
+
This reuses existing cloud endpoints; no new cloud verb requested.
|
|
3164
|
+
|
|
3165
|
+
### Changed — `memory` skill: add a concrete oneshot WRITE flow so agents actually persist PKF
|
|
3166
|
+
|
|
3167
|
+
- **Why:** a live agent ran a turn but wrote NO memory page — the skill's write guidance was
|
|
3168
|
+
too abstract. The corrected, baked-in flow is **construct-PKF-first, then find where to
|
|
3169
|
+
store it**: (1) CONSTRUCT the full PKF page from the conversation, (2) PLACE by consulting
|
|
3170
|
+
the index (`cloud memory read INDEX.pkf` + `cloud memory recall`) and deciding extend /
|
|
3171
|
+
attach-under-hub / new-semantic-path (never an orphan leaf), (3) WRITE to the decided path.
|
|
3172
|
+
- `built-in-skills/memory/SKILL.md`: new **"## Write flow (oneshot)"** section with a single
|
|
3173
|
+
copy-pasteable end-to-end example (conversation excerpt → constructed `decision` PKF →
|
|
3174
|
+
index-consultation commands + placement reasoning → `cloud memory write`). Reconciled the
|
|
3175
|
+
Operating Rules → Write list so reading `INDEX.pkf` is a WRITE step, not only a Read step.
|
|
3176
|
+
PKF format rules are referenced, not duplicated. The example PKF validates clean against
|
|
3177
|
+
`src/lib/pkf` (frontmatter + `<h2 id>` sections + typed `prismer://workspace` link).
|
|
3178
|
+
- New standalone reference `scripts/cookbook/memory-write-oneshot.md` (the canonical example
|
|
3179
|
+
the skill points to).
|
|
3180
|
+
|
|
3181
|
+
### Added — DAEMON-SIDE automatic memory extraction (the "自动" leg) + remove cloud-Dream from curate (memory203/13 §0.5 + line 101, Fix Batch B)
|
|
3182
|
+
|
|
3183
|
+
- **Why:** automatic extraction was DEAD. Hermes's native `background_review`
|
|
3184
|
+
spawns with `skip_memory=True`, so our `memory_write` provider tool is never
|
|
3185
|
+
injected into the review fork — whatever it "remembers" lands in Hermes's
|
|
3186
|
+
built-in MEMORY.md, never our cloud PKF wiki. So the "自动" leg is now
|
|
3187
|
+
implemented OURSELVES, in the agent runtime (the daemon in the agent's pod).
|
|
3188
|
+
- **In-pod gateway extraction (`extract.ts` rewritten):** instead of forwarding
|
|
3189
|
+
to the retired cloud `/api/im/memory/extract` (410), `extractFromTurn` now calls
|
|
3190
|
+
the LLM gateway DIRECTLY from the daemon — `${cloud.baseUrl}/api/v1/messages`
|
|
3191
|
+
(Anthropic wire) authed with `Bearer ${cloud.apiKey}`, the SAME agent-credentialed
|
|
3192
|
+
gateway path the code-agent providers use (`PRISMER_BASE_URL` + sk-prismer token,
|
|
3193
|
+
injected for the hosted agent at runner.ts:325-329). The INITIATOR is the agent
|
|
3194
|
+
runtime; the cloud only proxies. **NO cloud LLM call remains.** The prompt carries
|
|
3195
|
+
a recall context built from the LOCAL store (INDEX + hubs + nearest pages) so the
|
|
3196
|
+
model decides placement (extend / attach-under-hub / new leaf) against the real
|
|
3197
|
+
wiki, and returns minimal valid PKF (`<h1>`/`<h2 id>`/`<p>`; cloud materialize
|
|
3198
|
+
validates). Bounded retry (2 attempts) on transient gateway failure; never blocks.
|
|
3199
|
+
- **Post-turn hook re-purposed (`hook-server.ts`):** `handlePostLlmCall` (a no-op
|
|
3200
|
+
after P3') now responds 204 immediately, then fire-and-forget builds the recall
|
|
3201
|
+
context, runs the in-pod extraction, and writes each PKF page via the SAME direct
|
|
3202
|
+
path `handleWrite` uses — `slot.store.write` + outbox `memory.page.upsert` — so
|
|
3203
|
+
pages up-sync and get anti-orphan-anchored on cloud. Scratch/eval sessions skipped.
|
|
3204
|
+
`extract.ts` is re-wired (dead cloud-extract imports removed).
|
|
3205
|
+
- **`run_dream` removed from curate:** the Python provider `memory_curate` no longer
|
|
3206
|
+
advertises `op=run_dream` to every agent, and the daemon `handleCurate` (rpc.ts)
|
|
3207
|
+
no longer forwards `run_dream` to the cloud `/page-dream` LLM. Dream is the
|
|
3208
|
+
orchestrator's memory-dream skill calling the write VERBS, not a cloud-LLM trigger.
|
|
3209
|
+
The three write-verb ops (promote_to_hub / supersede / rebuild_index) are kept.
|
|
3210
|
+
|
|
3211
|
+
### Changed — retire cloud-side memory extraction; extraction moves to the agent runtime (memory203/13 §0.5, P3')
|
|
3212
|
+
|
|
3213
|
+
- **Governance (用户裁决 2026-06-30):** all memory LLM work runs in the agent's
|
|
3214
|
+
OWN runtime; the cloud does ZERO LLM for memory. The cloud is storage only
|
|
3215
|
+
(materialize page upsert + anti-orphan-anchor to INDEX + recall queries).
|
|
3216
|
+
- `memory_write` is a DIRECT write again. The daemon `POST /local/memory/write`
|
|
3217
|
+
handler reverts to `slot.store.write(...)` + outbox `memory.page.upsert`
|
|
3218
|
+
enqueue (the page then syncs to cloud, materializes, and is anchored to INDEX
|
|
3219
|
+
by the existing cloud path). It no longer wraps the content into a synthetic
|
|
3220
|
+
turn and routes it through any cloud-extract lane — that prior design is废'd.
|
|
3221
|
+
`memory_write` = the agent persisting a PKF page it ALREADY authored in its own
|
|
3222
|
+
runtime (主动 explicit, or Hermes `background_review` 自动).
|
|
3223
|
+
- The daemon `extract-turn` / `extract-compress` routes (which forwarded turns to
|
|
3224
|
+
cloud `/api/im/memory/extract`) are RETIRED — they now short-circuit to an
|
|
3225
|
+
inert no-op and forward NOTHING to any cloud LLM. The `runExtract` /
|
|
3226
|
+
`runExtractInBackground` / `handleExtractTurn` / `handleExtractCompress`
|
|
3227
|
+
helpers (and the `extractFromTurn` import) were removed from `rpc.ts`.
|
|
3228
|
+
- The Python Hermes provider: `memory_write` reverts to "write the PKF page you
|
|
3229
|
+
authored" (direct write; `path`/`content` are the literal page, not extraction
|
|
3230
|
+
hints). `sync_turn` / `on_pre_compress` are now NO-OPs (they no longer forward
|
|
3231
|
+
to the retired cloud-extract routes); automatic auto-extraction is Hermes's
|
|
3232
|
+
native `background_review` in-runtime. The unused `_http_post_async` helper was
|
|
3233
|
+
removed.
|
|
3234
|
+
- (cloud, src/im) `extractMemories` (the cloud LLM extractor) + its
|
|
3235
|
+
`buildWikiRecallContext` structured-recall helper are retired: `memory-extract.ts`
|
|
3236
|
+
is now an inert tombstone that throws, and `POST /api/im/memory/extract` returns
|
|
3237
|
+
410 Gone. No cloud LLM call remains for memory.
|
|
3238
|
+
|
|
3239
|
+
### Changed — auto-extraction is now non-blocking + index-TOC injection is structure-preserving (memory203 scale prep)
|
|
3240
|
+
|
|
3241
|
+
- **Non-blocking extract** — `sync_turn` (provider shell) now dispatches the
|
|
3242
|
+
`extract-turn` POST on a daemon thread (`_http_post_async`) and returns
|
|
3243
|
+
instantly, so an agent reading many docs never stalls the turn loop on the
|
|
3244
|
+
cloud LLM round-trip. The daemon `extract-turn` handler ACKs `202 {queued}`
|
|
3245
|
+
immediately and runs `extractFromTurn` in a detached promise (errors logged,
|
|
3246
|
+
never lost). In-process callers (`hook-server`) keep the awaitable
|
|
3247
|
+
`{pages,extracted,error}` contract — fire-and-forget is the HTTP-path default
|
|
3248
|
+
only. New `test/extract-nonblocking.test.ts`.
|
|
3249
|
+
- **Index-TOC scaling** — `index-toc.ts` no longer drops the tail when the
|
|
3250
|
+
injected workspace index exceeds the (hard, Hermes-2200-capped) ≤1,800-char
|
|
3251
|
+
managed-section budget. It now truncates DEPTH-FIRST — every top-level hub
|
|
3252
|
+
heading (the navigational spine) survives; only the deepest leaf detail is
|
|
3253
|
+
trimmed — and appends an observable marker (`… N more sections — load the
|
|
3254
|
+
index page`) so the agent knows to drill in rather than assume it saw
|
|
3255
|
+
everything. Stays a pure function.
|
|
3256
|
+
|
|
3257
|
+
### Added — multi-device remote-conflict status surfaced to the daemon (memory203 doc 07)
|
|
3258
|
+
|
|
3259
|
+
- The distributed reconcile core (invalidate fan-out + LWW/DAG conflict
|
|
3260
|
+
resolution + idempotency) already worked (7/7 e2e), but a daemon never learned
|
|
3261
|
+
its write LOST a conflict — the `remote-conflict` status lived only in cloud
|
|
3262
|
+
metadata. Now it travels cloud→daemon two ways: (A) inlined in the sync-inbox
|
|
3263
|
+
ACK (`SyncInboxResult.conflicts[]`) so an online loser learns immediately, and
|
|
3264
|
+
(B) on down-sync re-pull, the cloud page summary/detail carries
|
|
3265
|
+
`syncStatus:'remote-conflict'` (computed in `enrichSummaries` from the live
|
|
3266
|
+
curation-conflict version pointer) and `cloud-sync.materialisePage` stamps the
|
|
3267
|
+
local row via the new `MemoryStore.setSyncStatus`.
|
|
3268
|
+
- New `GET /local/memory/conflicts` (peek, not resolve) lists local
|
|
3269
|
+
remote-conflict pages to the host, behind the same F5 boundary predicate.
|
|
3270
|
+
- Tests: cloud `conflict-status.test.ts` 4/4 + daemon `memory-conflict-status.test.ts`
|
|
3271
|
+
3/3, both with negative controls; existing `memory-e2e-sync.test.ts` stays 7/7.
|
|
3272
|
+
Offline catch-up cursor (C7) remains the pre-existing deferred limitation.
|
|
3273
|
+
|
|
3274
|
+
### Added — at-rest memory encryption ACTIVATION behind flag (memory203 M-ENC, default OFF)
|
|
3275
|
+
|
|
3276
|
+
- The AES-256-GCM primitives (`crypto-cipher.ts`, `key-manager.ts`), the outbox
|
|
3277
|
+
encrypt path, and the cloud decrypt/FTS-exclude were already present but never
|
|
3278
|
+
fired — no caller marked a page `encrypted=true`. Activated the daemon write
|
|
3279
|
+
side: `MemoryStore` gains an `encryptionPolicy?` seam; `runner-wiring` installs
|
|
3280
|
+
a policy that returns true only when `isEncryptionEnabled()`
|
|
3281
|
+
(`FF_MEMORY_ENCRYPTION_ENABLED==='true'`, dynamic, default OFF) AND storage is
|
|
3282
|
+
not ephemeral AND a durable workspace key exists. `store.write` consults it
|
|
3283
|
+
only when the caller didn't set `encrypted` explicitly (down-sync's explicit
|
|
3284
|
+
per-page flag is never overridden). Fail-closed: flag off / ephemeral / no key
|
|
3285
|
+
→ plaintext, never keyless ciphertext.
|
|
3286
|
+
- New `test/memory-encryption-roundtrip.test.ts` (6 tests): encrypt→outbound
|
|
3287
|
+
ciphertext→down-sync decrypt round-trip + 2 negative controls. Cross-device key
|
|
3288
|
+
sharing stays out of scope (doc 06 deferral).
|
|
3289
|
+
|
|
3290
|
+
### Fixed — auto-extract error surfacing + read-after-write + PKF round-trip (memory203 Wave 1)
|
|
3291
|
+
|
|
3292
|
+
- **`daemon/memory/extract.ts#extractFromTurn`** — no longer returns a bare
|
|
3293
|
+
`ExtractedPage[]` (which collapsed LLM timeout / bad-response into a silent
|
|
3294
|
+
`extracted:0`). Now returns `{ pages, extracted, error }`: `error` is set on a
|
|
3295
|
+
cloud/LLM transport failure OR when the cloud produced candidates that were all
|
|
3296
|
+
rejected by the PKF round-trip gate (`saved=0 && skipped>0`), so a degraded
|
|
3297
|
+
extract is observable instead of masquerading as "nothing to save". Adds a
|
|
3298
|
+
bounded 1-retry on transient (status 0 / 5xx / network) failures with warn logs.
|
|
3299
|
+
- **read-after-write** — after mirroring an extracted page into the local
|
|
3300
|
+
`ScopedMemoryStore`, `confirmMirror()` re-reads it via the same bucket so the
|
|
3301
|
+
next turn's recall is guaranteed to see it; a miss logs loudly (best-effort, the
|
|
3302
|
+
cloud holds the canonical row).
|
|
3303
|
+
- **`rpc.ts` / `hook-server.ts`** — both extract consumers propagate the new
|
|
3304
|
+
`error` field instead of dropping it.
|
|
3305
|
+
- New `test/memory-extract-turn.test.ts` (9 tests incl. negative controls: all-
|
|
3306
|
+
rejected candidates → error surfaced, 5xx → retried, malformed PKF → not
|
|
3307
|
+
persisted) + cloud-side `assertPkfRoundTrips` gate in `memory-extract.ts`.
|
|
3308
|
+
|
|
3309
|
+
### Changed — memory203 agent-integration convergence onto the native MemoryProvider (doc 10 §3)
|
|
3310
|
+
|
|
3311
|
+
- **`adapters/persistence/hermes/index.ts#configurePrismerProvider`** — when the
|
|
3312
|
+
Prismer MemoryProvider is active (`installMemoryProvider` / `PRISMER_MEMORY_PROVIDER=1`)
|
|
3313
|
+
it now SUPERSEDES the two fragmented paths: the curl `installMemoryHooks` block is
|
|
3314
|
+
skipped (the provider's in-process `sync_turn`/`prefetch` are the native equivalent —
|
|
3315
|
+
running both double-extracts the same turn) and the standalone recall-tools plugin is
|
|
3316
|
+
NOT installed (the provider's `get_tool_schemas` already exposes `memory_search`/
|
|
3317
|
+
`memory_load`/`memory_curate`). Both paths remain intact as the **no-provider
|
|
3318
|
+
fallback** (eval opt-out via `installMemoryHooks: false` preserved). `configurePrismerProvider`
|
|
3319
|
+
+ `HermesProfileConfigSchema` are now exported for the config-gen unit test.
|
|
3320
|
+
- **Port fix** — the daemon-port default across the hermes config-gen + the Python
|
|
3321
|
+
provider shell (`plugins/memory/prismer/__init__.py`) moved from the stale `3210`
|
|
3322
|
+
to `7878` (the daemon's real loopback port in agent-rt; `PRISMER_DAEMON_PORT` env
|
|
3323
|
+
still wins). The provider's `_http_get` + the `prismer memory` CLI both read
|
|
3324
|
+
`PRISMER_DAEMON_PORT`, which the config-gen writes into the profile `.env`.
|
|
3325
|
+
- **`test/memory-provider-configgen.test.ts`** — new: asserts provider-on pins
|
|
3326
|
+
`memory.provider: prismer`, writes `PRISMER_DAEMON_PORT` to `.env`, installs neither
|
|
3327
|
+
hooks nor double recall-tools; provider-off fallback still wires the curl hooks (7878).
|
|
3328
|
+
- **Provider-install path fix (`installMemoryProviderShell`)** — the shell was copied
|
|
3329
|
+
to `<profile>/plugins/memory/prismer/`, but Hermes's MEMORY-provider scanner
|
|
3330
|
+
(`plugins/memory/__init__.py` `_iter_provider_dirs`) discovers USER-installed
|
|
3331
|
+
providers ONE level deep at `<HERMES_HOME>/plugins/<name>/` — the extra `memory/`
|
|
3332
|
+
segment exists only for BUNDLED providers. So `find_provider_dir("prismer")` returned
|
|
3333
|
+
`None` and the provider was NEVER loaded: the agent saw no `memory_search` tool and
|
|
3334
|
+
reported "memory not available" despite correct config + a working daemon RPC. Fixed
|
|
3335
|
+
to install at `<profile>/plugins/prismer/`. Verified live in agent-rt: relocation →
|
|
3336
|
+
provider loads (`3 tools`) → agent recalls the seeded Helios corpus (18,400 ev/s)
|
|
3337
|
+
end-to-end. The two install/config-gen tests asserted the broken nested path (green
|
|
3338
|
+
while encoding the bug); both updated to the discoverable layout + a guard that the
|
|
3339
|
+
nested path is absent.
|
|
3340
|
+
|
|
3341
|
+
### Fixed — daemon FTS recall is AND-first with OR-fallback (not AND-only)
|
|
3342
|
+
|
|
3343
|
+
- **`search.ts#hybrid`** — the local FTS MATCH joined every term with implicit
|
|
3344
|
+
AND, so a natural multi-word recall ("helios throughput target") only hit pages
|
|
3345
|
+
containing ALL terms; a padded query silently returned zero. Now runs AND first
|
|
3346
|
+
(precision), then relaxes to OR when AND yields nothing and the query has ≥2
|
|
3347
|
+
terms (recall gate). BM25 still ranks pages matching more terms higher.
|
|
3348
|
+
|
|
3349
|
+
### Fixed — cloud→daemon subset projection preserved visibility + canonical id (live MVP3/MVP4)
|
|
3350
|
+
|
|
3351
|
+
- **ACL leak (`cloud-sync.ts#materialisePage`)** — the visibility projection was
|
|
3352
|
+
`page.visibility === 'agent'`, but the cloud stores an owner-PREFIXED string
|
|
3353
|
+
(`agent:<imUserId>`). The exact match never hit, so EVERY agent-private page
|
|
3354
|
+
was stored `{kind:'workspace'}` and recallable by every in-workspace agent (a
|
|
3355
|
+
second agent surfaced another agent's private cost page in the live MVP3 run).
|
|
3356
|
+
New `parseCloudVisibility()` maps `workspace` / `agent:<id>` / fail-closed
|
|
3357
|
+
`private:<id>` (human:/task:/unknown owner kinds), preserving the owner id the
|
|
3358
|
+
boundary predicate (`canCapReadPage`) gates on.
|
|
3359
|
+
- **Curate 404 (`store.ts#write` + `MemoryWriteInput.id`)** — the subset minted a
|
|
3360
|
+
local `page_<uuid>` id instead of the cloud's canonical id, so `memory_curate`
|
|
3361
|
+
promote-to-hub/supersede forwarded an id the cloud didn't know → 404 (the agent
|
|
3362
|
+
then faked success). `materialisePage` now passes `page.id`; the subset mirrors
|
|
3363
|
+
the superset's id. Daemon-authored writes still mint a local id; path conflicts
|
|
3364
|
+
retain the existing id (content/fts/links FK-safe).
|
|
3365
|
+
- **`test/memory-cloud-sync.test.ts`** — extended with agent:/human: pages
|
|
3366
|
+
asserting visibility kind+owner AND the preserved cloud id (the corpus only
|
|
3367
|
+
exercised `visibility:'workspace'`, which is why both bugs hid).
|
|
3368
|
+
|
|
3369
|
+
### Added — memory203 local-first load fallback + invalidate re-pull + F5 search收口 (doc 07 §3/§4, doc 08 §4a)
|
|
3370
|
+
|
|
3371
|
+
- **`daemon/memory/cloud-sync.ts#fetchPageFromCloudByPath`** — targeted single-page
|
|
3372
|
+
回源 for the local-first `load` fallback: a subset MISS pulls just that page from
|
|
3373
|
+
the cloud superset (`GET /api/im/memory/resolve?uri=`), materialises it via the
|
|
3374
|
+
existing `materialisePage` (identical decrypt + visibility + write), and the handler
|
|
3375
|
+
re-loads it locally (<5ms FTS). Strictly best-effort + local-first: offline → the
|
|
3376
|
+
fetch returns false → genuine 404, never a 5xx ("断云仍 load 本地").
|
|
3377
|
+
- **`daemon/memory/rpc.ts`** — `handleLoad` is now async; `attachMemoryRpc` gained a
|
|
3378
|
+
`keyManager` option (runner wires `memoryWiring.keyManager`) so回源'd encrypted pages
|
|
3379
|
+
decrypt to local plaintext (fail-closed to sentinel when keyless).
|
|
3380
|
+
- **`daemon/memory/ws-invalidate.ts`** — a non-`soft_delete` cloud invalidate
|
|
3381
|
+
(visibility_changed / promoted / archive) now fires a best-effort
|
|
3382
|
+
`initialSyncFromCloud` to re-pull the fresh SUBSET projection (watermark-bounded,
|
|
3383
|
+
subset fields only — never the full aclJson). `soft_delete` stays mark-only.
|
|
3384
|
+
- **`daemon/memory/rpc.ts#handleSearch`** — F5 search收口 (option a): when a cap is
|
|
3385
|
+
present, search hits are filtered through `canCapReadPage` (bounded `loadById` point
|
|
3386
|
+
lookup per hit, topK ≤ 20) so another agent's private-page snippet never leaks via
|
|
3387
|
+
search — F5 is now uniform across load / list / search.
|
|
3388
|
+
- **Tests** — `memory-load-fallback` (4), `memory-ws-invalidate` (+1 re-pull),
|
|
3389
|
+
`memory-acl-predicate` (+1 search filter). True daemon↔cloud contract проven by
|
|
3390
|
+
`scripts/cookbook/e2e-memory-sync.ts` (real outbox envelope → real `ingestSyncInbox`
|
|
3391
|
+
→ materialises `IMMemoryPageSection` + `IMMemorySyncEvent`; flushed:1 deadLettered:0).
|
|
3392
|
+
|
|
3393
|
+
### Added — P0 memory security spine: per-agent scoped capability tokens (memory203 doc 08, F1–F5)
|
|
3394
|
+
|
|
3395
|
+
Closes the 🔴 daemon memory空腔 (doc 06 §2): `/local/memory/*` RPC + key access had
|
|
3396
|
+
no credential / workspace scope — any same-pod agent could pass another `workspaceId`
|
|
3397
|
+
to read/write/decrypt across workspaces. The fix is mechanism **C3**: the daemon
|
|
3398
|
+
self-signs + self-verifies a per-agent **capability** with a per-boot key.
|
|
3399
|
+
|
|
3400
|
+
- **`daemon/memory/cap.ts`** (new) — `mintCap(sub, ws)` / `mintSystemCap()` /
|
|
3401
|
+
`systemCap()` / `verifyCap()` / `capAllowsWorkspace()` / `isSystemCap()`. Token =
|
|
3402
|
+
`v1.<b64url(payload)>.<HMAC>`; payload = `{aud:'memory', sub, ws, scope:['ws:'+ws],
|
|
3403
|
+
iat, exp}`. The per-boot key is `randomBytes(32)` held ONLY in module memory — never
|
|
3404
|
+
written, serialized, returned, or injected (daemon restart → new key → all prior
|
|
3405
|
+
caps reject). Agent caps are single-workspace; `ws:*` is hard-rejected for agents and
|
|
3406
|
+
reserved for the daemon-internal system cap.
|
|
3407
|
+
- **`daemon/memory/rpc.ts`** — every handler now passes a verified cap and gates the
|
|
3408
|
+
effective workspaceId (query / body / `?uri=`) via `scopeOk`/`effectiveWs`: cross-ws
|
|
3409
|
+
→ 403 `memory_ws_scope_violation`. `PRISMER_MEMORY_CAP_ENFORCE` (default OFF one
|
|
3410
|
+
release cycle): no/invalid cap → 401 `memory_cap_invalid` when on, warn+proceed when
|
|
3411
|
+
off — but a present cap is ALWAYS scope-checked. Out-of-process provider routes now
|
|
3412
|
+
stamp `actorImUserId` from `cap.sub` instead of `''`.
|
|
3413
|
+
- **`daemon/memory/key-manager.ts`** — `getKey(workspaceId, cap)` gates key access via
|
|
3414
|
+
`capAllowsWorkspace` and returns null WITHOUT touching the filesystem when the cap is
|
|
3415
|
+
not authorized (fail-closed; the AES key is never the cap). `getKeyOrNull` is now the
|
|
3416
|
+
internal raw loader. Daemon-internal flush/write-down callers
|
|
3417
|
+
(`outbox-worker.ts`, `cloud-sync.ts`) pass `systemCap()`.
|
|
3418
|
+
- **`daemon/memory/acl-predicate.ts`** (new) — `canCapReadPage(cap, page)`: the daemon's
|
|
3419
|
+
BOUNDARY projection of the shared ACL predicate (ws scope + page visibility kind),
|
|
3420
|
+
wired into `load` (cross-agent private → 404, no existence leak) and `list` (filter).
|
|
3421
|
+
The cloud keeps the FULL projection (`memory-acl.ts` aclJson) — two layers, no drift.
|
|
3422
|
+
- **Injection chain (F4)** — `adapters/prismer-env.ts` mints `PRISMER_MEMORY_CAP` from
|
|
3423
|
+
the dispatch metadata (covers claude-code/codex/provider-proxy via
|
|
3424
|
+
`applyPrismerScopeEnv`); the hermes gateway spawn injects it per-boot;
|
|
3425
|
+
`adapters/memory-tools.ts` and the two python shells
|
|
3426
|
+
(`plugins/{memory/prismer,tools/prismer-recall}/__init__.py`) forward it as the
|
|
3427
|
+
`x-prismer-memory-cap` header (env-default, zero extra wiring for in-process tools).
|
|
3428
|
+
- **Tests** — `memory-cap` (15), `memory-cap-rpc` (10), `memory-cap-injection` (5),
|
|
3429
|
+
`memory-acl-predicate` (6), `memory-encryption` (+2 key-gate). 274 memory tests green.
|
|
3430
|
+
|
|
3431
|
+
### Changed — `cloud task create` delegation is now a tracked board card (release203/21 §4 T1)
|
|
3432
|
+
|
|
3433
|
+
- **`cli/commands/task.ts`** — `cloud task create --agent <id>` now defaults to a
|
|
3434
|
+
**delegated work_item** (`metadata.kind='work_item'` + `dispatchPolicy='on-assign'`)
|
|
3435
|
+
instead of `kind='agent_run'`. The task therefore lands on the Kanban board (留痕/
|
|
3436
|
+
可观测) **and** auto-dispatches the moment the assignee is set — decoupling "shows
|
|
3437
|
+
on the board" (kind) from "auto-dispatches" (dispatchPolicy). Result write-back +
|
|
3438
|
+
session-anchor binding are unchanged (the cloud settles the board card itself via
|
|
3439
|
+
the IMTask fallback path).
|
|
3440
|
+
- **New `--no-card` flag** preserves the prior pure no-card run channel
|
|
3441
|
+
(`kind='agent_run'`) for internal/orchestrator sub-steps that should NOT surface
|
|
3442
|
+
as a board card.
|
|
3443
|
+
|
|
3444
|
+
### Added — `agent.fs.read` / `agent.fs.write` repo data-plane RPCs (release203/21 §6 R, C3)
|
|
3445
|
+
|
|
3446
|
+
- **`daemon/fs-read.ts`** (`readReposFile`) + **`daemon/fs-write.ts`** (`writeReposFile`)
|
|
3447
|
+
— new reverse-channel RPCs mirroring the existing `agent.fs.list`: same
|
|
3448
|
+
`resolveProjectReposDir` base + path-jail (no `../` escape out of the workspace
|
|
3449
|
+
root). `agent.fs.read` returns `{ content, encoding:'utf8'|'base64', sizeBytes,
|
|
3450
|
+
mtimeMs, sha256, mime? }`, rejects >1 MiB (`too_large`), base64s binary.
|
|
3451
|
+
`agent.fs.write` is atomic (temp+rename, `mkdir -p` parents), honours optional
|
|
3452
|
+
`ifMatchSha256` (mismatch → `conflict`, no overwrite).
|
|
3453
|
+
- **`daemon/runner.ts`** — wired `agent.fs.read` / `agent.fs.write` cases
|
|
3454
|
+
(`onAgentFsRead` / `onAgentFsWrite`, mirroring `onAgentFsList`); replies on
|
|
3455
|
+
`agent.fs.read.reply` / `agent.fs.write.reply`. Old daemons lacking these cases
|
|
3456
|
+
fall through to `unknown-message` → cloud invoke times out → frontend degrades to
|
|
3457
|
+
read-only (no version negotiation needed).
|
|
3458
|
+
- Cloud side: `POST /api/im/workspaces/:wid/fs/{read,write}` + `RepoCodePanel` /
|
|
3459
|
+
`/repo-code-lab` Monaco harness (see cloud repo). Live-verified: write→read exact
|
|
3460
|
+
roundtrip + `ifMatchSha256` conflict guard.
|
|
3461
|
+
|
|
3462
|
+
### Added — INDEX dynamic core-inject (memory202 doc 05 §4.2a, flag-gated OFF)
|
|
3463
|
+
|
|
3464
|
+
- **`daemon/memory/index-toc.ts`** — `buildIndexToc(indexMarkdown, budget)`: a
|
|
3465
|
+
PURE, section-aware, bounded TOC builder over the workspace INDEX page (the
|
|
3466
|
+
curated memory MAP). Within budget → the whole map prefixed with a short
|
|
3467
|
+
`# Memory Map` header; over budget → drops to the SKELETON of headings (the map
|
|
3468
|
+
shape) cut at heading boundaries, NEVER mid-section. Reuses the recall path's
|
|
3469
|
+
fence-aware ATX scanner (`section.ts#scanHeadingsForToc`) so "what is a section"
|
|
3470
|
+
is identical across slice + TOC. Empty / whitespace / budget≤0 → `''` (no throw).
|
|
3471
|
+
- **`daemon/memory/index-toc-inject.ts`** — flag-gated wiring that reads the local
|
|
3472
|
+
INDEX page → `buildIndexToc` → the EXISTING `coreInject` carrier
|
|
3473
|
+
(`createHermesMemoryIntegration(...).coreInject` → MEMORY.md managed section,
|
|
3474
|
+
already tracked by `recall-stats.recordCoreInject`). So an agent always has the
|
|
3475
|
+
current memory map in context. Gated on **`FF_MEMORY_INDEX_INJECT_ENABLED`**
|
|
3476
|
+
(default OFF → zero behaviour change, no `coreInject` call).
|
|
3477
|
+
- **`MemoryStore.loadIndexPageContent()`** — reads the workspace's
|
|
3478
|
+
`pageType='index'` page content (queried by the raw `'index'` string the cloud
|
|
3479
|
+
emits and `materialisePage` stores verbatim).
|
|
3480
|
+
- **Daemon trigger:** wired into `syncMemoryFromCloud` (runs on initial sync +
|
|
3481
|
+
post `host.acked`) — "dynamic" = re-injects whenever the synced map changes. The
|
|
3482
|
+
runner supplies the per-hermes-profile MEMORY.md carrier path(s); the wiring
|
|
3483
|
+
stops at "build TOC → call existing coreInject" and does NOT touch adapter-turn /
|
|
3484
|
+
envelope orchestration (that live-activation path stays held back).
|
|
3485
|
+
- **Deferred (follow-up):** a "memory map injected" UI indicator (dim 1) and the
|
|
3486
|
+
default-on activation (a separate convergence gate, like envelope).
|
|
3487
|
+
|
|
3488
|
+
### Added — Memory at-rest encryption (memory202 doc 06, MVP, flag-gated OFF)
|
|
3489
|
+
|
|
3490
|
+
- **Local-first AES-256-GCM encryption for memory pages.** The daemon holds a
|
|
3491
|
+
per-workspace symmetric key; the cloud stores only ciphertext and NEVER holds
|
|
3492
|
+
the key. Encrypted pages are excluded from cloud full-text search (cloud can't
|
|
3493
|
+
read them) — searchable only on the daemon (local plaintext). Non-encrypted
|
|
3494
|
+
pages are completely unaffected. Gated on `FF_MEMORY_ENCRYPTION_ENABLED`
|
|
3495
|
+
(default OFF → zero behavior change).
|
|
3496
|
+
- **`daemon/memory/crypto-cipher.ts`** — `encrypt`/`decrypt` over
|
|
3497
|
+
`crypto.createCipheriv('aes-256-gcm', …)` with a random 12-byte IV per call;
|
|
3498
|
+
self-describing packed format `v1:<iv>:<tag>:<ct>` (base64url). Authenticated:
|
|
3499
|
+
a GCM-tag/ciphertext tamper or wrong key throws on decrypt (never leaks
|
|
3500
|
+
plaintext).
|
|
3501
|
+
- **`daemon/memory/key-manager.ts`** — per-workspace 32-byte key generated on
|
|
3502
|
+
first need, persisted `<baseDir>/<workspaceId>/.memkey` (0600), reloaded on
|
|
3503
|
+
demand. The key never leaves the daemon. **Fail-closed:**
|
|
3504
|
+
`PRISMER_EPHEMERAL_STORAGE=true` (agent-rt emptyDir) → refuses to encrypt
|
|
3505
|
+
(returns null; caller writes plaintext with a loud warn) so a key written to
|
|
3506
|
+
ephemeral storage can never be lost-then-orphan-the-ciphertext. A persist /
|
|
3507
|
+
round-trip-verify failure likewise fails closed.
|
|
3508
|
+
- **`MemoryStore.write({ encrypted })`** threads the flag to the local
|
|
3509
|
+
`memory_pages.encrypted` column (was hardcoded 0); local SQLite + FTS keep
|
|
3510
|
+
PLAINTEXT (recall needs it) — encryption happens only on the cloud-bound
|
|
3511
|
+
outbox payload.
|
|
3512
|
+
- **Outbox flush encryption** (`outbox-worker.ts`): a `memory.page.upsert` whose
|
|
3513
|
+
page row is `encrypted=true` has its `payload.content` encrypted in the POST
|
|
3514
|
+
body only. A page that should be encrypted but cannot be (no key) is held back
|
|
3515
|
+
(left pending), NEVER POSTed in the clear.
|
|
3516
|
+
- **Sync-down decrypt** (`cloud-sync.ts`): ciphertext pulled from the cloud is
|
|
3517
|
+
decrypted with the local key before landing as plaintext in local FTS. **Cross-
|
|
3518
|
+
device deferral:** key absent / decrypt fails → stores an unreadable sentinel
|
|
3519
|
+
(never ciphertext-as-content), sync never crashes.
|
|
3520
|
+
- **Deferred (per doc 06):** agent-rt ephemeral-pod encrypted memory (fail-closed
|
|
3521
|
+
for now), cross-device key exchange, key rotation / re-encryption.
|
|
3522
|
+
|
|
3523
|
+
### Added — Hermes per-dispatch identifier auto-resolution (release203/15c WS-E3)
|
|
3524
|
+
|
|
3525
|
+
- **`cloud deliver` / `cloud task attach` / `cloud file send` / `cloud attach`
|
|
3526
|
+
now work on Hermes agents WITHOUT manually copying `--run-id` /
|
|
3527
|
+
`--conversation-id`.** Hermes (persistence) agents have no per-dispatch env
|
|
3528
|
+
(the gateway spawns once with a frozen env; per-dispatch ids only reach the
|
|
3529
|
+
model via `<execution_context>` XML, never the tool-shell). Previously the
|
|
3530
|
+
agent had to abstract the ids out of `<execution_context>` and pass them as
|
|
3531
|
+
flags or the daemon proxy returned "taskId required".
|
|
3532
|
+
- **`RunSessionRegistry.lookupActiveByAgent(agentImUserId, { adapterName?,
|
|
3533
|
+
conversationId?, ttlMs? })`** (`daemon/memory/run-session-map.ts`) — reverse-
|
|
3534
|
+
lookup the agent's CURRENT in-flight dispatch from `local_run_sessions`
|
|
3535
|
+
(reusing the existing `(conversation_id, agent_im_user_id, adapter_name)`
|
|
3536
|
+
index; NO new table/migration). Returns `null` (0 active), the ctx (exactly
|
|
3537
|
+
1), or `{ ambiguous, candidates }` (>1 with no narrowing key). 15min in-flight
|
|
3538
|
+
TTL window.
|
|
3539
|
+
- **`daemon/asset/deliver.ts`** `validate()` relaxed: `taskId` may be empty when
|
|
3540
|
+
`resolveActiveDispatch:true` + `agentImUserId` present; the handler then
|
|
3541
|
+
reverse-looks-up to fill taskId(=runId)/conversationId. Race-safety (doc §5):
|
|
3542
|
+
conversation-narrowed lookups are unambiguous; **>1 in-flight run with no
|
|
3543
|
+
narrowing → HTTP 409, NEVER a silent mis-attach**; miss → 400 "未找到活跃
|
|
3544
|
+
dispatch,请传 --run-id". `DeliverRequest` gains `agentImUserId?` /
|
|
3545
|
+
`resolveActiveDispatch?`; `DeliverHandlerResult` status union gains `409`.
|
|
3546
|
+
- **Hermes `terminal.env_passthrough` config (`adapters/persistence/hermes/
|
|
3547
|
+
index.ts`)** — registers `PRISMER_AGENT_USERNAME` / `PRISMER_AGENT_IM_USER_ID`
|
|
3548
|
+
/ `PRISMER_DAEMON_PORT` / `PRISMER_WORKSPACE_ID` as passthrough so they survive
|
|
3549
|
+
Hermes' `_scrub_child_env` (allowlist scrub: only `_SAFE_ENV_PREFIXES` +
|
|
3550
|
+
registered passthrough reach the tool subprocess). **This corrects the design
|
|
3551
|
+
doc's premise** that the identity vars were already present in the tool-shell
|
|
3552
|
+
env: they are present in the GATEWAY env but were silently scrubbed from the
|
|
3553
|
+
CHILD env, which would otherwise leave `cloud deliver` with neither a dispatch
|
|
3554
|
+
id NOR an agent identity to auto-resolve from. These vars are identity/
|
|
3555
|
+
transport (not credentials, no KEY/TOKEN substring → not GHSA-blocklisted).
|
|
3556
|
+
- Live cookbook `scripts/cookbook/regress-hermes-deliver-autoresolve.ts`: a real
|
|
3557
|
+
hermes agent runs `cloud deliver` with no flags → daemon log shows
|
|
3558
|
+
`[deliver] active-dispatch auto-resolve … runId=<this dispatch>` + the file
|
|
3559
|
+
lands as an asset; the >1-run ambiguity → 409 path is verified against the real
|
|
3560
|
+
migrated local SQLite schema.
|
|
3561
|
+
|
|
3562
|
+
### Added
|
|
3563
|
+
|
|
3564
|
+
- **Daemon-side Dream scheduler wired into boot** (memory202 doc 05). The
|
|
3565
|
+
`DreamScheduler` existed but was never instantiated (`new DreamScheduler`
|
|
3566
|
+
appeared only in tests) — the memory lifecycle was dead code. `attachMemoryRunner`
|
|
3567
|
+
now instantiates + `start()`s it behind `FF_MEMORY_DREAM_ENABLED` (default OFF,
|
|
3568
|
+
mirrors `FF_MEMORY_HTML_SIDECAR_ENABLED`), injecting a `CloudDreamRunner` that
|
|
3569
|
+
POSTs `/api/im/memory/page-dream` (fixed from a stale `/memory/page-dream` that
|
|
3570
|
+
would 404). Workspace tracking is fed via a new `MemoryOutboxWorker.onWorkspaceFlushed`
|
|
3571
|
+
hook → `scheduler.recordSessionEnd`. `recordActivity` is intentionally NOT fed
|
|
3572
|
+
from outbox flush (it drives the idle gate; feeding background flushes would
|
|
3573
|
+
perma-block dream) — the live-activity idle gate stays inert until adapter glue
|
|
3574
|
+
feeds `recordActivity` at LLM-call / tool-dispatch points (follow-up).
|
|
3575
|
+
`MemoryRunnerWiring.stop()` stops the scheduler. Flag OFF = zero behaviour change.
|
|
3576
|
+
|
|
3577
|
+
### Changed
|
|
3578
|
+
|
|
3579
|
+
- **`workspace member` list/add/update/remove logic extracted to a shared
|
|
3580
|
+
command-builder** (release203/15b WS-E4 §7.3 "B" rollout). The member-management
|
|
3581
|
+
verbs — identical endpoints (`/api/im/workspaces/:id/members`), identical role
|
|
3582
|
+
contract (`admin|member`; `owner` rejected as a v2.1+ RFC), identical
|
|
3583
|
+
remove-cascade — were duplicated verbatim against `cloud workspace member`. They
|
|
3584
|
+
now live once in `src/cli/shared/workspace-member-builder.ts`
|
|
3585
|
+
(`buildWorkspaceMemberCommand(adapter)`, parameterized by a
|
|
3586
|
+
`WorkspaceMemberAdapter` seam: `list/add/update/remove` + `emitList/emitMember/
|
|
3587
|
+
emitRemove` + `fail`). `prismer` injects a `CloudClient` + `ui.table`/`ui.line`
|
|
3588
|
+
adapter; `cloud` injects a `PrismerClient.workspaces.members` + padded-text
|
|
3589
|
+
adapter. Same no-dependency-edge / tsup-inline property as the `task wait` PoC
|
|
3590
|
+
(verified empirically: builder body inlined into `dist/cli.js`, no runtime
|
|
3591
|
+
`runtime/src` require). Behaviour preserved: success-path output and per-verb
|
|
3592
|
+
error codes byte-identical; the `--role` validation message is now the single
|
|
3593
|
+
canonical one (was already cloud's; prismer's variant retired). The `add` verb
|
|
3594
|
+
accepts BOTH the prismer positional (`<imUserId>`) and the cloud `--user` flag,
|
|
3595
|
+
so neither binary's existing invocation breaks. Gates:
|
|
3596
|
+
`regress-cloud-task-wait.ts` 4/4 + `regress-skill-cli-contract.ts` no new drift
|
|
3597
|
+
(workspace subcommands resolve via the followed builder import). The other
|
|
3598
|
+
candidate namespaces (`agent`/`memory`/`asset`) were audited and NOT extracted —
|
|
3599
|
+
their cross-CLI "overlap" is name-only over divergent transports/outputs, so a
|
|
3600
|
+
shared builder would change one side's UX (see `docs/release203/15b-*` §5).
|
|
3601
|
+
|
|
3602
|
+
- **`task wait` poll/settle logic extracted to a shared command-builder**
|
|
3603
|
+
(release203/15 WS-E4 §7.3 "B", PoC). The settle loop that both `prismer task
|
|
3604
|
+
wait` and `cloud task wait` (in `@prismer/sdk`) carried verbatim now lives once
|
|
3605
|
+
in `src/cli/shared/task-wait-builder.ts` — a transport/output-agnostic
|
|
3606
|
+
`buildTaskWaitCommand(adapter)` parameterized by an injected `TaskWaitAdapter`
|
|
3607
|
+
(`getTask`/`emit`/`fail`). `prismer task wait` supplies a `CloudClient` + JSON
|
|
3608
|
+
adapter; `cloud task wait` supplies a `PrismerClient.im` + text adapter. The
|
|
3609
|
+
two packages have no dependency edge — `@prismer/sdk` imports this module via a
|
|
3610
|
+
build-time relative path and `tsup` inlines it, so both tarballs stay
|
|
3611
|
+
standalone. Behaviour is byte-for-byte unchanged (settle on
|
|
3612
|
+
review/completed/failed/cancelled by default, `--terminal-only`,
|
|
3613
|
+
failed/cancelled → non-zero exit). Rollout for the remaining overlapping
|
|
3614
|
+
namespaces: `docs/release203/15b-shared-builder-plan.md`. Gates:
|
|
3615
|
+
`regress-cloud-task-wait.ts` 4/4 + `regress-skill-cli-contract.ts` green.
|
|
3616
|
+
|
|
3617
|
+
### Added
|
|
3618
|
+
|
|
3619
|
+
- **`cloud role create` / `validate` accept a role bundle DIRECTORY**
|
|
3620
|
+
(release203/16 §5.4 / §9 decision 2, P4). A role authoring unit is now either a
|
|
3621
|
+
single `role.json` (legacy, still accepted) OR a directory containing
|
|
3622
|
+
`role.json` + optional `SOUL.md`. When a directory carries a non-empty
|
|
3623
|
+
`SOUL.md`, its raw markdown becomes the role's `operatingPrinciples`
|
|
3624
|
+
(Hermes-compatible persona, slot#1), overriding/filling role.json's field;
|
|
3625
|
+
`AGENTS.md` is NOT part of the bundle (it's a per-role skill concept). Detection
|
|
3626
|
+
is by `statSync().isDirectory()`. New shared pure helpers `readRoleBundle` /
|
|
3627
|
+
`validateRoleBundle` (`src/bundle/index.ts`) back both the CLI and the zero-dep
|
|
3628
|
+
`role-builder/scripts/ingest-role.mjs` offline path. No cloud schema or
|
|
3629
|
+
service-signature change — only the source of `operatingPrinciples` (SOUL.md
|
|
3630
|
+
file vs json field). Tests: `bundle.test.ts` +7 dir-bundle cases,
|
|
3631
|
+
`skill-role-lifecycle-cli.test.ts` +2 CLI cases (49/49 green); runtime tsc clean.
|
|
3632
|
+
- **Daemon honors cloud version-skew directive on `host.acked`** (release203/19
|
|
3633
|
+
§1.2/§2.2/§3, P2). The daemon already reports `daemonVersion` on every
|
|
3634
|
+
`agent.host.declare`; the cloud now compares it against its configured
|
|
3635
|
+
`EXPECTED_DAEMON_VERSION` and, per the `DAEMON_VERSION_SKEW_POLICY` Nacos flag
|
|
3636
|
+
(default `warn` ⇒ observability only), may attach an `upgradeDirective` to
|
|
3637
|
+
`host.acked`. The daemon reacts via `applyUpgradeDirective`:
|
|
3638
|
+
- `warn` / absent — no-op (normal operation; the common case).
|
|
3639
|
+
- `refuse_dispatch` — reject NEW dispatches in `onTaskDispatch` while in-flight
|
|
3640
|
+
runs drain and the daemon stays up.
|
|
3641
|
+
- `drain_respawn` — reject NEW dispatches AND arm a one-shot drain watcher that,
|
|
3642
|
+
once `runningTasks` empties (or a 30-min cap elapses), runs `stop()` and
|
|
3643
|
+
`process.exit(0)` so the device/k8s controller re-pulls a new-image pod.
|
|
3644
|
+
|
|
3645
|
+
Backward compatible: a legacy cloud never sends a directive (field absent ⇒
|
|
3646
|
+
ignored). `HostAckedPayload.upgradeDirective` + `DaemonUpgradeDirective` added
|
|
3647
|
+
to `types/im-events.ts`. Covered by
|
|
3648
|
+
`test/daemon-version-skew-directive.test.ts`.
|
|
3649
|
+
|
|
3650
|
+
### Fixed
|
|
3651
|
+
|
|
3652
|
+
- **Daemon start-lock no longer self-locks on container restart** (release203/19
|
|
3653
|
+
§1.1/§2.1, P0). In a container the daemon runs as **pid 1** and writes
|
|
3654
|
+
`daemon.pid=1`. On a same-pod-sandbox container restart the emptyDir keeps the
|
|
3655
|
+
pidfile, so the new daemon — also pid 1 — saw `pidAlive(1)===true` and
|
|
3656
|
+
crash-looped with `Daemon already running (pid 1)` (Exited(1) loop hit by the
|
|
3657
|
+
doc17 e2e). The start guard now treats a **self-referential pidfile**
|
|
3658
|
+
(`existingPid === process.pid`) as our own stale leftover and proceeds,
|
|
3659
|
+
clearing the stale file before claiming it. New `daemonAlreadyRunning()` helper
|
|
3660
|
+
in `cli/util.ts` centralises the decision (`existingPid && existingPid !== ourPid
|
|
3661
|
+
&& pidAlive(existingPid)`); both `runForeground` and `startBackground` use it.
|
|
3662
|
+
The pidfile is retained for `status`/`logs`/`stop`, but is no longer the sole
|
|
3663
|
+
liveness authority. The crash-safe positive fix for the residual pid-reuse class
|
|
3664
|
+
on non-pid-1 hosts (OS advisory flock) is intentionally deferred — Node has no
|
|
3665
|
+
built-in flock and the runtime carries no `fs-ext`/`proper-lockfile` dep; the
|
|
3666
|
+
self-pid guard fully resolves the container scenario without a native addon.
|
|
3667
|
+
Covered by `test/daemon-start-lock.test.ts`.
|
|
3668
|
+
|
|
3669
|
+
### Added
|
|
3670
|
+
|
|
3671
|
+
- **`prismer task wait <id>` / `cloud task wait <id>`** (release203/15 §WS-E2).
|
|
3672
|
+
Block until an ALREADY-delegated task settles, then print it — the
|
|
3673
|
+
orchestrator's "delegate → wait → act on the result" primitive (the
|
|
3674
|
+
`tasks`/`agent-coordination` skills point here) so it doesn't hand-roll a
|
|
3675
|
+
`cloud task get` poll loop. Reuses `pollUntilTerminal` (now parameterised by
|
|
3676
|
+
stop-statuses). Settles on `review/completed/failed/cancelled` by default —
|
|
3677
|
+
`review` included because for a delegated task it means "assignee finished,
|
|
3678
|
+
awaiting YOUR approval"; blocking until `completed` would deadlock the very
|
|
3679
|
+
orchestrator that must approve. `--terminal-only` for strict terminal; exits
|
|
3680
|
+
non-zero on `failed`/`cancelled`. Gate: `regress-cloud-task-wait.ts` (real CLI
|
|
3681
|
+
against the live API on completed + review tasks).
|
|
3682
|
+
|
|
3683
|
+
- **Hermes (persistence) tool-call-mapper → structured `ToolCallDetail`**
|
|
3684
|
+
(release203/15 §WS-G). Hermes tool steps now carry a structured `detail` so
|
|
3685
|
+
`execute_code`/terminal/file/search/fetch calls render rich + expandable in the
|
|
3686
|
+
unified timeline, exactly like coding agents — instead of the bare row
|
|
3687
|
+
("import subprocess (+3 行)" = inputSummary) with no expand. New
|
|
3688
|
+
`adapters/persistence/hermes/tool-call-mapper.ts` reuses the
|
|
3689
|
+
`ToolCallDetail` union from `coding/shared/agent-sdk-types.ts` (imported, not
|
|
3690
|
+
redefined) and maps the Hermes tool `args` shape: `execute_code`/`terminal`/
|
|
3691
|
+
`bash`/`shell`/`run_command` → `shell{command}` (command pulled from
|
|
3692
|
+
`args.code`/`args.command`), read/write/edit file tools → `read`/`write`/`edit`,
|
|
3693
|
+
`search`/`grep`/`web_search` → `search`, `fetch`/`browser`/`open_url` → `fetch`;
|
|
3694
|
+
unknown tools → `undefined` (falls back to inputSummary, no regression).
|
|
3695
|
+
Wired at `sessions-sse.ts` `tool.started` (pass `{detail, status:'running'}`,
|
|
3696
|
+
which also fixes altitude — shell → `'action'` so consecutive `execute_code`
|
|
3697
|
+
collapse into a group like coding) and `tool.completed` (rebuilds the
|
|
3698
|
+
same-shape detail from started args + output/exitCode, stashed by `toolCallId`
|
|
3699
|
+
since the completion payload drops the original args). The entire downstream
|
|
3700
|
+
(recorder `capDetail` → cloud task-step-recorder → unified WS → agent-message
|
|
3701
|
+
decode → ActivityDetail rendering + `canExpand=detailHasBody`) already supported
|
|
3702
|
+
rich details — this is purely the missing producer half; no cloud/schema/render
|
|
3703
|
+
change. ⚠️ The Hermes gateway's `_tool_progress` callback frequently drops the
|
|
3704
|
+
real tool output (`sessions-sse.ts:375-401`); where output is absent,
|
|
3705
|
+
`detail.output`/`result` is left undefined (never a fake) — command + expand +
|
|
3706
|
+
exitCode-when-present still render. Real-output forwarding is a separate
|
|
3707
|
+
Hermes-gateway gap. 13 unit tests.
|
|
3708
|
+
|
|
3709
|
+
- **`cloud skill create` + `cloud role` commands** (Standardization SS-01/SS-02
|
|
3710
|
+
ingestion). New CLI verbs that push material-derived bundles into the live
|
|
3711
|
+
platform with the configured API key: `cloud skill create <bundleDir>
|
|
3712
|
+
[--install --agent <id>]` reads a SS-01 skill bundle (frontmatter + files),
|
|
3713
|
+
builds a server-matching merkle `contentManifest`, and `POST`s
|
|
3714
|
+
`/api/im/skills` (+ install). New `cloud role` group — `create <role.json>
|
|
3715
|
+
[--apply --agent <id> --workspace-id <id>]`, `apply <slug>`, `list`, `show` —
|
|
3716
|
+
ingests/applies SS-02 role templates (`/api/im/role-templates` + `/apply`).
|
|
3717
|
+
Backs the new `skill-builder` / `role-builder` built-in skills (each also ships
|
|
3718
|
+
a zero-dependency `scripts/ingest*.mjs` for SDK-only/CI environments). Note:
|
|
3719
|
+
community skill creates are slug-prefixed server-side (`name` →
|
|
3720
|
+
`community-<name>`); use the returned slug for install + role `requiredSkills`.
|
|
3721
|
+
Verified 8/8 against the local stack (skill create+install+GET, role
|
|
3722
|
+
create+apply+GET, profile roleTemplate projection).
|
|
3723
|
+
|
|
3724
|
+
- **Per-role Hermes-native skill scope** (release203/13 P1). Roles can now govern
|
|
3725
|
+
which of Hermes' ~90 bundled native skills an agent sees via `skills_list`, not
|
|
3726
|
+
just our injected built-ins. The hermes adapter reads `nativeSkillScope`
|
|
3727
|
+
(`{ mode: 'allow'|'deny', categories?, skills? }`) — projected from
|
|
3728
|
+
`IMRoleTemplate.nativeSkillScope` into `profile.config` (top-level, with a
|
|
3729
|
+
`roleTemplate` snapshot fallback; `resolveNativeSkillScope` mirrors
|
|
3730
|
+
`resolveMcpAllowlist`'s Priority-1/2). `deny` subtracts more on top of the
|
|
3731
|
+
global `HERMES_NATIVE_SKILL_DENYLIST` floor; `allow` keeps ONLY the listed
|
|
3732
|
+
categories/skills (net-body) — the bundled set (enumerated from the home-root
|
|
3733
|
+
`skills/` dir, cached by mtime) minus the allow-set. allow-mode fail-safes to
|
|
3734
|
+
the deny floor when the bundle can't be enumerated (fresh boot). Our injected
|
|
3735
|
+
skills are never disabled (enumeration reads the pure-bundle root, not
|
|
3736
|
+
`profileDir/skills/`). Verified: 9 unit tests + 11 e2e assertions on the real
|
|
3737
|
+
bundle.
|
|
3738
|
+
|
|
3739
|
+
### Changed
|
|
3740
|
+
|
|
3741
|
+
- **Daemon-side FORCE: all coding agents launch autonomous (no permission
|
|
3742
|
+
confirmation)** (release203). Coding agents run in agent-rt pods via the daemon
|
|
3743
|
+
and are ALWAYS non-interactive — a tool permission prompt can never be answered,
|
|
3744
|
+
so the provider blocked until the 300s watchdog (confirmed live: run f27vyi hung
|
|
3745
|
+
328s). The pod itself is the isolation boundary, so coding sessions now launch
|
|
3746
|
+
fully autonomous regardless of the stored profile config. Force-seam = the
|
|
3747
|
+
canonical `CodeAgentDriver` (`adapters/coding/shared/code-agent-driver.ts`):
|
|
3748
|
+
`withAutonomousLaunch(provider, config)` is applied on BOTH `createSession`
|
|
3749
|
+
(`buildSessionConfig`) and `resumeSession` (the resume overrides), so it covers
|
|
3750
|
+
EXISTING agents whose stored handle metadata carries a stale/absent `modeId`,
|
|
3751
|
+
not just new ones. Policy: keep an already-autonomous `modeId`, otherwise force
|
|
3752
|
+
the per-provider autonomous mode:
|
|
3753
|
+
- **claude-code** → `modeId: 'bypassPermissions'` (the claude-agent-sdk
|
|
3754
|
+
`permissionMode` equivalent of `--dangerously-skip-permissions`; the SDK skips
|
|
3755
|
+
`canUseTool` entirely in this mode).
|
|
3756
|
+
- **codex** → `modeId: 'full-access'` + `sandboxMode: 'danger-full-access'` +
|
|
3757
|
+
`approvalPolicy: 'never'` (≙ `--dangerously-bypass-approvals-and-sandbox`).
|
|
3758
|
+
- **opencode** → `modeId: 'build'` + `featureValues.auto_accept = true` (opencode
|
|
3759
|
+
has no CLI bypass flag — `auto_accept` drives its `tryAutoApproveToolPermission`
|
|
3760
|
+
path, auto-replying `"once"` to every tool permission request).
|
|
3761
|
+
|
|
3762
|
+
CLI fallback adapters (registered as `claude-code-cli` / `codex-cli` D21
|
|
3763
|
+
fallbacks in `daemon/runner.ts`) also launch with the real bypass flags:
|
|
3764
|
+
`claude --dangerously-skip-permissions` (`adapters/coding/claude-code/index.ts`)
|
|
3765
|
+
and `codex … --dangerously-bypass-approvals-and-sandbox` (supersedes `--sandbox`,
|
|
3766
|
+
`adapters/coding/codex/index.ts` `buildCodexArgs`; the codex `resume` subcommand
|
|
3767
|
+
rejects sandbox/approval flags and inherits the persisted session's policy from
|
|
3768
|
+
turn-1, so no re-pass). Cloud `buildProfileConfig` is unchanged — this daemon
|
|
3769
|
+
force is defense-in-depth. New assertions:
|
|
3770
|
+
`test/code-agent-driver-autonomous.test.ts` (+ colocated driver test) and a
|
|
3771
|
+
codex CLI-args case in `test/codex-adapter-session-streaming.test.ts`.
|
|
3772
|
+
|
|
3773
|
+
- **Persistence agents (Hermes) launch autonomous too — no approval gating**
|
|
3774
|
+
(release203, "Hermes 也全自治"). The persistence analog of the coding-agent
|
|
3775
|
+
force above. The hermes gateway spawn (`adapters/persistence/hermes/index.ts`)
|
|
3776
|
+
now sets `HERMES_YOLO_MODE: 'true'` in the child env, disabling hermes' native
|
|
3777
|
+
dangerous-command approval gate. Rationale: the non-interactive agent-rt pod has
|
|
3778
|
+
no human at a prompt to answer an `approval.request`, and that event tears down
|
|
3779
|
+
our SSE stream rather than routing to an approval UI — so a flagged command
|
|
3780
|
+
would just block ~5min then fail. The pod IS the sandbox; hermes' hardline
|
|
3781
|
+
catastrophic patterns still block even with YOLO on (desired floor). Companion
|
|
3782
|
+
change in `daemon/dispatch.ts`: `DEFAULT_OPERATING_PRINCIPLES` no longer carries
|
|
3783
|
+
an approval-seeking line — `resolveOperatingPrinciples()` appends the policy line
|
|
3784
|
+
per `approvalPolicy`, and the `autonomous` branch injects zero approval-seeking
|
|
3785
|
+
text. Role-template refs (`templates/roles/ceo.json`, `skill-author.json`) set
|
|
3786
|
+
`approvalPolicy: 'autonomous'`, drop `human-approval` from required skills, and
|
|
3787
|
+
rewrote the `[Authority and approval]` clauses to autonomous + an
|
|
3788
|
+
anti-confabulation rule. NOTE: the 205 DB-seeded role templates are NOT migrated
|
|
3789
|
+
— existing agents keep their snapshot and pick up the fix on recreation (same
|
|
3790
|
+
model as the coding `modeId` fix).
|
|
3791
|
+
|
|
3792
|
+
### Added
|
|
3793
|
+
|
|
3794
|
+
- **Daemon follows the unified single-WS realtime** (release203/12 P3.1). New
|
|
3795
|
+
`WsRealtimeSubscriber` (`daemon/gateway/ws-realtime-subscriber.ts`) consumes the
|
|
3796
|
+
cloud's unified `WS /ws/realtime?token=&since=` (the SAME endpoint the browser
|
|
3797
|
+
uses) instead of the legacy `GET /api/im/sync/stream` SSE. It demuxes the
|
|
3798
|
+
`{ch:'sync',name:'sync',data}` frames, ignores the `ch:'tasks'` projection (the
|
|
3799
|
+
daemon only ever materialized the sync fan-out), and feeds the SAME
|
|
3800
|
+
`Materializer` (rm_* upsert + per-conversation watermark + local relay) as the
|
|
3801
|
+
SSE path. Cursor parity: it persists the per-user seq under the SAME
|
|
3802
|
+
`chats`/`__sse__` watermark, so a daemon upgrading SSE→WS resumes from exactly
|
|
3803
|
+
where it left off; `sync.backfill.truncated{newestSeq}` still jumps the cursor
|
|
3804
|
+
(no replay storm). The runner constructs the WS subscriber by DEFAULT;
|
|
3805
|
+
`PRISMER_UNIFIED_WS=0` opts back to `SseSubscriber` (per-deploy kill switch,
|
|
3806
|
+
mirrors the client flag). Removes the daemon's second long-lived SSE connection
|
|
3807
|
+
to cloud — one user = one realtime connection.
|
|
3808
|
+
|
|
3809
|
+
- **Canonical agent identity injection** (release203/11 §2, Slice A — fixes净身
|
|
3810
|
+
coding agents answering "who are you?" as generic Claude). `TaskDispatchRequestPayload`
|
|
3811
|
+
gains an additive `identityContext?: { identity, user, scope }` (composed
|
|
3812
|
+
CLOUD-side — only cloud has the names). `dispatch.ts` now: (a) substitutes a
|
|
3813
|
+
new `CODING_SOUL_DEFAULT` engineering persona into the SOUL/persona slot when a
|
|
3814
|
+
coding agent (`claude-code`/`codex`/`opencode`) has an empty `config.systemPrompt`,
|
|
3815
|
+
and (b) forwards `identityContext` on a SEPARATE `metadata.identityContext` key
|
|
3816
|
+
(NOT folded into `metadata.systemPrompt`) so hermes' `SOUL.md` stays persona-only
|
|
3817
|
+
per Hermes docs. The hermes adapter places identity/user/scope in its per-turn
|
|
3818
|
+
`instructions` slot; the code-agent driver + legacy claude-code CLI prepend them
|
|
3819
|
+
to `--system-prompt`. New exported helpers `isCodingAdapter` / `renderIdentityLines`
|
|
3820
|
+
/ `CODING_SOUL_DEFAULT`. Purely additive — legacy dispatches with no
|
|
3821
|
+
`identityContext` are byte-identical on the wire.
|
|
3822
|
+
|
|
3823
|
+
- **Seed-on-dispatch for coding agents with no workdir** (release203/11 §2.4 —
|
|
3824
|
+
AGENTS-layer reliability fix). `seedDevPreset` previously only ran via
|
|
3825
|
+
`ensureWorkdir`, which is gated on a `payload.workdir` override; default
|
|
3826
|
+
RepoDirPicker coding agents carry no workdir, so their cwd never got
|
|
3827
|
+
`CLAUDE.md`/`AGENTS.md`. `dispatch.ts` now idempotently seeds the effective cwd
|
|
3828
|
+
(`profile.config.cwd`, `seedDevPreset(cwd, 'verified')` — append-only, never
|
|
3829
|
+
stomps user files, non-fatal) for any coding dispatch that didn't already
|
|
3830
|
+
materialize a workdir.
|
|
3831
|
+
|
|
3832
|
+
- **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).
|
|
3833
|
+
|
|
3834
|
+
- **Category-aware built-in skill filtering** (release203/09 §7.6.3). New runtime
|
|
3835
|
+
SSOT `src/adapters/agent-category.ts` (`ADAPTER_CATEGORY` / `categoryForAdapter`
|
|
3836
|
+
/ `skillScopeMatchesCategory`) mirrors the cloud-side adapter→category map.
|
|
3837
|
+
`SKILL.md` frontmatter `scope: common|persistence|coding` is now parsed into
|
|
3838
|
+
`LoadedSkill.scope` (defaults to `common`), and `FileSystemSkillLoader.loadForDispatch(profile)`
|
|
3839
|
+
filters to `scope ∈ {common, <adapter category>}`; unknown adapter or no profile
|
|
3840
|
+
loads everything (default-safe — an unrecognized scope never drops a skill). The
|
|
3841
|
+
hermes/openclaw persistence loaders now thread `profile` through instead of
|
|
3842
|
+
ignoring it.
|
|
3843
|
+
|
|
3844
|
+
- **`agent.workdir.materialize` control frame** (release203/09 §7.3 — on-demand
|
|
3845
|
+
persistent workdir provisioning for the Pro coding-agent picker). The daemon
|
|
3846
|
+
answers a new reverse-channel RPC (`{ workspaceId, projectId?, source:
|
|
3847
|
+
'clone'|'init'|'container-pick', sourceRef?, name?, cwd?, _rpcId }`): it
|
|
3848
|
+
resolves the per-project `repos/` base (`resolveProjectReposDir`), jails the
|
|
3849
|
+
target cwd inside `workspaces/<wid>`, then reuses `ensureWorkdir` to git-clone
|
|
3850
|
+
/ git-init / verify a container-picked path, and echoes `agent.workdir.reply`
|
|
3851
|
+
`{ _rpcId, ok, data:{ cwd, action: 'cloned'|'init'|'reused'|'verified' } |
|
|
3852
|
+
error:{ code: 'path_escape'|'materialize_failed'|'bad_request' } }`. clone/init
|
|
3853
|
+
require a single-segment `name` (`/`, `\`, `..`, empty → `bad_request`);
|
|
3854
|
+
container-pick verifies the supplied absolute `cwd` against the jail
|
|
3855
|
+
(`path_escape` on escape). The pure resolve+jail decision is extracted as
|
|
3856
|
+
`resolveWorkdirCwd` for unit testing (mirrors `fs-list.ts`).
|
|
3857
|
+
|
|
3858
|
+
- **`agent.fs.list` control frame + per-project `repos/` standard directory**
|
|
3859
|
+
(release203/09 — container directory picker for Pro coding-agent creation).
|
|
3860
|
+
New `resolveProjectReposDir(paths, workspaceId, projectId)`
|
|
3861
|
+
(`workspaces/<wid>/projects/<pid|_unscoped>/repos/`); the dispatcher now does a
|
|
3862
|
+
non-fatal `mkdir -p` of it alongside the session `artifacts/`/`scratch/` so a
|
|
3863
|
+
coding agent's default `cwd` always exists. The daemon answers a new
|
|
3864
|
+
`agent.fs.list` reverse-channel RPC (`{ workspaceId, projectId?, subpath?,
|
|
3865
|
+
_rpcId }`) by `readdir`-ing that `repos/` scope — jailed inside
|
|
3866
|
+
`workspaces/<wid>` (escape → `path_escape`), flagging each dir's `.git` as
|
|
3867
|
+
`isRepo`, dirs-first/alphabetical sort — and echoes `agent.fs.reply`
|
|
3868
|
+
`{ _rpcId, ok, data:{ absPath, parentRel, entries } | error:{ code } }`. A
|
|
3869
|
+
not-yet-created `repos/` returns `ok` with empty `entries` (never an error).
|
|
3870
|
+
|
|
3871
|
+
- **`cloud okr pack` subgroup** (release203 Task 2 — R4 Scenario Packs). Global,
|
|
3872
|
+
data-driven OKR templates resolved by `domain` KEY (no runtime branch).
|
|
3873
|
+
`okr pack list` (GET `/api/im/okr/packs`), `okr pack get <domain>` (GET
|
|
3874
|
+
`/api/im/okr/packs/:domain`), and `okr pack adopt <domain> <archetypeId>
|
|
3875
|
+
--workspace <id>` (POST `…/archetypes/:archetypeId/adopt`) which seeds a draft
|
|
3876
|
+
Objective + its KRs and stamps a commit-time pack snapshot
|
|
3877
|
+
(`metadataJson.scenarioPack {domain,version,archetypeId}`). The `software` pack
|
|
3878
|
+
is deliverable; `sales`/`marketing` are DATA-ONLY spec (`deliverable:false`) and
|
|
3879
|
+
adopting one returns `PACK_NOT_DELIVERABLE` (422). Dev KRs declare an internal
|
|
3880
|
+
source: task/acceptance-backed KRs emit today; latency/CI/defect/DORA KRs
|
|
3881
|
+
declare a `metricBinding` but `backedNow:false` → recompute returns null
|
|
3882
|
+
(—/pending, no fabricated numbers). approvalPolicy / resourcePolicy /
|
|
3883
|
+
proactivityTriggers / compensation are FROZEN and absent.
|
|
3884
|
+
|
|
3885
|
+
- **`cloud okr` command + `okr` built-in skill** (release203 Task 3 — OKR control
|
|
3886
|
+
loop). The canonical "agent drafts an OKR charter" path: from a human's plain-
|
|
3887
|
+
language goal an agent drafts ONE Objective + 2-5 Key Results, links existing
|
|
3888
|
+
tasks to the KR they serve, and routes the COMMIT to a human sponsor. Subcommands
|
|
3889
|
+
(all over the committed `/api/im/okr/*` + `/api/im/insights/okr` endpoints):
|
|
3890
|
+
`okr objective create|list|get|commit|close`, `okr kr add|recompute`,
|
|
3891
|
+
`okr link <objectiveId> <keyResultId> <taskId>`, `okr insights --workspace`.
|
|
3892
|
+
`kr add` assembles a `metricBinding` from `--metric-namespace/--metric-name/
|
|
3893
|
+
--metric-agg` and defaults `--source` to `agent-proposed`. Hard rules (enforced
|
|
3894
|
+
server-side, mirrored in the skill): an agent may PROPOSE but never COMMIT
|
|
3895
|
+
(`commit` → `AGENT_CANNOT_COMMIT` 403); a committed-type objective REQUIRES a
|
|
3896
|
+
human/admin sponsor (`SPONSOR_MUST_BE_HUMAN`); a qualitative KR may only be
|
|
3897
|
+
scored with a human-confirmed `--value` + `--evidence`. Guardrails / resource
|
|
3898
|
+
allocation / scenario packs / check-ins / evaluations / compensation are FROZEN
|
|
3899
|
+
and absent from both the CLI and the skill.
|
|
3900
|
+
|
|
3901
|
+
- **`cloud okr objective checkin|grade|archive`** (release203 Task 1 — OKR
|
|
3902
|
+
lifecycle back-half, `committed → graded → archived`). `checkin <id>
|
|
3903
|
+
[--note] [--confidence] [--decision continue|rescope|add-resource|pause|cancel]`
|
|
3904
|
+
records a check-in (snapshots the objective score + each KR's current/status);
|
|
3905
|
+
an AGENT may draft check-ins. `grade <id> [--score] [--narrative]` runs the
|
|
3906
|
+
formal evaluation (committed|at_risk|paused → graded) and `archive <id>`
|
|
3907
|
+
(graded|closed → archived, read-only) are **HUMAN decisions** — an agent caller
|
|
3908
|
+
gets `AGENT_CANNOT_GRADE` (403). Grade freezes the objective score. Reward /
|
|
3909
|
+
resource fields carry no token/credit meaning (compensation stays FROZEN).
|
|
3910
|
+
|
|
3911
|
+
- **Vision-gated image context for non-vision recipients** (release202/17). The
|
|
3912
|
+
daemon now consumes the envelope's new `assets.imageReferences` bucket and
|
|
3913
|
+
renders each as a one-line `[image: <filename> · asset <assetId> · <w>×<h>]`
|
|
3914
|
+
text token instead of vision pixels. A DEFENSIVE double-gate
|
|
3915
|
+
(`gateImageRefsByVision`, `adapters/shared/image-reference.ts`) re-checks the
|
|
3916
|
+
resolved model in the Hermes sessions-dispatcher, runs-dispatcher, and the
|
|
3917
|
+
envelope renderer (`hermes/context-render.ts`): when the model is NOT
|
|
3918
|
+
vision-capable, image inputs are degraded to the same reference lines and are
|
|
3919
|
+
never lifted into `image_url` — protecting against a cloud-side gating miss.
|
|
3920
|
+
Vision-capable / unknown models keep the current `image_url` behavior.
|
|
3921
|
+
|
|
3922
|
+
### Changed
|
|
3923
|
+
|
|
3924
|
+
- **zod 3 → 4.** `@anthropic-ai/claude-agent-sdk@^0.2.141` (newly added with the
|
|
3925
|
+
code-agent engine port) peer-requires `zod@^4.0.0` across its entire 0.2.x line,
|
|
3926
|
+
which conflicted with the previous `zod@^3.23.8` pin and broke `npm install`.
|
|
3927
|
+
Bumped to `zod@^4.0.0` and migrated the runtime's zod usage to the v4 API:
|
|
3928
|
+
`z.record(v)` → `z.record(z.string(), v)` (key type now mandatory),
|
|
3929
|
+
`ZodError.errors` → `.issues`, `z.ZodType<O, ZodTypeDef, I>` → `z.ZodType<O, I>`
|
|
3930
|
+
(`ZodTypeDef` removed in v4), and explicit transform-value typing where v4's
|
|
3931
|
+
stricter inference over generic `z.ZodTypeAny` schema params collapsed. No
|
|
3932
|
+
runtime behavior change. `zod-to-json-schema@^3.25.2` already supports zod 4.
|
|
3933
|
+
|
|
3934
|
+
### Removed
|
|
3935
|
+
|
|
3936
|
+
- **OpenClaw adapter retired** (release203/11 §2.5, Slice B). OpenClaw was a
|
|
3937
|
+
decay/version-rotting candidate and its identity model was already absorbed into
|
|
3938
|
+
the canonical four-piece model (§2.1). Deleted the whole
|
|
3939
|
+
`src/adapters/persistence/openclaw/` adapter (index / context-render /
|
|
3940
|
+
skill-loader / memory-tools), its registration in `daemon/runner.ts`, its
|
|
3941
|
+
`openclawAdapter` / `OpenClawProfileConfig` exports from `src/index.ts`, the
|
|
3942
|
+
`openclaw` pin in `known-versions.ts`, the openclaw branch in `agent-category.ts`
|
|
3943
|
+
`ADAPTER_CATEGORY`, the `FallbackAdapter` openclaw member, the openclaw
|
|
3944
|
+
`INSTALL_SPECS` / `BUILTIN_ADAPTERS` / auth + hook branches in the
|
|
3945
|
+
`prismer adapter` CLI, the openclaw `ADAPTER_BINARY` entry in the `prismer agent`
|
|
3946
|
+
CLI, and the openclaw branches in `dispatch.ts` (skill-dir gate),
|
|
3947
|
+
`skill-sync.ts` (skill-root resolution), and `local-server.ts` (state-root
|
|
3948
|
+
snapshot). Removed `openclawConfig` keys + `"openclaw"` from `applicableAdapters`
|
|
3949
|
+
in the role-template JSONs. Persistence is now hermes-only. Existing openclaw
|
|
3950
|
+
profile/agent rows are left untouched in the DB (they simply no longer dispatch —
|
|
3951
|
+
adapter gone); the `IMRoleTemplate.openclawConfig` column is kept as a dead column
|
|
3952
|
+
(no migration). Deleted the openclaw-specific tests
|
|
3953
|
+
(`openclaw-adapter-*`, `openclaw-skill-loader`, `context-render-openclaw`) and the
|
|
3954
|
+
`sa7-openclaw-gateway-verify` cookbook; trimmed the openclaw rows from the
|
|
3955
|
+
cross-adapter tests (multimodal / memory-tools / version-check / templates /
|
|
3956
|
+
skill-sync / workdir-materialize). Also dropped the `@prismer/openclaw-channel`
|
|
3957
|
+
plugin package and its references in the SDK build scripts.
|
|
3958
|
+
|
|
3959
|
+
### Fixed
|
|
3960
|
+
|
|
3961
|
+
- **Transient dispatch-precondition failures no longer hard-fail the run**
|
|
3962
|
+
(release202, HTTP 404 postmortem). A freshly-(re)connected daemon's FIRST
|
|
3963
|
+
cloud precondition fetch (`resolveProfile` → `GET /api/im/agent_profiles/:id`)
|
|
3964
|
+
can 404 during the warm-up window before its api-key-proxy identity is hot —
|
|
3965
|
+
the cloud stamps a valid `profileId`, but the owner-scoped lookup transiently
|
|
3966
|
+
misses. Previously that 404 was thrown straight past the 3× adapter-retry loop
|
|
3967
|
+
(which only wraps `adapter.dispatch()`, *after* `resolveProfile`) into the
|
|
3968
|
+
top-level catch, replying `{code:'adapter_dispatch_failed', message:'HTTP 404'}`
|
|
3969
|
+
→ terminal "Agent 失败 … HTTP 404" pill, recoverable only by the user
|
|
3970
|
+
resending. Now: (1) `resolveProfileResilient` retries TRANSIENT cloud failures
|
|
3971
|
+
(404 / 408 / 429 / 5xx / network) with backoff (~5s) before giving up;
|
|
3972
|
+
(2) the top-level catch classifies the final error via
|
|
3973
|
+
`isTransientPreconditionError` and replies with a distinct retryable code
|
|
3974
|
+
`dispatch_precondition_unavailable` (vs `adapter_dispatch_failed`) so the
|
|
3975
|
+
cloud re-queues instead of terminal-failing. 400/401/403 stay PERMANENT.
|
|
3976
|
+
|
|
3977
|
+
- **Upstream LLM failures no longer surface as fake-successful tasks**
|
|
3978
|
+
(release202/12, D1+D2). Hermes serializes an exhausted LLM call
|
|
3979
|
+
(`API call failed after N retries: …`) into `assistant.completed` content;
|
|
3980
|
+
the sessions SSE consumer now detects that signature (anchored, requires zero
|
|
3981
|
+
streamed deltas to avoid false-positives) and sets `SessionsSseResult.upstreamError`.
|
|
3982
|
+
`sessions-dispatcher` turns it into a FAILED `AdapterResult`
|
|
3983
|
+
(`code:'upstream_llm_error'`, carrying the HTTP status) instead of `ok:true`
|
|
3984
|
+
with the error string as output. The daemon retry loop (`dispatch.ts`) adds
|
|
3985
|
+
`isPermanentUpstreamError` so permanent causes skip the 3× retry and surface
|
|
3986
|
+
the real reason instead of `daemon_local_retry_exhausted`. Detection +
|
|
3987
|
+
classification key on the hermes message WORDING — `has no usable upstream
|
|
3988
|
+
source` (provider chain unconfigured), `Billing or credits exhausted:` (HTTP
|
|
3989
|
+
402 from the cloud balance gate), `HTTP <4xx>` — because hermes'
|
|
3990
|
+
`_summarize_provider_error` DROPS the JSON `error.type`, so a machine token
|
|
3991
|
+
can't be matched. Mirrored to the `/v1/runs` dispatcher so the flag-gated path
|
|
3992
|
+
can't reintroduce the bug. Pairs with the cloud-side
|
|
3993
|
+
`503 provider_chain_unconfigured` (release202/07 §5b) so a provider-chain
|
|
3994
|
+
misconfig reads as a clear failed task end-to-end.
|
|
3995
|
+
|
|
5
3996
|
### Added
|
|
6
3997
|
|
|
7
3998
|
- **`mode:'message-attach'` for `POST /local/deliver`** (release202/09 P5#3, 动作
|
|
@@ -526,3 +4517,16 @@ First published cut of `@prismer/runtime`. The runtime is the TS-only daemon tha
|
|
|
526
4517
|
## v1.8.x — Pre-publish scaffolding
|
|
527
4518
|
|
|
528
4519
|
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.
|
|
4520
|
+
|
|
4521
|
+
## 2.2.10 (2026-08-07)
|
|
4522
|
+
|
|
4523
|
+
- Fix Hermes "No LLM provider configured" on sandbox agents: the provider
|
|
4524
|
+
bootstrap wrote `model.provider: custom:prismer`, but hermes' custom-provider
|
|
4525
|
+
resolution matches `custom_providers[].name` verbatim — `custom:prismer`
|
|
4526
|
+
never matched the `prismer` entry, the gateway fell through with
|
|
4527
|
+
provider='custom' and no key, and every turn failed at AIAgent init.
|
|
4528
|
+
Exposed on 2.2.9: the dual-write made the gateway read the profile config
|
|
4529
|
+
(HERMES_HOME override) for the first time, surfacing the format mismatch.
|
|
4530
|
+
Write the bare provider name (`prismer`); verified in-pod that
|
|
4531
|
+
`_resolve_runtime_agent_kwargs` then resolves api_key + base_url correctly.
|
|
4532
|
+
## 2.2.9
|