@haaaiawd/second-nature 0.1.21 → 0.1.23

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 (73) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/runtime/cli/commands/connector-init.d.ts +19 -0
  4. package/runtime/cli/commands/connector-init.js +168 -0
  5. package/runtime/cli/commands/connector-status.d.ts +12 -0
  6. package/runtime/cli/commands/connector-status.js +156 -0
  7. package/runtime/cli/commands/index.js +40 -0
  8. package/runtime/cli/index.js +8 -6
  9. package/runtime/cli/ops/ops-router.d.ts +5 -0
  10. package/runtime/cli/ops/ops-router.js +34 -0
  11. package/runtime/cli/ops/workspace-heartbeat-runner.js +22 -0
  12. package/runtime/connectors/base/manifest.d.ts +13 -0
  13. package/runtime/connectors/base/manifest.js +47 -0
  14. package/runtime/connectors/manifest/manifest-parser.d.ts +16 -0
  15. package/runtime/connectors/manifest/manifest-parser.js +35 -0
  16. package/runtime/connectors/manifest/manifest-schema.d.ts +145 -0
  17. package/runtime/connectors/manifest/manifest-schema.js +51 -0
  18. package/runtime/connectors/registry/dynamic-connector-registry.d.ts +29 -0
  19. package/runtime/connectors/registry/dynamic-connector-registry.js +123 -0
  20. package/runtime/connectors/registry/index.d.ts +3 -0
  21. package/runtime/connectors/registry/index.js +3 -0
  22. package/runtime/connectors/registry/manifest-scanner.d.ts +9 -0
  23. package/runtime/connectors/registry/manifest-scanner.js +29 -0
  24. package/runtime/connectors/registry/trust-policy.d.ts +13 -0
  25. package/runtime/connectors/registry/trust-policy.js +37 -0
  26. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
  27. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +52 -1
  28. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -0
  29. package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +19 -0
  30. package/runtime/core/second-nature/orchestrator/goal-priority.js +67 -0
  31. package/runtime/core/second-nature/orchestrator/intent-planner.js +8 -0
  32. package/runtime/core/second-nature/orchestrator/narrative-update.d.ts +27 -0
  33. package/runtime/core/second-nature/orchestrator/narrative-update.js +107 -0
  34. package/runtime/core/second-nature/runtime/service-entry.js +1 -1
  35. package/runtime/core/second-nature/types.d.ts +4 -0
  36. package/runtime/guidance/draft-narrative-outreach.d.ts +36 -0
  37. package/runtime/guidance/draft-narrative-outreach.js +84 -0
  38. package/runtime/guidance/index.d.ts +1 -0
  39. package/runtime/guidance/index.js +1 -0
  40. package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
  41. package/runtime/observability/connector-inventory-ledger.d.ts +45 -0
  42. package/runtime/observability/connector-inventory-ledger.js +72 -0
  43. package/runtime/observability/db/index.js +13 -0
  44. package/runtime/observability/db/schema/connector-inventory.d.ts +174 -0
  45. package/runtime/observability/db/schema/connector-inventory.js +15 -0
  46. package/runtime/observability/db/schema/index.d.ts +1 -0
  47. package/runtime/observability/db/schema/index.js +1 -0
  48. package/runtime/storage/chronicle/session-chronicle-store.d.ts +42 -0
  49. package/runtime/storage/chronicle/session-chronicle-store.js +66 -0
  50. package/runtime/storage/db/index.js +75 -0
  51. package/runtime/storage/db/schema/agent-goal.d.ts +235 -0
  52. package/runtime/storage/db/schema/agent-goal.js +19 -0
  53. package/runtime/storage/db/schema/index.d.ts +5 -0
  54. package/runtime/storage/db/schema/index.js +5 -0
  55. package/runtime/storage/db/schema/memory-store.d.ts +199 -0
  56. package/runtime/storage/db/schema/memory-store.js +18 -0
  57. package/runtime/storage/db/schema/narrative-state.d.ts +195 -0
  58. package/runtime/storage/db/schema/narrative-state.js +16 -0
  59. package/runtime/storage/db/schema/relationship-memory.d.ts +174 -0
  60. package/runtime/storage/db/schema/relationship-memory.js +14 -0
  61. package/runtime/storage/db/schema/session-chronicle.d.ts +199 -0
  62. package/runtime/storage/db/schema/session-chronicle.js +18 -0
  63. package/runtime/storage/goal/agent-goal-store.d.ts +57 -0
  64. package/runtime/storage/goal/agent-goal-store.js +109 -0
  65. package/runtime/storage/index.d.ts +5 -0
  66. package/runtime/storage/index.js +5 -0
  67. package/runtime/storage/memory-store/memory-store-lifecycle.d.ts +70 -0
  68. package/runtime/storage/memory-store/memory-store-lifecycle.js +113 -0
  69. package/runtime/storage/narrative/narrative-state-store.d.ts +40 -0
  70. package/runtime/storage/narrative/narrative-state-store.js +79 -0
  71. package/runtime/storage/relationship/relationship-memory-store.d.ts +42 -0
  72. package/runtime/storage/relationship/relationship-memory-store.js +76 -0
  73. package/workspace-ops-bridge.js +1 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * T2.1.4 — Goal-Directed Intent Priority.
