@prismer/runtime 2.0.7 → 2.2.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3631 -0
- package/README.md +34 -12
- package/apc/skills/FIELD-DICTIONARY.md +111 -0
- package/apc/skills/bug-reproduce/SKILL.md +150 -0
- package/apc/skills/bug-reproduce/skill.json +96 -0
- package/apc/skills/code-review/SKILL.md +198 -0
- package/apc/skills/code-review/skill.json +124 -0
- package/apc/skills/design-review/SKILL.md +122 -0
- package/apc/skills/design-review/skill.json +88 -0
- package/apc/skills/doc-sync/SKILL.md +168 -0
- package/apc/skills/doc-sync/skill.json +81 -0
- package/apc/skills/env-doctor/SKILL.md +194 -0
- package/apc/skills/env-doctor/skill.json +209 -0
- package/apc/skills/git-ops/SKILL.md +189 -0
- package/apc/skills/git-ops/skill.json +94 -0
- package/apc/skills/impact-trace/SKILL.md +168 -0
- package/apc/skills/impact-trace/skill.json +104 -0
- package/apc/skills/observability/SKILL.md +195 -0
- package/apc/skills/observability/skill.json +116 -0
- package/apc/skills/release-db-config-sync/SKILL.md +186 -0
- package/apc/skills/release-db-config-sync/skill.json +109 -0
- package/apc/skills/release-ota-promote/SKILL.md +195 -0
- package/apc/skills/release-ota-promote/skill.json +176 -0
- package/apc/skills/release-preflight/SKILL.md +174 -0
- package/apc/skills/release-preflight/skill.json +175 -0
- package/apc/skills/release-rollback/SKILL.md +214 -0
- package/apc/skills/release-rollback/skill.json +230 -0
- package/apc/skills/release-tag/SKILL.md +194 -0
- package/apc/skills/release-tag/skill.json +94 -0
- package/apc/skills/releasing-prod/SKILL.md +49 -0
- package/apc/skills/releasing-test/SKILL.md +135 -0
- package/apc/skills/sdk-release/SKILL.md +200 -0
- package/apc/skills/spec-intake/SKILL.md +169 -0
- package/apc/skills/spec-intake/skill.json +93 -0
- package/apc/skills/test-result-feedback/SKILL.md +239 -0
- package/apc/skills/test-result-feedback/skill.json +193 -0
- package/apc/skills/test-runner/SKILL.md +169 -0
- package/apc/skills/test-runner/skill.json +103 -0
- package/apc/skills/ui-align/SKILL.md +209 -0
- package/apc/skills/ui-align/skill.json +114 -0
- package/apc/skills/ui-canvas/SKILL.md +148 -0
- package/apc/skills/ui-canvas/skill.json +127 -0
- package/built-in-skills/agent-coordination/SKILL.md +257 -0
- package/built-in-skills/agent-meta/SKILL.md +53 -0
- package/built-in-skills/assets/SKILL.md +133 -0
- package/built-in-skills/browser-use/SKILL.md +93 -0
- package/built-in-skills/canvas-design/LICENSE.txt +202 -0
- package/built-in-skills/canvas-design/SKILL.md +157 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/built-in-skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/built-in-skills/claim-agent-ownership/SKILL.md +255 -0
- package/built-in-skills/claude-api/LICENSE.txt +202 -0
- package/built-in-skills/claude-api/SKILL.md +325 -0
- package/built-in-skills/claude-api/csharp/claude-api.md +402 -0
- package/built-in-skills/claude-api/curl/examples.md +216 -0
- package/built-in-skills/claude-api/curl/managed-agents.md +336 -0
- package/built-in-skills/claude-api/go/claude-api.md +421 -0
- package/built-in-skills/claude-api/go/managed-agents/README.md +561 -0
- package/built-in-skills/claude-api/java/claude-api.md +432 -0
- package/built-in-skills/claude-api/java/managed-agents/README.md +442 -0
- package/built-in-skills/claude-api/php/claude-api.md +375 -0
- package/built-in-skills/claude-api/php/managed-agents/README.md +435 -0
- package/built-in-skills/claude-api/python/claude-api/README.md +420 -0
- package/built-in-skills/claude-api/python/claude-api/batches.md +185 -0
- package/built-in-skills/claude-api/python/claude-api/files-api.md +165 -0
- package/built-in-skills/claude-api/python/claude-api/streaming.md +162 -0
- package/built-in-skills/claude-api/python/claude-api/tool-use.md +590 -0
- package/built-in-skills/claude-api/python/managed-agents/README.md +332 -0
- package/built-in-skills/claude-api/ruby/claude-api.md +113 -0
- package/built-in-skills/claude-api/ruby/managed-agents/README.md +389 -0
- package/built-in-skills/claude-api/shared/agent-design.md +101 -0
- package/built-in-skills/claude-api/shared/error-codes.md +213 -0
- package/built-in-skills/claude-api/shared/live-sources.md +135 -0
- package/built-in-skills/claude-api/shared/managed-agents-api-reference.md +378 -0
- package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +209 -0
- package/built-in-skills/claude-api/shared/managed-agents-core.md +238 -0
- package/built-in-skills/claude-api/shared/managed-agents-environments.md +215 -0
- package/built-in-skills/claude-api/shared/managed-agents-events.md +195 -0
- package/built-in-skills/claude-api/shared/managed-agents-memory.md +197 -0
- package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +99 -0
- package/built-in-skills/claude-api/shared/managed-agents-onboarding.md +114 -0
- package/built-in-skills/claude-api/shared/managed-agents-outcomes.md +106 -0
- package/built-in-skills/claude-api/shared/managed-agents-overview.md +68 -0
- package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
- package/built-in-skills/claude-api/shared/managed-agents-tools.md +321 -0
- package/built-in-skills/claude-api/shared/managed-agents-webhooks.md +110 -0
- package/built-in-skills/claude-api/shared/model-migration.md +779 -0
- package/built-in-skills/claude-api/shared/models.md +121 -0
- package/built-in-skills/claude-api/shared/prompt-caching.md +171 -0
- package/built-in-skills/claude-api/shared/tool-use-concepts.md +327 -0
- package/built-in-skills/claude-api/typescript/claude-api/README.md +333 -0
- package/built-in-skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/built-in-skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/built-in-skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +527 -0
- package/built-in-skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/built-in-skills/codebase-design/DEEPENING.md +37 -0
- package/built-in-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/built-in-skills/codebase-design/LICENSE +21 -0
- package/built-in-skills/codebase-design/SKILL.md +116 -0
- package/built-in-skills/conversation-compaction/SKILL.md +114 -0
- package/built-in-skills/council-creator/SKILL.md +426 -0
- package/built-in-skills/diagnosing-bugs/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/SKILL.md +136 -0
- package/built-in-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/built-in-skills/doc-coauthoring/SKILL.md +376 -0
- package/built-in-skills/document-generation/SKILL.md +105 -0
- package/built-in-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/built-in-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/built-in-skills/domain-modeling/LICENSE +21 -0
- package/built-in-skills/domain-modeling/SKILL.md +76 -0
- package/built-in-skills/frontend-design/LICENSE.txt +177 -0
- package/built-in-skills/frontend-design/SKILL.md +43 -0
- package/built-in-skills/human-approval/SKILL.md +129 -0
- package/built-in-skills/image-generate/SKILL.md +128 -0
- package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +289 -0
- package/built-in-skills/ingest/SKILL.md +73 -0
- package/built-in-skills/internal-comms/LICENSE.txt +202 -0
- package/built-in-skills/internal-comms/SKILL.md +33 -0
- package/built-in-skills/internal-comms/examples/3p-updates.md +47 -0
- package/built-in-skills/internal-comms/examples/company-newsletter.md +65 -0
- package/built-in-skills/internal-comms/examples/faq-answers.md +30 -0
- package/built-in-skills/internal-comms/examples/general-comms.md +16 -0
- package/built-in-skills/liteparse/SKILL.md +176 -0
- package/built-in-skills/mcp-builder/LICENSE.txt +202 -0
- package/built-in-skills/mcp-builder/SKILL.md +237 -0
- package/built-in-skills/mcp-builder/reference/evaluation.md +602 -0
- package/built-in-skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/built-in-skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/built-in-skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/built-in-skills/mcp-builder/scripts/connections.py +151 -0
- package/built-in-skills/mcp-builder/scripts/evaluation.py +373 -0
- package/built-in-skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -0
- package/built-in-skills/memory/SKILL.md +471 -0
- package/built-in-skills/memory-dream/SKILL.md +339 -0
- package/built-in-skills/office-artifacts/SKILL.md +211 -0
- package/built-in-skills/okr/SKILL.md +154 -0
- package/built-in-skills/persona/SKILL.md +81 -0
- package/built-in-skills/persona-generator/SKILL.md +296 -0
- package/built-in-skills/pkf-svg/SKILL.md +253 -0
- package/built-in-skills/pkf-writing/SKILL.md +236 -0
- package/built-in-skills/prismer-im-collab/SKILL.md +168 -0
- package/built-in-skills/proactivity/SKILL.md +84 -0
- package/built-in-skills/remotion/SKILL.md +431 -0
- package/built-in-skills/role-builder/SKILL.md +203 -0
- package/built-in-skills/role-builder/scripts/author-role.mjs +334 -0
- package/built-in-skills/role-builder/scripts/ingest-role.mjs +223 -0
- package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +290 -0
- package/built-in-skills/role-builder/scripts/operation-harness.mjs +267 -0
- package/built-in-skills/skill-authoring/SKILL.md +134 -0
- package/built-in-skills/skill-authoring/skill.json +74 -0
- package/built-in-skills/skill-builder/SKILL.md +171 -0
- package/built-in-skills/skill-builder/scripts/ingest.mjs +265 -0
- package/built-in-skills/skill-creator/LICENSE.txt +202 -0
- package/built-in-skills/skill-creator/SKILL.md +227 -0
- package/built-in-skills/skill-creator/agents/analyzer.md +274 -0
- package/built-in-skills/skill-creator/agents/comparator.md +202 -0
- package/built-in-skills/skill-creator/agents/grader.md +223 -0
- package/built-in-skills/skill-creator/assets/eval_review.html +146 -0
- package/built-in-skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/built-in-skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/built-in-skills/skill-creator/references/external-library-import.md +110 -0
- package/built-in-skills/skill-creator/references/schemas.md +430 -0
- package/built-in-skills/skill-creator/scripts/__init__.py +0 -0
- package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/built-in-skills/skill-creator/scripts/generate_report.py +326 -0
- package/built-in-skills/skill-creator/scripts/import-library.mjs +475 -0
- package/built-in-skills/skill-creator/scripts/improve_description.py +247 -0
- package/built-in-skills/skill-creator/scripts/package_skill.py +136 -0
- package/built-in-skills/skill-creator/scripts/quick_validate.py +103 -0
- package/built-in-skills/skill-creator/scripts/run_eval.py +310 -0
- package/built-in-skills/skill-creator/scripts/run_loop.py +328 -0
- package/built-in-skills/skill-creator/scripts/utils.py +47 -0
- package/built-in-skills/slack-gif-creator/LICENSE.txt +202 -0
- package/built-in-skills/slack-gif-creator/SKILL.md +291 -0
- package/built-in-skills/slack-gif-creator/core/easing.py +234 -0
- package/built-in-skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/built-in-skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/built-in-skills/slack-gif-creator/core/validators.py +136 -0
- package/built-in-skills/slack-gif-creator/requirements.txt +4 -0
- package/built-in-skills/tasks/SKILL.md +413 -0
- package/built-in-skills/tdd/LICENSE +21 -0
- package/built-in-skills/tdd/SKILL.md +110 -0
- package/built-in-skills/tdd/mocking.md +59 -0
- package/built-in-skills/tdd/refactoring.md +10 -0
- package/built-in-skills/tdd/tests.md +61 -0
- package/built-in-skills/team/SKILL.md +77 -0
- package/built-in-skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/built-in-skills/web-artifacts-builder/SKILL.md +105 -0
- package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +334 -0
- package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/built-in-skills/webapp-testing/LICENSE.txt +202 -0
- package/built-in-skills/webapp-testing/SKILL.md +97 -0
- package/built-in-skills/webapp-testing/examples/console_logging.py +35 -0
- package/built-in-skills/webapp-testing/examples/element_discovery.py +40 -0
- package/built-in-skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/built-in-skills/webapp-testing/scripts/with_server.py +106 -0
- package/built-in-skills/wechat-pay/SKILL.md +59 -0
- package/dist/cli.cjs +72577 -16101
- package/dist/cli.js +72752 -16232
- package/dist/index.cjs +72632 -16024
- package/dist/index.d.cts +4956 -640
- package/dist/index.d.ts +4956 -640
- package/dist/index.js +72564 -15963
- package/package.json +39 -6
- package/plugins/memory/prismer/__init__.py +1211 -0
- package/plugins/memory/prismer/plugin.yaml +8 -0
- package/plugins/memory/prismer/tool-schemas.generated.json +249 -0
- package/plugins/tools/prismer-recall/__init__.py +282 -0
- package/plugins/tools/prismer-recall/plugin.yaml +15 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HarnessError,
|
|
5
|
+
invocationHash,
|
|
6
|
+
loadHarnessConfig,
|
|
7
|
+
readLedger,
|
|
8
|
+
requestJson,
|
|
9
|
+
sleep,
|
|
10
|
+
taskIdempotencyKey,
|
|
11
|
+
writeLedger,
|
|
12
|
+
} from './operation-harness.mjs';
|
|
13
|
+
|
|
14
|
+
const HELP = `Usage:
|
|
15
|
+
# with the required PRISMER_* environment already injected:
|
|
16
|
+
node scripts/instantiate-and-run.mjs [--json]
|
|
17
|
+
|
|
18
|
+
Configuration stays in environment variables. Non-secret flags are available:
|
|
19
|
+
--role --workspace --task --title --handle --display-name --adapter
|
|
20
|
+
--target-daemon --request-id --ledger --timeout-ms --poll-ms
|
|
21
|
+
--preflight-only --no-wait --json
|
|
22
|
+
|
|
23
|
+
Remote writes require PRISMER_ALLOW_REMOTE_WRITE=1 from the trusted environment.
|
|
24
|
+
Set PRISMER_REQUEST_ID once when an interrupted operation must be resumed.
|
|
25
|
+
|
|
26
|
+
Exit codes: 2 local harness rejection, 3 auth, 4 transient/network, 5 remote contract.`;
|
|
27
|
+
|
|
28
|
+
function instanceBody(config) {
|
|
29
|
+
return {
|
|
30
|
+
workspaceId: config.workspaceId,
|
|
31
|
+
requestId: config.requestId,
|
|
32
|
+
...(config.handle ? { handle: config.handle } : {}),
|
|
33
|
+
...(config.displayName ? { displayName: config.displayName } : {}),
|
|
34
|
+
adapterName: config.adapterName,
|
|
35
|
+
...(config.targetDaemonId ? { targetDaemonId: config.targetDaemonId } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function emit(config, ledger) {
|
|
40
|
+
if (config.json) process.stdout.write(`${JSON.stringify(ledger)}\n`);
|
|
41
|
+
else {
|
|
42
|
+
process.stdout.write(
|
|
43
|
+
`[role-builder] ${ledger.stage}: role=${config.role} workspace=${config.workspaceId}` +
|
|
44
|
+
`${ledger.agent?.id ? ` agent=${ledger.agent.id}` : ''}${ledger.task?.id ? ` task=${ledger.task.id}` : ''}\n`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function findExistingInstance(config) {
|
|
50
|
+
try {
|
|
51
|
+
const read = await requestJson(
|
|
52
|
+
config,
|
|
53
|
+
'GET',
|
|
54
|
+
`/api/im/role-templates/${encodeURIComponent(config.role)}/instances/${encodeURIComponent(config.requestId)}`,
|
|
55
|
+
undefined,
|
|
56
|
+
'operation_lookup',
|
|
57
|
+
);
|
|
58
|
+
return read.data;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof HarnessError && error.details?.status === 404) return null;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function waitForInstance(config, initial, ledger) {
|
|
66
|
+
let instance = initial;
|
|
67
|
+
const started = Date.now();
|
|
68
|
+
let lastResumeAt = started;
|
|
69
|
+
while (Date.now() - started < config.timeoutMs) {
|
|
70
|
+
if (instance.status === 'failed') {
|
|
71
|
+
throw new HarnessError(
|
|
72
|
+
'instance',
|
|
73
|
+
instance.error?.code || 'ROLE_INSTANCE_FAILED',
|
|
74
|
+
instance.error?.message || 'role instance provisioning failed',
|
|
75
|
+
instance.error?.retryable ? 4 : 5,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (instance.execution?.state === 'wrong_daemon') {
|
|
79
|
+
throw new HarnessError(
|
|
80
|
+
'instance_wait',
|
|
81
|
+
'ROLE_INSTANCE_WRONG_DAEMON',
|
|
82
|
+
`agent is bound to ${instance.execution.boundDaemonId || 'an unknown daemon'} instead of ${instance.execution.targetDaemonId || 'the requested daemon'}`,
|
|
83
|
+
5,
|
|
84
|
+
{
|
|
85
|
+
targetDaemonId: instance.execution.targetDaemonId ?? null,
|
|
86
|
+
boundDaemonId: instance.execution.boundDaemonId ?? null,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
// A task must never be created for an agent that the runtime has not yet
|
|
91
|
+
// adopted. `--no-wait` skips only the final task-result wait.
|
|
92
|
+
if (instance.status === 'ready' && instance.execution?.state === 'ready') return instance;
|
|
93
|
+
await sleep(config.pollMs);
|
|
94
|
+
const read = await requestJson(
|
|
95
|
+
config,
|
|
96
|
+
'GET',
|
|
97
|
+
`/api/im/role-templates/${encodeURIComponent(config.role)}/instances/${encodeURIComponent(config.requestId)}`,
|
|
98
|
+
undefined,
|
|
99
|
+
'instance_wait',
|
|
100
|
+
);
|
|
101
|
+
instance = read.data;
|
|
102
|
+
ledger.instance = instance;
|
|
103
|
+
ledger.stage = instance.status === 'ready' ? 'waiting_for_runtime' : 'provisioning_agent';
|
|
104
|
+
ledger.updatedAt = new Date().toISOString();
|
|
105
|
+
writeLedger(config.ledgerPath, ledger);
|
|
106
|
+
|
|
107
|
+
// A process may die after accepting the operation. Periodically re-POSTing
|
|
108
|
+
// is safe: the server lease decides whether this caller may resume it.
|
|
109
|
+
if (instance.status === 'provisioning' && Date.now() - lastResumeAt >= 35_000) {
|
|
110
|
+
const resumed = await requestJson(
|
|
111
|
+
config,
|
|
112
|
+
'POST',
|
|
113
|
+
`/api/im/role-templates/${encodeURIComponent(config.role)}/instances`,
|
|
114
|
+
instanceBody(config),
|
|
115
|
+
'instance_resume',
|
|
116
|
+
);
|
|
117
|
+
instance = resumed.data;
|
|
118
|
+
lastResumeAt = Date.now();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw new HarnessError('instance_wait', 'WORKFLOW_TIMEOUT', 'timed out waiting for agent runtime binding', 4);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function waitForTask(config, task, ledger) {
|
|
125
|
+
const started = Date.now();
|
|
126
|
+
while (Date.now() - started < config.timeoutMs) {
|
|
127
|
+
if (['completed', 'failed', 'cancelled'].includes(task.status)) return task;
|
|
128
|
+
await sleep(config.pollMs);
|
|
129
|
+
const read = await requestJson(
|
|
130
|
+
config,
|
|
131
|
+
'GET',
|
|
132
|
+
`/api/im/tasks/${encodeURIComponent(task.id)}`,
|
|
133
|
+
undefined,
|
|
134
|
+
'task_wait',
|
|
135
|
+
);
|
|
136
|
+
task = read.data;
|
|
137
|
+
ledger.task = task;
|
|
138
|
+
ledger.stage = 'waiting_for_task';
|
|
139
|
+
ledger.updatedAt = new Date().toISOString();
|
|
140
|
+
writeLedger(config.ledgerPath, ledger);
|
|
141
|
+
}
|
|
142
|
+
throw new HarnessError('task_wait', 'WORKFLOW_TIMEOUT', 'timed out waiting for task result', 4);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function runRoleWorkflow(config) {
|
|
146
|
+
const acceptedHash = invocationHash(config);
|
|
147
|
+
const previous = readLedger(config.ledgerPath);
|
|
148
|
+
if (
|
|
149
|
+
previous &&
|
|
150
|
+
(previous.requestId !== config.requestId ||
|
|
151
|
+
previous.role !== config.role ||
|
|
152
|
+
previous.workspaceId !== config.workspaceId ||
|
|
153
|
+
(previous.invocationHash && previous.invocationHash !== acceptedHash))
|
|
154
|
+
) {
|
|
155
|
+
throw new HarnessError(
|
|
156
|
+
'local_validation',
|
|
157
|
+
'OPERATION_LEDGER_CONFLICT',
|
|
158
|
+
'the ledger path belongs to a different role workflow request',
|
|
159
|
+
2,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const ledger = {
|
|
163
|
+
schemaVersion: 2,
|
|
164
|
+
requestId: config.requestId,
|
|
165
|
+
invocationHash: acceptedHash,
|
|
166
|
+
role: config.role,
|
|
167
|
+
workspaceId: config.workspaceId,
|
|
168
|
+
stage: 'local_validation_complete',
|
|
169
|
+
startedAt: new Date().toISOString(),
|
|
170
|
+
updatedAt: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
// Persist the recovery key before the first request. Credentials are never
|
|
173
|
+
// written to the ledger or included in the result.
|
|
174
|
+
writeLedger(config.ledgerPath, ledger);
|
|
175
|
+
|
|
176
|
+
const existing = config.preflightOnly ? null : await findExistingInstance(config);
|
|
177
|
+
if (!existing || config.preflightOnly) {
|
|
178
|
+
const preflight = await requestJson(
|
|
179
|
+
config,
|
|
180
|
+
'POST',
|
|
181
|
+
`/api/im/role-templates/${encodeURIComponent(config.role)}/instances/preflight`,
|
|
182
|
+
instanceBody(config),
|
|
183
|
+
'remote_preflight',
|
|
184
|
+
);
|
|
185
|
+
ledger.preflight = preflight.data;
|
|
186
|
+
ledger.stage = 'remote_preflight_complete';
|
|
187
|
+
ledger.updatedAt = new Date().toISOString();
|
|
188
|
+
writeLedger(config.ledgerPath, ledger);
|
|
189
|
+
if (config.preflightOnly) return ledger;
|
|
190
|
+
} else {
|
|
191
|
+
ledger.instance = existing;
|
|
192
|
+
ledger.stage = 'operation_recovered';
|
|
193
|
+
ledger.updatedAt = new Date().toISOString();
|
|
194
|
+
writeLedger(config.ledgerPath, ledger);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// A ready/provisioning durable operation can be resumed from its read face.
|
|
198
|
+
// A failed operation needs an idempotent POST to re-enter server recovery.
|
|
199
|
+
const provisioned =
|
|
200
|
+
existing && existing.status !== 'failed'
|
|
201
|
+
? { data: existing }
|
|
202
|
+
: await requestJson(
|
|
203
|
+
config,
|
|
204
|
+
'POST',
|
|
205
|
+
`/api/im/role-templates/${encodeURIComponent(config.role)}/instances`,
|
|
206
|
+
instanceBody(config),
|
|
207
|
+
'instance',
|
|
208
|
+
);
|
|
209
|
+
ledger.instance = provisioned.data;
|
|
210
|
+
ledger.stage = 'provisioning_agent';
|
|
211
|
+
ledger.updatedAt = new Date().toISOString();
|
|
212
|
+
writeLedger(config.ledgerPath, ledger);
|
|
213
|
+
const instance = await waitForInstance(config, provisioned.data, ledger);
|
|
214
|
+
ledger.instance = instance;
|
|
215
|
+
ledger.agent = instance.agent;
|
|
216
|
+
ledger.stage = config.wait ? 'agent_ready' : 'agent_created';
|
|
217
|
+
ledger.updatedAt = new Date().toISOString();
|
|
218
|
+
writeLedger(config.ledgerPath, ledger);
|
|
219
|
+
|
|
220
|
+
const taskRequestKey = taskIdempotencyKey(config.requestId);
|
|
221
|
+
const createdTask = await requestJson(
|
|
222
|
+
config,
|
|
223
|
+
'POST',
|
|
224
|
+
'/api/im/tasks',
|
|
225
|
+
{
|
|
226
|
+
title: config.title,
|
|
227
|
+
description: config.task,
|
|
228
|
+
workspaceId: config.workspaceId,
|
|
229
|
+
assigneeId: instance.agent.id,
|
|
230
|
+
runtimeRoute: 'agent',
|
|
231
|
+
dispatchPolicy: 'on-assign',
|
|
232
|
+
idempotencyKey: taskRequestKey,
|
|
233
|
+
metadata: {
|
|
234
|
+
kind: 'agent_run',
|
|
235
|
+
createdVia: 'role-builder-script',
|
|
236
|
+
roleInstanceRequestId: config.requestId,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
'task_create',
|
|
240
|
+
);
|
|
241
|
+
ledger.task = createdTask.data;
|
|
242
|
+
ledger.stage = 'task_created';
|
|
243
|
+
ledger.updatedAt = new Date().toISOString();
|
|
244
|
+
writeLedger(config.ledgerPath, ledger);
|
|
245
|
+
|
|
246
|
+
if (config.wait) {
|
|
247
|
+
ledger.task = await waitForTask(config, createdTask.data, ledger);
|
|
248
|
+
if (ledger.task.status !== 'completed') {
|
|
249
|
+
throw new HarnessError(
|
|
250
|
+
'task_wait',
|
|
251
|
+
`TASK_${String(ledger.task.status).toUpperCase()}`,
|
|
252
|
+
ledger.task.error || `task ended as ${ledger.task.status}`,
|
|
253
|
+
5,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
ledger.stage = config.wait ? 'completed' : 'dispatched';
|
|
258
|
+
ledger.completedAt = new Date().toISOString();
|
|
259
|
+
ledger.updatedAt = ledger.completedAt;
|
|
260
|
+
writeLedger(config.ledgerPath, ledger);
|
|
261
|
+
return ledger;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function main() {
|
|
265
|
+
let config;
|
|
266
|
+
try {
|
|
267
|
+
config = loadHarnessConfig(process.argv.slice(2));
|
|
268
|
+
if (config.help) {
|
|
269
|
+
process.stdout.write(`${HELP}\n`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const result = await runRoleWorkflow(config);
|
|
273
|
+
emit(config, result);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const harnessError =
|
|
276
|
+
error instanceof HarnessError
|
|
277
|
+
? error
|
|
278
|
+
: new HarnessError('internal', 'UNEXPECTED_ERROR', error?.message || String(error), 1);
|
|
279
|
+
const failure = {
|
|
280
|
+
ok: false,
|
|
281
|
+
stage: harnessError.stage,
|
|
282
|
+
error: { code: harnessError.code, message: harnessError.message, details: harnessError.details },
|
|
283
|
+
...(config ? { requestId: config.requestId, ledger: config.ledgerPath } : {}),
|
|
284
|
+
};
|
|
285
|
+
process.stderr.write(`${JSON.stringify(failure)}\n`);
|
|
286
|
+
process.exitCode = harnessError.exitCode;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (import.meta.url === `file://${process.argv[1]}`) await main();
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export class HarnessError extends Error {
|
|
8
|
+
constructor(stage, code, message, exitCode = 1, details = undefined) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'HarnessError';
|
|
11
|
+
this.stage = stage;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.exitCode = exitCode;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VALUE_OPTIONS = new Set([
|
|
19
|
+
'--role',
|
|
20
|
+
'--workspace',
|
|
21
|
+
'--task',
|
|
22
|
+
'--title',
|
|
23
|
+
'--handle',
|
|
24
|
+
'--display-name',
|
|
25
|
+
'--adapter',
|
|
26
|
+
'--target-daemon',
|
|
27
|
+
'--request-id',
|
|
28
|
+
'--timeout-ms',
|
|
29
|
+
'--poll-ms',
|
|
30
|
+
'--ledger',
|
|
31
|
+
]);
|
|
32
|
+
const BOOLEAN_OPTIONS = new Set(['--json', '--no-wait', '--preflight-only', '--help']);
|
|
33
|
+
const SECRET_OPTIONS = new Set(['--api-key', '--token', '--jwt', '--secret', '--password']);
|
|
34
|
+
|
|
35
|
+
function optionName(arg) {
|
|
36
|
+
if (typeof arg !== 'string') return '';
|
|
37
|
+
const equalsAt = arg.indexOf('=');
|
|
38
|
+
return equalsAt === -1 ? arg : arg.slice(0, equalsAt);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseHarnessArgs(argv) {
|
|
42
|
+
const values = {};
|
|
43
|
+
const flags = new Set();
|
|
44
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
45
|
+
const arg = argv[index];
|
|
46
|
+
const name = optionName(arg);
|
|
47
|
+
if (SECRET_OPTIONS.has(name)) {
|
|
48
|
+
throw new HarnessError(
|
|
49
|
+
'local_validation',
|
|
50
|
+
'SECRET_ARGUMENT_FORBIDDEN',
|
|
51
|
+
`${name} is forbidden; credentials must come from environment variables`,
|
|
52
|
+
2,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (BOOLEAN_OPTIONS.has(arg)) {
|
|
56
|
+
flags.add(arg);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!VALUE_OPTIONS.has(arg)) {
|
|
60
|
+
// Never reflect an untrusted argv token. Besides ordinary typos, it may
|
|
61
|
+
// contain a credential in an unrecognised --name=value form.
|
|
62
|
+
throw new HarnessError('local_validation', 'UNKNOWN_ARGUMENT', `unknown argument at position ${index + 1}`, 2);
|
|
63
|
+
}
|
|
64
|
+
const value = argv[index + 1];
|
|
65
|
+
if (!value || value.startsWith('--')) {
|
|
66
|
+
throw new HarnessError('local_validation', 'MISSING_ARGUMENT_VALUE', `${arg} requires a value`, 2);
|
|
67
|
+
}
|
|
68
|
+
values[arg] = value;
|
|
69
|
+
index += 1;
|
|
70
|
+
}
|
|
71
|
+
return { values, flags };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function envOrArg(parsed, argName, envName, env) {
|
|
75
|
+
return parsed.values[argName] ?? env[envName];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function positiveInt(raw, fallback, name) {
|
|
79
|
+
if (raw === undefined) return fallback;
|
|
80
|
+
const value = Number(raw);
|
|
81
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
82
|
+
throw new HarnessError('local_validation', 'INVALID_NUMBER', `${name} must be a positive integer`, 2);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLocalWriteTarget(hostname) {
|
|
88
|
+
return ['localhost', '127.0.0.1', '::1', 'host.docker.internal'].includes(hostname);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loadHarnessConfig(argv, env = process.env) {
|
|
92
|
+
const parsed = parseHarnessArgs(argv);
|
|
93
|
+
if (parsed.flags.has('--help')) return { help: true };
|
|
94
|
+
|
|
95
|
+
const apiKey = envOrArg(parsed, '__never__', 'PRISMER_API_KEY', env);
|
|
96
|
+
const role = envOrArg(parsed, '--role', 'PRISMER_ROLE_SLUG', env)?.trim();
|
|
97
|
+
const workspaceId = envOrArg(parsed, '--workspace', 'PRISMER_WORKSPACE_ID', env)?.trim();
|
|
98
|
+
const task = envOrArg(parsed, '--task', 'PRISMER_TASK', env)?.trim();
|
|
99
|
+
const preflightOnly = parsed.flags.has('--preflight-only');
|
|
100
|
+
const missing = [
|
|
101
|
+
!apiKey && 'PRISMER_API_KEY',
|
|
102
|
+
!role && 'PRISMER_ROLE_SLUG/--role',
|
|
103
|
+
!workspaceId && 'PRISMER_WORKSPACE_ID/--workspace',
|
|
104
|
+
!task && !preflightOnly && 'PRISMER_TASK/--task',
|
|
105
|
+
].filter(Boolean);
|
|
106
|
+
if (missing.length > 0) {
|
|
107
|
+
throw new HarnessError(
|
|
108
|
+
'local_validation',
|
|
109
|
+
'CONFIG_MISSING',
|
|
110
|
+
`missing required environment/config: ${missing.join(', ')}`,
|
|
111
|
+
2,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let baseUrl;
|
|
116
|
+
try {
|
|
117
|
+
baseUrl = new URL((env.PRISMER_CLOUD_BASE || 'http://127.0.0.1:3000').replace(/\/$/, ''));
|
|
118
|
+
} catch {
|
|
119
|
+
throw new HarnessError('local_validation', 'INVALID_BASE_URL', 'PRISMER_CLOUD_BASE must be a valid URL', 2);
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
!['http:', 'https:'].includes(baseUrl.protocol) ||
|
|
123
|
+
baseUrl.username ||
|
|
124
|
+
baseUrl.password ||
|
|
125
|
+
baseUrl.search ||
|
|
126
|
+
baseUrl.hash
|
|
127
|
+
) {
|
|
128
|
+
throw new HarnessError(
|
|
129
|
+
'local_validation',
|
|
130
|
+
'UNSAFE_BASE_URL',
|
|
131
|
+
'PRISMER_CLOUD_BASE must be a plain http(s) origin without credentials, query, or hash',
|
|
132
|
+
2,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
// Remote mutation authority is deliberately environment-owned. A prompt or
|
|
136
|
+
// copied command must not be able to grant itself authority with a CLI flag.
|
|
137
|
+
const allowRemote = ['1', 'true', 'yes'].includes(String(env.PRISMER_ALLOW_REMOTE_WRITE).toLowerCase());
|
|
138
|
+
if (!preflightOnly && !isLocalWriteTarget(baseUrl.hostname) && !allowRemote) {
|
|
139
|
+
throw new HarnessError(
|
|
140
|
+
'local_validation',
|
|
141
|
+
'REMOTE_WRITE_NOT_CONFIRMED',
|
|
142
|
+
'remote mutation is blocked; set PRISMER_ALLOW_REMOTE_WRITE=1 after confirming the target environment',
|
|
143
|
+
2,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const adapterName = envOrArg(parsed, '--adapter', 'PRISMER_AGENT_ADAPTER', env)?.trim() || 'hermes';
|
|
148
|
+
if (!['hermes', 'pi-core', 'claude-code'].includes(adapterName)) {
|
|
149
|
+
throw new HarnessError('local_validation', 'INVALID_ADAPTER', 'adapter must be hermes, pi-core, or claude-code', 2);
|
|
150
|
+
}
|
|
151
|
+
const requestId =
|
|
152
|
+
envOrArg(parsed, '--request-id', 'PRISMER_REQUEST_ID', env)?.trim() ||
|
|
153
|
+
`role-run-${randomUUID()}`;
|
|
154
|
+
if (requestId.length > 191) {
|
|
155
|
+
throw new HarnessError('local_validation', 'INVALID_REQUEST_ID', 'request id exceeds 191 characters', 2);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const config = {
|
|
159
|
+
apiKey,
|
|
160
|
+
baseUrl: baseUrl.toString().replace(/\/$/, ''),
|
|
161
|
+
role,
|
|
162
|
+
workspaceId,
|
|
163
|
+
task: task || '',
|
|
164
|
+
title:
|
|
165
|
+
envOrArg(parsed, '--title', 'PRISMER_TASK_TITLE', env)?.trim() || (task || `Preflight ${role}`).slice(0, 100),
|
|
166
|
+
handle: envOrArg(parsed, '--handle', 'PRISMER_AGENT_HANDLE', env)?.trim(),
|
|
167
|
+
displayName: envOrArg(parsed, '--display-name', 'PRISMER_AGENT_DISPLAY_NAME', env)?.trim(),
|
|
168
|
+
adapterName,
|
|
169
|
+
targetDaemonId: envOrArg(parsed, '--target-daemon', 'PRISMER_TARGET_DAEMON_ID', env)?.trim(),
|
|
170
|
+
requestId,
|
|
171
|
+
timeoutMs: positiveInt(envOrArg(parsed, '--timeout-ms', 'PRISMER_WORKFLOW_TIMEOUT_MS', env), 600_000, 'timeout'),
|
|
172
|
+
pollMs: positiveInt(envOrArg(parsed, '--poll-ms', 'PRISMER_WORKFLOW_POLL_MS', env), 2_000, 'poll interval'),
|
|
173
|
+
ledgerPath: resolve(
|
|
174
|
+
envOrArg(parsed, '--ledger', 'PRISMER_OPERATION_LEDGER', env) || `.prismer/operations/${requestId}.json`,
|
|
175
|
+
),
|
|
176
|
+
wait: !parsed.flags.has('--no-wait'),
|
|
177
|
+
preflightOnly,
|
|
178
|
+
json: parsed.flags.has('--json'),
|
|
179
|
+
};
|
|
180
|
+
if (config.handle && !/^[a-z0-9][a-z0-9_-]{1,63}$/.test(config.handle)) {
|
|
181
|
+
throw new HarnessError('local_validation', 'INVALID_HANDLE', 'agent handle has an invalid shape', 2);
|
|
182
|
+
}
|
|
183
|
+
return config;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function writeLedger(path, value) {
|
|
187
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
188
|
+
const temporary = `${path}.tmp`;
|
|
189
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
190
|
+
renameSync(temporary, path);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function readLedger(path) {
|
|
194
|
+
if (!existsSync(path)) return null;
|
|
195
|
+
try {
|
|
196
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
197
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
198
|
+
} catch {
|
|
199
|
+
throw new HarnessError(
|
|
200
|
+
'local_validation',
|
|
201
|
+
'INVALID_OPERATION_LEDGER',
|
|
202
|
+
'the operation ledger exists but is not valid JSON',
|
|
203
|
+
2,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function invocationHash(config) {
|
|
209
|
+
return createHash('sha256')
|
|
210
|
+
.update(
|
|
211
|
+
JSON.stringify({
|
|
212
|
+
requestId: config.requestId,
|
|
213
|
+
role: config.role,
|
|
214
|
+
workspaceId: config.workspaceId,
|
|
215
|
+
task: config.task,
|
|
216
|
+
title: config.title,
|
|
217
|
+
handle: config.handle ?? null,
|
|
218
|
+
displayName: config.displayName ?? null,
|
|
219
|
+
adapterName: config.adapterName,
|
|
220
|
+
targetDaemonId: config.targetDaemonId ?? null,
|
|
221
|
+
}),
|
|
222
|
+
)
|
|
223
|
+
.digest('hex');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function taskIdempotencyKey(requestId) {
|
|
227
|
+
return `role-task-${createHash('sha256').update(requestId).digest('hex')}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function requestJson(config, method, path, body, stage) {
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timer = setTimeout(() => controller.abort(), Math.min(config.timeoutMs, 120_000));
|
|
233
|
+
let response;
|
|
234
|
+
try {
|
|
235
|
+
response = await fetch(`${config.baseUrl}${path}`, {
|
|
236
|
+
method,
|
|
237
|
+
headers: {
|
|
238
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
239
|
+
'content-type': 'application/json',
|
|
240
|
+
...(body?.idempotencyKey ? { 'x-idempotency-key': body.idempotencyKey } : {}),
|
|
241
|
+
},
|
|
242
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
243
|
+
signal: controller.signal,
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const message = error?.name === 'AbortError' ? 'request timed out' : error?.message || String(error);
|
|
247
|
+
throw new HarnessError(stage, 'NETWORK_ERROR', message, 4);
|
|
248
|
+
} finally {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
}
|
|
251
|
+
const payload = await response.json().catch(() => null);
|
|
252
|
+
if (!response.ok || payload?.ok === false) {
|
|
253
|
+
const serverError = payload?.error;
|
|
254
|
+
throw new HarnessError(
|
|
255
|
+
stage,
|
|
256
|
+
serverError?.code || `HTTP_${response.status}`,
|
|
257
|
+
serverError?.message || serverError || `request failed with HTTP ${response.status}`,
|
|
258
|
+
response.status === 401 || response.status === 403 ? 3 : response.status >= 500 ? 4 : 5,
|
|
259
|
+
{ status: response.status, retryable: serverError?.retryable === true },
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return { status: response.status, data: payload?.data ?? payload };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function sleep(ms) {
|
|
266
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
267
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-authoring
|
|
3
|
+
scope: common
|
|
4
|
+
description: Low-level helper for the DRAFT-GATED (review) tier of skill authoring — it is NOT the entry point for creating skills. To make/build/author a skill, use `skill-creator` (the single canonical skill author). This page only documents skill-creator's draft tier — generating a `status=draft` skill via `POST /api/im/skills/draft` (`cloud skill draft create`) for human review in Studio Authoring, plus the skill.json + sampleTasks/acceptanceCriteria test contract. Do NOT route here when the user wants to create a skill — route to skill-creator and add `--draft` only when human review is required.
|
|
5
|
+
license: MIT
|
|
6
|
+
compatibility:
|
|
7
|
+
- prismer-sdk
|
|
8
|
+
- hermes
|
|
9
|
+
- claude-code
|
|
10
|
+
- codex
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Skill Authoring — the draft-gated tier of `skill-creator`
|
|
14
|
+
|
|
15
|
+
> **Not an authoring entry point.** Creating or editing a skill is the job of
|
|
16
|
+
> **`skill-creator`** — the single canonical skill author. This page only
|
|
17
|
+
> documents one of skill-creator's two publish trust tiers (**draft-gated**),
|
|
18
|
+
> for the case where a skill must be reviewed by a human before it goes live.
|
|
19
|
+
> If the user says "make this a skill" / "create a skill for X", **use
|
|
20
|
+
> `skill-creator`**, not this.
|
|
21
|
+
|
|
22
|
+
## Where this fits
|
|
23
|
+
|
|
24
|
+
`skill-creator` drives the full lifecycle author→validate→test→package→publish.
|
|
25
|
+
At the publish step there are two trust tiers ([[16-skill-role-authoring-as-agent-capability]] §5.3):
|
|
26
|
+
|
|
27
|
+
- **Direct create (default)** — `cloud skill create ./<dir>` → `status=active`,
|
|
28
|
+
`publishScope=workspace` (**private**, installable — NOT the public marketplace).
|
|
29
|
+
Putting it in the marketplace is a separate `cloud skill publish`. Under `skill-builder`.
|
|
30
|
+
- **Draft-gated (explicit, this page)** — `cloud skill draft create` →
|
|
31
|
+
`status=draft`, Studio Authoring reviews and promotes it. Opt into this **only
|
|
32
|
+
when human review is required** — the default is direct publish (decision 4,
|
|
33
|
+
locked 2026-06-24).
|
|
34
|
+
|
|
35
|
+
Both tiers are reached from inside `skill-creator`; this is a publish-verb
|
|
36
|
+
choice, not a separate skill to author in.
|
|
37
|
+
|
|
38
|
+
## Draft submission (the review-gated path)
|
|
39
|
+
|
|
40
|
+
When the workflow requires human review before a skill is live, submit a draft
|
|
41
|
+
instead of publishing directly:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cloud skill draft create ./<bundle-dir> # optional: --workspace-id <id> --json
|
|
45
|
+
# → POST /api/im/skills/draft. Cloud reads the directory, runs the 7 validation
|
|
46
|
+
# gates, and returns { id, slug, status: draft }. Does NOT publish.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> ⚠️ **`<bundle-dir>` is a directory** holding `SKILL.md` + `skill.json` (+ an
|
|
50
|
+
> optional `scripts/`). The CLI reads and packages the directory on every run, so
|
|
51
|
+
> edits on disk take effect immediately — there is no separate manifest to
|
|
52
|
+
> regenerate. There is **no** `--slug` / `--manifest` flag; the slug is taken from
|
|
53
|
+
> the SKILL.md frontmatter `name`.
|
|
54
|
+
|
|
55
|
+
The draft lands in `status=draft`; the workspace owner reviews and promotes it
|
|
56
|
+
via Studio Authoring (`/evolution → Studio → Authoring`). Do **not** auto-publish
|
|
57
|
+
or chain into install — promotion is the owner's call.
|
|
58
|
+
|
|
59
|
+
> Do NOT call `POST /api/im/skills` (direct publish) when the intent is review-gated —
|
|
60
|
+
> that bypasses the draft state. Use `--draft` deliberately.
|
|
61
|
+
|
|
62
|
+
## `skill.json` — where security + the test contract live
|
|
63
|
+
|
|
64
|
+
**Read this first — it is the #1 draft-tier gotcha.** The bundle must contain a
|
|
65
|
+
`skill.json` alongside `SKILL.md` (cloud locates each file by name, so a
|
|
66
|
+
`scripts/` dir sorted in between is fine). Cloud reads `security` and the test
|
|
67
|
+
contract from **`skill.json`**, and **NEVER** from the `SKILL.md` YAML
|
|
68
|
+
frontmatter. If you put `security:` / `dataAccess:` in the
|
|
69
|
+
SKILL.md frontmatter, gate 5 still fails with "security.dataAccess must be
|
|
70
|
+
non-empty" no matter how you format the YAML — it is reading a different file.
|
|
71
|
+
|
|
72
|
+
### `security` (blocking — gate 5)
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"schemaVersion": 1,
|
|
77
|
+
"slug": "<slug>",
|
|
78
|
+
"security": {
|
|
79
|
+
"dataAccess": ["workspace-assets"],
|
|
80
|
+
"humanApprovalRequiredFor": []
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **`security.dataAccess`** — non-empty array. Scopes: `workspace-assets` |
|
|
86
|
+
`memory` | `external-network` | `secrets` | `filesystem`.
|
|
87
|
+
- **`security.humanApprovalRequiredFor`** — **required non-empty IF** `dataAccess`
|
|
88
|
+
contains a **sensitive** scope (`external-network`, `secrets`, `filesystem`).
|
|
89
|
+
List the actions needing human approval (e.g. `["filesystem-write"]`).
|
|
90
|
+
- **Least privilege:** if the skill only reads env + prints (no arbitrary user
|
|
91
|
+
files), declare `["memory"]` — a non-sensitive scope — and
|
|
92
|
+
`humanApprovalRequiredFor` may stay empty. Only reach for `filesystem` when the
|
|
93
|
+
skill genuinely reads/writes user files, and then you must also declare
|
|
94
|
+
approval.
|
|
95
|
+
|
|
96
|
+
### test contract (drives `cloud skill test`)
|
|
97
|
+
|
|
98
|
+
The same `skill.json` also carries the VERIFICATION contract exercised by
|
|
99
|
+
`cloud skill test --agent` in lifecycle step ③:
|
|
100
|
+
|
|
101
|
+
- `runtime.kind` = `'inline-script'` (or `'http-endpoint'`), `runtime.requires`
|
|
102
|
+
declaring `bins`/`env` the script needs.
|
|
103
|
+
- `inputs` / `outputs` describing the call contract.
|
|
104
|
+
- **`sampleTasks[]`** — at least 2 concrete tasks. EACH MUST have
|
|
105
|
+
`acceptanceCriteria[]` written as substrings/regex the dispatch OUTPUT must
|
|
106
|
+
contain (e.g. `"\"status\":\\s*200"`, `"results"`). These are the auto-scored
|
|
107
|
+
tests: `cloud skill test` dispatches each sampleTask to a real agent and
|
|
108
|
+
matches its output against the criteria. A sampleTask with no
|
|
109
|
+
acceptanceCriteria scores `inconclusive` (NOT a pass), so always write them.
|
|
110
|
+
|
|
111
|
+
> Derive acceptanceCriteria from the spec: required response fields, status
|
|
112
|
+
> codes, schema keys. Tight enough to catch a broken call, not so tight they
|
|
113
|
+
> depend on volatile data.
|
|
114
|
+
|
|
115
|
+
## Draft validation gates (self-check before submit)
|
|
116
|
+
|
|
117
|
+
The cloud server runs these at `createDraft` time and rejects with HTTP 400 on a
|
|
118
|
+
blocking failure. Run the same checks locally first (or just `cloud skill validate ./<dir>`):
|
|
119
|
+
|
|
120
|
+
| Gate | Check | Blocking |
|
|
121
|
+
|----------------|------------------------------------------------------------------|----------|
|
|
122
|
+
| `manifest` | files[] complete; merkle root reproducible | yes |
|
|
123
|
+
| `frontmatter` | `name` matches `^[a-z][a-z0-9-]*$`; description ≥ 50 quality units (ASCII=1, non-ASCII=2); YAML `|`/`>` supported | yes |
|
|
124
|
+
| `package` | bundle contains SKILL.md + skill.json (located by name) | yes |
|
|
125
|
+
| `requires` | runtime.requires declares env/bins/python/node explicitly | warn |
|
|
126
|
+
| `security` | **skill.json** security.dataAccess non-empty; sensitive scopes (external-network/secrets/filesystem) require security.humanApprovalRequiredFor. NOT read from SKILL.md frontmatter. | yes |
|
|
127
|
+
| `sample` | at least 1 sampleTask + 1 acceptance criterion | warn |
|
|
128
|
+
| `runtime` | sandbox executes sample task | deferred |
|
|
129
|
+
|
|
130
|
+
## Reference
|
|
131
|
+
|
|
132
|
+
- The skill author (use this): `built-in-skills/skill-creator/SKILL.md`
|
|
133
|
+
- Direct-publish tier: `built-in-skills/skill-builder/SKILL.md`
|
|
134
|
+
- Bundle + catalog contract: `public/docs/Standardization/01-skill-standard.md` (SS-01)
|