@haaaiawd/second-nature 0.1.27 → 0.1.29

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.
Files changed (157) hide show
  1. package/SKILL.md +35 -33
  2. package/agent-inner-guide.md +144 -124
  3. package/index.js +76 -1
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +2 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +67 -77
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  138. package/runtime/storage/services/diary-dream-store.js +165 -0
  139. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  140. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  141. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  142. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  143. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  144. package/runtime/storage/services/history-digest-store.js +140 -0
  145. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  146. package/runtime/storage/services/identity-profile-store.js +81 -0
  147. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  149. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  150. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  151. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  153. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  154. package/runtime/storage/services/tool-experience-store.js +116 -0
  155. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  156. package/runtime/storage/services/write-validation-gate.js +200 -0
  157. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ProbeSignalAdapter — T-BTS.C.4
3
+ *
4
+ * Core logic: Bridge between WetProbeRunner and state-memory.
5
+ * 1. Runs a wet probe for a capability.
6
+ * 2. Persists the CapabilityProbeResult to state-memory.
7
+ * 3. If the probe indicates degradation/unavailability, records a
8
+ * corresponding ToolExperience row with triggerSource="probe".
9
+ *
10
+ * Dependencies:
11
+ * - `WetProbeRunner` from `../../../connectors/base/wet-probe-runner.js`
12
+ * - `CapabilityContractRegistryV7` from `../../../connectors/base/manifest-v7.js`
13
+ * - `CapabilityProbeResultStore` from `../../../storage/services/tool-experience-store.js`
14
+ * - `ToolExperienceStore` from `../../../storage/services/tool-experience-store.js`
15
+ * - `ExperienceWriter` from `./tool-experience/experience-writer.js`
16
+ *
17
+ * Boundary:
18
+ * - Does NOT modify breaker state — caller (CircuitBreakerManager) decides.
19
+ * - probePolicyDenied is treated as a valid result, not an error.
20
+ *
21
+ * Test coverage: tests/unit/body/probe-signal-adapter.test.ts
22
+ */
23
+ import { createExperienceWriter } from "./tool-experience/experience-writer.js";
24
+ export function createProbeSignalAdapter(deps) {
25
+ const { wetProbeRunner, probeResultStore, toolExperienceStore } = deps;
26
+ const experienceWriter = createExperienceWriter(toolExperienceStore);
27
+ return {
28
+ async runAndRecordProbe(platformId, capabilityId, registry) {
29
+ const result = await wetProbeRunner.runWetProbe(platformId, capabilityId, registry);
30
+ // Persist probe result
31
+ await probeResultStore.appendProbeResult(result.probeResult);
32
+ // Record experience for non-success probes
33
+ let experienceRecorded = false;
34
+ if (result.probeResult.actualStatus !== "available") {
35
+ const mockResult = {
36
+ status: "terminal_failure",
37
+ failureClass: "transport_failure",
38
+ metadata: {
39
+ platformId,
40
+ channel: "api_rest",
41
+ latencyMs: 0,
42
+ },
43
+ };
44
+ await experienceWriter.recordExperience({
45
+ connectorId: platformId,
46
+ capabilityId,
47
+ result: mockResult,
48
+ triggerSource: "probe",
49
+ });
50
+ experienceRecorded = true;
51
+ }
52
+ return {
53
+ actualStatus: result.probeResult.actualStatus,
54
+ httpStatus: result.httpStatus,
55
+ recorded: true,
56
+ experienceRecorded,
57
+ };
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * AffordanceAssembler — T-BTS.C.1
3
+ *
4
+ * Core logic: Assemble a platform→capability affordance map from the v7
5
+ * capability registry and recent probe results.
6
+ *
7
+ * Mapping rules (probe actualStatus → affordance status):
8
+ * - available → safe
9
+ * - degraded → exploratory
10
+ * - unavailable → unavailable
11
+ * - no probe + credential required → needs_auth
12
+ * - no probe + no credential required → unavailable
13
+ *
14
+ * Caching:
15
+ * - TTL cache (default 30s) keyed by serialized scope.
16
+ * - Invalidate on explicit call or when underlying data changes.
17
+ *
18
+ * Performance target: P95 < 1s for 50 manifests.
19
+ *
20
+ * Dependencies:
21
+ * - `CapabilityContractRegistryV7` from `../../../../connectors/base/manifest-v7.js`
22
+ * - `AffordanceItem`, `AffordanceMap`, `AffordanceContextScope`
23
+ * from `../../../../shared/types/v7-entities.js`
24
+ * - `applyAffordanceContextScope` from `./affordance-context-scope.js`
25
+ *
26
+ * Boundary:
27
+ * - Does NOT perform HTTP probes — reads cached probe results only.
28
+ * - Credential-bearing entries are excluded (ADR-003).
29
+ * - Returns a plain object; caller decides persistence.
30
+ *
31
+ * Test coverage: tests/unit/body/affordance-assembler.test.ts
32
+ */
33
+ import type { CapabilityContractRegistryV7 } from "../../../../connectors/base/manifest-v7.js";
34
+ import type { AffordanceMap, AffordanceContextScope, ProbeActualStatus } from "../../../../shared/types/v7-entities.js";
35
+ export interface ProbeResultReader {
36
+ getLatestProbeResult(platformId: string, capabilityId: string): {
37
+ actualStatus: ProbeActualStatus;
38
+ createdAt: string;
39
+ } | undefined;
40
+ }
41
+ export interface AffordanceAssembler {
42
+ assembleAffordanceMap(scope?: AffordanceContextScope): Promise<AffordanceMap>;
43
+ invalidateCache(): void;
44
+ }
45
+ export interface AffordanceAssemblerOptions {
46
+ registry: CapabilityContractRegistryV7;
47
+ probeReader: ProbeResultReader;
48
+ credentialRequired?: (platformId: string, capabilityId: string) => boolean;
49
+ ttlMs?: number;
50
+ }
51
+ export declare function createAffordanceAssembler(options: AffordanceAssemblerOptions): AffordanceAssembler;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * AffordanceAssembler — T-BTS.C.1
3
+ *
4
+ * Core logic: Assemble a platform→capability affordance map from the v7
5
+ * capability registry and recent probe results.
6
+ *
7
+ * Mapping rules (probe actualStatus → affordance status):
8
+ * - available → safe
9
+ * - degraded → exploratory
10
+ * - unavailable → unavailable
11
+ * - no probe + credential required → needs_auth
12
+ * - no probe + no credential required → unavailable
13
+ *
14
+ * Caching:
15
+ * - TTL cache (default 30s) keyed by serialized scope.
16
+ * - Invalidate on explicit call or when underlying data changes.
17
+ *
18
+ * Performance target: P95 < 1s for 50 manifests.
19
+ *
20
+ * Dependencies:
21
+ * - `CapabilityContractRegistryV7` from `../../../../connectors/base/manifest-v7.js`
22
+ * - `AffordanceItem`, `AffordanceMap`, `AffordanceContextScope`
23
+ * from `../../../../shared/types/v7-entities.js`
24
+ * - `applyAffordanceContextScope` from `./affordance-context-scope.js`
25
+ *
26
+ * Boundary:
27
+ * - Does NOT perform HTTP probes — reads cached probe results only.
28
+ * - Credential-bearing entries are excluded (ADR-003).
29
+ * - Returns a plain object; caller decides persistence.
30
+ *
31
+ * Test coverage: tests/unit/body/affordance-assembler.test.ts
32
+ */
33
+ import { applyAffordanceContextScope } from "./affordance-context-scope.js";
34
+ function statusFromProbe(probeStatus) {
35
+ switch (probeStatus) {
36
+ case "available":
37
+ return "safe";
38
+ case "degraded":
39
+ return "exploratory";
40
+ case "unavailable":
41
+ return "unavailable";
42
+ default:
43
+ return "unavailable";
44
+ }
45
+ }
46
+ function scopeKey(scope) {
47
+ return JSON.stringify([
48
+ scope.platformIds?.sort() ?? [],
49
+ scope.goalKind ?? "",
50
+ scope.allowedStatuses?.sort() ?? [],
51
+ ]);
52
+ }
53
+ export function createAffordanceAssembler(options) {
54
+ const { registry, probeReader, credentialRequired, ttlMs = 30_000, } = options;
55
+ let cache;
56
+ function buildRawMap() {
57
+ const map = {};
58
+ for (const platformId of registry.listRegisteredPlatformIds()) {
59
+ const caps = registry.listCapabilities(platformId);
60
+ const items = [];
61
+ for (const cap of caps) {
62
+ const probe = probeReader.getLatestProbeResult(platformId, cap.capabilityId);
63
+ let status;
64
+ let reason;
65
+ let lastProbedAt;
66
+ if (probe) {
67
+ status = statusFromProbe(probe.actualStatus);
68
+ lastProbedAt = probe.createdAt;
69
+ if (status === "unavailable") {
70
+ reason = `probe_unavailable:${cap.capabilityId}`;
71
+ }
72
+ }
73
+ else {
74
+ const needsCred = credentialRequired?.(platformId, cap.capabilityId) ?? true;
75
+ if (needsCred) {
76
+ status = "needs_auth";
77
+ reason = "credential_not_probed";
78
+ }
79
+ else {
80
+ status = "unavailable";
81
+ reason = "no_probe_result";
82
+ }
83
+ }
84
+ items.push({
85
+ platformId,
86
+ capabilityId: cap.capabilityId,
87
+ intent: cap.intent,
88
+ status,
89
+ reason,
90
+ lastProbedAt,
91
+ });
92
+ }
93
+ if (items.length > 0) {
94
+ map[platformId] = items;
95
+ }
96
+ }
97
+ return map;
98
+ }
99
+ return {
100
+ async assembleAffordanceMap(scope = {}) {
101
+ const key = scopeKey(scope);
102
+ const now = Date.now();
103
+ if (cache && cache.key === key && now - cache.cachedAt < ttlMs) {
104
+ // Re-apply scope filter on cached raw map (scope may change)
105
+ const filtered = {};
106
+ for (const [pid, items] of Object.entries(cache.map)) {
107
+ const scoped = applyAffordanceContextScope(items, scope);
108
+ if (scoped.length > 0) {
109
+ filtered[pid] = scoped;
110
+ }
111
+ }
112
+ return filtered;
113
+ }
114
+ const raw = buildRawMap();
115
+ cache = { key, map: raw, cachedAt: now };
116
+ const filtered = {};
117
+ for (const [pid, items] of Object.entries(raw)) {
118
+ const scoped = applyAffordanceContextScope(items, scope);
119
+ if (scoped.length > 0) {
120
+ filtered[pid] = scoped;
121
+ }
122
+ }
123
+ return filtered;
124
+ },
125
+ invalidateCache() {
126
+ cache = undefined;
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * AffordanceContextScope — T-BTS.C.2
3
+ *
4
+ * Core logic: Filter semantics for affordance map assembly.
5
+ * - platformIds whitelist (empty = all platforms)
6
+ * - goalKind trust-tier filtering (task_completion prefers write/claim;
7
+ * passive_sensing exposes only read-only)
8
+ * - allowedStatuses defaults to safe subset; blocked/pending_trust always excluded
9
+ * - Credential-bearing items never enter affordance (ADR-003)
10
+ *
11
+ * Dependencies:
12
+ * - `AffordanceItem`, `AffordanceContextScope` from `../../../../shared/types/v7-entities.js`
13
+ *
14
+ * Boundary:
15
+ * - Pure filter function; no side effects, no caching.
16
+ * - Does NOT map probe status to affordance status — that is the assembler's job.
17
+ *
18
+ * Test coverage: tests/unit/body/affordance-context-scope.test.ts
19
+ */
20
+ import type { AffordanceItem, AffordanceContextScope, AffordanceStatus } from "../../../../shared/types/v7-entities.js";
21
+ export declare const DEFAULT_ALLOWED_STATUSES: readonly AffordanceStatus[];
22
+ /**
23
+ * Apply context scope filtering to an affordance item list.
24
+ * Returns a new array; does not mutate input.
25
+ */
26
+ export declare function applyAffordanceContextScope(items: readonly AffordanceItem[], scope?: AffordanceContextScope): AffordanceItem[];
27
+ /**
28
+ * Build a default scope suitable for heartbeat-cycle affordance assembly.
29
+ */
30
+ export declare function defaultHeartbeatScope(): AffordanceContextScope;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * AffordanceContextScope — T-BTS.C.2
3
+ *
4
+ * Core logic: Filter semantics for affordance map assembly.
5
+ * - platformIds whitelist (empty = all platforms)
6
+ * - goalKind trust-tier filtering (task_completion prefers write/claim;
7
+ * passive_sensing exposes only read-only)
8
+ * - allowedStatuses defaults to safe subset; blocked/pending_trust always excluded
9
+ * - Credential-bearing items never enter affordance (ADR-003)
10
+ *
11
+ * Dependencies:
12
+ * - `AffordanceItem`, `AffordanceContextScope` from `../../../../shared/types/v7-entities.js`
13
+ *
14
+ * Boundary:
15
+ * - Pure filter function; no side effects, no caching.
16
+ * - Does NOT map probe status to affordance status — that is the assembler's job.
17
+ *
18
+ * Test coverage: tests/unit/body/affordance-context-scope.test.ts
19
+ */
20
+ export const DEFAULT_ALLOWED_STATUSES = [
21
+ "safe",
22
+ "exploratory",
23
+ ];
24
+ const BLOCKED_STATUSES = [
25
+ "unavailable", // painful is allowed for diagnostics; unavailable is blocked from active use
26
+ ];
27
+ const READ_ONLY_INTENTS = new Set([
28
+ "feed.read",
29
+ "notification.list",
30
+ "work.discover",
31
+ ]);
32
+ const WRITE_INTENTS = new Set([
33
+ "post.publish",
34
+ "comment.reply",
35
+ "message.send",
36
+ "agent.register",
37
+ "agent.heartbeat",
38
+ "task.claim",
39
+ ]);
40
+ function isReadOnlyIntent(intent) {
41
+ return READ_ONLY_INTENTS.has(intent);
42
+ }
43
+ function isWriteIntent(intent) {
44
+ return WRITE_INTENTS.has(intent);
45
+ }
46
+ /**
47
+ * Apply context scope filtering to an affordance item list.
48
+ * Returns a new array; does not mutate input.
49
+ */
50
+ export function applyAffordanceContextScope(items, scope = {}) {
51
+ const allowedStatuses = scope.allowedStatuses && scope.allowedStatuses.length > 0
52
+ ? scope.allowedStatuses
53
+ : [...DEFAULT_ALLOWED_STATUSES];
54
+ // blocked/pending_trust always excluded at the status level
55
+ const effectiveAllowed = allowedStatuses.filter((s) => !BLOCKED_STATUSES.includes(s));
56
+ const platformSet = scope.platformIds && scope.platformIds.length > 0
57
+ ? new Set(scope.platformIds)
58
+ : undefined;
59
+ return items.filter((item) => {
60
+ // 1. Status filter
61
+ if (!effectiveAllowed.includes(item.status)) {
62
+ return false;
63
+ }
64
+ // 2. Platform whitelist
65
+ if (platformSet && !platformSet.has(item.platformId)) {
66
+ return false;
67
+ }
68
+ // 3. Goal-kind intent filtering
69
+ if (scope.goalKind) {
70
+ if (scope.goalKind === "passive_sensing") {
71
+ // Only read-only capabilities
72
+ if (!isReadOnlyIntent(item.intent)) {
73
+ return false;
74
+ }
75
+ }
76
+ else if (scope.goalKind === "task_completion") {
77
+ // Prefer write/claim — but do not exclude read; just allow all
78
+ // (higher-level assembler may sort/prioritize)
79
+ }
80
+ // Other goalKinds: no additional intent filter
81
+ }
82
+ return true;
83
+ });
84
+ }
85
+ /**
86
+ * Build a default scope suitable for heartbeat-cycle affordance assembly.
87
+ */
88
+ export function defaultHeartbeatScope() {
89
+ return {
90
+ allowedStatuses: [...DEFAULT_ALLOWED_STATUSES],
91
+ };
92
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ExperienceWriter — T-BTS.C.4
3
+ *
4
+ * Core logic: Records connector execution results as ToolExperience rows.
5
+ * - triggerSource is mandatory (DR-010)
6
+ * - failureClass directly transcribed from ConnectorResult (DR-007)
7
+ * - WriteValidationGate rejects raw payload before append
8
+ * - outcome mapping: success → "success", retryable_failure → "failure",
9
+ * terminal_failure → "failure"
10
+ *
11
+ * Dependencies:
12
+ * - `ToolExperienceStore` from `../../../../storage/services/tool-experience-store.js`
13
+ * - `validateWritePayload` from `../../../../storage/services/write-validation-gate.js`
14
+ * - `ConnectorResult` type from `../../../../connectors/base/contract.js`
15
+ *
16
+ * Boundary:
17
+ * - Does NOT probe; only records completed attempts.
18
+ * - Caller supplies triggerSource explicitly (no default).
19
+ *
20
+ * Test coverage: tests/unit/body/experience-writer.test.ts
21
+ */
22
+ import type { ToolExperienceStore } from "../../../../storage/services/tool-experience-store.js";
23
+ import type { ConnectorResult } from "../../../../connectors/base/contract.js";
24
+ import type { ToolExperienceTriggerSource } from "../../../../shared/types/v7-entities.js";
25
+ export interface ExperienceRecordInput {
26
+ connectorId: string;
27
+ capabilityId: string;
28
+ result: ConnectorResult<unknown>;
29
+ triggerSource: ToolExperienceTriggerSource;
30
+ }
31
+ export interface ExperienceWriter {
32
+ recordExperience(input: ExperienceRecordInput): Promise<void>;
33
+ }
34
+ export declare function createExperienceWriter(store: ToolExperienceStore): ExperienceWriter;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ExperienceWriter — T-BTS.C.4
3
+ *
4
+ * Core logic: Records connector execution results as ToolExperience rows.
5
+ * - triggerSource is mandatory (DR-010)
6
+ * - failureClass directly transcribed from ConnectorResult (DR-007)
7
+ * - WriteValidationGate rejects raw payload before append
8
+ * - outcome mapping: success → "success", retryable_failure → "failure",
9
+ * terminal_failure → "failure"
10
+ *
11
+ * Dependencies:
12
+ * - `ToolExperienceStore` from `../../../../storage/services/tool-experience-store.js`
13
+ * - `validateWritePayload` from `../../../../storage/services/write-validation-gate.js`
14
+ * - `ConnectorResult` type from `../../../../connectors/base/contract.js`
15
+ *
16
+ * Boundary:
17
+ * - Does NOT probe; only records completed attempts.
18
+ * - Caller supplies triggerSource explicitly (no default).
19
+ *
20
+ * Test coverage: tests/unit/body/experience-writer.test.ts
21
+ */
22
+ import { validateWritePayload } from "../../../../storage/services/write-validation-gate.js";
23
+ import * as crypto from "node:crypto";
24
+ function outcomeFromConnectorStatus(status) {
25
+ switch (status) {
26
+ case "success":
27
+ return "success";
28
+ case "retryable_failure":
29
+ case "terminal_failure":
30
+ return "failure";
31
+ default:
32
+ return "failure";
33
+ }
34
+ }
35
+ export function createExperienceWriter(store) {
36
+ return {
37
+ async recordExperience(input) {
38
+ const { result, connectorId, capabilityId, triggerSource } = input;
39
+ const payload = {
40
+ connectorId,
41
+ capabilityId,
42
+ status: result.status,
43
+ failureClass: result.failureClass,
44
+ latencyMs: result.metadata.latencyMs,
45
+ triggerSource,
46
+ sourceRefs: ["experience:record"],
47
+ };
48
+ const gate = validateWritePayload(payload);
49
+ if (!gate.ok) {
50
+ throw new Error(`ExperienceWriter rejected by gate: ${gate.reason}`);
51
+ }
52
+ const experience = {
53
+ experienceId: crypto.randomUUID(),
54
+ connectorId,
55
+ capabilityId,
56
+ outcome: outcomeFromConnectorStatus(result.status),
57
+ failureClass: result.failureClass,
58
+ latencyMs: result.metadata.latencyMs,
59
+ evidenceQuality: result.status === "success" ? 1.0 : 0.0,
60
+ sourceRefs: ["experience:record"],
61
+ triggerSource,
62
+ createdAt: new Date().toISOString(),
63
+ };
64
+ await store.appendToolExperience(experience);
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * PainSignal query — T-BTS.C.4
3
+ *
4
+ * Core logic: Computes a bounded pain signal from recent ToolExperience rows.
5
+ * Does NOT expose raw payload. Returns aggregated metrics for affordance map
6
+ * and heartbeat guard consumption.
7
+ *
8
+ * Dependencies:
9
+ * - `ToolExperienceStore` from `../../../../storage/services/tool-experience-store.js`
10
+ * - `ToolExperience` from `../../../../shared/types/v7-entities.js`
11
+ *
12
+ * Boundary:
13
+ * - Read-only; no side effects.
14
+ * - Bounded to last N rows (default 10) to keep computation constant-time.
15
+ *
16
+ * Test coverage: tests/unit/body/pain-signal-query.test.ts
17
+ */
18
+ import type { ToolExperienceStore } from "../../../../storage/services/tool-experience-store.js";
19
+ export interface PainSignal {
20
+ connectorId: string;
21
+ capabilityId: string;
22
+ painLevel: number;
23
+ recentFailureRate: number;
24
+ consecutiveFailures: number;
25
+ cooldownRecommended: boolean;
26
+ lastOutcomes: Array<{
27
+ outcome: string;
28
+ createdAt: string;
29
+ }>;
30
+ }
31
+ export interface PainSignalQuery {
32
+ getPainSignal(connectorId: string, capabilityId?: string): Promise<PainSignal | undefined>;
33
+ }
34
+ export declare function createPainSignalQuery(store: ToolExperienceStore, options?: {
35
+ lookbackLimit?: number;
36
+ cooldownThreshold?: number;
37
+ }): PainSignalQuery;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * PainSignal query — T-BTS.C.4
3
+ *
4
+ * Core logic: Computes a bounded pain signal from recent ToolExperience rows.
5
+ * Does NOT expose raw payload. Returns aggregated metrics for affordance map
6
+ * and heartbeat guard consumption.
7
+ *
8
+ * Dependencies:
9
+ * - `ToolExperienceStore` from `../../../../storage/services/tool-experience-store.js`
10
+ * - `ToolExperience` from `../../../../shared/types/v7-entities.js`
11
+ *
12
+ * Boundary:
13
+ * - Read-only; no side effects.
14
+ * - Bounded to last N rows (default 10) to keep computation constant-time.
15
+ *
16
+ * Test coverage: tests/unit/body/pain-signal-query.test.ts
17
+ */
18
+ export function createPainSignalQuery(store, options = {}) {
19
+ const lookbackLimit = options.lookbackLimit ?? 10;
20
+ const cooldownThreshold = options.cooldownThreshold ?? 3;
21
+ return {
22
+ async getPainSignal(connectorId, capabilityId) {
23
+ const rows = await store.listToolExperience({
24
+ connectorId,
25
+ capabilityId,
26
+ limit: lookbackLimit,
27
+ });
28
+ if (rows.length === 0) {
29
+ return undefined;
30
+ }
31
+ // Sort by createdAt ASC to compute consecutive failures correctly
32
+ const sorted = [...rows].sort((a, b) => new Date(a.createdAt).getTime() -
33
+ new Date(b.createdAt).getTime());
34
+ const failures = sorted.filter((r) => r.outcome === "failure" || r.outcome === "timeout");
35
+ const recentFailureRate = failures.length / sorted.length;
36
+ // Count trailing consecutive failures
37
+ let consecutiveFailures = 0;
38
+ for (let i = sorted.length - 1; i >= 0; i--) {
39
+ if (sorted[i].outcome === "failure" ||
40
+ sorted[i].outcome === "timeout") {
41
+ consecutiveFailures++;
42
+ }
43
+ else {
44
+ break;
45
+ }
46
+ }
47
+ const painLevel = Math.min(1.0, recentFailureRate * 0.5 + consecutiveFailures * 0.15);
48
+ return {
49
+ connectorId,
50
+ capabilityId: capabilityId ?? "*",
51
+ painLevel,
52
+ recentFailureRate,
53
+ consecutiveFailures,
54
+ cooldownRecommended: consecutiveFailures >= cooldownThreshold,
55
+ lastOutcomes: sorted.slice(-5).map((r) => ({
56
+ outcome: r.outcome,
57
+ createdAt: r.createdAt,
58
+ })),
59
+ };
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * DecisionTraceEmitter — T-CP.C.2
3
+ *
4
+ * Core logic: Emits machine-readable DecisionTracePayload to observability.
5
+ * Trace persistence belongs to observability-health-system; this module only
6
+ * constructs and forwards the payload.
7
+ *
8
+ * Boundary:
9
+ * - Emission errors are swallowed — trace must not block heartbeat cycle.
10
+ * - No-op emitter available for carrier-only paths and tests.
11
+ *
12
+ * Test coverage: tests/unit/control-plane/decision-trace-emitter.test.ts
13
+ */
14
+ export interface DecisionTracePayload {
15
+ traceId: string;
16
+ decisionId: string;
17
+ contextId?: string;
18
+ scope: string;
19
+ status: string;
20
+ reasons: string[];
21
+ selectedIntentId?: string;
22
+ emittedAt: string;
23
+ }
24
+ export interface DecisionTraceEmitter {
25
+ emit(trace: DecisionTracePayload): Promise<void>;
26
+ }
27
+ export declare function createDecisionTraceEmitter(port: (payload: DecisionTracePayload) => Promise<void>): DecisionTraceEmitter;
28
+ /** No-op emitter for carrier-only or test paths. */
29
+ export declare function createNoOpTraceEmitter(): DecisionTraceEmitter;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DecisionTraceEmitter — T-CP.C.2
3
+ *
4
+ * Core logic: Emits machine-readable DecisionTracePayload to observability.
5
+ * Trace persistence belongs to observability-health-system; this module only
6
+ * constructs and forwards the payload.
7
+ *
8
+ * Boundary:
9
+ * - Emission errors are swallowed — trace must not block heartbeat cycle.
10
+ * - No-op emitter available for carrier-only paths and tests.
11
+ *
12
+ * Test coverage: tests/unit/control-plane/decision-trace-emitter.test.ts
13
+ */
14
+ export function createDecisionTraceEmitter(port) {
15
+ return {
16
+ async emit(trace) {
17
+ await port(trace);
18
+ },
19
+ };
20
+ }
21
+ /** No-op emitter for carrier-only or test paths. */
22
+ export function createNoOpTraceEmitter() {
23
+ return {
24
+ async emit() {
25
+ // intentionally empty
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * EmbodiedContextAssembler — T-CP.C.1
3
+ *
4
+ * Core logic: Assembles a complete EmbodiedContext from up to 7 read ports:
5
+ * - 5 state-memory slices via EmbodiedContextStatePort
6
+ * - affordanceMap via AffordanceAssembler
7
+ * - selfHealth via SelfHealthProvider (observability hook)
8
+ *
9
+ * Trim policies (DR-020):
10
+ * - recentInteractions: LIFO 10
11
+ * - toolExperience: LIFO 10
12
+ * - sourceRefs: deduplicated to 20 per slice
13
+ *
14
+ * Performance: P95 < 400ms for full assembly (DR-016).
15
+ *
16
+ * Dependencies:
17
+ * - `EmbodiedContextStatePort` from `../../../storage/services/embodied-context-state-port.js`
18
+ * - `AffordanceAssembler` from `../body/tool-affordance/affordance-assembler.js`
19
+ * - `EmbodiedContext` from `../../../shared/types/v7-entities.js`
20
+ *
21
+ * Boundary:
22
+ * - Candidate dream outputs are NOT included (DR-011).
23
+ * - Each slice gets its own loaded/degraded/blocked status.
24
+ * - Does NOT throw on partial failure; assembles best-effort context.
25
+ *
26
+ * Test coverage: tests/unit/control-plane/embodied-context-assembler.test.ts
27
+ */
28
+ import type { EmbodiedContextStatePort } from "../../../storage/services/embodied-context-state-port.js";
29
+ import type { AffordanceAssembler } from "../body/tool-affordance/affordance-assembler.js";
30
+ import type { EmbodiedContext, SelfHealthSnapshot } from "../../../shared/types/v7-entities.js";
31
+ export interface SelfHealthProvider {
32
+ loadSelfHealth(): Promise<{
33
+ status: "loaded";
34
+ data: SelfHealthSnapshot;
35
+ } | {
36
+ status: "degraded";
37
+ reason: string;
38
+ }>;
39
+ }
40
+ export interface EmbodiedContextAssemblerDeps {
41
+ statePort: EmbodiedContextStatePort;
42
+ affordanceAssembler: AffordanceAssembler;
43
+ selfHealthProvider?: SelfHealthProvider;
44
+ options?: {
45
+ interactionLimit?: number;
46
+ experienceLimit?: number;
47
+ sourceRefLimit?: number;
48
+ profileId?: string;
49
+ };
50
+ }
51
+ export interface EmbodiedContextAssembler {
52
+ assembleEmbodiedContext(): Promise<EmbodiedContext>;
53
+ }
54
+ export declare function createEmbodiedContextAssembler(deps: EmbodiedContextAssemblerDeps): EmbodiedContextAssembler;