3
+ *
4
+ * `applyGoalPriority` adjusts candidate intent priorities based on accepted AgentGoals.
5
+ * Priority order: user_task > accepted_goal > rhythm.
6
+ * Only goals with status === "accepted" and origin !== "agent_proposed" are considered.
7
+ * All other statuses (proposal / rejected / completed / paused) are implicitly excluded.
8
+ */
9
+ import type { CandidateIntent } from "../types.js";
10
+ import type { AgentGoal } from "../../../storage/goal/agent-goal-store.js";
11
+ export interface ApplyGoalPriorityResult {
12
+ candidates: CandidateIntent[];
13
+ goalInfluences: Array<{
14
+ candidateId: string;
15
+ goalIds: string[];
16
+ boost: number;
17
+ }>;
18
+ }
19
+ export declare function applyGoalPriority(candidates: CandidateIntent[], goals: AgentGoal[] | undefined): ApplyGoalPriorityResult;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Per-goal priority boost applied when an accepted goal matches a candidate.
3
+ *
4
+ * Rationale: +20 per goal keeps a priority-50 candidate under 200
5
+ * even with 7 matching goals (50 + 140 = 190). Planner baselines
6
+ * range from 40–100, so 200 provides ample headroom without overflow.
7
+ */
8
+ const GOAL_PRIORITY_BOOST = 20;
9
+ function isGoalRelatedToCandidate(goal, candidate) {
10
+ if (!candidate.platformId)
11
+ return false;
12
+ const goalText = `${goal.description} ${goal.completionCriteria}`.toLowerCase();
13
+ const platformId = candidate.platformId.toLowerCase();
14
+ // Direct platformId mention in goal text
15
+ if (goalText.includes(platformId))
16
+ return true;
17
+ // Goal description contains candidate summary keywords
18
+ const summaryWords = candidate.summary.toLowerCase().split(/\s+/);
19
+ for (const word of summaryWords) {
20
+ if (word.length > 3 && goalText.includes(word))
21
+ return true;
22
+ }
23
+ return false;
24
+ }
25
+ export function applyGoalPriority(candidates, goals) {
26
+ const acceptedGoals = (goals ?? []).filter((g) => g.status === "accepted" && g.origin !== "agent_proposed");
27
+ if (acceptedGoals.length === 0) {
28
+ return {
29
+ candidates: candidates.map((c) => ({
30
+ ...c,
31
+ priorityReasons: c.priorityReasons ?? ["rhythm"],
32
+ })),
33
+ goalInfluences: [],
34
+ };
35
+ }
36
+ const influences = [];
37
+ const adjusted = candidates.map((candidate) => {
38
+ const relatedGoals = acceptedGoals.filter((g) => isGoalRelatedToCandidate(g, candidate));
39
+ if (relatedGoals.length === 0) {
40
+ return {
41
+ ...candidate,
42
+ priorityReasons: candidate.priorityReasons ?? ["rhythm"],
43
+ };
44
+ }
45
+ const boost = GOAL_PRIORITY_BOOST * relatedGoals.length;
46
+ const goalIds = relatedGoals.map((g) => g.goalId);
47
+ influences.push({
48
+ candidateId: candidate.id,
49
+ goalIds,
50
+ boost,
51
+ });
52
+ const reasons = [
53
+ ...(candidate.priorityReasons ?? ["rhythm"]),
54
+ ...relatedGoals.map((g) => `goal_boost:${g.goalId}`),
55
+ ];
56
+ return {
57
+ ...candidate,
58
+ priority: candidate.priority + boost,
59
+ goalInfluenceRefs: goalIds,
60
+ priorityReasons: reasons,
61
+ };
62
+ });
63
+ return {
64
+ candidates: adjusted.sort((a, b) => b.priority - a.priority),
65
+ goalInfluences: influences,
66
+ };
67
+ }
@@ -34,6 +34,7 @@ function planWorkIntents(runtime) {
34
34
  effectClass: "connector_action",
35
35
  sourceRefs: [...OBLIGATION_SOURCE],
36
36
  idempotencyKey: `obligation:${obligation}:${index}`,
37
+ goalInfluenceRefs: [],
37
38
  }));
