blun-king-cli 9.1.567 → 9.1.569
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/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +1581 -0
- package/agent-spine-plugin/README.md +30 -4
- package/agent-spine-plugin/blun.plugin.json +3 -3
- package/agent-spine-plugin/docs/acceptance.md +61 -0
- package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
- package/agent-spine-plugin/docs/host-integration.md +178 -0
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/preservation-contract.md +53 -0
- package/agent-spine-plugin/docs/quality-gates.md +50 -0
- package/agent-spine-plugin/docs/releasing.md +85 -0
- package/agent-spine-plugin/docs/session-timeline.md +251 -0
- package/agent-spine-plugin/docs/source-roots.md +113 -0
- package/agent-spine-plugin/docs/structured-completion.md +67 -0
- package/agent-spine-plugin/docs/world-model.md +94 -0
- package/agent-spine-plugin/hooks/codex.json +2 -2
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/hooks/version.json +1 -1
- package/agent-spine-plugin/package.json +13 -3
- package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
- package/agent-spine-plugin/scripts/check-hosts.js +14 -5
- package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
- package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
- package/agent-spine-plugin/scripts/check-install.js +478 -0
- package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
- package/agent-spine-plugin/scripts/check-syntax.js +29 -0
- package/agent-spine-plugin/scripts/github-actions.js +11 -0
- package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
- package/agent-spine-plugin/scripts/release-check.js +145 -0
- package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
- package/agent-spine-plugin/scripts/run-checks.js +47 -0
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
- package/agent-spine-plugin/spine-example/1-identity.md +12 -0
- package/agent-spine-plugin/spine-example/2-voice.md +6 -0
- package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
- package/agent-spine-plugin/spine-example/4-history.md +4 -0
- package/agent-spine-plugin/src/cli-agent.js +296 -0
- package/agent-spine-plugin/src/cli-attention.js +95 -0
- package/agent-spine-plugin/src/cli-autonomy.js +36 -0
- package/agent-spine-plugin/src/cli-common.js +71 -0
- package/agent-spine-plugin/src/cli-continuity.js +116 -0
- package/agent-spine-plugin/src/cli-core.js +128 -0
- package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
- package/agent-spine-plugin/src/cli-host.js +21 -0
- package/agent-spine-plugin/src/cli-learning.js +305 -0
- package/agent-spine-plugin/src/cli-premortem.js +16 -0
- package/agent-spine-plugin/src/cli-sharing.js +230 -0
- package/agent-spine-plugin/src/cli.js +40 -1350
- package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
- package/agent-spine-plugin/src/hook.js +185 -567
- package/agent-spine-plugin/src/index.js +16 -2
- package/agent-spine-plugin/src/lib/acceptance.js +113 -11
- package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
- package/agent-spine-plugin/src/lib/attention-context.js +167 -0
- package/agent-spine-plugin/src/lib/attention-events.js +113 -0
- package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
- package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
- package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
- package/agent-spine-plugin/src/lib/attention.js +9 -600
- package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
- package/agent-spine-plugin/src/lib/audit.js +56 -6
- package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
- package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
- package/agent-spine-plugin/src/lib/autonomy.js +8 -0
- package/agent-spine-plugin/src/lib/briefing.js +67 -4
- package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
- package/agent-spine-plugin/src/lib/catalog.js +1 -1
- package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
- package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
- package/agent-spine-plugin/src/lib/context.js +3 -1
- package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
- package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
- package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
- package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
- package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
- package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
- package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
- package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
- package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
- package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
- package/agent-spine-plugin/src/lib/documents.js +27 -5
- package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
- package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
- package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
- package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
- package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
- package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
- package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
- package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
- package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
- package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
- package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
- package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
- package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
- package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
- package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
- package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
- package/agent-spine-plugin/src/lib/hook-context.js +424 -0
- package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
- package/agent-spine-plugin/src/lib/hook-input.js +27 -0
- package/agent-spine-plugin/src/lib/hook-output.js +140 -0
- package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
- package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
- package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
- package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
- package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
- package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
- package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
- package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
- package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
- package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
- package/agent-spine-plugin/src/lib/learning-context.js +130 -0
- package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
- package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
- package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
- package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
- package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
- package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
- package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
- package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
- package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
- package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
- package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
- package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
- package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
- package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
- package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
- package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
- package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
- package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
- package/agent-spine-plugin/src/lib/learning.js +36 -6923
- package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
- package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
- package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
- package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
- package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
- package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
- package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
- package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
- package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
- package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
- package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
- package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
- package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
- package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
- package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
- package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
- package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
- package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
- package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
- package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
- package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
- package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
- package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
- package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
- package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
- package/agent-spine-plugin/src/lib/source-roots.js +69 -136
- package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
- package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
- package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
- package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
- package/agent-spine-plugin/src/lib/world-model.js +278 -0
- package/agent-spine-plugin/src/mcp.js +20 -161
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +22 -5
- package/bin/agent-resume-snapshot.cjs +2 -2
- package/bin/agentspine-king-goal-inbox.mjs +111 -0
- package/bin/agentspine-king-goal-intake.mjs +106 -0
- package/bin/baseline-skill-performance-policy.cjs +1 -16
- package/bin/core-bootstrap.js +2 -0
- package/bin/curiosity-scout-policy.cjs +5 -1
- package/bin/input-draft-persistence.cjs +2 -2
- package/bin/king-tui-function-contract.json +33 -0
- package/bin/launcher-restart-policy.cjs +150 -0
- package/bin/launcher-runtime.js +56 -44
- package/bin/managed-context-startup-policy.cjs +27 -0
- package/bin/managed-plugin-selection.cjs +116 -0
- package/bin/mistake-relevance-policy.cjs +1 -1
- package/bin/observer-hooks.cjs +14 -0
- package/bin/oversized-context-offload-policy.cjs +86 -0
- package/bin/plugin-bootstrap.js +7 -40
- package/bin/proactive-compaction-policy.cjs +1 -1
- package/bin/provider-model-refresh-deadline.cjs +53 -0
- package/bin/provider-model-refresh-policy.cjs +107 -0
- package/bin/release-artifact-freeze-policy.cjs +30 -0
- package/bin/repeated-user-message-projection.cjs +3 -126
- package/bin/research-page-result.cjs +74 -0
- package/bin/runtime-exit-ledger.cjs +1 -0
- package/bin/session-compaction-policy.cjs +84 -0
- package/bin/skill-listing-performance-policy.cjs +2 -2
- package/bin/standard-tools-bootstrap.js +0 -37
- package/bin/subagent-skill-policy.cjs +3 -1
- package/bin/telegram-approval-relay.cjs +12 -7
- package/bin/telegram-private-conversation-policy.cjs +3 -2
- package/bin/telegram-queue-handoff-policy.cjs +24 -0
- package/bin/thinking-activity-status-policy.cjs +1 -1
- package/bin/thinking-only-guard.cjs +16 -12
- package/bin/tool-call-loop-policy.cjs +0 -2
- package/bin/tool-result-offload-policy.cjs +11 -2
- package/bin/tui-functional-contract.cjs +55 -0
- package/bin/update-notice.js +18 -14
- package/bin/user-message-offload-policy.cjs +1 -1
- package/bin/user-prompt-hook-origin-policy.cjs +34 -0
- package/bin/windows-node-crash-dump.cjs +110 -0
- package/blun.mjs +2517 -1048
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +3 -17
- package/standard-skills/research-evidence/SKILL.md +39 -0
- package/standard-skills/research-evidence/references/evidence-format.md +82 -0
- package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
- package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
- package/standard-skills/web-lesen/SKILL.md +37 -22
- package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
- package/telegram-plugin/DELIVERY.md +36 -0
- package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
- package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
- package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
- package/telegram-plugin/dist/bridge.mjs +7 -56
- package/telegram-plugin/dist/mcp-server.mjs +33 -4
- package/agent-spine-plugin/skill/SKILL.md +0 -76
- package/bin/mnemo-connect-heartbeat.cjs +0 -204
- package/bin/mnemo-tool-agent-policy.cjs +0 -22
- package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { runAudit } from "./audit.js";
|
|
2
|
+
import {
|
|
3
|
+
attentionContext, configureAttention, deleteAttention,
|
|
4
|
+
recordActivity, resolveAttention, upsertAttention
|
|
5
|
+
} from "./attention.js";
|
|
6
|
+
import { sessionBriefing } from "./briefing.js";
|
|
7
|
+
import { deliveryKnowledgeQuery } from "./delivery-knowledge.js";
|
|
8
|
+
import { advisoryDeliveryUse } from "./hook-briefing-use.js";
|
|
9
|
+
import { recordDeliveryBriefingUse, recordDeliveryKnowledgeUse,
|
|
10
|
+
verifyDeliveryAgentUse } from "./delivery-agent-usage.js";
|
|
11
|
+
import { scanAndSave, verifyCatalog } from "./catalog.js";
|
|
12
|
+
import { checkDelegation, createTask, taskContext, updateTask } from "./coordination.js";
|
|
13
|
+
import { readDocument, resolveContext } from "./context.js";
|
|
14
|
+
import { recordDeliveryPremortem } from "./delivery-premortem.js";
|
|
15
|
+
import { recoverDeliveryPremortem } from "./delivery-premortem-correction.js";
|
|
16
|
+
import { completeDelivery } from "./mcp-delivery-completion.js";
|
|
17
|
+
import {
|
|
18
|
+
annotateDocument, linkDocuments, linkEntities,
|
|
19
|
+
relationshipContext, upsertEntity
|
|
20
|
+
} from "./graph.js";
|
|
21
|
+
import {
|
|
22
|
+
addLearningEvidence, configureLearning, deleteLearning, evaluateLearning,
|
|
23
|
+
learningContext, learningOutcomeStatus, proposeLearning, reviewLearning, rollbackLearning
|
|
24
|
+
} from "./learning.js";
|
|
25
|
+
import { sharedContext } from "./sharing.js";
|
|
26
|
+
import { recordWorldAssertion, worldContext } from "./world-model.js";
|
|
27
|
+
import { indexSessionTimeline, searchSessionTimeline } from "./session-timeline.js";
|
|
28
|
+
import { gatewayEnvironmentContext } from "./hook-context.js";
|
|
29
|
+
import {
|
|
30
|
+
timelineInvocationInput, timelineInvocationRequest
|
|
31
|
+
} from "./mcp-timeline-tools.js";
|
|
32
|
+
import { sessionTimelineBinding } from "./session-timeline-contract.js";
|
|
33
|
+
import { timelineTransportDigest } from "./session-timeline-transport.js";
|
|
34
|
+
import { runtimeHostForTimeline, timelineHostHome } from "./session-timeline-provider.js";
|
|
35
|
+
import { boundBriefingArguments, rejectInternalSourceArguments, resolveMcpSources } from "./mcp-source-context.js";
|
|
36
|
+
import { evaluateAutonomyAction, projectPortfolioContext, recordProjectObservation } from "./autonomy.js";
|
|
37
|
+
|
|
38
|
+
function textResult(value, isError = false) {
|
|
39
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], isError };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function timelineTransport(input, root, environment) {
|
|
43
|
+
const { scope } = input;
|
|
44
|
+
const host = input.request.host || "claude";
|
|
45
|
+
return { scope, host, transportDigest: timelineTransportDigest({ root,
|
|
46
|
+
binding: sessionTimelineBinding({ host, sessionId: input.request.sessionId, scope }), environment }) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function absent(value) { return value === undefined || value === null || value === ""; }
|
|
50
|
+
|
|
51
|
+
// The MCP process can outlive a host turn. A group claim that appears after a
|
|
52
|
+
// private permit was issued must therefore suppress consumption here as well
|
|
53
|
+
// as in PreToolUse; tool arguments cannot clear an authenticated environment.
|
|
54
|
+
function runtimeTimelineGroup(environment, input) {
|
|
55
|
+
try {
|
|
56
|
+
const gateway = gatewayEnvironmentContext(environment);
|
|
57
|
+
return Boolean(input?.groupClaim) || !absent(environment?.AGENTSPINE_GROUP_ID) || !absent(gateway?.groupId);
|
|
58
|
+
} catch {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function runtimeGatewayMatchesTimeline(environment, scope, host) {
|
|
64
|
+
let gateway;
|
|
65
|
+
try { gateway = gatewayEnvironmentContext(environment); }
|
|
66
|
+
catch { return false; }
|
|
67
|
+
if (!gateway) return true;
|
|
68
|
+
if (!absent(gateway.groupId)) return false;
|
|
69
|
+
const claims = [
|
|
70
|
+
[gateway.host, runtimeHostForTimeline(host)], [gateway.entityId, scope.entityId], [gateway.projectId, scope.projectId],
|
|
71
|
+
[gateway.taskId, scope.currentTaskId], [gateway.goalId, scope.goalId], [gateway.goalStepId, scope.goalStepId]
|
|
72
|
+
];
|
|
73
|
+
if (!claims.every(([actual, expected]) => actual === expected)) return false;
|
|
74
|
+
// Gateway context predates these two claims. In an authenticated gateway
|
|
75
|
+
// process, accepting an unverifiable user or tenant would let a stale private
|
|
76
|
+
// permit cross a newly selected identity, so their canonical env claims are
|
|
77
|
+
// deliberately mandatory until the gateway context carries them itself.
|
|
78
|
+
return environment?.AGENTSPINE_USER_ID === scope.userId
|
|
79
|
+
&& environment?.AGENTSPINE_TENANT_ID === scope.tenantId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function unavailableTimelineInvocation(reason) {
|
|
83
|
+
return { status: "unavailable", reason, authority: "context-only" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function callTool(name, args = {}, environment = process.env) {
|
|
87
|
+
const root = args.root || process.cwd();
|
|
88
|
+
if (name === "scan") return textResult(await scanAndSave(root));
|
|
89
|
+
if (name === "resolve_context") {
|
|
90
|
+
rejectInternalSourceArguments(args);
|
|
91
|
+
const sources = await resolveMcpSources({ root, cwd: args.cwd || root, host: args.host });
|
|
92
|
+
return textResult(await resolveContext({
|
|
93
|
+
root,
|
|
94
|
+
cwd: args.cwd || root,
|
|
95
|
+
host: sources.host,
|
|
96
|
+
maxBytes: args.maxBytes ?? 65536,
|
|
97
|
+
includeContent: args.includeContent ?? true,
|
|
98
|
+
catalog: sources.catalog
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
if (name === "session_briefing") {
|
|
102
|
+
const scoped = await boundBriefingArguments({ ...args, root });
|
|
103
|
+
const sources = await resolveMcpSources({ ...scoped, required: Boolean(args.requirementId) });
|
|
104
|
+
const briefing = await sessionBriefing({ ...scoped, host: sources.host,
|
|
105
|
+
catalog: sources.catalog, userStateRoot: sources.userStateRoot, sourceDiagnostics: sources.diagnostics });
|
|
106
|
+
if (!args.requirementId) return textResult(briefing);
|
|
107
|
+
const receipt = await recordDeliveryBriefingUse({ root,
|
|
108
|
+
requirementId: args.requirementId, input: args, result: briefing });
|
|
109
|
+
return textResult({ ...briefing, deliveryUseReceipt: advisoryDeliveryUse(receipt) });
|
|
110
|
+
}
|
|
111
|
+
if (name === "delivery_knowledge_query") {
|
|
112
|
+
rejectInternalSourceArguments(args);
|
|
113
|
+
const knowledge = await deliveryKnowledgeQuery({ ...args, root });
|
|
114
|
+
if (knowledge.blocked) return textResult(knowledge, true);
|
|
115
|
+
const receipt = await recordDeliveryKnowledgeUse({ root,
|
|
116
|
+
requirementId: args.requirementId, input: args, result: knowledge });
|
|
117
|
+
return textResult({ ...knowledge, deliveryUseReceipt: advisoryDeliveryUse(receipt) });
|
|
118
|
+
}
|
|
119
|
+
if (name === "read_document") {
|
|
120
|
+
rejectInternalSourceArguments(args);
|
|
121
|
+
const sources = await resolveMcpSources({ root, host: args.host });
|
|
122
|
+
return textResult(await readDocument({
|
|
123
|
+
root, path: args.path, offset: args.offset ?? 0, length: args.length ?? 65536,
|
|
124
|
+
catalog: sources.catalog
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
if (name === "verify") return textResult(await verifyCatalog(root));
|
|
128
|
+
if (name === "link_documents") return textResult(await linkDocuments({ ...args, root }));
|
|
129
|
+
if (name === "annotate_document") return textResult(await annotateDocument({ ...args, root }));
|
|
130
|
+
if (name === "upsert_entity") return textResult(await upsertEntity({ ...args, root }));
|
|
131
|
+
if (name === "link_entities") return textResult(await linkEntities({ ...args, root }));
|
|
132
|
+
if (name === "relationship_context") return textResult(await relationshipContext({ ...args, root }));
|
|
133
|
+
if (name === "upsert_attention") return textResult(await upsertAttention({ ...args, root }));
|
|
134
|
+
if (name === "record_activity") return textResult(await recordActivity({ ...args, root }));
|
|
135
|
+
if (name === "attention_context") return textResult(await attentionContext({ ...args, root }));
|
|
136
|
+
if (name === "resolve_attention") return textResult(await resolveAttention({ ...args, root }));
|
|
137
|
+
if (name === "configure_attention") return textResult(await configureAttention({ ...args, root }));
|
|
138
|
+
if (name === "delete_attention") return textResult(await deleteAttention({ ...args, root }));
|
|
139
|
+
if (name === "propose_learning") return textResult(await proposeLearning({ ...args, root }));
|
|
140
|
+
if (name === "add_learning_evidence") return textResult(await addLearningEvidence({ ...args, root }));
|
|
141
|
+
if (name === "review_learning") return textResult(await reviewLearning({ ...args, root }));
|
|
142
|
+
if (name === "learning_context") return textResult(await learningContext({ ...args, root }));
|
|
143
|
+
if (name === "learning_outcome_status") return textResult(await learningOutcomeStatus({ ...args, root }));
|
|
144
|
+
if (name === "evaluate_learning") return textResult(await evaluateLearning({ ...args, root }));
|
|
145
|
+
if (name === "rollback_learning") return textResult(await rollbackLearning({ ...args, root }));
|
|
146
|
+
if (name === "configure_learning") return textResult(await configureLearning({ ...args, root }));
|
|
147
|
+
if (name === "delete_learning") return textResult(await deleteLearning({ ...args, root }));
|
|
148
|
+
if (name === "check_delegation") return textResult(await checkDelegation({ ...args, root }));
|
|
149
|
+
if (name === "create_task") return textResult(await createTask({ ...args, root }));
|
|
150
|
+
if (name === "update_task") return textResult(await updateTask({ ...args, root }));
|
|
151
|
+
if (name === "task_context") return textResult(await taskContext({ ...args, root }));
|
|
152
|
+
if (name === "shared_context") return textResult(await sharedContext({ ...args, root }));
|
|
153
|
+
if (name === "record_world_assertion") return textResult(await recordWorldAssertion({ ...args, root }));
|
|
154
|
+
if (name === "world_context") return textResult(await worldContext({ ...args, root }));
|
|
155
|
+
if (name === "project_portfolio") return textResult(await projectPortfolioContext({ ...args, root }));
|
|
156
|
+
if (name === "record_project_observation") return textResult(await recordProjectObservation({ ...args, root }));
|
|
157
|
+
if (name === "evaluate_autonomy_action") return textResult(await evaluateAutonomyAction({ ...args, root }));
|
|
158
|
+
if (name === "session_timeline_index") {
|
|
159
|
+
const input = timelineInvocationInput(args);
|
|
160
|
+
if (runtimeTimelineGroup(environment, input)) return textResult(unavailableTimelineInvocation("timeline-group-suppressed"));
|
|
161
|
+
if (!input.valid) return textResult(unavailableTimelineInvocation(input.reason || "timeline-scope-invalid"));
|
|
162
|
+
const timeline = timelineTransport(input, root, environment);
|
|
163
|
+
if (!runtimeGatewayMatchesTimeline(environment, timeline.scope, timeline.host)) {
|
|
164
|
+
return textResult(unavailableTimelineInvocation("timeline-gateway-binding-mismatch"));
|
|
165
|
+
}
|
|
166
|
+
const invocationRequest = timelineInvocationRequest("index", args, root);
|
|
167
|
+
if (!invocationRequest) return textResult(unavailableTimelineInvocation("timeline-invocation-unavailable"));
|
|
168
|
+
return textResult(await indexSessionTimeline({ root, host: timeline.host, sessionId: input.request.sessionId, scope: timeline.scope,
|
|
169
|
+
maxBytes: args.maxBytes, enrollmentDigest: input.request.enrollmentDigest,
|
|
170
|
+
hostHome: timelineHostHome(timeline.host, environment), transportDigest: timeline.transportDigest, invocationRequest }));
|
|
171
|
+
}
|
|
172
|
+
if (name === "session_timeline_search") {
|
|
173
|
+
const input = timelineInvocationInput(args);
|
|
174
|
+
if (runtimeTimelineGroup(environment, input)) return textResult(unavailableTimelineInvocation("timeline-group-suppressed"));
|
|
175
|
+
if (!input.valid) return textResult(unavailableTimelineInvocation(input.reason || "timeline-scope-invalid"));
|
|
176
|
+
const timeline = timelineTransport(input, root, environment);
|
|
177
|
+
if (!runtimeGatewayMatchesTimeline(environment, timeline.scope, timeline.host)) {
|
|
178
|
+
return textResult(unavailableTimelineInvocation("timeline-gateway-binding-mismatch"));
|
|
179
|
+
}
|
|
180
|
+
const invocationRequest = timelineInvocationRequest("search", args, root);
|
|
181
|
+
if (!invocationRequest) return textResult(unavailableTimelineInvocation("timeline-invocation-unavailable"));
|
|
182
|
+
return textResult(await searchSessionTimeline({ root, host: timeline.host, sessionId: input.request.sessionId, scope: timeline.scope,
|
|
183
|
+
at: args.at, query: args.query, windowSeconds: args.windowSeconds, includePriorSessions: args.includePriorSessions,
|
|
184
|
+
enrollmentDigest: input.request.enrollmentDigest,
|
|
185
|
+
hostHome: timelineHostHome(timeline.host, environment), transportDigest: timeline.transportDigest,
|
|
186
|
+
invocationRequest }));
|
|
187
|
+
}
|
|
188
|
+
if (name === "audit") return textResult(await runAudit(root));
|
|
189
|
+
if (name === "complete_delivery") {
|
|
190
|
+
const completed = await completeDelivery(args);
|
|
191
|
+
return textResult(completed, completed.blocked || completed.status === "degraded");
|
|
192
|
+
}
|
|
193
|
+
if (name === "record_delivery_premortem") {
|
|
194
|
+
if (typeof args.root !== "string" || !args.root) {
|
|
195
|
+
throw new Error("record_delivery_premortem requires the exact project root");
|
|
196
|
+
}
|
|
197
|
+
const usage = await verifyDeliveryAgentUse({ root: args.root,
|
|
198
|
+
requirementId: args.requirementId });
|
|
199
|
+
if (usage.status === "mismatch") return textResult(usage, true);
|
|
200
|
+
if (usage.blocked || usage.status === "degraded") return textResult({ status: "unverified",
|
|
201
|
+
blocked: false, items: args.items, agentSpineUse: advisoryDeliveryUse(usage),
|
|
202
|
+
completionVerified: false, automaticRetry: false });
|
|
203
|
+
const premortem = await recordDeliveryPremortem({
|
|
204
|
+
root: args.root,
|
|
205
|
+
requirementId: args.requirementId,
|
|
206
|
+
items: args.items
|
|
207
|
+
});
|
|
208
|
+
return textResult({ ...premortem, agentSpineUse: usage }, premortem.blocked);
|
|
209
|
+
}
|
|
210
|
+
if (name === "recover_delivery_premortem") {
|
|
211
|
+
if (typeof args.root !== "string" || !args.root) {
|
|
212
|
+
throw new Error("recover_delivery_premortem requires the exact project root");
|
|
213
|
+
}
|
|
214
|
+
const recovery = await recoverDeliveryPremortem({ root: args.root,
|
|
215
|
+
predecessorRequirementId: args.predecessorRequirementId,
|
|
216
|
+
taskId: args.taskId || null });
|
|
217
|
+
return textResult(recovery, recovery.blocked);
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function dispatch(message, tools, version, environment) {
|
|
223
|
+
const { id, method, params = {} } = message;
|
|
224
|
+
if (method === "initialize") {
|
|
225
|
+
return {
|
|
226
|
+
jsonrpc: "2.0", id,
|
|
227
|
+
result: {
|
|
228
|
+
protocolVersion: params.protocolVersion || "2025-06-18",
|
|
229
|
+
capabilities: { tools: { listChanged: false } },
|
|
230
|
+
serverInfo: { name: "agent-spine", version }
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
|
|
235
|
+
if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools } };
|
|
236
|
+
if (method === "tools/call") {
|
|
237
|
+
try {
|
|
238
|
+
return { jsonrpc: "2.0", id, result: await callTool(params.name, params.arguments, environment) };
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return { jsonrpc: "2.0", id, result: textResult({ error: error.message }, true) };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (method?.startsWith("notifications/")) return null;
|
|
244
|
+
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function startMcpProtocol({ input, output, tools, version, environment = process.env }) {
|
|
248
|
+
input.setEncoding("utf8");
|
|
249
|
+
let buffer = "";
|
|
250
|
+
let queue = Promise.resolve();
|
|
251
|
+
input.on("data", (chunk) => {
|
|
252
|
+
buffer += chunk;
|
|
253
|
+
let newline;
|
|
254
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
255
|
+
const line = buffer.slice(0, newline).trim();
|
|
256
|
+
buffer = buffer.slice(newline + 1);
|
|
257
|
+
if (!line) continue;
|
|
258
|
+
queue = queue.then(async () => {
|
|
259
|
+
let response;
|
|
260
|
+
try {
|
|
261
|
+
response = await dispatch(JSON.parse(line), tools, version, environment);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
response = { jsonrpc: "2.0", id: null, error: { code: -32700, message: error.message } };
|
|
264
|
+
}
|
|
265
|
+
if (response) output.write(`${JSON.stringify(response)}\n`);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { canonicalPath, isInside } from "./paths.js";
|
|
2
|
+
import { resolveHostSourceCatalog } from "./source-roots.js";
|
|
3
|
+
import { inspectPremortemState } from "./delivery-premortem.js";
|
|
4
|
+
|
|
5
|
+
const INTERNAL = new Set(["catalog", "userStateRoot", "sourceDiagnostics", "sourceRegistry",
|
|
6
|
+
"env", "input", "memoryHooks", "resolvedSources"]);
|
|
7
|
+
|
|
8
|
+
export function rejectInternalSourceArguments(args) {
|
|
9
|
+
for (const key of INTERNAL) {
|
|
10
|
+
if (Object.hasOwn(args, key)) throw new Error(`unsupported internal source argument: ${key}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function boundBriefingArguments(args) {
|
|
15
|
+
rejectInternalSourceArguments(args);
|
|
16
|
+
if (!args.requirementId) return args;
|
|
17
|
+
const current = await inspectPremortemState({ root: args.root, requirementId: args.requirementId });
|
|
18
|
+
if (current.blocked || current.status === "degraded" || !current.binding) {
|
|
19
|
+
throw new Error("delivery source binding is unavailable; no briefing receipt issued");
|
|
20
|
+
}
|
|
21
|
+
const result = { ...args };
|
|
22
|
+
for (const [key, field] of [["host", "host"], ["entityId", "entityId"],
|
|
23
|
+
["projectId", "projectId"], ["groupId", "groupId"], ["currentTaskId", "taskId"]]) {
|
|
24
|
+
const expected = current.binding[field];
|
|
25
|
+
if (Object.hasOwn(args, key) && args[key] !== expected) {
|
|
26
|
+
throw new Error(`delivery source ${key} does not match its requirement`);
|
|
27
|
+
}
|
|
28
|
+
result[key] = expected;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function resolveMcpSources({ root, cwd = root,
|
|
34
|
+
host = process.env.AGENTSPINE_HOST || "generic", entityId = null,
|
|
35
|
+
groupId = null, projectId = null, currentTaskId = null, required = false }) {
|
|
36
|
+
const canonicalRoot = await canonicalPath(root);
|
|
37
|
+
const canonicalCwd = await canonicalPath(cwd);
|
|
38
|
+
if (!isInside(canonicalRoot, canonicalCwd)) throw new Error("MCP cwd must remain inside its root");
|
|
39
|
+
// Only the process environment and the locally read registry select sources.
|
|
40
|
+
// No MCP-provided catalog, registry, profile, or state location enters here.
|
|
41
|
+
const sources = await resolveHostSourceCatalog({ host, cwd: canonicalCwd,
|
|
42
|
+
input: { agent_spine_scope: { entity_id: entityId, group_id: groupId,
|
|
43
|
+
project_id: projectId, task_id: currentTaskId } } });
|
|
44
|
+
if (sources.projectRoot !== canonicalRoot) {
|
|
45
|
+
throw new Error("MCP root differs from the resolved host project root");
|
|
46
|
+
}
|
|
47
|
+
if (required && (sources.diagnostics.incomplete || sources.diagnostics.skipped.length)) {
|
|
48
|
+
throw new Error("MCP source discovery is incomplete; no delivery receipt issued");
|
|
49
|
+
}
|
|
50
|
+
return sources;
|
|
51
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { TIMELINE_HOSTS } from "./session-timeline-provider.js";
|
|
2
|
+
|
|
3
|
+
const stableId = {
|
|
4
|
+
type: "string",
|
|
5
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$"
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const optionalId = { anyOf: [stableId, { type: "null" }] };
|
|
9
|
+
|
|
10
|
+
// Hosts qualify MCP tools differently. These prefixes must stay exact: a
|
|
11
|
+
// foreign MCP server can expose an identically named tool but must never
|
|
12
|
+
// receive a timeline invocation permit.
|
|
13
|
+
export const AGENTSPINE_TIMELINE_TOOL_PREFIX = "mcp__plugin_agent-spine_agent-spine__";
|
|
14
|
+
export const BLUN_TIMELINE_TOOL_PREFIX = "mcp__agent-spine__";
|
|
15
|
+
export const KING_PLUGIN_TIMELINE_TOOL_PREFIX = "mcp__plugin-agent-spine_agent-spine__";
|
|
16
|
+
|
|
17
|
+
const TIMELINE_TOOL_PREFIXES = [
|
|
18
|
+
AGENTSPINE_TIMELINE_TOOL_PREFIX,
|
|
19
|
+
BLUN_TIMELINE_TOOL_PREFIX,
|
|
20
|
+
KING_PLUGIN_TIMELINE_TOOL_PREFIX
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const SCOPE_FIELDS = [
|
|
24
|
+
["entityId", ["entityId", "entity_id"]],
|
|
25
|
+
["userId", ["userId", "user_id"]],
|
|
26
|
+
["tenantId", ["tenantId", "tenant_id"]],
|
|
27
|
+
["projectId", ["projectId", "project_id"]],
|
|
28
|
+
["groupId", ["groupId", "group_id"]],
|
|
29
|
+
["currentTaskId", ["taskId", "task_id", "currentTaskId", "current_task_id"]],
|
|
30
|
+
["goalId", ["goalId", "goal_id"]],
|
|
31
|
+
["goalStepId", ["goalStepId", "goal_step_id"]],
|
|
32
|
+
["timelineVisibility", ["timelineVisibility", "timeline_visibility"]]
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const REQUEST_FIELDS = [
|
|
36
|
+
["host", ["host"]],
|
|
37
|
+
["sessionId", ["sessionId", "session_id"]],
|
|
38
|
+
["enrollmentDigest", ["enrollmentDigest", "enrollment_digest"]]
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function owns(value, key) {
|
|
42
|
+
return Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function plainObject(value) {
|
|
46
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
47
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function absent(value) { return value === undefined || value === null || value === ""; }
|
|
51
|
+
|
|
52
|
+
function containers(args) {
|
|
53
|
+
if (!plainObject(args)) return { valid: false, reason: "timeline-scope-invalid", values: [] };
|
|
54
|
+
if (!owns(args, "agent_spine_scope") || args.agent_spine_scope === null) {
|
|
55
|
+
return { valid: true, reason: null, values: [args] };
|
|
56
|
+
}
|
|
57
|
+
if (!plainObject(args.agent_spine_scope)) {
|
|
58
|
+
return { valid: false, reason: "timeline-scope-invalid", values: [args] };
|
|
59
|
+
}
|
|
60
|
+
return { valid: true, reason: null, values: [args, args.agent_spine_scope] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function claim(values, aliases) {
|
|
64
|
+
const supplied = values.flatMap((value) => aliases.filter((key) => owns(value, key)).map((key) => value[key]));
|
|
65
|
+
const present = supplied.filter((value) => !absent(value));
|
|
66
|
+
if (!present.length) return { valid: true, value: null, present: false };
|
|
67
|
+
if (present.some((value) => typeof value !== "string") || !present.every((value) => value === present[0])) {
|
|
68
|
+
return { valid: false, value: null, present: true };
|
|
69
|
+
}
|
|
70
|
+
return { valid: true, value: present[0], present: true };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validId(value) {
|
|
74
|
+
return value === null || (typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$/.test(value));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolvedClaims(args) {
|
|
78
|
+
const nested = containers(args);
|
|
79
|
+
const values = nested.values;
|
|
80
|
+
const result = { valid: nested.valid, reason: nested.reason, groupClaim: false, scope: {}, request: {} };
|
|
81
|
+
for (const [field, aliases] of [...SCOPE_FIELDS, ...REQUEST_FIELDS]) {
|
|
82
|
+
const resolved = claim(values, aliases);
|
|
83
|
+
if (field === "groupId") result.groupClaim = resolved.present;
|
|
84
|
+
if (!resolved.valid || (field === "host" && resolved.value !== null && !TIMELINE_HOSTS.includes(resolved.value))
|
|
85
|
+
|| (field !== "timelineVisibility" && !validId(resolved.value))
|
|
86
|
+
|| (field === "timelineVisibility" && resolved.value !== null && resolved.value !== "private-verified")
|
|
87
|
+
|| (field === "enrollmentDigest" && resolved.value !== null && !/^[a-f0-9]{64}$/.test(resolved.value))) {
|
|
88
|
+
result.valid = false;
|
|
89
|
+
result.reason ||= "timeline-scope-conflict";
|
|
90
|
+
}
|
|
91
|
+
if (SCOPE_FIELDS.some(([name]) => name === field)) result.scope[field] = resolved.value;
|
|
92
|
+
else result.request[field] = resolved.value;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Direct stdio callers can bypass the MCP schema. Merge the flat and nested
|
|
98
|
+
// representations only when every present claim agrees. In particular, a
|
|
99
|
+
// nested group claim stays visible when a top-level argument tries to clear
|
|
100
|
+
// it with null, so no existing private permit crosses into a group turn.
|
|
101
|
+
export function timelineInvocationInput(args = {}) {
|
|
102
|
+
return resolvedClaims(args);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function timelineScope(args = {}) {
|
|
106
|
+
return resolvedClaims(args).scope;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function timelineToolKind(name) {
|
|
110
|
+
const qualified = String(name || "");
|
|
111
|
+
if (TIMELINE_TOOL_PREFIXES.some((prefix) => qualified === `${prefix}session_timeline_index`)) return "index";
|
|
112
|
+
if (TIMELINE_TOOL_PREFIXES.some((prefix) => qualified === `${prefix}session_timeline_search`)) return "search";
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function timelineInvocationRequest(tool, args, root) {
|
|
117
|
+
const input = resolvedClaims(args);
|
|
118
|
+
if (!input.valid || input.groupClaim || !input.request.sessionId) return null;
|
|
119
|
+
const scope = input.scope;
|
|
120
|
+
const request = { root, tool, sessionId: input.request.sessionId, entityId: scope.entityId, userId: scope.userId,
|
|
121
|
+
tenantId: scope.tenantId, projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId,
|
|
122
|
+
goalId: scope.goalId, goalStepId: scope.goalStepId, timelineVisibility: scope.timelineVisibility,
|
|
123
|
+
enrollmentDigest: input.request.enrollmentDigest };
|
|
124
|
+
if (tool === "index") return { ...request, maxBytes: args.maxBytes ?? 4 * 1024 * 1024 };
|
|
125
|
+
return { ...request, at: args.at ?? null, query: args.query ?? null,
|
|
126
|
+
windowSeconds: args.windowSeconds === undefined ? 0 : args.windowSeconds,
|
|
127
|
+
includePriorSessions: args.includePriorSessions === true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const scopeProperties = {
|
|
131
|
+
host: { enum: TIMELINE_HOSTS },
|
|
132
|
+
entityId: stableId, userId: stableId, tenantId: stableId, projectId: stableId,
|
|
133
|
+
taskId: stableId, goalId: optionalId, goalStepId: optionalId
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const sessionTimelineTools = [
|
|
137
|
+
{
|
|
138
|
+
name: "session_timeline_index",
|
|
139
|
+
description: "Index bounded evidence from one explicitly enrolled immutable provider transcript snapshot. Claude, Codex and King use separate format adapters; changed sources require a renewed host receipt and this tool never copies a transcript or grants authority.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object", additionalProperties: false,
|
|
142
|
+
required: [],
|
|
143
|
+
properties: { root: { type: "string", minLength: 1 }, sessionId: stableId, ...scopeProperties,
|
|
144
|
+
enrollmentDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
|
|
145
|
+
timelineVisibility: { const: "private-verified" }, groupId: { anyOf: [stableId, { type: "null" }] },
|
|
146
|
+
maxBytes: { type: "integer", minimum: 65536, maximum: 16777216 } }
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "session_timeline_search",
|
|
151
|
+
description: "Search the current or a same-task prior explicitly enrolled immutable Claude, Codex or King source for redacted objective evidence by exact UTC time or at least two concrete terms. Prior-session search opens only one indexed source and never grants authority.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object", additionalProperties: false,
|
|
154
|
+
required: [],
|
|
155
|
+
anyOf: [{ required: ["at"] }, { required: ["query"] }],
|
|
156
|
+
properties: { root: { type: "string", minLength: 1 }, sessionId: stableId, ...scopeProperties,
|
|
157
|
+
enrollmentDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
|
|
158
|
+
timelineVisibility: { const: "private-verified" }, groupId: { anyOf: [stableId, { type: "null" }] },
|
|
159
|
+
at: { type: "string", format: "date-time" }, query: { type: "string", minLength: 3, maxLength: 512 },
|
|
160
|
+
windowSeconds: { type: "integer", minimum: 0, maximum: 900 },
|
|
161
|
+
includePriorSessions: { type: "boolean" } }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const stableId = {
|
|
2
|
+
type: "string",
|
|
3
|
+
pattern: "^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$"
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const nullableStableId = { anyOf: [stableId, { type: "null" }] };
|
|
7
|
+
|
|
8
|
+
export const worldModelTools = [
|
|
9
|
+
{
|
|
10
|
+
name: "record_world_assertion",
|
|
11
|
+
description: "Persist one provenance-bound world, user, relationship, or team assertion outside source files. Optional typed knowledge distinguishes facts, user preferences, decisions, task state, and error lessons. Measurements and explicit user feedback become context-only facts; model output remains a proposal. Authority predicates and secret-shaped structured knowledge are rejected.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object", additionalProperties: false,
|
|
14
|
+
required: ["id", "subjectId", "predicate", "value", "evidenceKind", "evidenceId", "evidenceDigest", "observedAt"],
|
|
15
|
+
properties: {
|
|
16
|
+
root: { type: "string" }, id: stableId, subjectId: stableId,
|
|
17
|
+
predicate: { type: "string", pattern: "^[a-z][a-z0-9.-]{0,127}$" },
|
|
18
|
+
value: {},
|
|
19
|
+
evidenceKind: { type: "string", enum: ["objective-measurement", "explicit-user-feedback", "model-suggestion"] },
|
|
20
|
+
evidenceId: stableId,
|
|
21
|
+
evidenceDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
|
|
22
|
+
knowledgeKind: {
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: ["fact", "user-preference", "decision", "task-state", "error-lesson"]
|
|
25
|
+
},
|
|
26
|
+
sessionRef: { type: "string", pattern: "^session-ref:[a-f0-9]{32}$" },
|
|
27
|
+
messageRef: stableId,
|
|
28
|
+
observedAt: { type: "string", format: "date-time" },
|
|
29
|
+
expiresAt: { anyOf: [{ type: "string", format: "date-time" }, { type: "null" }] },
|
|
30
|
+
projectId: nullableStableId, groupId: nullableStableId,
|
|
31
|
+
privacy: { type: "string", enum: ["private", "shared", "group"] },
|
|
32
|
+
supersedes: { type: "array", maxItems: 50, uniqueItems: true, items: stableId },
|
|
33
|
+
reason: { type: "string", maxLength: 500 }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "world_context",
|
|
39
|
+
description: "Read privacy-filtered world context. Conflicts, expiry, and suggestions remain separate; continuationTaskId adds bounded, sourced task-relevant knowledge.",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object", additionalProperties: false,
|
|
42
|
+
properties: {
|
|
43
|
+
root: { type: "string" }, subjectId: nullableStableId,
|
|
44
|
+
projectId: nullableStableId, groupId: nullableStableId,
|
|
45
|
+
includePrivate: { type: "boolean" }, includeKnowledgeHistory: { type: "boolean" },
|
|
46
|
+
continuationTaskId: nullableStableId,
|
|
47
|
+
maxItems: { type: "integer", minimum: 1, maximum: 500 },
|
|
48
|
+
now: { type: "string", format: "date-time" }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
];
|
|
@@ -35,9 +35,10 @@ function sameFile(left, right) {
|
|
|
35
35
|
&& left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async function removeStaleLock(path, staleAfterMs) {
|
|
38
|
+
async function removeStaleLock(path, staleAfterMs, assertPath = null) {
|
|
39
39
|
let before;
|
|
40
40
|
try {
|
|
41
|
+
await assertPath?.();
|
|
41
42
|
before = await stat(path);
|
|
42
43
|
} catch (error) {
|
|
43
44
|
if (isTransientLockMetadataError(error)) return false;
|
|
@@ -47,6 +48,7 @@ async function removeStaleLock(path, staleAfterMs) {
|
|
|
47
48
|
await readOwner(path);
|
|
48
49
|
let after;
|
|
49
50
|
try {
|
|
51
|
+
await assertPath?.();
|
|
50
52
|
after = await stat(path);
|
|
51
53
|
} catch (error) {
|
|
52
54
|
if (isTransientLockMetadataError(error)) return false;
|
|
@@ -54,7 +56,9 @@ async function removeStaleLock(path, staleAfterMs) {
|
|
|
54
56
|
}
|
|
55
57
|
if (!sameFile(before, after) || Date.now() - after.mtimeMs <= staleAfterMs) return false;
|
|
56
58
|
try {
|
|
59
|
+
await assertPath?.();
|
|
57
60
|
await unlink(path);
|
|
61
|
+
await assertPath?.();
|
|
58
62
|
return true;
|
|
59
63
|
} catch (error) {
|
|
60
64
|
if (isFileLockContention(error) || isTransientLockMetadataError(error)) return false;
|
|
@@ -66,7 +70,8 @@ export async function withOwnedFileLock(path, task, {
|
|
|
66
70
|
staleAfterMs = 15000,
|
|
67
71
|
heartbeatIntervalMs = 1000,
|
|
68
72
|
retryDelayMs = 25,
|
|
69
|
-
maxAttempts = 80
|
|
73
|
+
maxAttempts = 80,
|
|
74
|
+
assertPath = null
|
|
70
75
|
} = {}) {
|
|
71
76
|
if (typeof task !== "function") throw new Error("owned file lock requires a task");
|
|
72
77
|
if (!Number.isInteger(staleAfterMs) || staleAfterMs < 50
|
|
@@ -78,13 +83,16 @@ export async function withOwnedFileLock(path, task, {
|
|
|
78
83
|
}
|
|
79
84
|
const token = randomUUID();
|
|
80
85
|
const acquiredAt = new Date().toISOString();
|
|
81
|
-
let acquired = false;
|
|
86
|
+
let acquired = false; let recovered = false;
|
|
82
87
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
83
|
-
let handle;
|
|
88
|
+
let handle; let created = false;
|
|
84
89
|
try {
|
|
90
|
+
await assertPath?.();
|
|
85
91
|
handle = await open(path, "wx", 0o600);
|
|
92
|
+
created = true;
|
|
86
93
|
const payload = `${JSON.stringify(lockPayload(token, acquiredAt, staleAfterMs))}\n`;
|
|
87
94
|
await handle.writeFile(payload, "utf8");
|
|
95
|
+
await assertPath?.();
|
|
88
96
|
acquired = true;
|
|
89
97
|
break;
|
|
90
98
|
} catch (error) {
|
|
@@ -92,13 +100,16 @@ export async function withOwnedFileLock(path, task, {
|
|
|
92
100
|
if (handle) {
|
|
93
101
|
await handle.close();
|
|
94
102
|
handle = null;
|
|
95
|
-
|
|
103
|
+
try {
|
|
104
|
+
await assertPath?.();
|
|
105
|
+
if (created) await unlink(path);
|
|
106
|
+
} catch (cleanupError) {
|
|
96
107
|
if (cleanupError.code !== "ENOENT") error.cleanupError = cleanupError;
|
|
97
|
-
}
|
|
108
|
+
}
|
|
98
109
|
}
|
|
99
110
|
throw error;
|
|
100
111
|
}
|
|
101
|
-
await removeStaleLock(path, staleAfterMs);
|
|
112
|
+
recovered ||= await removeStaleLock(path, staleAfterMs, assertPath);
|
|
102
113
|
if (attempt + 1 < maxAttempts) await delay(retryDelayMs);
|
|
103
114
|
} finally {
|
|
104
115
|
await handle?.close();
|
|
@@ -110,7 +121,9 @@ export async function withOwnedFileLock(path, task, {
|
|
|
110
121
|
let heartbeat = Promise.resolve();
|
|
111
122
|
const assertOwned = async () => {
|
|
112
123
|
if (ownershipError) throw ownershipError;
|
|
124
|
+
await assertPath?.();
|
|
113
125
|
const owner = await readOwner(path);
|
|
126
|
+
await assertPath?.();
|
|
114
127
|
if (!owner || owner.token !== token) {
|
|
115
128
|
ownershipError = new Error("state lock ownership was lost; mutation aborted");
|
|
116
129
|
throw ownershipError;
|
|
@@ -120,6 +133,7 @@ export async function withOwnedFileLock(path, task, {
|
|
|
120
133
|
await assertOwned();
|
|
121
134
|
const now = new Date();
|
|
122
135
|
await utimes(path, now, now);
|
|
136
|
+
await assertPath?.();
|
|
123
137
|
};
|
|
124
138
|
const timer = setInterval(() => {
|
|
125
139
|
heartbeat = heartbeat.then(renew).catch((error) => { ownershipError ||= error; });
|
|
@@ -127,15 +141,21 @@ export async function withOwnedFileLock(path, task, {
|
|
|
127
141
|
timer.unref?.();
|
|
128
142
|
|
|
129
143
|
try {
|
|
130
|
-
const result = await task({ token, acquiredAt, assertOwned });
|
|
144
|
+
const result = await task({ token, acquiredAt, recovered, assertOwned });
|
|
131
145
|
await assertOwned();
|
|
132
146
|
return result;
|
|
133
147
|
} finally {
|
|
134
148
|
clearInterval(timer);
|
|
135
149
|
await heartbeat;
|
|
136
|
-
const owner = await
|
|
150
|
+
const owner = await (async () => {
|
|
151
|
+
await assertPath?.();
|
|
152
|
+
return readOwner(path);
|
|
153
|
+
})().catch(() => null);
|
|
137
154
|
if (owner?.token === token) {
|
|
138
|
-
await
|
|
155
|
+
await (async () => {
|
|
156
|
+
await assertPath?.();
|
|
157
|
+
await unlink(path);
|
|
158
|
+
})().catch((error) => {
|
|
139
159
|
if (error.code !== "ENOENT") throw error;
|
|
140
160
|
});
|
|
141
161
|
}
|