@haaaiawd/second-nature 0.1.8 → 0.1.9

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 (183) hide show
  1. package/index.js +73 -1
  2. package/openclaw.plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/runtime/cli/commands/index.d.ts +2 -0
  5. package/runtime/cli/commands/index.js +61 -6
  6. package/runtime/cli/explain/explain-surface-subject.d.ts +8 -0
  7. package/runtime/cli/explain/explain-surface-subject.js +9 -0
  8. package/runtime/cli/explain/format-explanation.d.ts +2 -0
  9. package/runtime/cli/explain/format-explanation.js +2 -0
  10. package/runtime/cli/explain/resolve-subject.js +15 -0
  11. package/runtime/cli/host-capability/classify-delivery.d.ts +14 -0
  12. package/runtime/cli/host-capability/classify-delivery.js +20 -0
  13. package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -0
  14. package/runtime/cli/host-capability/probe-host-capability.js +58 -0
  15. package/runtime/cli/host-capability/record-host-capability.d.ts +6 -0
  16. package/runtime/cli/host-capability/record-host-capability.js +14 -0
  17. package/runtime/cli/host-capability/types.d.ts +71 -0
  18. package/runtime/cli/host-capability/types.js +6 -0
  19. package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -0
  20. package/runtime/cli/host-smoke/run-host-smoke.js +40 -0
  21. package/runtime/cli/host-smoke/types.d.ts +35 -0
  22. package/runtime/cli/host-smoke/types.js +6 -0
  23. package/runtime/cli/index.js +18 -0
  24. package/runtime/cli/ops/heartbeat-surface.d.ts +35 -0
  25. package/runtime/cli/ops/heartbeat-surface.js +71 -0
  26. package/runtime/cli/ops/ops-router.d.ts +16 -0
  27. package/runtime/cli/ops/ops-router.js +83 -0
  28. package/runtime/cli/ops/show-operator-fallback.d.ts +13 -0
  29. package/runtime/cli/ops/show-operator-fallback.js +22 -0
  30. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +10 -0
  31. package/runtime/cli/ops/workspace-heartbeat-runner.js +26 -0
  32. package/runtime/cli/read-models/index.d.ts +11 -2
  33. package/runtime/cli/read-models/index.js +50 -0
  34. package/runtime/cli/read-models/operator-explain-map.d.ts +6 -0
  35. package/runtime/cli/read-models/operator-explain-map.js +10 -0
  36. package/runtime/cli/read-models/types.d.ts +5 -1
  37. package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -0
  38. package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -0
  39. package/runtime/connectors/base/contract.d.ts +6 -0
  40. package/runtime/connectors/base/execution-policy.d.ts +47 -0
  41. package/runtime/connectors/base/execution-policy.js +82 -0
  42. package/runtime/connectors/base/index.d.ts +2 -0
  43. package/runtime/connectors/base/index.js +2 -0
  44. package/runtime/connectors/base/manifest.d.ts +55 -2
  45. package/runtime/connectors/base/manifest.js +50 -0
  46. package/runtime/connectors/base/map-life-evidence.d.ts +16 -0
  47. package/runtime/connectors/base/map-life-evidence.js +79 -0
  48. package/runtime/connectors/base/policy-layer.d.ts +2 -0
  49. package/runtime/connectors/base/policy-layer.js +16 -0
  50. package/runtime/connectors/base/route-planner.js +1 -0
  51. package/runtime/connectors/index.d.ts +1 -0
  52. package/runtime/connectors/index.js +1 -0
  53. package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -0
  54. package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -0
  55. package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +2 -0
  56. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +37 -16
  57. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +95 -29
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -1
  59. package/runtime/core/second-nature/heartbeat/index.js +4 -1
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -0
  62. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -0
  63. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -0
  64. package/runtime/core/second-nature/heartbeat/signal.d.ts +9 -2
  65. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +19 -1
  66. package/runtime/core/second-nature/index.d.ts +8 -0
  67. package/runtime/core/second-nature/index.js +8 -0
  68. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +1 -1
  69. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +9 -4
  70. package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +6 -0
  71. package/runtime/core/second-nature/orchestrator/guard-layer.js +76 -20
  72. package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +10 -0
  73. package/runtime/core/second-nature/orchestrator/intent-planner.js +135 -28
  74. package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +1 -1
  75. package/runtime/core/second-nature/orchestrator/lease-manager.js +1 -1
  76. package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -0
  77. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -0
  78. package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -0
  79. package/runtime/core/second-nature/outreach/delivery-target.js +70 -0
  80. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -0
  81. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -0
  82. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -0
  83. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -0
  84. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -0
  85. package/runtime/core/second-nature/outreach/judge-outreach.js +121 -0
  86. package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -0
  87. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -0
  88. package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -0
  89. package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -0
  90. package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -0
  91. package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -0
  92. package/runtime/core/second-nature/types.d.ts +16 -2
  93. package/runtime/guidance/draft-outreach-message.d.ts +7 -0
  94. package/runtime/guidance/draft-outreach-message.js +42 -0
  95. package/runtime/guidance/evidence-guidance.d.ts +40 -0
  96. package/runtime/guidance/evidence-guidance.js +52 -0
  97. package/runtime/guidance/index.d.ts +3 -0
  98. package/runtime/guidance/index.js +3 -0
  99. package/runtime/guidance/outreach-draft-schema.d.ts +228 -0
  100. package/runtime/guidance/outreach-draft-schema.js +80 -0
  101. package/runtime/observability/audit/append-only-audit-store.d.ts +14 -0
  102. package/runtime/observability/audit/append-only-audit-store.js +21 -0
  103. package/runtime/observability/audit/audit-envelope.d.ts +51 -0
  104. package/runtime/observability/audit/audit-envelope.js +130 -0
  105. package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -0
  106. package/runtime/observability/audit/verify-audit-hash-chain.js +83 -0
  107. package/runtime/observability/db/index.js +11 -0
  108. package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -0
  109. package/runtime/observability/db/schema/host-capability-reports.js +12 -0
  110. package/runtime/observability/db/schema/index.d.ts +1 -0
  111. package/runtime/observability/db/schema/index.js +1 -0
  112. package/runtime/observability/index.d.ts +7 -0
  113. package/runtime/observability/index.js +7 -0
  114. package/runtime/observability/query/explain-query.d.ts +48 -0
  115. package/runtime/observability/query/explain-query.js +114 -0
  116. package/runtime/observability/query/export-audit-bundle.d.ts +22 -0
  117. package/runtime/observability/query/export-audit-bundle.js +27 -0
  118. package/runtime/observability/services/decision-ledger.d.ts +1 -1
  119. package/runtime/observability/services/decision-ledger.js +4 -0
  120. package/runtime/observability/services/governance-audit.d.ts +14 -0
  121. package/runtime/observability/services/governance-audit.js +25 -1
  122. package/runtime/observability/services/governance-plane-recorder.d.ts +47 -0
  123. package/runtime/observability/services/governance-plane-recorder.js +55 -0
  124. package/runtime/observability/services/lived-experience-audit.d.ts +97 -0
  125. package/runtime/observability/services/lived-experience-audit.js +161 -0
  126. package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -0
  127. package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -0
  128. package/runtime/storage/bootstrap/repair-gate.d.ts +17 -0
  129. package/runtime/storage/bootstrap/repair-gate.js +71 -0
  130. package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -0
  131. package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -0
  132. package/runtime/storage/db/index.js +49 -0
  133. package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -0
  134. package/runtime/storage/db/schema/delivery-attempts.js +13 -0
  135. package/runtime/storage/db/schema/index.d.ts +3 -0
  136. package/runtime/storage/db/schema/index.js +3 -0
  137. package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -0
  138. package/runtime/storage/db/schema/life-evidence-index.js +11 -0
  139. package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -0
  140. package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -0
  141. package/runtime/storage/db/schema/policies.d.ts +17 -0
  142. package/runtime/storage/db/schema/policies.js +1 -0
  143. package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -0
  144. package/runtime/storage/delivery/query-delivery-attempts.js +32 -0
  145. package/runtime/storage/delivery/types.d.ts +27 -0
  146. package/runtime/storage/delivery/types.js +1 -0
  147. package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -0
  148. package/runtime/storage/delivery/write-delivery-attempt.js +36 -0
  149. package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -0
  150. package/runtime/storage/fallback/load-operator-fallback.js +47 -0
  151. package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -0
  152. package/runtime/storage/fallback/operator-fallback-types.js +1 -0
  153. package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -0
  154. package/runtime/storage/fallback/operator-fallback-view.js +1 -0
  155. package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -0
  156. package/runtime/storage/fallback/write-operator-fallback.js +21 -0
  157. package/runtime/storage/index.d.ts +21 -0
  158. package/runtime/storage/index.js +14 -0
  159. package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -0
  160. package/runtime/storage/life-evidence/append-life-evidence.js +64 -0
  161. package/runtime/storage/life-evidence/types.d.ts +45 -0
  162. package/runtime/storage/life-evidence/types.js +6 -0
  163. package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -0
  164. package/runtime/storage/quiet/persist-quiet-artifact.js +22 -0
  165. package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -0
  166. package/runtime/storage/quiet/quiet-artifact-types.js +1 -0
  167. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -0
  168. package/runtime/storage/quiet/quiet-artifact-writer.js +56 -0
  169. package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -0
  170. package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -0
  171. package/runtime/storage/services/credential-vault.d.ts +5 -0
  172. package/runtime/storage/services/credential-vault.js +46 -9
  173. package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -0
  174. package/runtime/storage/snapshots/continuity-snapshot.js +41 -0
  175. package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -0
  176. package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -0
  177. package/runtime/storage/snapshots/types.d.ts +58 -0
  178. package/runtime/storage/snapshots/types.js +1 -0
  179. package/runtime/storage/state-api.js +11 -4
  180. package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -0
  181. package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -0
  182. package/runtime/storage/user-interest/types.d.ts +25 -0
  183. package/runtime/storage/user-interest/types.js +1 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Continuity snapshot read model (T4.2.1) — lightweight bridge until full state feeds land.