38
39
  }
39
40
  function planExplorationIntents(runtime) {
@@ -50,6 +51,7 @@ function planExplorationIntents(runtime) {
50
51
  effectClass: "connector_action",
51
52
  sourceRefs: refs,
52
53
  idempotencyKey: "exploration:scan platform opportunities",
54
+ goalInfluenceRefs: [],
53
55
  },
54
56
  ];
55
57
  }
@@ -67,6 +69,7 @@ function planSocialIntents(runtime) {
67
69
  effectClass: "connector_action",
68
70
  sourceRefs: refs,
69
71
  idempotencyKey: "social:engage social platforms",
72
+ goalInfluenceRefs: [],
70
73
  },
71
74
  ];
72
75
  }
@@ -85,6 +88,7 @@ function planQuietReflectionIntents(runtime) {
85
88
  effectClass: "no_effect",
86
89
  sourceRefs: [],
87
90
  idempotencyKey: "quiet:bookkeeping",
91
+ goalInfluenceRefs: [],
88
92
  });
89
93
  }
90
94
  if (isAllowedKind("maintenance", runtime)) {
@@ -97,6 +101,7 @@ function planQuietReflectionIntents(runtime) {
97
101
  effectClass: "maintenance",
98
102
  sourceRefs: [],
99
103
  idempotencyKey: "maintenance:checks",
104
+ goalInfluenceRefs: [],
100
105
  });
101
106
  }
102
107
  if (isAllowedKind("reflection", runtime)) {
@@ -110,6 +115,7 @@ function planQuietReflectionIntents(runtime) {
110
115
  effectClass: "narrative_reflection",
111
116
  sourceRefs: refs,
112
117
  idempotencyKey: "reflection:narrative",
118
+ goalInfluenceRefs: [],
113
119
  });
114
120
  }
115
121
  return out;
@@ -131,6 +137,7 @@ function planOutreachIntents(runtime) {
131
137
  effectClass: "user_outreach",
132
138
  sourceRefs: refs,
133
139
  idempotencyKey: "outreach:consider proactive user outreach",
140
+ goalInfluenceRefs: [],
134
141
  },
135
142
  ];
136
143
  }
@@ -149,6 +156,7 @@ export function planCandidateIntents(runtime) {
149
156
  effectClass: "maintenance",
150
157
  sourceRefs: [],
151
158
  idempotencyKey: "maintenance:checks",
159
+ goalInfluenceRefs: [],
152
160
  },
153
161
  ];
154
162
  return pausedMaintenance
