blun-king-cli 9.1.567 → 9.1.570
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 +33 -0
- 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 +3 -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 +2861 -1173
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +5 -18
- 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,202 @@
|
|
|
1
|
+
import { canonicalPath } from "./paths.js";
|
|
2
|
+
import { resolveHostSourceCatalog } from "./source-roots.js";
|
|
3
|
+
import { authorizeSessionTimelineInvocation } from "./session-timeline.js";
|
|
4
|
+
import { timelineInvocationRequest, timelineToolKind } from "./mcp-timeline-tools.js";
|
|
5
|
+
import { gatewayEnvironmentContext, hookDeliveryId, hostFromInput, sessionId } from "./hook-context.js";
|
|
6
|
+
import { blockedHookOutput } from "./hook-output.js";
|
|
7
|
+
import { sessionTimelineBinding } from "./session-timeline-contract.js";
|
|
8
|
+
import { resolvePrivateSessionTimelineEnrollment } from "./session-timeline-enrollment.js";
|
|
9
|
+
import { timelineTransportDigest } from "./session-timeline-transport.js";
|
|
10
|
+
import { runtimeHostForTimeline, timelineHostForRuntime, timelineHostHome } from "./session-timeline-provider.js";
|
|
11
|
+
|
|
12
|
+
function plainObject(value) {
|
|
13
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
14
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function absent(value) { return value === undefined || value === null || value === ""; }
|
|
18
|
+
|
|
19
|
+
function rawGroupClaim(input, gateway) {
|
|
20
|
+
const nested = plainObject(input?.agent_spine_scope) ? input.agent_spine_scope : null;
|
|
21
|
+
return !absent(process.env.AGENTSPINE_GROUP_ID) || !absent(gateway?.groupId)
|
|
22
|
+
|| !absent(input?.group_id ?? input?.groupId)
|
|
23
|
+
|| !absent(nested?.group_id ?? nested?.groupId);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function gatewayMatchesEnrollment(gateway, binding) {
|
|
27
|
+
if (!gateway) return true;
|
|
28
|
+
if (!absent(gateway.groupId)) return false;
|
|
29
|
+
const fields = [
|
|
30
|
+
["host", "host"], ["entityId", "entityId"], ["projectId", "projectId"],
|
|
31
|
+
["taskId", "taskId"], ["goalId", "goalId"], ["goalStepId", "goalStepId"]
|
|
32
|
+
];
|
|
33
|
+
return gateway.host === runtimeHostForTimeline(binding.host)
|
|
34
|
+
&& fields.slice(1).every(([gatewayField, bindingField]) => gateway[gatewayField] === binding[bindingField])
|
|
35
|
+
&& process.env.AGENTSPINE_USER_ID === binding.userId && process.env.AGENTSPINE_TENANT_ID === binding.tenantId;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function bindingScope(binding, enrollment) {
|
|
39
|
+
return { entityId: binding.entityId, userId: binding.userId, tenantId: binding.tenantId,
|
|
40
|
+
projectId: binding.projectId, groupId: null, currentTaskId: binding.taskId,
|
|
41
|
+
goalId: binding.goalId, goalStepId: binding.goalStepId,
|
|
42
|
+
timelineVisibility: enrollment.timelineVisibility };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function nestedScope(args) {
|
|
46
|
+
return plainObject(args?.agent_spine_scope) ? args.agent_spine_scope : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validNestedScope(args) {
|
|
50
|
+
return args?.agent_spine_scope === undefined || args.agent_spine_scope === null || plainObject(args.agent_spine_scope);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scopeValues(args, keys) {
|
|
54
|
+
const nested = nestedScope(args);
|
|
55
|
+
return keys.flatMap((key) => [args?.[key], nested?.[key]]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function scopeClaimsMatch(args, keys, expected) {
|
|
59
|
+
return scopeValues(args, keys).every((value) => absent(value) || value === expected);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function noScopeClaims(args, keys) {
|
|
63
|
+
return scopeValues(args, keys).every(absent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function matchesScopeBinding(args, binding) {
|
|
67
|
+
return validNestedScope(args)
|
|
68
|
+
&& scopeClaimsMatch(args, ["sessionId", "session_id"], binding.sessionId)
|
|
69
|
+
&& scopeClaimsMatch(args, ["entityId", "entity_id"], binding.entityId)
|
|
70
|
+
&& scopeClaimsMatch(args, ["userId", "user_id"], binding.userId)
|
|
71
|
+
&& scopeClaimsMatch(args, ["tenantId", "tenant_id"], binding.tenantId)
|
|
72
|
+
&& scopeClaimsMatch(args, ["projectId", "project_id"], binding.projectId)
|
|
73
|
+
&& scopeClaimsMatch(args, ["taskId", "task_id", "currentTaskId", "current_task_id"], binding.taskId)
|
|
74
|
+
&& scopeClaimsMatch(args, ["goalId", "goal_id"], binding.goalId)
|
|
75
|
+
&& scopeClaimsMatch(args, ["goalStepId", "goal_step_id"], binding.goalStepId)
|
|
76
|
+
&& noScopeClaims(args, ["groupId", "group_id"]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function matchesBinding(args, binding) {
|
|
80
|
+
return matchesScopeBinding(args, binding)
|
|
81
|
+
&& noScopeClaims(args, ["accessProof", "access_proof", "invocationPermit", "invocation_permit",
|
|
82
|
+
"transportDigest", "transport_digest", "timelineVisibility", "timeline_visibility",
|
|
83
|
+
"enrollmentDigest", "enrollment_digest"]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function matchesHostBinding(input, binding) {
|
|
87
|
+
return matchesScopeBinding(input, binding)
|
|
88
|
+
&& scopeClaimsMatch(input, ["host", "provider"], binding.host)
|
|
89
|
+
&& noScopeClaims(input, ["accessProof", "access_proof", "invocationPermit", "invocation_permit",
|
|
90
|
+
"transportDigest", "transport_digest", "enrollmentDigest", "enrollment_digest"]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function claimedScope(input) {
|
|
94
|
+
const claim = (keys) => {
|
|
95
|
+
const values = scopeValues(input, keys).filter((value) => !absent(value));
|
|
96
|
+
return values.length && values.every((value) => value === values[0]) ? values[0] : null;
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
entityId: claim(["entityId", "entity_id"]), userId: claim(["userId", "user_id"]),
|
|
100
|
+
tenantId: claim(["tenantId", "tenant_id"]), projectId: claim(["projectId", "project_id"]),
|
|
101
|
+
groupId: claim(["groupId", "group_id"]), currentTaskId: claim(["taskId", "task_id", "currentTaskId", "current_task_id"]),
|
|
102
|
+
goalId: claim(["goalId", "goal_id"]), goalStepId: claim(["goalStepId", "goal_step_id"])
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function matchesRoot(value, root) {
|
|
107
|
+
if (absent(value)) return true;
|
|
108
|
+
if (typeof value !== "string") return false;
|
|
109
|
+
try { return await canonicalPath(value) === root; }
|
|
110
|
+
catch { return false; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function requestInput(tool, args) {
|
|
114
|
+
if (tool === "index") return args.maxBytes === undefined ? {} : { maxBytes: args.maxBytes };
|
|
115
|
+
if (args.at === undefined && args.query === undefined) return null;
|
|
116
|
+
if (args.includePriorSessions !== undefined && typeof args.includePriorSessions !== "boolean") return null;
|
|
117
|
+
return { ...(args.at === undefined ? {} : { at: args.at }), ...(args.query === undefined ? {} : { query: args.query }),
|
|
118
|
+
...(args.windowSeconds === undefined ? {} : { windowSeconds: args.windowSeconds }),
|
|
119
|
+
...(args.includePriorSessions === undefined ? {} : { includePriorSessions: args.includePriorSessions }) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function denied(reason) {
|
|
123
|
+
return { blocked: true, reason, hostOutput: blockedHookOutput("PreToolUse", reason) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function allowed(updatedInput) {
|
|
127
|
+
const permission = ["codex", "king"].includes(updatedInput.host) ? {} : {
|
|
128
|
+
permissionDecision: "ask",
|
|
129
|
+
permissionDecisionReason: "AgentSpine binds this one context-only timeline lookup to the current host session and local transport."
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
blocked: false,
|
|
133
|
+
updatedInput,
|
|
134
|
+
hostOutput: {
|
|
135
|
+
hookSpecificOutput: {
|
|
136
|
+
hookEventName: "PreToolUse",
|
|
137
|
+
...permission,
|
|
138
|
+
updatedInput
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function runTimelineToolGuard(input) {
|
|
145
|
+
try {
|
|
146
|
+
const tool = timelineToolKind(input?.tool_name);
|
|
147
|
+
const runtimeHost = hostFromInput(input || {});
|
|
148
|
+
const host = timelineHostForRuntime(runtimeHost, process.env);
|
|
149
|
+
if (input?.hook_event_name !== "PreToolUse" || !tool || !host || !plainObject(input.tool_input)) {
|
|
150
|
+
return denied("AgentSpine session timeline requires a verified supported-host PreToolUse payload.");
|
|
151
|
+
}
|
|
152
|
+
const resolved = await resolveHostSourceCatalog({ host: runtimeHost, cwd: input.cwd || process.cwd(), input });
|
|
153
|
+
const root = resolved.projectRoot;
|
|
154
|
+
const historyHome = timelineHostHome(host) ?? resolved.hostHome;
|
|
155
|
+
const hostSession = sessionId(input);
|
|
156
|
+
const gateway = gatewayEnvironmentContext();
|
|
157
|
+
if (rawGroupClaim(input, gateway)) return denied("AgentSpine session timeline is unavailable for group-scoped activity.");
|
|
158
|
+
const provisionalBinding = sessionTimelineBinding({ host, sessionId: hostSession, scope: claimedScope(input) });
|
|
159
|
+
const transportDigest = timelineTransportDigest({ root, binding: provisionalBinding });
|
|
160
|
+
if (!transportDigest) {
|
|
161
|
+
return denied("AgentSpine session timeline requires a locally configured per-session transport capability.");
|
|
162
|
+
}
|
|
163
|
+
const enrollment = await resolvePrivateSessionTimelineEnrollment({ root, host, sessionId: hostSession,
|
|
164
|
+
transcriptPath: input.transcript_path ?? input.transcriptPath, hostHome: historyHome,
|
|
165
|
+
expectedTransportDigest: transportDigest });
|
|
166
|
+
if (enrollment.status !== "enrolled") {
|
|
167
|
+
return denied("AgentSpine session timeline has no active private local enrollment for this host session.");
|
|
168
|
+
}
|
|
169
|
+
const hostClaimsMatch = host === "king"
|
|
170
|
+
? scopeClaimsMatch(input, ["host", "provider"], runtimeHost)
|
|
171
|
+
: matchesHostBinding(input, enrollment.binding);
|
|
172
|
+
if (!gatewayMatchesEnrollment(gateway, enrollment.binding) || !hostClaimsMatch
|
|
173
|
+
|| !matchesScopeBinding(input, enrollment.binding)) {
|
|
174
|
+
return denied("AgentSpine session timeline binding does not match the authenticated gateway scope.");
|
|
175
|
+
}
|
|
176
|
+
if (!await matchesRoot(input.tool_input.root, root) || !matchesBinding(input.tool_input, enrollment.binding)) {
|
|
177
|
+
return denied("AgentSpine session timeline binding does not match this host session and scope.");
|
|
178
|
+
}
|
|
179
|
+
const scope = bindingScope(enrollment.binding, enrollment);
|
|
180
|
+
const requestFields = requestInput(tool, input.tool_input);
|
|
181
|
+
if (!requestFields) return denied("AgentSpine session timeline search needs an exact time or a concrete query.");
|
|
182
|
+
if (input.tool_input.host && input.tool_input.host !== host) return denied("Timeline provider binding mismatch.");
|
|
183
|
+
const updatedInput = { ...requestFields, ...(["codex", "king"].includes(host) ? { host } : {}), root, sessionId: enrollment.binding.sessionId,
|
|
184
|
+
entityId: enrollment.binding.entityId, userId: enrollment.binding.userId, tenantId: enrollment.binding.tenantId,
|
|
185
|
+
projectId: enrollment.binding.projectId, taskId: enrollment.binding.taskId, groupId: null,
|
|
186
|
+
goalId: enrollment.binding.goalId, goalStepId: enrollment.binding.goalStepId,
|
|
187
|
+
timelineVisibility: enrollment.timelineVisibility, enrollmentDigest: enrollment.enrollmentDigest };
|
|
188
|
+
const request = timelineInvocationRequest(tool, updatedInput, root);
|
|
189
|
+
const authorization = await authorizeSessionTimelineInvocation({
|
|
190
|
+
root, host, sessionId: hostSession, scope, hostHome: historyHome,
|
|
191
|
+
tool, request, toolUseId: hookDeliveryId(input), transportDigest, enrollmentDigest: enrollment.enrollmentDigest
|
|
192
|
+
});
|
|
193
|
+
if (!authorization) return denied("AgentSpine session timeline invocation is unavailable for this bound source.");
|
|
194
|
+
return allowed(updatedInput);
|
|
195
|
+
} catch {
|
|
196
|
+
return denied("AgentSpine session timeline invocation is unavailable for this bound source.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function emitTimelineToolGuard(result) {
|
|
201
|
+
process.stdout.write(`${JSON.stringify(result.hostOutput)}\n`);
|
|
202
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { taskKnowledgeContext } from "./task-knowledge-context.js";
|
|
2
|
+
|
|
3
|
+
const KNOWLEDGE_KINDS = new Set([
|
|
4
|
+
"fact", "user-preference", "decision", "task-state", "error-lesson"
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
const SESSION_REF = /^session-ref:[a-f0-9]{32}$/;
|
|
8
|
+
const MESSAGE_REF = /^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$/;
|
|
9
|
+
const SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk[-_](?:proj[-_])?|gh[opusu]_)[A-Za-z0-9_-]{20,}\b|\b(?:xox[bapcrs]-|github_pat_|glpat-|npm_)[A-Za-z0-9_-]{12,}\b|\bAKIA[0-9A-Z]{16}\b|\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/i;
|
|
10
|
+
const TASK_CONTINUATION_SCHEMA = "agentspine.task-continuation/v1";
|
|
11
|
+
const TASK_STATUSES = new Set(["active", "blocked", "paused", "completed"]);
|
|
12
|
+
const STEP_RESULTS = new Set(["passed", "failed", "blocked"]);
|
|
13
|
+
const STABLE_ID = /^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$/;
|
|
14
|
+
const DIGEST = /^[a-f0-9]{64}$/;
|
|
15
|
+
|
|
16
|
+
function containsSecret(value) {
|
|
17
|
+
const pending = [value];
|
|
18
|
+
while (pending.length) {
|
|
19
|
+
const item = pending.pop();
|
|
20
|
+
if (typeof item === "string" && SECRET_RE.test(item)) return true;
|
|
21
|
+
if (Array.isArray(item)) pending.push(...item);
|
|
22
|
+
else if (item && typeof item === "object") pending.push(...Object.values(item));
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pairedSourceRefs(sessionRef, messageRef) {
|
|
28
|
+
if ((sessionRef === null) !== (messageRef === null)) {
|
|
29
|
+
throw new Error("sessionRef and messageRef must be provided together");
|
|
30
|
+
}
|
|
31
|
+
if (sessionRef !== null && (!SESSION_REF.test(sessionRef) || !MESSAGE_REF.test(messageRef))) {
|
|
32
|
+
throw new Error("sessionRef and messageRef must be stable provenance references");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function exactKeys(value, expected, field) {
|
|
37
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
38
|
+
|| Object.keys(value).sort().join("\0") !== [...expected].sort().join("\0")) {
|
|
39
|
+
throw new Error(`${field} has an invalid structure`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function boundedText(value, field, maximum) {
|
|
44
|
+
if (typeof value !== "string" || !value.trim() || value.length > maximum) {
|
|
45
|
+
throw new Error(`${field} must be a bounded non-empty string`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateContinuation(input, value) {
|
|
50
|
+
if (value?.schema !== TASK_CONTINUATION_SCHEMA) return;
|
|
51
|
+
exactKeys(value, ["schema", "taskId", "status", "objective", "lastVerifiedStep", "openQuestions", "nextStep"], "task continuation");
|
|
52
|
+
if (input.knowledgeKind !== "task-state" || input.predicate !== "task.continuation"
|
|
53
|
+
|| value.taskId !== input.subjectId || !STABLE_ID.test(value.taskId || "")) {
|
|
54
|
+
throw new Error("task continuation must be a task-state bound to its task subject and predicate");
|
|
55
|
+
}
|
|
56
|
+
if (input.sessionRef === undefined || input.messageRef === undefined) {
|
|
57
|
+
throw new Error("task continuation requires stable session and message source references");
|
|
58
|
+
}
|
|
59
|
+
if (!TASK_STATUSES.has(value.status)) throw new Error("task continuation status is invalid");
|
|
60
|
+
boundedText(value.objective, "task continuation objective", 500);
|
|
61
|
+
if (!Array.isArray(value.openQuestions) || value.openQuestions.length > 8) {
|
|
62
|
+
throw new Error("task continuation openQuestions must be a bounded array");
|
|
63
|
+
}
|
|
64
|
+
for (const question of value.openQuestions) {
|
|
65
|
+
exactKeys(question, ["id", "question"], "task continuation question");
|
|
66
|
+
if (!STABLE_ID.test(question.id || "")) throw new Error("task continuation question id is invalid");
|
|
67
|
+
boundedText(question.question, "task continuation question", 300);
|
|
68
|
+
}
|
|
69
|
+
if (new Set(value.openQuestions.map((item) => item.id)).size !== value.openQuestions.length) {
|
|
70
|
+
throw new Error("task continuation question ids must be unique");
|
|
71
|
+
}
|
|
72
|
+
if (value.nextStep !== null) {
|
|
73
|
+
exactKeys(value.nextStep, ["id", "summary"], "task continuation nextStep");
|
|
74
|
+
if (!STABLE_ID.test(value.nextStep.id || "")) throw new Error("task continuation next step id is invalid");
|
|
75
|
+
boundedText(value.nextStep.summary, "task continuation next step", 500);
|
|
76
|
+
}
|
|
77
|
+
if (value.lastVerifiedStep !== null) {
|
|
78
|
+
exactKeys(value.lastVerifiedStep,
|
|
79
|
+
["id", "summary", "result", "evidenceId", "evidenceDigest", "observedAt", "sessionRef", "messageRef"],
|
|
80
|
+
"task continuation lastVerifiedStep");
|
|
81
|
+
if (!STABLE_ID.test(value.lastVerifiedStep.id || "") || !STEP_RESULTS.has(value.lastVerifiedStep.result)
|
|
82
|
+
|| !STABLE_ID.test(value.lastVerifiedStep.evidenceId || "")
|
|
83
|
+
|| !DIGEST.test(value.lastVerifiedStep.evidenceDigest || "")) {
|
|
84
|
+
throw new Error("task continuation verified step evidence is invalid");
|
|
85
|
+
}
|
|
86
|
+
boundedText(value.lastVerifiedStep.summary, "task continuation verified step", 500);
|
|
87
|
+
if (new Date(value.lastVerifiedStep.observedAt).toISOString() !== value.lastVerifiedStep.observedAt
|
|
88
|
+
|| new Date(value.lastVerifiedStep.observedAt).getTime() > new Date(input.observedAt).getTime()) {
|
|
89
|
+
throw new Error("task continuation verified step timestamp is invalid");
|
|
90
|
+
}
|
|
91
|
+
pairedSourceRefs(value.lastVerifiedStep.sessionRef, value.lastVerifiedStep.messageRef);
|
|
92
|
+
}
|
|
93
|
+
if (value.status === "completed" && (value.nextStep !== null || value.openQuestions.length)) {
|
|
94
|
+
throw new Error("completed task continuation cannot retain a next step or open questions");
|
|
95
|
+
}
|
|
96
|
+
if (value.status === "completed" && value.lastVerifiedStep?.result !== "passed") {
|
|
97
|
+
throw new Error("completed task continuation requires a passed verified step");
|
|
98
|
+
}
|
|
99
|
+
if (value.status === "completed" && input.evidenceKind !== "objective-measurement") {
|
|
100
|
+
throw new Error("completed task continuation requires objective measurement evidence");
|
|
101
|
+
}
|
|
102
|
+
if (value.status !== "completed" && value.nextStep === null) {
|
|
103
|
+
throw new Error("resumable task continuation requires a next step");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function normalizeKnowledgeFields(input, evidenceKind, value) {
|
|
108
|
+
const kind = input.knowledgeKind ?? null;
|
|
109
|
+
if (kind === null) {
|
|
110
|
+
if (input.sessionRef !== undefined || input.messageRef !== undefined) {
|
|
111
|
+
throw new Error("session references require knowledgeKind");
|
|
112
|
+
}
|
|
113
|
+
return { knowledgeKind: null, sessionRef: null, messageRef: null };
|
|
114
|
+
}
|
|
115
|
+
if (!KNOWLEDGE_KINDS.has(kind)) throw new Error("unsupported knowledgeKind");
|
|
116
|
+
if (containsSecret(value) || containsSecret(input.reason || "")) {
|
|
117
|
+
throw new Error("secret-shaped content cannot enter structured knowledge");
|
|
118
|
+
}
|
|
119
|
+
validateContinuation(input, value);
|
|
120
|
+
if (kind === "user-preference" && !["explicit-user-feedback", "model-suggestion"].includes(evidenceKind)) {
|
|
121
|
+
throw new Error("user preferences require explicit feedback or remain a model suggestion");
|
|
122
|
+
}
|
|
123
|
+
if (kind === "decision" && !["explicit-user-feedback", "model-suggestion"].includes(evidenceKind)) {
|
|
124
|
+
throw new Error("decisions require explicit feedback or remain a model suggestion");
|
|
125
|
+
}
|
|
126
|
+
if (kind === "decision" && (typeof input.reason !== "string" || !input.reason.trim())) {
|
|
127
|
+
throw new Error("decisions require a bounded rationale");
|
|
128
|
+
}
|
|
129
|
+
const sessionRef = input.sessionRef ?? null;
|
|
130
|
+
const messageRef = input.messageRef ?? null;
|
|
131
|
+
pairedSourceRefs(sessionRef, messageRef);
|
|
132
|
+
return { knowledgeKind: kind, sessionRef, messageRef };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function validKnowledgeFields(assertion) {
|
|
136
|
+
const kind = assertion.knowledgeKind ?? null;
|
|
137
|
+
if (kind === null) return assertion.sessionRef === undefined && assertion.messageRef === undefined;
|
|
138
|
+
if (!KNOWLEDGE_KINDS.has(kind)) return false;
|
|
139
|
+
if (containsSecret(assertion.value) || containsSecret(assertion.reason || "")) return false;
|
|
140
|
+
if (kind === "user-preference" && !["explicit-user-feedback", "model-suggestion"].includes(assertion.evidenceKind)) return false;
|
|
141
|
+
if (kind === "decision" && !["explicit-user-feedback", "model-suggestion"].includes(assertion.evidenceKind)) return false;
|
|
142
|
+
if (kind === "decision" && !assertion.reason) return false;
|
|
143
|
+
try {
|
|
144
|
+
validateContinuation(assertion, assertion.value);
|
|
145
|
+
pairedSourceRefs(assertion.sessionRef ?? null, assertion.messageRef ?? null);
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function continuationView(entries, taskId, maxItems) {
|
|
153
|
+
const eligible = entries.filter((item) => item.kind === "task-state" && item.status === "confirmed"
|
|
154
|
+
&& item.predicate === "task.continuation" && item.value?.schema === TASK_CONTINUATION_SCHEMA
|
|
155
|
+
&& (!taskId || item.subjectId === taskId));
|
|
156
|
+
const selected = new Map();
|
|
157
|
+
for (const item of eligible) {
|
|
158
|
+
if (!selected.has(item.subjectId)) selected.set(item.subjectId, item);
|
|
159
|
+
}
|
|
160
|
+
const capsules = [...selected.values()].map((item) => ({
|
|
161
|
+
schema: TASK_CONTINUATION_SCHEMA,
|
|
162
|
+
assertionId: item.id,
|
|
163
|
+
...structuredClone(item.value),
|
|
164
|
+
source: structuredClone(item.source),
|
|
165
|
+
observedAt: item.observedAt,
|
|
166
|
+
scope: structuredClone(item.scope),
|
|
167
|
+
authority: "context-only"
|
|
168
|
+
}));
|
|
169
|
+
const limited = capsules.slice(0, Math.min(maxItems, 8));
|
|
170
|
+
return {
|
|
171
|
+
schema: "agentspine.task-continuation-context/v1",
|
|
172
|
+
tasks: limited.filter((item) => item.status !== "completed"),
|
|
173
|
+
terminal: limited.filter((item) => item.status === "completed"),
|
|
174
|
+
omitted: capsules.length - limited.length,
|
|
175
|
+
authority: "context-only",
|
|
176
|
+
note: "Continuation is descriptive context only and never grants permission, tools, delegation, or execution."
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function publicEntry(assertion, status, statusReason) {
|
|
181
|
+
return {
|
|
182
|
+
id: assertion.id,
|
|
183
|
+
kind: assertion.knowledgeKind,
|
|
184
|
+
subjectId: assertion.subjectId,
|
|
185
|
+
predicate: assertion.predicate,
|
|
186
|
+
value: structuredClone(assertion.value),
|
|
187
|
+
status,
|
|
188
|
+
statusReason,
|
|
189
|
+
source: {
|
|
190
|
+
kind: assertion.evidenceKind,
|
|
191
|
+
id: assertion.evidenceId,
|
|
192
|
+
digest: assertion.evidenceDigest,
|
|
193
|
+
sessionRef: assertion.sessionRef ?? null,
|
|
194
|
+
messageRef: assertion.messageRef ?? null
|
|
195
|
+
},
|
|
196
|
+
observedAt: assertion.observedAt,
|
|
197
|
+
expiresAt: assertion.expiresAt,
|
|
198
|
+
recordedAt: assertion.recordedAt,
|
|
199
|
+
rationale: assertion.reason || null,
|
|
200
|
+
scope: {
|
|
201
|
+
projectId: assertion.projectId,
|
|
202
|
+
groupId: assertion.groupId,
|
|
203
|
+
privacy: assertion.privacy
|
|
204
|
+
},
|
|
205
|
+
authority: "context-only"
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function statusFor(assertion, { staleIds, supersededIds, conflictIds }) {
|
|
210
|
+
if (staleIds.has(assertion.id)) return ["superseded", "expired"];
|
|
211
|
+
if (supersededIds.has(assertion.id)) return ["superseded", "replaced"];
|
|
212
|
+
if (assertion.status === "proposed") return ["assumption", "model-suggestion"];
|
|
213
|
+
if (conflictIds.has(assertion.id)) return ["contradictory", "active-conflict"];
|
|
214
|
+
return ["confirmed", "established-evidence"];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function structuredKnowledgeView({
|
|
218
|
+
candidates, stale, active, supersededIds, conflictAssertions,
|
|
219
|
+
includeHistory = false, continuationTaskId = null, maxItems = 100
|
|
220
|
+
}) {
|
|
221
|
+
const staleIds = new Set(stale.map((item) => item.id));
|
|
222
|
+
const activeIds = new Set(active.map((item) => item.id));
|
|
223
|
+
const conflictIds = new Set(conflictAssertions.flatMap((items) => items.map((item) => item.id)));
|
|
224
|
+
const entries = candidates.filter((item) => item.knowledgeKind).map((assertion) => {
|
|
225
|
+
const [status, reason] = statusFor(assertion, { staleIds, supersededIds, conflictIds });
|
|
226
|
+
return publicEntry(assertion, status, reason);
|
|
227
|
+
}).sort((left, right) => right.observedAt.localeCompare(left.observedAt) || left.id.localeCompare(right.id));
|
|
228
|
+
const current = entries.filter((item) => activeIds.has(item.id));
|
|
229
|
+
const historical = entries.filter((item) => !activeIds.has(item.id));
|
|
230
|
+
const history = includeHistory ? historical : [];
|
|
231
|
+
const counts = Object.fromEntries(["confirmed", "assumption", "superseded", "contradictory"]
|
|
232
|
+
.map((status) => [status, entries.filter((item) => item.status === status).length]));
|
|
233
|
+
const continuation = continuationView(current, continuationTaskId, maxItems);
|
|
234
|
+
return {
|
|
235
|
+
schema: "agentspine.structured-knowledge/v1",
|
|
236
|
+
current: current.slice(0, maxItems),
|
|
237
|
+
history: history.slice(0, maxItems),
|
|
238
|
+
continuation,
|
|
239
|
+
taskContext: taskKnowledgeContext(current, {
|
|
240
|
+
taskId: continuationTaskId, continuation, maxItems
|
|
241
|
+
}),
|
|
242
|
+
counts,
|
|
243
|
+
omitted: {
|
|
244
|
+
current: Math.max(0, current.length - maxItems),
|
|
245
|
+
history: includeHistory ? Math.max(0, historical.length - maxItems) : historical.length
|
|
246
|
+
},
|
|
247
|
+
authority: "context-only"
|
|
248
|
+
};
|
|
249
|
+
}
|