@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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/connector-init.d.ts +19 -0
- package/runtime/cli/commands/connector-init.js +168 -0
- package/runtime/cli/commands/connector-status.d.ts +12 -0
- package/runtime/cli/commands/connector-status.js +156 -0
- package/runtime/cli/commands/index.js +40 -0
- package/runtime/cli/index.js +8 -6
- package/runtime/cli/ops/ops-router.d.ts +5 -0
- package/runtime/cli/ops/ops-router.js +34 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +22 -0
- package/runtime/connectors/base/manifest.d.ts +13 -0
- package/runtime/connectors/base/manifest.js +47 -0
- package/runtime/connectors/manifest/manifest-parser.d.ts +16 -0
- package/runtime/connectors/manifest/manifest-parser.js +35 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +145 -0
- package/runtime/connectors/manifest/manifest-schema.js +51 -0
- package/runtime/connectors/registry/dynamic-connector-registry.d.ts +29 -0
- package/runtime/connectors/registry/dynamic-connector-registry.js +123 -0
- package/runtime/connectors/registry/index.d.ts +3 -0
- package/runtime/connectors/registry/index.js +3 -0
- package/runtime/connectors/registry/manifest-scanner.d.ts +9 -0
- package/runtime/connectors/registry/manifest-scanner.js +29 -0
- package/runtime/connectors/registry/trust-policy.d.ts +13 -0
- package/runtime/connectors/registry/trust-policy.js +37 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +52 -1
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +19 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.js +67 -0
- package/runtime/core/second-nature/orchestrator/intent-planner.js +8 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.d.ts +27 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.js +107 -0
- package/runtime/core/second-nature/runtime/service-entry.js +1 -1
- package/runtime/core/second-nature/types.d.ts +4 -0
- package/runtime/guidance/draft-narrative-outreach.d.ts +36 -0
- package/runtime/guidance/draft-narrative-outreach.js +84 -0
- package/runtime/guidance/index.d.ts +1 -0
- package/runtime/guidance/index.js +1 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
- package/runtime/observability/connector-inventory-ledger.d.ts +45 -0
- package/runtime/observability/connector-inventory-ledger.js +72 -0
- package/runtime/observability/db/index.js +13 -0
- package/runtime/observability/db/schema/connector-inventory.d.ts +174 -0
- package/runtime/observability/db/schema/connector-inventory.js +15 -0
- package/runtime/observability/db/schema/index.d.ts +1 -0
- package/runtime/observability/db/schema/index.js +1 -0
- package/runtime/storage/chronicle/session-chronicle-store.d.ts +42 -0
- package/runtime/storage/chronicle/session-chronicle-store.js +66 -0
- package/runtime/storage/db/index.js +75 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +235 -0
- package/runtime/storage/db/schema/agent-goal.js +19 -0
- package/runtime/storage/db/schema/index.d.ts +5 -0
- package/runtime/storage/db/schema/index.js +5 -0
- package/runtime/storage/db/schema/memory-store.d.ts +199 -0
- package/runtime/storage/db/schema/memory-store.js +18 -0
- package/runtime/storage/db/schema/narrative-state.d.ts +195 -0
- package/runtime/storage/db/schema/narrative-state.js +16 -0
- package/runtime/storage/db/schema/relationship-memory.d.ts +174 -0
- package/runtime/storage/db/schema/relationship-memory.js +14 -0
- package/runtime/storage/db/schema/session-chronicle.d.ts +199 -0
- package/runtime/storage/db/schema/session-chronicle.js +18 -0
- package/runtime/storage/goal/agent-goal-store.d.ts +57 -0
- package/runtime/storage/goal/agent-goal-store.js +109 -0
- package/runtime/storage/index.d.ts +5 -0
- package/runtime/storage/index.js +5 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.d.ts +70 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.js +113 -0
- package/runtime/storage/narrative/narrative-state-store.d.ts +40 -0
- package/runtime/storage/narrative/narrative-state-store.js +79 -0
- package/runtime/storage/relationship/relationship-memory-store.d.ts +42 -0
- package/runtime/storage/relationship/relationship-memory-store.js +76 -0
- 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.
|
|
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
|
+
}
|
|
@@ -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" | "
|
|
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:") {
|