@@ -0,0 +1,27 @@
1
+ /**
2
+ * T2.1.5 — NarrativeState update after heartbeat effect/fallback.
3
+ *
4
+ * Produces a source-backed narrative revision or an honest empty-state
5
+ * (`awaiting_sources` / `insufficient_sources`) based on the cycle result.
6
+ *
7
+ * Rules-only implementation; no live LLM.
8
+ */
9
+ import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
10
+ import type { CandidateIntent } from "../types.js";
11
+ import type { PlannerLifeEvidenceSlice } from "../heartbeat/runtime-snapshot.js";
12
+ import type { NarrativeState, NarrativeStateUpdate } from "../../../storage/narrative/narrative-state-store.js";
13
+ export interface UpdateNarrativeAfterEffectInput {
14
+ result: HeartbeatCycleResult;
15
+ selectedIntent?: CandidateIntent;
16
+ lifeEvidence: PlannerLifeEvidenceSlice;
17
+ priorNarrative?: NarrativeState | null;
18
+ }
19
+ /**
20
+ * Build the next NarrativeState revision from a completed heartbeat cycle.
21
+ *
22
+ * - `intent_selected` + sources → `active`, focus/progress updated.
23
+ * - `intent_selected` without sources → `awaiting_sources`, claim recorded as unsupported.
24
+ * - No action (heartbeat_ok / denied / deferred / etc.) → preserve prior state or
25
+ * seed an empty `awaiting_sources` state when no prior exists.
26
+ */
27
+ export declare function updateNarrativeAfterEffect(input: UpdateNarrativeAfterEffectInput): NarrativeStateUpdate;
@@ -0,0 +1,107 @@
1
+ const MAX_PROGRESS_ENTRIES = 10;
2
+ const DEFAULT_NARRATIVE_ID = "default";
3
+ function mapControlPlaneRefToSourceRef(ref) {
4
+ return {
5
+ sourceId: ref.id,
6
+ kind: ref.kind,
7
+ url: ref.uri,
8
+ snippet: ref.excerptHash,
9
+ };
10
+ }
11
+ /**
12
+ * Compute narrative confidence based on source evidence.
13
+ *
14
+ * Formula: min(intentSources / 3, 1) + (lifeEvidenceSources > 0 ? 0.1 : 0)
15
+ *
16
+ * Rationale:
17
+ * - Base: 1/3 per intent source (3 sources = 100% confidence)
18
+ * - Boost: +0.1 if any life evidence exists (signals corroboration)
19
+ * - Capped at 1.0 (100%)
20
+ */
21
+ function computeConfidence(intentSources, lifeEvidenceSources) {
22
+ if (intentSources === 0 && lifeEvidenceSources === 0)
23
+ return 0;
24
+ const base = Math.min(intentSources / 3, 1);
25
+ const boost = lifeEvidenceSources > 0 ? 0.1 : 0;
26
+ return Math.min(base + boost, 1);
27
+ }
28
+ /**
29
+ * Build the next NarrativeState revision from a completed heartbeat cycle.
30
+ *
31
+ * - `intent_selected` + sources → `active`, focus/progress updated.
32
+ * - `intent_selected` without sources → `awaiting_sources`, claim recorded as unsupported.
33
+ * - No action (heartbeat_ok / denied / deferred / etc.) → preserve prior state or
34
+ * seed an empty `awaiting_sources` state when no prior exists.
35
+ */
36
+ export function updateNarrativeAfterEffect(input) {
37
+ const { result, selectedIntent, lifeEvidence, priorNarrative } = input;
38
+ const now = new Date().toISOString();
39
+ const prior = priorNarrative ?? null;
40
+ const narrativeId = prior?.narrativeId ?? DEFAULT_NARRATIVE_ID;
41
+ const nextRevision = (prior?.revision ?? 0) + 1;
42
+ // --- intent_selected branch ------------------------------------------------
43
+ if (result.status === "intent_selected" && selectedIntent) {
44
+ const hasIntentSources = selectedIntent.sourceRefs.length > 0;
45
+ const hasLifeEvidence = !(lifeEvidence.evidenceRefs.length === 0 &&
46
+ lifeEvidence.platformEventCount === 0 &&
47
+ lifeEvidence.workEventCount === 0);
48
+ const sourceRefs = selectedIntent.sourceRefs.map(mapControlPlaneRefToSourceRef);
49
+ if (hasIntentSources || hasLifeEvidence) {
50
+ // Source-backed revision
51
+ const progressEntry = `${selectedIntent.effectClass}: ${selectedIntent.summary}`;
52
+ const progress = [...(prior?.progress ?? [])];
53
+ if (!progress.includes(progressEntry)) {
54
+ progress.push(progressEntry);
55
+ }
56
+ const boundedProgress = progress.slice(-MAX_PROGRESS_ENTRIES);
57
+ return {
58
+ narrativeId,
59
+ revision: nextRevision,
60
+ focus: selectedIntent.summary,
61
+ progress: boundedProgress,
62
+ nextIntent: "continue",
63
+ confidence: computeConfidence(selectedIntent.sourceRefs.length, lifeEvidence.evidenceRefs.length),
64
+ sourceRefs,
65
+ unsupportedClaims: [],
66
+ status: "active",
67
+ updatedAt: now,
68
+ };
69
+ }
70
+ // No sources → awaiting_sources
71
+ return {
72
+ narrativeId,
73
+ revision: nextRevision,
74
+ focus: prior?.focus ?? "awaiting_evidence",
75
+ progress: prior?.progress ?? [],
76
+ nextIntent: "await_sources",
77
+ confidence: 0,
78
+ sourceRefs: [],
79
+ unsupportedClaims: [selectedIntent.summary],
80
+ status: "awaiting_sources",
81
+ updatedAt: now,
82
+ };
83
+ }
84
+ // --- No action / fallback branches -----------------------------------------
85
+ if (prior) {
86
+ // Preserve existing state, bump revision and timestamp so observers
87
+ // know the heartbeat cycle ran even when no intent was selected.
88
+ return {
89
+ ...prior,
90
+ revision: nextRevision,
91
+ updatedAt: now,
92
+ };
93
+ }
94
+ // No prior state and no action → seed empty awaiting_sources state
95
+ return {
96
+ narrativeId,
97
+ revision: nextRevision,
98
+ focus: "awaiting_evidence",
99
+ progress: [],
100
+ nextIntent: "await_sources",
101
+ confidence: 0,
102
+ sourceRefs: [],
103
+ unsupportedClaims: [],
104
+ status: "awaiting_sources",
105
+ updatedAt: now,
106
+ };
107
+ }
@@ -27,7 +27,7 @@ export function startRuntimeService(ctx) {
27
27
  // - control-plane-system (heartbeat bridge preparation)
28
28
  const workspaceRoot = ctx?.workspaceRoot ?? process.cwd();
29
29
  /** Keep in sync with `plugin/package.json` when cutting releases. */
30
- const version = "0.1.20";
30
+ const version = "0.1.21";
31
31
  activeHandle = {
32
32
  ready: true,
33
33
  version,
@@ -41,6 +41,10 @@ export interface CandidateIntent {
41
41
  sourceRefs: ControlPlaneSourceRef[];
42
42
  /** Dedupe / cooldown key; defaults to stable fingerprint in guard layer when omitted. */
43
43
  idempotencyKey?: string;
44
+ /** T2.1.4: IDs of accepted AgentGoals that influenced this candidate's priority. */
45
+ goalInfluenceRefs?: string[];
46
+ /** T2.1.4: Human-readable reasons for the priority value (goal influence, user task, rhythm). */
47
+ priorityReasons?: string[];
44
48
  }
45
49
  export interface GuardEvaluation {
46
50
  verdict: GuardVerdict;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * T6.1.1 — Narrative Outreach Draft generator.
3
+ *
4
+ * Produces source-backed outreach drafts from evidence + narrative + relationship.
5
+ * Rules-only implementation (no live LLM); honors grounding, redaction, and insufficient_history.
6
+ *
7
+ * NOTE: This function assumes narrativeState.focus / nextIntent and
8
+ * relationshipMemory fields are pre-redacted by upstream state-system.
9
+ * No additional PII filtering is applied here — defense-in-depth is
10
+ * the responsibility of the NarrativeState / RelationshipMemory stores.
11
+ */
12
+ import type { NarrativeState, SourceRef } from "../storage/narrative/narrative-state-store.js";
13
+ import type { RelationshipMemory } from "../storage/relationship/relationship-memory-store.js";
14
+ export interface NarrativeOutreachDraftRequest {
15
+ evidenceRefs: SourceRef[];
16
+ narrativeState?: NarrativeState;
17
+ relationshipMemory?: RelationshipMemory;
18
+ platformId?: string;
19
+ }
20
+ export interface GroundingReport {
21
+ status: "grounded" | "degraded" | "blocked";
22
+ sourceCoverage: number;
23
+ unsupportedClaims: string[];
24
+ reason: string;
25
+ }
26
+ export interface NarrativeOutreachDraftResult {
27
+ draft: {
28
+ whatHappened: string;
29
+ whyItMatters: string;
30
+ sourceRefs: SourceRef[];
31
+ tone: "friend" | "insufficient_history" | "blocked";
32
+ };
33
+ groundingReport: GroundingReport;
34
+ promptVersion: string;
35
+ }
36
+ export declare function draftNarrativeOutreach(request: NarrativeOutreachDraftRequest): NarrativeOutreachDraftResult;
@@ -0,0 +1,84 @@
1
+ function computeSourceCoverage(claims, _evidenceRefs, narrativeSourceRefs) {
2
+ if (claims.length === 0)
3
+ return { coverage: 1, unsupported: [] };
4
+ // Only narrative-level source refs count toward coverage.
5
+ const count = narrativeSourceRefs?.length ?? 0;
6
+ const coverage = Math.min(count / claims.length, 1);
7
+ return {
8
+ coverage,
9
+ unsupported: coverage >= 1 ? [] : claims,
10
+ };
11
+ }
12
+ export function draftNarrativeOutreach(request) {
13
+ const { evidenceRefs, narrativeState, relationshipMemory, platformId } = request;
14
+ // T6.1.1 — Block when source refs are missing
15
+ if (!evidenceRefs || evidenceRefs.length === 0) {
16
+ return {
17
+ draft: {
18
+ whatHappened: "",
19
+ whyItMatters: "",
20
+ sourceRefs: [],
21
+ tone: "blocked",
22
+ },
23
+ groundingReport: {
24
+ status: "blocked",
25
+ sourceCoverage: 0,
26
+ unsupportedClaims: ["missing_evidence_refs"],
27
+ reason: "no_source_refs_provided",
28
+ },
29
+ promptVersion: "v6-rules-only-1.0",
30
+ };
31
+ }
32
+ // Build claims from narrative state
33
+ const claims = [];
34
+ if (narrativeState?.focus) {
35
+ claims.push(narrativeState.focus);
36
+ }
37
+ if (narrativeState?.progress && narrativeState.progress.length > 0) {
38
+ claims.push(...narrativeState.progress);
39
+ }
40
+ const { coverage, unsupported } = computeSourceCoverage(claims, evidenceRefs, narrativeState?.sourceRefs);
41
+ // T6.1.1 — Degrade when source coverage is low
42
+ if (coverage < 0.5) {
43
+ return {
44
+ draft: {
45
+ whatHappened: narrativeState?.focus ?? "",
46
+ whyItMatters: "",
47
+ sourceRefs: evidenceRefs,
48
+ tone: "insufficient_history",
49
+ },
50
+ groundingReport: {
51
+ status: "degraded",
52
+ sourceCoverage: coverage,
53
+ unsupportedClaims: unsupported,
54
+ reason: "insufficient_source_coverage",
55
+ },
56
+ promptVersion: "v6-rules-only-1.0",
57
+ };
58
+ }
59
+ // T6.1.1 — insufficient_history tone when relationship is weak
60
+ const avgAffinity = relationshipMemory && relationshipMemory.topicAffinities.length > 0
61
+ ? relationshipMemory.topicAffinities.reduce((s, t) => s + t.affinity, 0) /
62
+ relationshipMemory.topicAffinities.length
63
+ : 0;
64
+ const tone = avgAffinity < 0.3 ? "insufficient_history" : "friend";
65
+ const whatHappened = narrativeState?.focus ?? "Recent activity observed.";
66
+ const whyItMatters = narrativeState?.nextIntent
67
+ ? `This relates to the current focus: ${narrativeState.nextIntent}.`
68
+ : "This may be relevant to your interests.";
69
+ return {
70
+ draft: {
71
+ whatHappened,
72
+ whyItMatters,
73
+ sourceRefs: evidenceRefs,
74
+ tone,
75
+ },
76
+ groundingReport: {
77
+ status: "grounded",
78
+ sourceCoverage: coverage,
79
+ unsupportedClaims: unsupported,
80
+ reason: "source_backed",
81
+ },
82
+ promptVersion: "v6-rules-only-1.0",
83
+ };
84
+ }
@@ -9,3 +9,4 @@ export * from "./guidance-assembler.js";
9
9
  export * from "./outreach-draft-schema.js";
10
10
  export * from "./draft-outreach-message.js";
11
11
  export * from "./evidence-guidance.js";
12
+ export * from "./draft-narrative-outreach.js";
@@ -9,3 +9,4 @@ export * from "./guidance-assembler.js";
9
9
  export * from "./outreach-draft-schema.js";
10
10
  export * from "./draft-outreach-message.js";
11
11
  export * from "./evidence-guidance.js";
12
+ export * from "./draft-narrative-outreach.js";
@@ -53,10 +53,10 @@ export declare const sceneGuidanceRequestSchema: z.ZodObject<{
53
53
  rhythmWindowKind: z.ZodOptional<z.ZodEnum<{
54
54
  quiet: "quiet";
55
55
  social: "social";
56
+ maintenance: "maintenance";
56
57
  work: "work";
57
58
  exploration: "exploration";
58
59
  reflection: "reflection";
59
- maintenance: "maintenance";
60
60
  }>>;
61
61
  riskLevel: z.ZodEnum<{
62
62
  medium: "medium";
@@ -107,10 +107,10 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
107
107
  rhythmWindowKind: z.ZodOptional<z.ZodEnum<{
108
108
  quiet: "quiet";
109
109
  social: "social";
110
+ maintenance: "maintenance";
110
111
  work: "work";
111
112
  exploration: "exploration";
112
113
  reflection: "reflection";
113
- maintenance: "maintenance";
114
114
  }>>;
115
115
  riskLevel: z.ZodEnum<{
116
116
  medium: "medium";
@@ -205,7 +205,7 @@ export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafe
205
205
  excerptHash?: string | undefined;
206
206
  observedAt?: string | undefined;
207
207
  }[];
208
- rhythmWindowKind?: "quiet" | "social" | "work" | "exploration" | "reflection" | "maintenance" | undefined;
208
+ rhythmWindowKind?: "quiet" | "social" | "maintenance" | "work" | "exploration" | "reflection" | undefined;
209
209
  deliveryContext?: {
210
210
  deliveryVerdict: "target_none" | "channel_missing" | "host_unsupported" | "delivery_failed" | "target_available";
211
211
  wordingMode: "sendable" | "not_sent_fallback_candidate";
@@ -0,0 +1,45 @@
1
+ import type { ObservabilityDatabase } from "./db/index.js";
2
+ export interface InventorySnapshot {
3
+ auditId: string;
4
+ snapshotId: string;
5
+ scanned: number;
6
+ registered: number;
7
+ skipped: number;
8
+ conflicts: Array<{
9
+ connectorId: string;
10
+ reason: string;
11
+ }>;
12
+ validationErrors: Array<{
13
+ connectorId: string;
14
+ errors: string[];
15
+ }>;
16
+ trustSummary: Record<string, number>;
17
+ createdAt: string;
18
+ }
19
+ export declare class ConnectorInventoryLedger {
20
+ private obs;
21
+ constructor(obs: ObservabilityDatabase);
22
+ private now;
23
+ private generateId;
24
+ recordAudit(input: {
25
+ snapshotId: string;
26
+ scanned: number;
27
+ registered: number;
28
+ skipped: number;
29
+ conflicts?: Array<{
30
+ connectorId: string;
31
+ reason: string;
32
+ }>;
33
+ validationErrors?: Array<{
34
+ connectorId: string;
35
+ errors: string[];
36
+ }>;
37
+ trustSummary?: Record<string, number>;
38
+ }): Promise<string>;
39
+ getLatestAudit(snapshotId: string): Promise<InventorySnapshot | undefined>;
40
+ listAudits(opts?: {
41
+ limit?: number;
42
+ offset?: number;
43
+ }): Promise<InventorySnapshot[]>;
44
+ private rowToSnapshot;
45
+ }
@@ -0,0 +1,72 @@
1
+ import { eq, desc } from "drizzle-orm";
2
+ import { connectorInventoryAudit } from "./db/schema/index.js";
3
+ export class ConnectorInventoryLedger {
4
+ obs;
5
+ constructor(obs) {
6
+ this.obs = obs;
7
+ }
8
+ now() {
9
+ return new Date().toISOString();
10
+ }
11
+ generateId() {
12
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
13
+ }
14
+ async recordAudit(input) {
15
+ const auditId = this.generateId();
16
+ const createdAt = this.now();
17
+ await this.obs.db
18
+ .insert(connectorInventoryAudit)
19
+ .values({
20
+ auditId,
21
+ snapshotId: input.snapshotId,
22
+ scanned: input.scanned,
23
+ registered: input.registered,
24
+ skipped: input.skipped,
25
+ conflictsJson: JSON.stringify(input.conflicts ?? []),
26
+ validationErrorsJson: JSON.stringify(input.validationErrors ?? []),
27
+ trustSummaryJson: JSON.stringify(input.trustSummary ?? {}),
28
+ createdAt,
29
+ })
30
+ .execute();
31
+ return auditId;
32
+ }
33
+ async getLatestAudit(snapshotId) {
34
+ const result = await this.obs.db
35
+ .select()
36
+ .from(connectorInventoryAudit)
37
+ .where(eq(connectorInventoryAudit.snapshotId, snapshotId))
38
+ .orderBy(desc(connectorInventoryAudit.createdAt))
39
+ .limit(1)
40
+ .execute();
41
+ if (!result || result.length === 0)
42
+ return undefined;
43
+ const row = result[0];
44
+ return this.rowToSnapshot(row);
45
+ }
46
+ async listAudits(opts) {
47
+ const limit = opts?.limit ?? 100;
48
+ const offset = opts?.offset ?? 0;
49
+ const rows = await this.obs.db
50
+ .select()
51
+ .from(connectorInventoryAudit)
52
+ .orderBy(desc(connectorInventoryAudit.createdAt))
53
+ .limit(limit)
54
+ .offset(offset)
55
+ .execute();
56
+ return rows.map((r) => this.rowToSnapshot(r));
57
+ }
58
+ rowToSnapshot(row) {
59
+ const r = row;
60
+ return {
61
+ auditId: String(r.auditId),
62
+ snapshotId: String(r.snapshotId),
63
+ scanned: Number(r.scanned),
64
+ registered: Number(r.registered),
65
+ skipped: Number(r.skipped),
66
+ conflicts: JSON.parse(String(r.conflictsJson ?? "[]")),
67
+ validationErrors: JSON.parse(String(r.validationErrorsJson ?? "[]")),
68
+ trustSummary: JSON.parse(String(r.trustSummaryJson ?? "{}")),
69
+ createdAt: String(r.createdAt),
70
+ };
71
+ }
72
+ }
@@ -83,6 +83,19 @@ const OBSERVABILITY_SCHEMA_SQL = `
83
83
  conflict_records_json TEXT NOT NULL,
84
84
  full_report_json TEXT NOT NULL
85
85
  );
86
+ CREATE TABLE IF NOT EXISTS connector_inventory_audit (
87
+ audit_id TEXT PRIMARY KEY,
88
+ snapshot_id TEXT NOT NULL,
89
+ scanned INTEGER NOT NULL,
90
+ registered INTEGER NOT NULL,
91
+ skipped INTEGER NOT NULL,
92
+ conflicts_json TEXT NOT NULL,
93
+ validation_errors_json TEXT NOT NULL,
94
+ trust_summary_json TEXT NOT NULL,
95
+ created_at TEXT NOT NULL
96
+ );
97
+ CREATE INDEX IF NOT EXISTS connector_inventory_snapshot_idx ON connector_inventory_audit(snapshot_id);
98
+ CREATE INDEX IF NOT EXISTS connector_inventory_created_at_idx ON connector_inventory_audit(created_at);
86
99
  `;
87
100
  function resolveDbPath(filename) {
88
101
  if (path.isAbsolute(filename) || filename === ":memory:") {