3
+ */
4
+ import * as crypto from "node:crypto";
5
+ import { desc } from "drizzle-orm";
6
+ import { decisionLedger } from "../../observability/db/schema/index.js";
7
+ import { repairStateIndexes } from "../bootstrap/repair-gate.js";
8
+ import { loadLifeEvidenceSnapshot } from "./life-evidence-snapshot.js";
9
+ export async function loadContinuitySnapshot(params) {
10
+ await repairStateIndexes(params.state, { startupGate: true, workspaceRoot: params.workspaceRoot });
11
+ const life = await loadLifeEvidenceSnapshot(params.state, params.workspaceRoot, { limit: 50 }, { runRepairGate: false });
12
+ let recentDecisionRefs = [];
13
+ if (params.observability) {
14
+ const rows = await params.observability.db
15
+ .select()
16
+ .from(decisionLedger)
17
+ .orderBy(desc(decisionLedger.createdAt))
18
+ .limit(5);
19
+ recentDecisionRefs = rows.map((r) => ({
20
+ id: r.id,
21
+ kind: "decision_record",
22
+ uri: `sn://decision/${r.id}`,
23
+ }));
24
+ }
25
+ const pendingCount = life.platformEvents.length + life.workEvents.length + life.userInteractionEvents.length;
26
+ const oldest = [...life.platformEvents, ...life.workEvents, ...life.userInteractionEvents]
27
+ .map((e) => e.timestamp)
28
+ .sort()[0];
29
+ return {
30
+ snapshotId: crypto.randomUUID(),
31
+ generatedAt: new Date().toISOString(),
32
+ recentDecisionRefs,
33
+ openObligations: [],
34
+ quietDebt: {
35
+ hasUnprocessedEvidence: !life.empty,
36
+ oldestUnprocessedEvidenceAt: oldest,
37
+ pendingCount,
38
+ },
39
+ fallbackRefs: [],
40
+ };
41
+ }
@@ -0,0 +1,6 @@
1
+ import type { StateDatabase } from "../db/index.js";
2
+ import type { LifeEvidenceQuery, LifeEvidenceSnapshot } from "./types.js";
3
+ export interface LoadLifeEvidenceSnapshotOptions {
4
+ runRepairGate?: boolean;
5
+ }
6
+ export declare function loadLifeEvidenceSnapshot(state: StateDatabase, workspaceRoot: string, query: LifeEvidenceQuery, options?: LoadLifeEvidenceSnapshotOptions): Promise<LifeEvidenceSnapshot>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Bounded life evidence snapshot read model (T4.2.1).
3
+ */
4
+ import * as crypto from "node:crypto";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { and, desc, gte, inArray, lte } from "drizzle-orm";
8
+ import { lifeEvidenceIndex } from "../db/schema/life-evidence-index.js";
9
+ import { repairStateIndexes } from "../bootstrap/repair-gate.js";
10
+ function toReadModel(stored) {
11
+ return {
12
+ id: stored.id,
13
+ timestamp: stored.timestamp,
14
+ evidenceType: stored.evidenceType,
15
+ platformId: stored.platformId,
16
+ summary: stored.summary,
17
+ rawContentRef: stored.rawContentRef,
18
+ sourceRefs: stored.sourceRefs,
19
+ sensitivity: stored.sensitivity,
20
+ confidence: stored.confidence,
21
+ tags: stored.tags,
22
+ producer: stored.producer,
23
+ };
24
+ }
25
+ function buildCoverage(events) {
26
+ if (events.length === 0) {
27
+ return { coverageRatio: 1, unsupportedClaims: [], claimCoverage: [] };
28
+ }
29
+ const backed = events.filter((e) => e.sourceRefs.length > 0).length;
30
+ return {
31
+ coverageRatio: backed / events.length,
32
+ unsupportedClaims: [],
33
+ claimCoverage: events.map((e, i) => ({
34
+ claimId: `ev:${e.id}:${i}`,
35
+ backed: e.sourceRefs.length > 0,
36
+ sourceRefs: e.sourceRefs,
37
+ })),
38
+ };
39
+ }
40
+ export async function loadLifeEvidenceSnapshot(state, workspaceRoot, query, options) {
41
+ const runGate = options?.runRepairGate !== false;
42
+ if (runGate) {
43
+ const repair = await repairStateIndexes(state, { startupGate: true, workspaceRoot });
44
+ if (repair.status === "repair_required") {
45
+ const err = new Error("state_repair_required");
46
+ err.code = "repair_required";
47
+ throw err;
48
+ }
49
+ }
50
+ const now = new Date().toISOString();
51
+ const windowEnd = query.windowEnd ?? now;
52
+ const windowStart = query.windowStart ?? new Date(Date.now() - 7 * 86400000).toISOString();
53
+ const limit = query.limit ?? 50;
54
+ const conditions = [gte(lifeEvidenceIndex.timestamp, windowStart), lte(lifeEvidenceIndex.timestamp, windowEnd)];
55
+ if (query.evidenceTypes?.length) {
56
+ conditions.push(inArray(lifeEvidenceIndex.evidenceType, query.evidenceTypes));
57
+ }
58
+ const rows = await state.db
59
+ .select()
60
+ .from(lifeEvidenceIndex)
61
+ .where(and(...conditions))
62
+ .orderBy(desc(lifeEvidenceIndex.timestamp))
63
+ .limit(limit);
64
+ const platformEvents = [];
65
+ const workEvents = [];
66
+ const userInteractionEvents = [];
67
+ const quietArtifacts = [];
68
+ const evidenceRefs = [];
69
+ for (const row of rows) {
70
+ const abs = path.join(workspaceRoot, row.artifactPath.replace(/\//g, path.sep));
71
+ if (!fs.existsSync(abs)) {
72
+ continue;
73
+ }
74
+ const stored = JSON.parse(fs.readFileSync(abs, "utf-8"));
75
+ const model = toReadModel(stored);
76
+ evidenceRefs.push({
77
+ id: `ref:${model.id}`,
78
+ kind: "workspace_artifact",
79
+ uri: row.artifactPath,
80
+ });
81
+ if (model.evidenceType === "platform_browse" || model.evidenceType === "platform_interaction") {
82
+ platformEvents.push(model);
83
+ }
84
+ else if (model.evidenceType === "work_progress" || model.evidenceType === "task_discovery") {
85
+ workEvents.push(model);
86
+ }
87
+ else if (model.evidenceType === "user_interaction") {
88
+ userInteractionEvents.push(model);
89
+ }
90
+ else if (model.evidenceType === "quiet_reflection") {
91
+ quietArtifacts.push({
92
+ id: `quiet:${model.id}`,
93
+ kind: "workspace_artifact",
94
+ uri: row.artifactPath,
95
+ });
96
+ }
97
+ }
98
+ const all = [...platformEvents, ...workEvents, ...userInteractionEvents];
99
+ const coverage = buildCoverage(all);
100
+ const empty = all.length === 0;
101
+ return {
102
+ snapshotId: crypto.randomUUID(),
103
+ generatedAt: now,
104
+ windowStart,
105
+ windowEnd,
106
+ evidenceRefs,
107
+ platformEvents,
108
+ workEvents,
109
+ userInteractionEvents,
110
+ quietArtifacts,
111
+ coverage,
112
+ empty,
113
+ };
114
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Read-model snapshots aligned with state-system v5 (subset for S1/S2).
3
+ */
4
+ import type { LifeEvidenceType, SourceRef } from "../life-evidence/types.js";
5
+ export interface LifeEvidenceQuery {
6
+ windowStart?: string;
7
+ windowEnd?: string;
8
+ evidenceTypes?: LifeEvidenceType[];
9
+ limit?: number;
10
+ }
11
+ export interface SourceCoverage {
12
+ coverageRatio: number;
13
+ unsupportedClaims: string[];
14
+ claimCoverage: Array<{
15
+ claimId: string;
16
+ backed: boolean;
17
+ sourceRefs: SourceRef[];
18
+ }>;
19
+ }
20
+ export interface LifeEvidenceReadModel {
21
+ id: string;
22
+ timestamp: string;
23
+ evidenceType: LifeEvidenceType;
24
+ platformId?: string;
25
+ summary: string;
26
+ rawContentRef?: string;
27
+ sourceRefs: SourceRef[];
28
+ sensitivity: import("../life-evidence/types.js").Sensitivity;
29
+ confidence: number;
30
+ tags: string[];
31
+ producer: string;
32
+ }
33
+ export interface LifeEvidenceSnapshot {
34
+ snapshotId: string;
35
+ generatedAt: string;
36
+ windowStart: string;
37
+ windowEnd: string;
38
+ evidenceRefs: SourceRef[];
39
+ platformEvents: LifeEvidenceReadModel[];
40
+ workEvents: LifeEvidenceReadModel[];
41
+ userInteractionEvents: LifeEvidenceReadModel[];
42
+ quietArtifacts: SourceRef[];
43
+ coverage: SourceCoverage;
44
+ empty: boolean;
45
+ }
46
+ export interface ContinuitySnapshot {
47
+ snapshotId: string;
48
+ generatedAt: string;
49
+ lastHeartbeatAt?: string;
50
+ recentDecisionRefs: SourceRef[];
51
+ openObligations: SourceRef[];
52
+ quietDebt: {
53
+ hasUnprocessedEvidence: boolean;
54
+ oldestUnprocessedEvidenceAt?: string;
55
+ pendingCount: number;
56
+ };
57
+ fallbackRefs: SourceRef[];
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -7,6 +7,7 @@ import { PolicyRepository } from "./repositories/policy-repository.js";
7
7
  import { createDailyLogPipeline, } from "./services/daily-log-pipeline.js";
8
8
  import { createQuietInputLoader, } from "./services/quiet-input-loader.js";
9
9
  import { createPersonaCandidateLoader } from "./services/persona-candidate-loader.js";
10
+ import { encryptCredentialAtRest, isCredentialCiphertext } from "./services/credential-vault.js";
10
11
  export class DefaultStateAPI {
11
12
  database;
12
13
  read;
@@ -31,11 +32,13 @@ export class DefaultStateAPI {
31
32
  const record = await policyRepository.findByPlatformId(platformId);
32
33
  if (!record)
33
34
  return undefined;
35
+ const r = record;
34
36
  return {
35
- platformId: record.platformId ?? record.platform_id,
36
- socialDailyLimit: record.socialDailyLimit ?? record.social_daily_limit,
37
- quietEnabled: record.quietEnabled ?? Boolean(record.quiet_enabled),
38
- updatedAt: record.updatedAt ?? record.updated_at,
37
+ platformId: r.platformId ?? r.platform_id ?? platformId,
38
+ socialDailyLimit: r.socialDailyLimit ?? r.social_daily_limit ?? 0,
39
+ quietEnabled: r.quietEnabled ?? Boolean(r.quiet_enabled),
40
+ outreachDailyBudget: r.outreachDailyBudget ?? r.outreach_daily_budget ?? 2,
41
+ updatedAt: r.updatedAt ?? r.updated_at ?? "",
39
42
  };
40
43
  },
41
44
  loadPersonaCandidates: (sceneContext) => personaCandidateLoader.loadPersonaCandidates(sceneContext),
@@ -50,6 +53,7 @@ export class DefaultStateAPI {
50
53
  platformId: policy.platformId,
51
54
  socialDailyLimit: policy.socialDailyLimit,
52
55
  quietEnabled: policy.quietEnabled,
56
+ outreachDailyBudget: policy.outreachDailyBudget ?? 2,
53
57
  updatedAt: new Date().toISOString(),
54
58
  });
55
59
  },
@@ -74,8 +78,11 @@ export class DefaultStateAPI {
74
78
  },
75
79
  saveCredentialContext: async (input) => {
76
80
  const ctx = input;
81
+ const raw = ctx.encryptedValue != null ? String(ctx.encryptedValue) : "";
82
+ const encryptedValue = !raw || isCredentialCiphertext(raw) ? raw : encryptCredentialAtRest(raw);
77
83
  await credentialRepository.upsert({
78
84
  ...ctx,
85
+ encryptedValue,
79
86
  updatedAt: ctx.updatedAt ?? new Date().toISOString(),
80
87
  });
81
88
  },
@@ -0,0 +1,2 @@
1
+ import type { UserInterestSnapshot } from "./types.js";
2
+ export declare function loadUserInterestSnapshot(workspaceRoot: string): Promise<UserInterestSnapshot>;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Minimal loadUserInterestSnapshot: anchor + curated presence, no fabricated preferences (T4.2.2).
3
+ *
4
+ * Core logic: USER.md / MEMORY.md / memory/curated/*.md drive signals; otherwise insufficient.
5
+ * Boundaries: never invent topics; staleness insufficient when anchors missing and no curated signals.
6
+ */
7
+ import * as crypto from "node:crypto";
8
+ import * as fs from "fs/promises";
9
+ import * as path from "node:path";
10
+ import { fileExists, readText } from "../memory/workspace/paths.js";
11
+ const MIN_MEANINGFUL_ANCHOR_CHARS = 24;
12
+ const STALE_MS = 30 * 24 * 60 * 60 * 1000;
13
+ const INSUFFICIENT_CONFIDENCE_THRESHOLD = 0.35;
14
+ function anchorRef(asset, workspaceRoot) {
15
+ return {
16
+ id: `anchor:${asset}`,
17
+ kind: "user_anchor",
18
+ uri: path.join(workspaceRoot, asset),
19
+ observedAt: new Date().toISOString(),
20
+ };
21
+ }
22
+ async function newestMtime(paths) {
23
+ let max = 0;
24
+ for (const p of paths) {
25
+ try {
26
+ const st = await fs.stat(p);
27
+ max = Math.max(max, st.mtimeMs);
28
+ }
29
+ catch {
30
+ /* missing */
31
+ }
32
+ }
33
+ return max;
34
+ }
35
+ async function listCuratedSignalFiles(workspaceRoot) {
36
+ const dir = path.join(workspaceRoot, "memory", "curated");
37
+ if (!(await fileExists(dir)))
38
+ return [];
39
+ const entries = await fs.readdir(dir, { withFileTypes: true });
40
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => path.join(dir, e.name));
41
+ }
42
+ export async function loadUserInterestSnapshot(workspaceRoot) {
43
+ const generatedAt = new Date().toISOString();
44
+ const userPath = path.join(workspaceRoot, "USER.md");
45
+ const memoryPath = path.join(workspaceRoot, "MEMORY.md");
46
+ const hasUser = await fileExists(userPath);
47
+ const hasMemory = await fileExists(memoryPath);
48
+ const userText = hasUser ? (await readText(userPath)).trim() : "";
49
+ const memoryText = hasMemory ? (await readText(memoryPath)).trim() : "";
50
+ const curatedFiles = await listCuratedSignalFiles(workspaceRoot);
51
+ const curatedNonEmpty = [];
52
+ for (const fp of curatedFiles) {
53
+ const body = (await readText(fp)).trim();
54
+ if (body.length >= MIN_MEANINGFUL_ANCHOR_CHARS)
55
+ curatedNonEmpty.push(fp);
56
+ }
57
+ const userOk = hasUser && userText.length >= MIN_MEANINGFUL_ANCHOR_CHARS;
58
+ const memoryOk = hasMemory && memoryText.length >= MIN_MEANINGFUL_ANCHOR_CHARS;
59
+ if (!userOk && !memoryOk && curatedNonEmpty.length === 0) {
60
+ return {
61
+ snapshotId: crypto.randomUUID(),
62
+ generatedAt,
63
+ signals: [],
64
+ sourceRefs: [],
65
+ confidence: 0,
66
+ staleness: "insufficient",
67
+ missingReasons: ["missing_user_interest_model"],
68
+ };
69
+ }
70
+ const signals = [];
71
+ const allRefs = [];
72
+ if (userOk) {
73
+ const ref = anchorRef("USER.md", workspaceRoot);
74
+ allRefs.push(ref);
75
+ signals.push({
76
+ id: `interest:user:${crypto.randomUUID()}`,
77
+ topic: "user_anchor_profile",
78
+ affinity: "unknown",
79
+ reason: "derived_from_USER.md_anchor",
80
+ confidence: 0.55,
81
+ sourceRefs: [ref],
82
+ updatedAt: generatedAt,
83
+ });
84
+ }
85
+ if (memoryOk) {
86
+ const ref = anchorRef("MEMORY.md", workspaceRoot);
87
+ allRefs.push(ref);
88
+ signals.push({
89
+ id: `interest:memory:${crypto.randomUUID()}`,
90
+ topic: "relational_memory_anchor",
91
+ affinity: "watching",
92
+ reason: "derived_from_MEMORY.md_anchor",
93
+ confidence: 0.52,
94
+ sourceRefs: [ref],
95
+ updatedAt: generatedAt,
96
+ });
97
+ }
98
+ for (const fp of curatedNonEmpty) {
99
+ const ref = {
100
+ id: `curated:${path.basename(fp, ".md")}`,
101
+ kind: "workspace_artifact",
102
+ uri: fp,
103
+ observedAt: generatedAt,
104
+ };
105
+ allRefs.push(ref);
106
+ signals.push({
107
+ id: `interest:curated:${crypto.randomUUID()}`,
108
+ topic: `curated:${path.basename(fp, ".md")}`,
109
+ affinity: "positive",
110
+ reason: "derived_from_curated_memory_file",
111
+ confidence: 0.48,
112
+ sourceRefs: [ref],
113
+ updatedAt: generatedAt,
114
+ });
115
+ }
116
+ if (signals.length === 0) {
117
+ return {
118
+ snapshotId: crypto.randomUUID(),
119
+ generatedAt,
120
+ signals: [],
121
+ sourceRefs: [],
122
+ confidence: 0,
123
+ staleness: "insufficient",
124
+ missingReasons: ["missing_user_interest_model"],
125
+ };
126
+ }
127
+ const confidence = signals.reduce((s, x) => s + x.confidence, 0) / signals.length;
128
+ if (confidence < INSUFFICIENT_CONFIDENCE_THRESHOLD) {
129
+ return {
130
+ snapshotId: crypto.randomUUID(),
131
+ generatedAt,
132
+ signals: [],
133
+ sourceRefs: [],
134
+ confidence: 0,
135
+ staleness: "insufficient",
136
+ missingReasons: ["missing_user_interest_model"],
137
+ };
138
+ }
139
+ const mtimes = await newestMtime([hasUser ? userPath : "", hasMemory ? memoryPath : "", ...curatedNonEmpty].filter(Boolean));
140
+ const staleness = mtimes > 0 && Date.now() - mtimes > STALE_MS ? "stale" : "fresh";
141
+ const uniqueRefs = [...new Map(allRefs.map((r) => [r.uri, r])).values()];
142
+ return {
143
+ snapshotId: crypto.randomUUID(),
144
+ generatedAt,
145
+ signals,
146
+ sourceRefs: uniqueRefs,
147
+ confidence,
148
+ staleness,
149
+ };
150
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * User interest read model (T4.2.2 / state-system v5 subset).
3
+ *
4
+ * Test coverage: tests/unit/storage/user-interest-snapshot.test.ts
5
+ */
6
+ import type { SourceRef } from "../life-evidence/types.js";
7
+ export type UserInterestStaleness = "fresh" | "stale" | "insufficient";
8
+ export interface UserInterestSignal {
9
+ id: string;
10
+ topic: string;
11
+ affinity: "positive" | "negative" | "watching" | "unknown";
12
+ reason: string;
13
+ confidence: number;
14
+ sourceRefs: SourceRef[];
15
+ updatedAt: string;
16
+ }
17
+ export interface UserInterestSnapshot {
18
+ snapshotId: string;
19
+ generatedAt: string;
20
+ signals: UserInterestSignal[];
21
+ sourceRefs: SourceRef[];
22
+ confidence: number;
23
+ staleness: UserInterestStaleness;
24
+ missingReasons?: string[];
25
+ }
@@ -0,0 +1 @@
1
+ export {};