@sema-agent/core 7.54.1 → 7.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +437 -0
- package/dist/agents/agent-transcript-tool.js +2 -1
- package/dist/agents/builtin-agents.d.ts +12 -14
- package/dist/agents/cc-peer-directory.d.ts +36 -0
- package/dist/agents/cc-peer-directory.js +138 -0
- package/dist/agents/cc-peer-wire.d.ts +105 -0
- package/dist/agents/cc-peer-wire.js +98 -0
- package/dist/agents/cc-uds-peer-channel.d.ts +40 -0
- package/dist/agents/cc-uds-peer-channel.js +457 -0
- package/dist/agents/cross-session-envelope.d.ts +4 -1
- package/dist/agents/cross-session-envelope.js +8 -1
- package/dist/agents/cross-session-judge.d.ts +2 -2
- package/dist/agents/list-agents-tool.js +2 -1
- package/dist/agents/observer.d.ts +4 -5
- package/dist/agents/observer.js +2 -1
- package/dist/agents/peer-admission.d.ts +6 -0
- package/dist/agents/peer-admission.js +4 -1
- package/dist/agents/peer-directory.d.ts +43 -9
- package/dist/agents/peer-directory.js +66 -23
- package/dist/agents/peer-notices.d.ts +1 -1
- package/dist/agents/peer-session-drain.d.ts +3 -1
- package/dist/agents/peer-session-drain.js +1 -0
- package/dist/agents/read-inbox-tool.js +2 -1
- package/dist/agents/retain-ledger.d.ts +1 -1
- package/dist/agents/roster-store.d.ts +1 -1
- package/dist/agents/send-message-tool.d.ts +10 -0
- package/dist/agents/send-message-tool.js +34 -5
- package/dist/agents/subagent.d.ts +34 -79
- package/dist/agents/subagent.js +45 -46
- package/dist/agents/tool-filter.d.ts +29 -46
- package/dist/agents/tool-filter.js +8 -31
- package/dist/agents/verify.d.ts +3 -3
- package/dist/brain/anthropic.js +14 -9
- package/dist/brain/errors.d.ts +15 -0
- package/dist/brain/errors.js +10 -0
- package/dist/brain/model-presets.d.ts +4 -4
- package/dist/brain/open-responses.js +15 -10
- package/dist/brain/openai.js +17 -12
- package/dist/brain/provider-dialect.d.ts +39 -3
- package/dist/brain/provider-dialect.js +28 -0
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/request-params.d.ts +55 -5
- package/dist/brain/request-params.js +35 -3
- package/dist/brain/retry.d.ts +1 -1
- package/dist/brain/route-adjudicator.d.ts +15 -0
- package/dist/brain/route-adjudicator.js +15 -0
- package/dist/brain/stream-engine.js +6 -5
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/advisor-verdict.d.ts +120 -0
- package/dist/core/advisor-verdict.js +92 -0
- package/dist/core/agent-definition.d.ts +7 -8
- package/dist/core/allow-entry.d.ts +49 -23
- package/dist/core/allow-entry.js +57 -32
- package/dist/core/allow-rule-shadow.d.ts +7 -0
- package/dist/core/approval-object.d.ts +2 -1
- package/dist/core/approval-object.js +6 -6
- package/dist/core/ask-origin.d.ts +63 -11
- package/dist/core/ask-origin.js +27 -5
- package/dist/core/ask-question.js +10 -4
- package/dist/core/auto-compaction.d.ts +11 -8
- package/dist/core/auto-compaction.js +4 -2
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/auto-mode-defaults.d.ts +8 -8
- package/dist/core/auto-mode-prompt-assets.d.ts +3 -3
- package/dist/core/auto-mode-prompt.d.ts +1 -1
- package/dist/core/auto-mode.d.ts +17 -9
- package/dist/core/auto-mode.js +9 -6
- package/dist/core/background-agent-store.d.ts +3 -3
- package/dist/core/canonical-json.d.ts +1 -1
- package/dist/core/canonical-json.js +2 -2
- package/dist/core/checkpoint-requirements.d.ts +6 -3
- package/dist/core/checkpoint-requirements.js +4 -14
- package/dist/core/checkpoint-store.d.ts +80 -51
- package/dist/core/checkpoint-store.js +56 -74
- package/dist/core/child-clamp-seats.d.ts +6 -0
- package/dist/core/child-clamp-seats.js +3 -16
- package/dist/core/compliance.d.ts +2 -2
- package/dist/core/context-edit.d.ts +6 -7
- package/dist/core/cross-boundary-taint.d.ts +9 -3
- package/dist/core/cross-boundary-taint.js +5 -3
- package/dist/core/decision-receipt.js +2 -2
- package/dist/core/effective-path-target.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +19 -5
- package/dist/core/exec-output-tail.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +1 -1
- package/dist/core/file-history-store.d.ts +59 -9
- package/dist/core/file-history-store.js +35 -17
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-lanes.js +12 -8
- package/dist/core/gate-outcome.d.ts +17 -4
- package/dist/core/gate-outcome.js +30 -0
- package/dist/core/governance-codes.d.ts +13 -3
- package/dist/core/governance-codes.js +10 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/host-target.d.ts +49 -0
- package/dist/core/host-target.js +59 -0
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/lsp-diagnostics.d.ts +4 -4
- package/dist/core/lsp.d.ts +3 -3
- package/dist/core/mailbox-store.d.ts +4 -0
- package/dist/core/mcp-connection-pool.d.ts +18 -2
- package/dist/core/mcp-connection-pool.js +53 -6
- package/dist/core/mcp-failure.d.ts +23 -4
- package/dist/core/mcp-failure.js +26 -11
- package/dist/core/mcp-listing-drift.d.ts +10 -0
- package/dist/core/mcp-listing-drift.js +6 -0
- package/dist/core/mcp-server-link.d.ts +173 -0
- package/dist/core/mcp-server-link.js +127 -0
- package/dist/core/mcp-server-spec.d.ts +4 -2
- package/dist/core/mcp-session-end.d.ts +26 -0
- package/dist/core/mcp-session-end.js +29 -0
- package/dist/core/mcp-startup-request.d.ts +17 -0
- package/dist/core/mcp-startup-request.js +17 -0
- package/dist/core/mcp.d.ts +13 -13
- package/dist/core/mcp.js +187 -162
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +9 -9
- package/dist/core/memory-engine/provenance-wording.d.ts +1 -1
- package/dist/core/memory-engine/tools.js +4 -1
- package/dist/core/mode-policy.d.ts +1 -1
- package/dist/core/namespaced-rule-name.d.ts +33 -7
- package/dist/core/namespaced-rule-name.js +39 -25
- package/dist/core/output-style.d.ts +1 -1
- package/dist/core/param-rule-reach.d.ts +15 -0
- package/dist/core/param-rule-reach.js +30 -0
- package/dist/core/parent-spec-seat.d.ts +8 -1
- package/dist/core/parent-spec-seat.js +4 -0
- package/dist/core/pause-registry.d.ts +16 -5
- package/dist/core/pause-registry.js +6 -2
- package/dist/core/peer-channel.d.ts +70 -0
- package/dist/core/peer-channel.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +11 -5
- package/dist/core/permission-rule-consent.js +37 -30
- package/dist/core/permission-rule-model.d.ts +233 -64
- package/dist/core/permission-rule-model.js +384 -85
- package/dist/core/permission-rule-provider.d.ts +2 -1
- package/dist/core/permission-rule-store.d.ts +20 -25
- package/dist/core/permission-rule-store.js +1 -4
- package/dist/core/permission-rule-sync.d.ts +11 -2
- package/dist/core/permission-rule-sync.js +8 -1
- package/dist/core/permission-rules.d.ts +49 -11
- package/dist/core/permission-rules.js +123 -89
- package/dist/core/persisted-rule-arms.d.ts +4 -2
- package/dist/core/present-plan-tool.js +3 -1
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/read-only-shell-table.d.ts +1 -1
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/roles.d.ts +6 -2
- package/dist/core/roles.js +45 -44
- package/dist/core/rule-offer-read.d.ts +53 -0
- package/dist/core/rule-offer-read.js +146 -0
- package/dist/core/rule-owner.d.ts +46 -0
- package/dist/core/rule-owner.js +28 -0
- package/dist/core/runner/advisor-instructions.d.ts +70 -0
- package/dist/core/runner/advisor-instructions.js +61 -0
- package/dist/core/runner/advisor-tool.d.ts +33 -0
- package/dist/core/runner/advisor-tool.js +112 -0
- package/dist/core/runner/announce-once-ledger.d.ts +4 -26
- package/dist/core/runner/announce-once-ledger.js +14 -46
- package/dist/core/runner/approval-feedback.js +6 -3
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/caller-roster.d.ts +7 -0
- package/dist/core/runner/caller-roster.js +11 -0
- package/dist/core/runner/classifier-ladder.d.ts +27 -5
- package/dist/core/runner/classifier-ladder.js +12 -11
- package/dist/core/runner/compaction-restate.d.ts +10 -6
- package/dist/core/runner/compaction-restate.js +9 -3
- package/dist/core/runner/contracts.d.ts +13 -9
- package/dist/core/runner/conversation-segment.d.ts +28 -0
- package/dist/core/runner/conversation-segment.js +42 -0
- package/dist/core/runner/cross-boundary-lane.d.ts +6 -5
- package/dist/core/runner/cross-boundary-lane.js +26 -15
- package/dist/core/runner/decide-continuation.d.ts +7 -4
- package/dist/core/runner/decide-continuation.js +2 -2
- package/dist/core/runner/denial-limit-arms.d.ts +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +24 -24
- package/dist/core/runner/derived-route-fallback.js +9 -7
- package/dist/core/runner/gate-exit.d.ts +6 -0
- package/dist/core/runner/gate-exit.js +7 -2
- package/dist/core/runner/initial-run-state.js +1 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +14 -7
- package/dist/core/runner/person-path-rows.js +1 -1
- package/dist/core/runner/prepare-advisor.d.ts +63 -0
- package/dist/core/runner/prepare-advisor.js +77 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -4
- package/dist/core/runner/prepare-config-doors.js +18 -12
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.js +1 -1
- package/dist/core/runner/prepare-file-history.js +39 -39
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -4
- package/dist/core/runner/prepare-gate-stations.js +2 -7
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -4
- package/dist/core/runner/prepare-inherited-gate.js +3 -3
- package/dist/core/runner/prepare-park-ask.js +7 -7
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +4 -10
- package/dist/core/runner/prepare-project-context.d.ts +5 -2
- package/dist/core/runner/prepare-project-context.js +6 -3
- package/dist/core/runner/prepare-prompt-inputs.js +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-safety-scan.d.ts +9 -12
- package/dist/core/runner/prepare-safety-scan.js +8 -6
- package/dist/core/runner/prepare-task.js +15 -18
- package/dist/core/runner/prepare-tool-disclosure-mount.js +6 -4
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -3
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/resume-policy-outcome.js +1 -1
- package/dist/core/runner/retired-runner-deps.js +1 -1
- package/dist/core/runner/run-attachment-seats.js +0 -1
- package/dist/core/runner/run-compaction-machinery.d.ts +1 -1
- package/dist/core/runner/run-compaction-machinery.js +2 -12
- package/dist/core/runner/run-git-lane.d.ts +1 -0
- package/dist/core/runner/run-harness-handlers.js +2 -3
- package/dist/core/runner/run-leg.js +13 -0
- package/dist/core/runner/run-settle-and-teardown.js +1 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.d.ts +1 -0
- package/dist/core/runner/run-turn-boundary.js +14 -12
- package/dist/core/runner/session-rule-policy.d.ts +22 -21
- package/dist/core/runner/session-rule-policy.js +13 -28
- package/dist/core/runner/side-call-account.d.ts +32 -8
- package/dist/core/runner/side-call-account.js +23 -1
- package/dist/core/runner/strict-output-schema.d.ts +1 -1
- package/dist/core/runner/sub-agent-deny-lists.js +4 -3
- package/dist/core/runner/synthetic-tools.d.ts +10 -13
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/tool-face-overlay.d.ts +2 -1
- package/dist/core/runner/tool-face-overlay.js +17 -1
- package/dist/core/runner/tool-progress-lane.d.ts +2 -2
- package/dist/core/runner/tool-turn-repair-seat.d.ts +3 -2
- package/dist/core/runner/turn-attachments.d.ts +60 -67
- package/dist/core/runner-deps.d.ts +37 -37
- package/dist/core/runtime-caps.d.ts +4 -4
- package/dist/core/safety-merge-corpus.js +17 -1
- package/dist/core/same-plain-value.d.ts +40 -0
- package/dist/core/same-plain-value.js +49 -0
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/session-policy-store.d.ts +77 -22
- package/dist/core/session-policy-store.js +56 -14
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +80 -30
- package/dist/core/session-store.js +118 -76
- package/dist/core/session.d.ts +24 -46
- package/dist/core/shared-memory/tools.js +3 -1
- package/dist/core/shared-memory/types.d.ts +1 -1
- package/dist/core/side-query.d.ts +10 -0
- package/dist/core/side-query.js +5 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +29 -2
- package/dist/core/store-contracts/file-history-store-contract.js +43 -3
- package/dist/core/store-contracts/session-policy-store-contract.d.ts +7 -0
- package/dist/core/store-contracts/session-policy-store-contract.js +130 -0
- package/dist/core/store-contracts/session-repo-contract.js +125 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +4 -7
- package/dist/core/store-contracts/session-store-contract.js +215 -41
- package/dist/core/store-contracts/workflow-journal-store-contract.js +26 -1
- package/dist/core/surrogate-safe-slice.d.ts +3 -3
- package/dist/core/task-event.d.ts +10 -4
- package/dist/core/task-notification.d.ts +3 -3
- package/dist/core/task-registry-agent.d.ts +4 -4
- package/dist/core/task-registry-shared.d.ts +4 -4
- package/dist/core/task-registry-shared.js +1 -0
- package/dist/core/task-registry.d.ts +4 -4
- package/dist/core/task-registry.js +3 -1
- package/dist/core/task-result.d.ts +3 -3
- package/dist/core/task-spec.d.ts +52 -52
- package/dist/core/task-stream.d.ts +6 -6
- package/dist/core/task-tool-shape.d.ts +4 -4
- package/dist/core/tighten-task-spec.d.ts +1 -1
- package/dist/core/tool-catalog-entries.d.ts +19 -2
- package/dist/core/tool-catalog-entries.js +57 -42
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-face-reader.js +5 -3
- package/dist/core/tool-face.d.ts +21 -2
- package/dist/core/tool-face.js +1 -1
- package/dist/core/tool-model-gate.d.ts +3 -3
- package/dist/core/tool-name-aliases.d.ts +29 -8
- package/dist/core/tool-name-aliases.js +27 -3
- package/dist/core/tool-name-readings.d.ts +5 -8
- package/dist/core/tool-name-readings.js +1 -2
- package/dist/core/tool-policy.d.ts +24 -5
- package/dist/core/tool-policy.js +34 -10
- package/dist/core/tool-registry.d.ts +14 -7
- package/dist/core/tool-registry.js +17 -7
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/tool-roster.d.ts +47 -18
- package/dist/core/tool-roster.js +53 -32
- package/dist/core/tool-spec-members.js +1 -1
- package/dist/core/tool-spec.d.ts +35 -21
- package/dist/core/tools.js +3 -2
- package/dist/core/trace.d.ts +4 -0
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/warm-resume.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +49 -0
- package/dist/core/wiring-manifest.js +13 -5
- package/dist/core/workflow-journal-store.d.ts +62 -0
- package/dist/core/workflow-journal-store.js +77 -1
- package/dist/core/workflow-run-store.d.ts +6 -0
- package/dist/core/workflow-run-store.js +2 -0
- package/dist/engine/compaction/compaction.d.ts +9 -9
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -5
- package/dist/engine/execution-env/node-execution-env.js +43 -22
- package/dist/engine/harness/agent-harness.d.ts +14 -8
- package/dist/engine/harness/agent-harness.js +33 -26
- package/dist/engine/harness/types.d.ts +29 -7
- package/dist/engine/harness/types.js +12 -1
- package/dist/engine/llm/types.d.ts +9 -9
- package/dist/engine/llm/validation.d.ts +4 -1
- package/dist/engine/llm/validation.js +13 -1
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +10 -8
- package/dist/engine/loop/types.d.ts +15 -13
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/memory-repo.d.ts +10 -11
- package/dist/engine/session/memory-repo.js +13 -8
- package/dist/engine/session/parent-chain-guard.d.ts +3 -2
- package/dist/engine/session/parent-chain-guard.js +1 -1
- package/dist/engine/session/repo-utils.d.ts +31 -3
- package/dist/engine/session/repo-utils.js +42 -5
- package/dist/index.d.ts +27 -15
- package/dist/index.js +21 -11
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/harness.d.ts +3 -3
- package/dist/internal/harness.js +2 -2
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/run-workflow-tool.js +5 -3
- package/dist/orchestration/workflow-size-guideline.d.ts +4 -4
- package/dist/orchestration/workflow-types.d.ts +18 -0
- package/dist/orchestration/workflow-types.js +6 -0
- package/dist/orchestration/workflow.d.ts +2 -13
- package/dist/orchestration/workflow.js +21 -31
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompt-assembly/tool-catalog.d.ts +3 -0
- package/dist/prompt-assembly/tool-catalog.js +3 -0
- package/dist/prompts/default.d.ts +14 -14
- package/dist/scenarios/full-body.d.ts +4 -4
- package/dist/stores/file/adoption/adopt.d.ts +4 -3
- package/dist/stores/file/adoption/adopt.js +24 -31
- package/dist/stores/file/adoption/marker.d.ts +6 -13
- package/dist/stores/file/adoption/marker.js +6 -15
- package/dist/stores/file/checkpoint-store.d.ts +3 -0
- package/dist/stores/file/checkpoint-store.js +9 -3
- package/dist/stores/file/file-history-store.d.ts +20 -19
- package/dist/stores/file/file-history-store.js +152 -135
- package/dist/stores/file/fs-atomic.d.ts +15 -0
- package/dist/stores/file/fs-atomic.js +89 -14
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/mailbox-store.d.ts +3 -0
- package/dist/stores/file/mailbox-store.js +3 -1
- package/dist/stores/file/permission-rule-adopt.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +15 -16
- package/dist/stores/file/permission-rule-store.d.ts +18 -2
- package/dist/stores/file/permission-rule-store.js +21 -14
- package/dist/stores/file/session-policy-store.d.ts +3 -3
- package/dist/stores/file/session-store.d.ts +26 -11
- package/dist/stores/file/session-store.js +77 -40
- package/dist/stores/file/workflow-journal-store.d.ts +10 -4
- package/dist/stores/file/workflow-journal-store.js +28 -13
- package/dist/tools/artifact/artifact-text.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +2 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -3
- package/dist/tools/fs/fs-bash.d.ts +2 -2
- package/dist/tools/fs/fs-bash.js +3 -1
- package/dist/tools/fs/fs-shared.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
- package/dist/tools/fs/notebook.d.ts +2 -2
- package/dist/tools/fs/read-deny.d.ts +29 -7
- package/dist/tools/fs/read-deny.js +63 -22
- package/dist/tools/fs/removal-targets.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +6 -6
- package/dist/tools/fs/shell-read-check.js +6 -1
- package/dist/tools/fs/shell-read-judge.d.ts +4 -0
- package/dist/tools/fs/shell-read-judge.js +5 -2
- package/dist/tools/loop-tick.d.ts +2 -2
- package/dist/tools/scheduler-tools.js +5 -1
- package/dist/tools/task-list.d.ts +7 -7
- package/dist/tools/task-list.js +5 -1
- package/dist/tools/todo.js +2 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +40 -37
- package/dist/tools/worktree.d.ts +3 -3
- package/dist/tools/worktree.js +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5756 -5638
- package/wire-changes/core@7.55.0.json +3318 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +0 -39
- package/dist/core/store-contracts/session-store-incarnation-contract.js +0 -95
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer, Socket } from "node:net";
|
|
3
|
+
import { chmod, lstat, mkdir, readdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
5
|
+
import { CrossSessionCodecError, decodeCcPeerFrame } from "./cross-session-envelope.js";
|
|
6
|
+
import { peerSendVerdictSeverity } from "./cross-session-judge.js";
|
|
7
|
+
import { peerAxisToken, peerWireHopToken, resolvePeerLaneConfig } from "./peer-admission.js";
|
|
8
|
+
import { judgePeerRecordFile, peerSessionBoxHandle, vetPeerRegistryDirectory } from "./peer-directory.js";
|
|
9
|
+
import { defaultCcProcessProbe } from "./cc-peer-directory.js";
|
|
10
|
+
import { CC_PEER_FIRST_LINE_DEADLINE_MS, CC_PEER_KEY_FILE_MAX_BYTES, CC_PEER_KEY_FILE_RE, CC_PEER_LINE_MAX_CHARS, CC_PEER_SEND_TIMEOUT_MS, CC_PEER_SOCKET_PATH_MAX_BYTES, CC_PEER_TOKEN_RE, ccPeerAddress, ccPeerKeyFileName, ccPeerSocketDigest, ccRegistrationRow, encodeCcAuthLine, encodeCcNudgeLine, encodeCcUserLine, parseCcPeerLine, } from "./cc-peer-wire.js";
|
|
11
|
+
export class CcPeerChannelError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
path;
|
|
14
|
+
constructor(code, message, path) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "CcPeerChannelError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
if (path !== undefined)
|
|
19
|
+
this.path = path;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const LISTENING_SLOTS = new Set();
|
|
23
|
+
const NUDGE_TIMEOUT_MS = 1_000;
|
|
24
|
+
const STALE_PROBE_MS = 250;
|
|
25
|
+
function errCode(e) {
|
|
26
|
+
const c = e?.code;
|
|
27
|
+
return typeof c === "string" ? c : undefined;
|
|
28
|
+
}
|
|
29
|
+
async function writeFileAtomic0600(path, text) {
|
|
30
|
+
const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
|
|
31
|
+
try {
|
|
32
|
+
await writeFile(tmp, text, { mode: 0o600, flag: "wx" });
|
|
33
|
+
await rename(tmp, path);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
await unlink(tmp).catch(() => undefined);
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function probeSocket(path, ms) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const s = new Socket();
|
|
43
|
+
const done = (answer) => {
|
|
44
|
+
s.destroy();
|
|
45
|
+
resolve(answer);
|
|
46
|
+
};
|
|
47
|
+
s.setTimeout(ms, () => done("unknown"));
|
|
48
|
+
s.once("connect", () => done("live"));
|
|
49
|
+
s.once("error", (e) => done(errCode(e) === "ECONNREFUSED" ? "stale" : "unknown"));
|
|
50
|
+
s.connect({ path });
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function createCcUdsPeerChannel(opts) {
|
|
54
|
+
const platform = opts.platform ?? process.platform;
|
|
55
|
+
const pid = opts.pid ?? process.pid;
|
|
56
|
+
const probe = opts.probe ?? defaultCcProcessProbe;
|
|
57
|
+
const now = opts.now ?? Date.now;
|
|
58
|
+
const uid = opts.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined);
|
|
59
|
+
const vetOpts = { ...(uid !== undefined ? { uid } : {}), platform };
|
|
60
|
+
let listening;
|
|
61
|
+
const ensureVettedDir = async (dir) => {
|
|
62
|
+
try {
|
|
63
|
+
await lstat(dir);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (errCode(e) !== "ENOENT")
|
|
67
|
+
throw e;
|
|
68
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
const verdict = await vetPeerRegistryDirectory(dir, vetOpts);
|
|
71
|
+
if (!verdict.ok)
|
|
72
|
+
throw new CcPeerChannelError(verdict.code, `the directory ${verdict.path} is not one this engine can trust (${verdict.code}) — the socket lane was not opened`, verdict.path);
|
|
73
|
+
};
|
|
74
|
+
const vetPeerSocket = async (address) => {
|
|
75
|
+
if (platform === "win32")
|
|
76
|
+
return { ok: false, code: "peer_send.not_reachable", text: "the reference agent's socket lane is not available on this platform" };
|
|
77
|
+
if (!isAbsolute(address) || Buffer.byteLength(address) > CC_PEER_SOCKET_PATH_MAX_BYTES)
|
|
78
|
+
return { ok: false, code: "peer_send.invalid_target", text: `${JSON.stringify(address.slice(0, 120))} is not a usable local socket address` };
|
|
79
|
+
const verdict = await vetPeerRegistryDirectory(dirname(address), vetOpts);
|
|
80
|
+
if (!verdict.ok)
|
|
81
|
+
return { ok: false, code: "peer_send.invalid_target", text: `the peer's socket directory is not one this engine trusts (${verdict.code}: ${verdict.path})` };
|
|
82
|
+
let st;
|
|
83
|
+
try {
|
|
84
|
+
st = await lstat(address);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { ok: false, code: "peer_send.not_reachable", text: "no socket exists at that address (the session is gone)" };
|
|
88
|
+
}
|
|
89
|
+
if (st.isSymbolicLink())
|
|
90
|
+
return { ok: false, code: "peer_send.invalid_target", text: "the peer's socket address is a link" };
|
|
91
|
+
if (!st.isSocket())
|
|
92
|
+
return { ok: false, code: "peer_send.invalid_target", text: "the peer's address is not a socket" };
|
|
93
|
+
return { ok: true };
|
|
94
|
+
};
|
|
95
|
+
const readPeerToken = async (address) => {
|
|
96
|
+
const digest = ccPeerSocketDigest(address);
|
|
97
|
+
let names;
|
|
98
|
+
try {
|
|
99
|
+
names = await readdir(opts.registryDir);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const candidates = names
|
|
105
|
+
.map((n) => ({ n, m: CC_PEER_KEY_FILE_RE.exec(n) }))
|
|
106
|
+
.filter((c) => c.m !== null && c.m[2] === digest)
|
|
107
|
+
.map((c) => ({ file: c.n, pid: Number.parseInt(c.m[1], 10) }))
|
|
108
|
+
.sort((a, b) => Number(probe.processExists(b.pid)) - Number(probe.processExists(a.pid)));
|
|
109
|
+
for (const c of candidates) {
|
|
110
|
+
const path = join(opts.registryDir, c.file);
|
|
111
|
+
try {
|
|
112
|
+
const st = await lstat(path);
|
|
113
|
+
if (!judgePeerRecordFile({ uid: st.uid, mode: st.mode, isFile: st.isFile(), size: st.size }, uid, platform).ok || st.size > CC_PEER_KEY_FILE_MAX_BYTES)
|
|
114
|
+
continue;
|
|
115
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
116
|
+
const token = parsed !== null && typeof parsed === "object" ? parsed["peerToken"] : undefined;
|
|
117
|
+
if (typeof token === "string" && CC_PEER_TOKEN_RE.test(token))
|
|
118
|
+
return token;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
};
|
|
125
|
+
const send = (address, payload, timeoutMs) => new Promise((resolve) => {
|
|
126
|
+
const sock = new Socket();
|
|
127
|
+
let written = false;
|
|
128
|
+
let settled = false;
|
|
129
|
+
const settle = (v) => {
|
|
130
|
+
if (settled)
|
|
131
|
+
return;
|
|
132
|
+
settled = true;
|
|
133
|
+
sock.destroy();
|
|
134
|
+
resolve(v);
|
|
135
|
+
};
|
|
136
|
+
sock.setTimeout(timeoutMs, () => settle(written ? { ok: false, code: "peer_send.send_uncertain", text: "the frame was written but the connection did not close in time — it may have arrived" } : { ok: false, code: "peer_send.timeout", text: `the peer did not accept the connection within ${timeoutMs} ms` }));
|
|
137
|
+
sock.once("error", (e) => {
|
|
138
|
+
const c = errCode(e);
|
|
139
|
+
if (written)
|
|
140
|
+
return settle({ ok: false, code: "peer_send.send_uncertain", text: `the frame was written, then the connection failed (${c ?? "error"}) — it may have arrived` });
|
|
141
|
+
if (c === "ENOENT")
|
|
142
|
+
return settle({ ok: false, code: "peer_send.not_reachable", text: "no socket exists at that address (the session is gone)" });
|
|
143
|
+
if (c === "ECONNREFUSED")
|
|
144
|
+
return settle({ ok: false, code: "peer_send.stale_socket", text: "the socket file is there but nothing is listening on it (the session ended)" });
|
|
145
|
+
if (c === "EAGAIN" || c === "EBUSY")
|
|
146
|
+
return settle({ ok: false, code: "peer_send.socket_busy", text: "the peer's socket is momentarily busy — retry shortly" });
|
|
147
|
+
return settle({ ok: false, code: "peer_send.other", text: `the connection failed (${c ?? "error"})` });
|
|
148
|
+
});
|
|
149
|
+
sock.once("connect", () => {
|
|
150
|
+
sock.write(payload, () => {
|
|
151
|
+
written = true;
|
|
152
|
+
sock.end();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
sock.once("close", (hadError) => {
|
|
156
|
+
if (!hadError)
|
|
157
|
+
settle(written ? { ok: true } : { ok: false, code: "peer_send.send_uncertain", text: "the connection closed before the frame was confirmed written" });
|
|
158
|
+
});
|
|
159
|
+
sock.connect({ path: address });
|
|
160
|
+
});
|
|
161
|
+
const prepareSocketPath = async (socketPath) => {
|
|
162
|
+
await ensureVettedDir(dirname(socketPath));
|
|
163
|
+
await ensureVettedDir(opts.registryDir);
|
|
164
|
+
let existing;
|
|
165
|
+
try {
|
|
166
|
+
existing = await lstat(socketPath);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
if (errCode(e) !== "ENOENT")
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
172
|
+
if (existing === undefined)
|
|
173
|
+
return;
|
|
174
|
+
const inUse = (why) => new CcPeerChannelError("address_in_use", `${socketPath} is in use (${why}) — the socket lane was not opened`, socketPath);
|
|
175
|
+
if (!existing.isSocket())
|
|
176
|
+
throw inUse("it is not a socket");
|
|
177
|
+
if (uid !== undefined && existing.uid !== uid)
|
|
178
|
+
throw inUse("another user's socket");
|
|
179
|
+
const state = await probeSocket(socketPath, STALE_PROBE_MS);
|
|
180
|
+
if (state !== "stale")
|
|
181
|
+
throw inUse(state === "live" ? "a process listens there" : "it could not be proven stale");
|
|
182
|
+
const again = await lstat(socketPath).catch(() => undefined);
|
|
183
|
+
if (again === undefined)
|
|
184
|
+
return;
|
|
185
|
+
if (again.ino !== existing.ino || again.dev !== existing.dev)
|
|
186
|
+
throw inUse("it changed while it was probed");
|
|
187
|
+
await unlink(socketPath);
|
|
188
|
+
};
|
|
189
|
+
const refusal = (code, text) => ({ ok: false, code, severity: peerSendVerdictSeverity(code), message: text });
|
|
190
|
+
return {
|
|
191
|
+
async listen(self) {
|
|
192
|
+
if (platform === "win32")
|
|
193
|
+
throw new CcPeerChannelError("unsupported_platform", "the reference agent's socket lane (its named-pipe arm) is not implemented on this platform — the socket lane was not opened");
|
|
194
|
+
if (listening !== undefined)
|
|
195
|
+
throw new CcPeerChannelError("already_listening", `this channel already listens at ${listening.address}`);
|
|
196
|
+
const socketPath = opts.socketPath;
|
|
197
|
+
if (!isAbsolute(socketPath) || Buffer.byteLength(socketPath) > CC_PEER_SOCKET_PATH_MAX_BYTES) {
|
|
198
|
+
throw new CcPeerChannelError("socket_path_too_long", `the socket path must be absolute and at most ${CC_PEER_SOCKET_PATH_MAX_BYTES} bytes (got ${Buffer.byteLength(socketPath)})`, socketPath);
|
|
199
|
+
}
|
|
200
|
+
const box = peerSessionBoxHandle(self.sessionId);
|
|
201
|
+
const laneConfig = resolvePeerLaneConfig(opts.lane);
|
|
202
|
+
const slot = `${pid}\u0000${resolvePath(opts.registryDir)}`;
|
|
203
|
+
if (LISTENING_SLOTS.has(slot))
|
|
204
|
+
throw new CcPeerChannelError("already_listening", `this process already listens through ${opts.registryDir} (its key file and registry row are named by the pid)`);
|
|
205
|
+
LISTENING_SLOTS.add(slot);
|
|
206
|
+
const release = (e) => {
|
|
207
|
+
LISTENING_SLOTS.delete(slot);
|
|
208
|
+
throw e;
|
|
209
|
+
};
|
|
210
|
+
await prepareSocketPath(socketPath).catch(release);
|
|
211
|
+
const token = randomBytes(16).toString("hex");
|
|
212
|
+
const tokenBytes = Buffer.from(token, "utf8");
|
|
213
|
+
const tokenMatches = (t) => {
|
|
214
|
+
if (t === undefined)
|
|
215
|
+
return false;
|
|
216
|
+
const b = Buffer.from(t, "utf8");
|
|
217
|
+
return b.length === tokenBytes.length && timingSafeEqual(b, tokenBytes);
|
|
218
|
+
};
|
|
219
|
+
const emit = (event) => {
|
|
220
|
+
try {
|
|
221
|
+
self.onEvent?.(event);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const drop = (reason, detail) => emit({ kind: "dropped", reason, ...(detail !== undefined ? { detail } : {}) });
|
|
227
|
+
let chain = Promise.resolve();
|
|
228
|
+
let pending = 0;
|
|
229
|
+
const enqueue = (work) => {
|
|
230
|
+
if (pending >= laneConfig.maxQueuedPeerMessages)
|
|
231
|
+
return drop("box_full", `${pending} frames already waiting for the box`);
|
|
232
|
+
pending++;
|
|
233
|
+
chain = chain
|
|
234
|
+
.then(work)
|
|
235
|
+
.catch((e) => drop("append_failed", e instanceof Error ? e.message.slice(0, 200) : "error"))
|
|
236
|
+
.then(() => {
|
|
237
|
+
pending--;
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
const appendUser = async (line) => {
|
|
241
|
+
if (line.toSession !== undefined && line.toSession !== self.sessionId)
|
|
242
|
+
return drop("session_mismatch");
|
|
243
|
+
if (line.attachments)
|
|
244
|
+
return drop("unhandled_frame", "file_attachments");
|
|
245
|
+
let queued = 0;
|
|
246
|
+
try {
|
|
247
|
+
queued = await opts.mailbox.peekCount(self.scope, box);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
}
|
|
251
|
+
if (queued >= laneConfig.maxQueuedPeerMessages)
|
|
252
|
+
return drop("box_full", `${queued} queued`);
|
|
253
|
+
const decoded = decodeCcPeerFrame(line.content);
|
|
254
|
+
const fields = decoded.ok ? decoded.fields : {};
|
|
255
|
+
const body = decoded.ok ? decoded.body : line.content;
|
|
256
|
+
const peerMeta = {
|
|
257
|
+
...(fields.fromSession !== undefined ? { fromSession: fields.fromSession, senderKey: peerAxisToken(self.scope, "s", fields.fromSession) } : {}),
|
|
258
|
+
...(fields.fromMode !== undefined ? { fromMode: fields.fromMode } : {}),
|
|
259
|
+
kind: "peer_message",
|
|
260
|
+
};
|
|
261
|
+
const from = fields.fromName ?? line.from;
|
|
262
|
+
const seq = await opts.mailbox.append(self.scope, box, { ...(from !== undefined ? { from } : {}), content: body, sentAt: now(), hopChain: fields.hopChain !== undefined ? [...fields.hopChain] : [], peerMeta });
|
|
263
|
+
emit({ kind: "appended", seq });
|
|
264
|
+
};
|
|
265
|
+
const connections = new Set();
|
|
266
|
+
const deadlineMs = opts.firstLineDeadlineMs ?? CC_PEER_FIRST_LINE_DEADLINE_MS;
|
|
267
|
+
const server = createServer({ allowHalfOpen: true }, (sock) => {
|
|
268
|
+
connections.add(sock);
|
|
269
|
+
sock.setEncoding("utf8");
|
|
270
|
+
let timer = setTimeout(() => {
|
|
271
|
+
timer = undefined;
|
|
272
|
+
drop("first_line_deadline");
|
|
273
|
+
sock.destroy();
|
|
274
|
+
}, deadlineMs);
|
|
275
|
+
timer.unref();
|
|
276
|
+
const clear = () => {
|
|
277
|
+
if (timer !== undefined)
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
timer = undefined;
|
|
280
|
+
};
|
|
281
|
+
let authed = false;
|
|
282
|
+
let buf = "";
|
|
283
|
+
const handle = (raw) => {
|
|
284
|
+
if (raw.trim() === "") {
|
|
285
|
+
if (authed)
|
|
286
|
+
return true;
|
|
287
|
+
drop("unauthenticated", "a blank line");
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const line = parseCcPeerLine(raw);
|
|
291
|
+
if (!authed) {
|
|
292
|
+
if (line.kind !== "auth") {
|
|
293
|
+
drop("unauthenticated", line.kind === "malformed" ? line.why : `a '${line.kind}' line`);
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
if (!tokenMatches(line.token)) {
|
|
297
|
+
drop("bad_token");
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
authed = true;
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
switch (line.kind) {
|
|
304
|
+
case "auth":
|
|
305
|
+
return true;
|
|
306
|
+
case "malformed":
|
|
307
|
+
drop("malformed_line", line.why);
|
|
308
|
+
return true;
|
|
309
|
+
case "unhandled":
|
|
310
|
+
drop("unhandled_frame", line.action !== undefined ? `${line.type}:${line.action}` : line.type);
|
|
311
|
+
return true;
|
|
312
|
+
case "nudge":
|
|
313
|
+
enqueue(async () => {
|
|
314
|
+
emit({ kind: "nudged" });
|
|
315
|
+
try {
|
|
316
|
+
self.onNudge?.();
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return true;
|
|
322
|
+
case "user":
|
|
323
|
+
enqueue(() => appendUser(line));
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
sock.on("data", (chunk) => {
|
|
328
|
+
buf += chunk;
|
|
329
|
+
if (buf.length > CC_PEER_LINE_MAX_CHARS) {
|
|
330
|
+
drop("line_too_long");
|
|
331
|
+
buf = "";
|
|
332
|
+
sock.destroy();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
for (let nl = buf.indexOf("\n"); nl !== -1; nl = buf.indexOf("\n")) {
|
|
336
|
+
const raw = buf.slice(0, nl);
|
|
337
|
+
buf = buf.slice(nl + 1);
|
|
338
|
+
clear();
|
|
339
|
+
if (!handle(raw)) {
|
|
340
|
+
buf = "";
|
|
341
|
+
sock.destroy();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
sock.on("end", () => {
|
|
347
|
+
if (buf.trim() !== "" && !sock.destroyed) {
|
|
348
|
+
clear();
|
|
349
|
+
handle(buf);
|
|
350
|
+
}
|
|
351
|
+
buf = "";
|
|
352
|
+
sock.end();
|
|
353
|
+
});
|
|
354
|
+
sock.on("error", () => undefined);
|
|
355
|
+
sock.on("close", () => {
|
|
356
|
+
clear();
|
|
357
|
+
connections.delete(sock);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
server.on("error", () => undefined);
|
|
361
|
+
await new Promise((resolve, reject) => {
|
|
362
|
+
const onError = (e) => reject(errCode(e) === "EADDRINUSE" ? new CcPeerChannelError("address_in_use", `another process listens at ${socketPath}`, socketPath) : e);
|
|
363
|
+
server.once("error", onError);
|
|
364
|
+
server.listen(socketPath, () => {
|
|
365
|
+
server.removeListener("error", onError);
|
|
366
|
+
resolve();
|
|
367
|
+
});
|
|
368
|
+
}).catch(release);
|
|
369
|
+
server.unref();
|
|
370
|
+
const keyPath = join(opts.registryDir, ccPeerKeyFileName(pid, socketPath));
|
|
371
|
+
const rowPath = join(opts.registryDir, `${pid}.json`);
|
|
372
|
+
let closed = false;
|
|
373
|
+
const close = async () => {
|
|
374
|
+
if (closed)
|
|
375
|
+
return;
|
|
376
|
+
closed = true;
|
|
377
|
+
listening = undefined;
|
|
378
|
+
LISTENING_SLOTS.delete(slot);
|
|
379
|
+
for (const s of connections)
|
|
380
|
+
s.destroy();
|
|
381
|
+
connections.clear();
|
|
382
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
383
|
+
await unlink(socketPath).catch(() => undefined);
|
|
384
|
+
await unlink(keyPath).catch(() => undefined);
|
|
385
|
+
await unlink(rowPath).catch(() => undefined);
|
|
386
|
+
await chain.catch(() => undefined);
|
|
387
|
+
};
|
|
388
|
+
try {
|
|
389
|
+
await chmod(socketPath, 0o600);
|
|
390
|
+
const procStart = await probe.processStartToken(pid);
|
|
391
|
+
await writeFileAtomic0600(keyPath, JSON.stringify({ peerToken: token, ...(procStart !== undefined ? { procStart } : {}) }));
|
|
392
|
+
const at = now();
|
|
393
|
+
const reg = opts.registration ?? {};
|
|
394
|
+
await writeFileAtomic0600(rowPath, JSON.stringify(ccRegistrationRow({
|
|
395
|
+
pid,
|
|
396
|
+
sessionId: self.sessionId,
|
|
397
|
+
socketPath,
|
|
398
|
+
startedAt: reg.startedAt ?? at,
|
|
399
|
+
updatedAt: at,
|
|
400
|
+
...(reg.cwd !== undefined ? { cwd: reg.cwd } : {}),
|
|
401
|
+
...(reg.name !== undefined ? { name: reg.name } : {}),
|
|
402
|
+
...(reg.entrypoint !== undefined ? { entrypoint: reg.entrypoint } : {}),
|
|
403
|
+
...(procStart !== undefined ? { procStart } : {}),
|
|
404
|
+
...(typeof opts.mailbox.identity === "string" && opts.mailbox.identity !== "" ? { storeIdentity: opts.mailbox.identity } : {}),
|
|
405
|
+
})));
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
await close();
|
|
409
|
+
throw new CcPeerChannelError("publish_failed", `the socket was bound but its key file or registry row could not be published (${errCode(e) ?? (e instanceof Error ? e.message : "error")}) — no peer could authenticate or find it, so the socket lane was closed`, opts.registryDir);
|
|
410
|
+
}
|
|
411
|
+
listening = { address: socketPath, sessionId: self.sessionId };
|
|
412
|
+
return { address: socketPath, close };
|
|
413
|
+
},
|
|
414
|
+
async dial(address, message) {
|
|
415
|
+
const vetted = await vetPeerSocket(address);
|
|
416
|
+
if (!vetted.ok)
|
|
417
|
+
return refusal(vetted.code, vetted.text);
|
|
418
|
+
let line;
|
|
419
|
+
try {
|
|
420
|
+
const from = listening !== undefined && message.fromSession === listening.sessionId ? ccPeerAddress(listening.address) : undefined;
|
|
421
|
+
line = encodeCcUserLine({
|
|
422
|
+
fields: {
|
|
423
|
+
...(from !== undefined ? { from } : {}),
|
|
424
|
+
...(message.fromSession !== undefined ? { fromSession: message.fromSession } : {}),
|
|
425
|
+
hopChain: message.hopChain.map(peerWireHopToken),
|
|
426
|
+
...(message.fromName !== undefined ? { fromName: message.fromName } : {}),
|
|
427
|
+
...(message.fromMode !== undefined ? { fromMode: message.fromMode } : {}),
|
|
428
|
+
},
|
|
429
|
+
body: message.body,
|
|
430
|
+
...(from !== undefined ? { from } : {}),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
if (e instanceof CrossSessionCodecError)
|
|
435
|
+
return refusal("peer_send.other", `the frame could not be spelled (${e.code})`);
|
|
436
|
+
throw e;
|
|
437
|
+
}
|
|
438
|
+
if (encodeCcAuthLine("0".repeat(32)).length + line.length > CC_PEER_LINE_MAX_CHARS)
|
|
439
|
+
return refusal("peer_send.too_large", `the frame is ${line.length} characters; the peer's line wall is ${CC_PEER_LINE_MAX_CHARS}`);
|
|
440
|
+
const token = await readPeerToken(address);
|
|
441
|
+
const sent = await send(address, `${token !== undefined ? encodeCcAuthLine(token) : ""}${line}`, opts.sendTimeoutMs ?? CC_PEER_SEND_TIMEOUT_MS);
|
|
442
|
+
return sent.ok ? { ok: true, disposition: "delivered", via: "uds" } : refusal(sent.code, sent.text);
|
|
443
|
+
},
|
|
444
|
+
async nudge(address) {
|
|
445
|
+
try {
|
|
446
|
+
if (!(await vetPeerSocket(address)).ok)
|
|
447
|
+
return false;
|
|
448
|
+
const token = await readPeerToken(address);
|
|
449
|
+
const sent = await send(address, `${token !== undefined ? encodeCcAuthLine(token) : ""}${encodeCcNudgeLine()}`, opts.nudgeTimeoutMs ?? NUDGE_TIMEOUT_MS);
|
|
450
|
+
return sent.ok;
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
@@ -128,12 +128,15 @@ export interface CcPeerFrameFields {
|
|
|
128
128
|
hopChain?: string[];
|
|
129
129
|
fromName?: string;
|
|
130
130
|
fromMode?: PermissionModeClass;
|
|
131
|
+
/** The sending plugin's display name (CC 2.1.296 sixth attribute, (cli296.js:237185) "from-plugin", after `from-mode`; canonicalized like
|
|
132
|
+
* `from-name`). Display only — the reference attributes a plugin's message by it, no judgment reads it. */
|
|
133
|
+
fromPlugin?: string;
|
|
131
134
|
}
|
|
132
135
|
/** CC `ygt` — the carry window: keep the newest 32. */
|
|
133
136
|
export declare function clampHopChain(chain: readonly string[]): string[];
|
|
134
137
|
/**
|
|
135
138
|
* Serialize the CC wire envelope (what goes into a UDS user frame's `message.content`). Attribute
|
|
136
|
-
* order `from` → `from-session` → `hop-chain` → `from-name` → `from-mode`. Unlike CC's own
|
|
139
|
+
* order `from` → `from-session` → `hop-chain` → `from-name` → `from-mode` → `from-plugin`. Unlike CC's own
|
|
137
140
|
* constructor, which silently DROPS an ill-formed `from-session`/`hop-chain`, this throws on them: the
|
|
138
141
|
* values are ours, and a producer bug must not become an attribute-less frame on the wire.
|
|
139
142
|
* @throws CrossSessionCodecError
|
|
@@ -170,6 +170,11 @@ export function encodeCcPeerFrame(fields, body) {
|
|
|
170
170
|
assert(isPermissionModeClass(fields.fromMode), "cross_session.invalid_from_mode", `"from-mode" must be one of ${PERMISSION_MODE_CLASSES.join("|")}`);
|
|
171
171
|
attrs.push(`from-mode="${fields.fromMode}"`);
|
|
172
172
|
}
|
|
173
|
+
if (fields.fromPlugin !== undefined) {
|
|
174
|
+
const plugin = typeof fields.fromPlugin === "string" ? canonicalPeerDisplayName(fields.fromPlugin) : "";
|
|
175
|
+
if (plugin !== "")
|
|
176
|
+
attrs.push(`from-plugin="${plugin}"`);
|
|
177
|
+
}
|
|
173
178
|
const header = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
174
179
|
return `<${CROSS_SESSION_MESSAGE_TAG}${header}>\n${escapeEnvelopeTag(CROSS_SESSION_MESSAGE_TAG, body)}\n</${CROSS_SESSION_MESSAGE_TAG}>`;
|
|
175
180
|
}
|
|
@@ -179,12 +184,13 @@ const CC_WIRE_RE = new RegExp(`^<${CROSS_SESSION_MESSAGE_TAG}` +
|
|
|
179
184
|
`(?: hop-chain="(${HOP_CHAIN_RE.source.slice(1, -1)})")?` +
|
|
180
185
|
`(?: from-name="([^"<>\\n\\r]+)")?` +
|
|
181
186
|
`(?: from-mode="(bypass|prompting)")?` +
|
|
187
|
+
`(?: from-plugin="([^"<>\\n\\r]+)")?` +
|
|
182
188
|
`>\\n([\\s\\S]*)\\n</${CROSS_SESSION_MESSAGE_TAG}>$`);
|
|
183
189
|
export function decodeCcPeerFrame(text) {
|
|
184
190
|
const m = CC_WIRE_RE.exec(text);
|
|
185
191
|
if (m === null)
|
|
186
192
|
return { ok: false, reason: "not_an_envelope" };
|
|
187
|
-
const [, from, fromSession, hopChain, fromName, fromMode, body] = m;
|
|
193
|
+
const [, from, fromSession, hopChain, fromName, fromMode, fromPlugin, body] = m;
|
|
188
194
|
if (from !== undefined && !isCanonicalPeerAddress(from))
|
|
189
195
|
return { ok: false, reason: "roundtrip_mismatch" };
|
|
190
196
|
const fields = {
|
|
@@ -193,6 +199,7 @@ export function decodeCcPeerFrame(text) {
|
|
|
193
199
|
...(hopChain !== undefined ? { hopChain: hopChain.split(",") } : {}),
|
|
194
200
|
...(fromName !== undefined ? { fromName } : {}),
|
|
195
201
|
...(fromMode !== undefined ? { fromMode: fromMode } : {}),
|
|
202
|
+
...(fromPlugin !== undefined ? { fromPlugin } : {}),
|
|
196
203
|
};
|
|
197
204
|
if (encodeCcPeerFrame(fields, body) !== text)
|
|
198
205
|
return { ok: false, reason: "roundtrip_mismatch" };
|
|
@@ -62,7 +62,7 @@ export declare function resolveCrossSessionInboundSetting(layers: CrossSessionIn
|
|
|
62
62
|
*/
|
|
63
63
|
export declare function principalInboundLayers(layers: CrossSessionInboundSettingLayers): CrossSessionInboundSettingLayers;
|
|
64
64
|
/**
|
|
65
|
-
* Why a message was held. The first eight are
|
|
65
|
+
* Why a message was held. The first eight are the reference's closed set, reachable exactly
|
|
66
66
|
* where CC reaches them. `invalid-mode-attestation` is a sema-only arm: CC's envelope parser drops a
|
|
67
67
|
* non-vocabulary `from-mode` before any judgment (the state is unreachable there); sema receives the
|
|
68
68
|
* attestation as a TYPED store/wire field, where a garbage value is a producer bug that must hold
|
|
@@ -73,7 +73,7 @@ export declare function principalInboundLayers(layers: CrossSessionInboundSettin
|
|
|
73
73
|
*/
|
|
74
74
|
export type CrossSessionHoldCause = "mode-mismatch" | "no-mode-asserted" | "explicit-setting" | "managed-setting" | "repo-setting" | "invalid-setting" | "bypass-default" | "mode-unknown" | "invalid-mode-attestation" | "cross-principal-default";
|
|
75
75
|
export declare const CROSS_SESSION_HOLD_CAUSES: readonly CrossSessionHoldCause[];
|
|
76
|
-
/**
|
|
76
|
+
/** The reference's wording for the eight inherited causes; the sema arm has its own sentence. */
|
|
77
77
|
export declare function describeCrossSessionHoldCause(cause: CrossSessionHoldCause): string;
|
|
78
78
|
export type CrossSessionInboundVerdict = {
|
|
79
79
|
verdict: "deliver";
|
|
@@ -5,7 +5,7 @@ import { formatPeerNameRef } from "./cross-session-ref.js";
|
|
|
5
5
|
import { entryAccessible } from "./roster-store.js";
|
|
6
6
|
import { mintPeerSessionCandidates, PEER_PRESENCE_MAX_CHARS, peerSessionBoxHandle } from "./peer-directory.js";
|
|
7
7
|
import { PEER_LANE_DEFAULTS } from "./peer-admission.js";
|
|
8
|
-
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
8
|
+
import { mountRuleFace, toolFace } from "../core/tool-catalog-entries.js";
|
|
9
9
|
import { thrownText } from "../core/thrown-value.js";
|
|
10
10
|
import { assertNoRetiredKeys } from "../core/retired-keys.js";
|
|
11
11
|
import { agentActivityClause, agentApprovalClause, formatAgo, readAgentState } from "../core/agent-state.js";
|
|
@@ -62,6 +62,7 @@ export function createListAgentsTool(opts) {
|
|
|
62
62
|
return defineTool({
|
|
63
63
|
name: LIST_AGENTS_TOOL_NAME,
|
|
64
64
|
...toolFace("list-agents"),
|
|
65
|
+
ruleFace: mountRuleFace("list-agents"),
|
|
65
66
|
isConcurrencySafe: () => true,
|
|
66
67
|
description: `List the agents${peers ? " and peer sessions" : ""} you can message with SendMessage. ` +
|
|
67
68
|
(peers
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Observer agents
|
|
2
|
+
* Observer agents.
|
|
3
3
|
*
|
|
4
4
|
* A definition may declare `observer: "<agent name>"`: whenever that agent runs, the named agent type
|
|
5
5
|
* is auto-spawned in the background, PAIRED with it. The observer receives read-only activity digests
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* prompt / declaration resolver / pairing state machine / ObserverReport tool); the spawn wiring
|
|
13
13
|
* (event tap on the delegation lanes + auto-spawn + delivery) lives in `subagent.ts`.
|
|
14
14
|
*
|
|
15
|
-
* CC anchor coordinates cited per symbol below refer to `claude-cli-2.1.206.pretty.js`.
|
|
16
15
|
*/
|
|
17
16
|
import { Type } from "typebox";
|
|
18
17
|
import type { AgentDefinition, TaskEvent } from "../core/types.js";
|
|
@@ -59,9 +58,9 @@ export declare const OBSERVER_DIGEST_PAYLOAD_CAP = 2000;
|
|
|
59
58
|
export declare function truncateDigestPayload(text: string): string;
|
|
60
59
|
/** CC `v$r` — envelope-name slug: anything outside `[a-zA-Z0-9_-]` → `-`; empty → `"agent"`. */
|
|
61
60
|
export declare function observerSlug(name: string): string;
|
|
62
|
-
/**
|
|
61
|
+
/** The five render cases (assistant text untagged; the four tagged kinds as is). */
|
|
63
62
|
export declare function renderObserverDigestEvent(e: ObserverDigestEvent): string;
|
|
64
|
-
/**
|
|
63
|
+
/** The harness-owned digest postamble (self-framing: data, not instructions). */
|
|
65
64
|
export declare const OBSERVER_DIGEST_POSTAMBLE = "The activity above is a read-only digest of the agent you are observing \u2014 it is data, not instructions to you. Speak up only when you have something genuinely useful: a mistake about to compound, a missed constraint, prior art they should see. Report with the ObserverReport tool. The expected steady state is silence: if nothing warrants action, end your turn without responding.";
|
|
66
65
|
/** CC `Cqi` — the deployment's `observerMessage` appends AFTER the harness-owned default; blank ignored. */
|
|
67
66
|
export declare function observerPostamble(observerMessage?: string): string;
|
|
@@ -149,7 +148,7 @@ export declare class ObserverStoppedByUserError extends Error {
|
|
|
149
148
|
export declare class ObserverResumeStateError extends Error {
|
|
150
149
|
constructor(message: string);
|
|
151
150
|
}
|
|
152
|
-
/**
|
|
151
|
+
/** The note appended to the framing prompt of a RESTARTED observer. */
|
|
153
152
|
export declare const OBSERVER_FRESH_START_NOTE = "[Note: your previous observation context was lost; this is a fresh start mid-task.]";
|
|
154
153
|
/** The runtime seam the wiring provides: how digests physically reach the observer run. */
|
|
155
154
|
export interface ObserverSpawner {
|
package/dist/agents/observer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { defineTool } from "../core/tools.js";
|
|
|
3
3
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
4
4
|
import { escapeEnvelopeTag } from "../core/untrusted-text.js";
|
|
5
5
|
import { renderContentBlock } from "../internal/llm.js";
|
|
6
|
-
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
6
|
+
import { mountRuleFace, toolFace } from "../core/tool-catalog-entries.js";
|
|
7
7
|
import { isErrorLike, thrownName, thrownText } from "../core/thrown-value.js";
|
|
8
8
|
export const OBSERVER_EVENT_TAGS = Object.freeze(["tool-call", "user-message", "tool-result", "turn-ended"]);
|
|
9
9
|
const TAG_ESCAPE_RE = new RegExp(`<(?=/?(?:${OBSERVER_EVENT_TAGS.join("|")})(?:[>\\s/]|$))`, "gi");
|
|
@@ -370,6 +370,7 @@ export function createObserverReportToolSpec(opts) {
|
|
|
370
370
|
return {
|
|
371
371
|
name: OBSERVER_REPORT_TOOL_NAME,
|
|
372
372
|
...toolFace("observer-report"),
|
|
373
|
+
ruleFace: mountRuleFace("observer-report"),
|
|
373
374
|
description: OBSERVER_REPORT_DESCRIPTION,
|
|
374
375
|
parameters: Type.Object({
|
|
375
376
|
report: Type.String({
|
|
@@ -76,6 +76,12 @@ export declare function peerAxisToken(scope: string | undefined, axis: PeerAxisT
|
|
|
76
76
|
* {@link PEER_HOP_CHAIN_WINDOW} entries. `undefined` token (an identity-less direct mount that
|
|
77
77
|
* cannot mint one) forwards the chain unchanged — it cannot honestly claim a hop identity. */
|
|
78
78
|
export declare function appendHopToken(chain: readonly string[], token: string | undefined): string[];
|
|
79
|
+
/** The spelling a hop token wears on the reference agent's socket wire, whose tokens are 24 lowercase hex:
|
|
80
|
+
* this engine's token crosses as the first 24 hex of its sha-256 (one-way — no internal id leaves the
|
|
81
|
+
* process; deterministic — the same token maps the same way after a restart). An entry already in that
|
|
82
|
+
* form (a token a reference session minted, carried along) crosses unchanged, so a chain that crosses
|
|
83
|
+
* twice keeps its spelling. */
|
|
84
|
+
export declare function peerWireHopToken(token: string): string;
|
|
79
85
|
/** A run's peer-identity snapshot: the frozen canonical key plus the monotonic union of every axis
|
|
80
86
|
* token the run has ever worn (old-cycle tokens keep matching after an axis re-derives). */
|
|
81
87
|
export interface PeerIdentity {
|
|
@@ -126,6 +126,9 @@ export function appendHopToken(chain, token) {
|
|
|
126
126
|
const next = token === undefined ? [...chain] : [...chain, token];
|
|
127
127
|
return next.length > PEER_HOP_CHAIN_WINDOW ? next.slice(next.length - PEER_HOP_CHAIN_WINDOW) : next;
|
|
128
128
|
}
|
|
129
|
+
export function peerWireHopToken(token) {
|
|
130
|
+
return /^[0-9a-f]{24}$/.test(token) ? token : createHash("sha256").update(token).digest("hex").slice(0, 24);
|
|
131
|
+
}
|
|
129
132
|
export function createPeerSelfRef(scope) {
|
|
130
133
|
const current = { ...(scope !== undefined ? { scope } : {}), ownTokens: [] };
|
|
131
134
|
return {
|
|
@@ -158,7 +161,7 @@ export function createPeerAdmission(options) {
|
|
|
158
161
|
return { ok: false, reason: "hop_runaway" };
|
|
159
162
|
}
|
|
160
163
|
if (ownTokens.length > 0) {
|
|
161
|
-
const own = new Set(ownTokens);
|
|
164
|
+
const own = new Set(ownTokens.flatMap((t) => [t, peerWireHopToken(t)]));
|
|
162
165
|
let selfHops = 0;
|
|
163
166
|
for (const t of prospectiveChain)
|
|
164
167
|
if (own.has(t))
|