@haaaiawd/second-nature 0.1.31 → 0.1.33
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/index.js +10 -3
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/runtime/cli/commands/index.js +1 -0
- package/runtime/cli/index.js +170 -0
- package/runtime/cli/ops/heartbeat-surface.d.ts +3 -0
- package/runtime/cli/ops/heartbeat-surface.js +1 -0
- package/runtime/cli/ops/ops-router.d.ts +6 -0
- package/runtime/cli/ops/ops-router.js +330 -8
- package/runtime/storage/services/history-digest-store.js +7 -1
- package/runtime/storage/services/restore-snapshot-store.js +10 -1
- package/workspace-ops-bridge.js +4 -0
package/index.js
CHANGED
|
@@ -71,7 +71,7 @@ process.stderr.write("[second-nature] module evaluated\n");
|
|
|
71
71
|
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
72
72
|
const HOST_SAFE_LIMITATION_MESSAGE = "Host-safe plugin package keeps synchronous register/load semantics, but mutating workspace runtime flows remain unavailable here.";
|
|
73
73
|
const SETUP_MARKER_RELATIVE_PATH = path.join(".second-nature", "setup", "agent-inner-guide-ack.json");
|
|
74
|
-
const SETUP_GUIDE_VERSION = "0.1.
|
|
74
|
+
const SETUP_GUIDE_VERSION = "0.1.33";
|
|
75
75
|
const SETUP_COMMANDS = new Set(["setup_hint", "setup_ack"]);
|
|
76
76
|
let activationSpine = null;
|
|
77
77
|
/** T1.1.4 — lazily opened full read bridge; closed when workspace root / resolution changes. */
|
|
@@ -111,6 +111,7 @@ const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
|
111
111
|
"self_health",
|
|
112
112
|
"tool_affordance",
|
|
113
113
|
"heartbeat_digest",
|
|
114
|
+
"snapshot:capture",
|
|
114
115
|
"narrative:diff",
|
|
115
116
|
"timeline",
|
|
116
117
|
"restore",
|
|
@@ -1094,6 +1095,12 @@ function parseCommandInput(rawArgs) {
|
|
|
1094
1095
|
command,
|
|
1095
1096
|
input: rest[0] ? { date: rest[0] } : undefined,
|
|
1096
1097
|
};
|
|
1098
|
+
case "snapshot:capture":
|
|
1099
|
+
return {
|
|
1100
|
+
ok: true,
|
|
1101
|
+
command,
|
|
1102
|
+
input: rest[0] ? { snapshotId: rest[0] } : undefined,
|
|
1103
|
+
};
|
|
1097
1104
|
case "narrative:diff":
|
|
1098
1105
|
return {
|
|
1099
1106
|
ok: true,
|
|
@@ -1200,7 +1207,7 @@ export default {
|
|
|
1200
1207
|
};
|
|
1201
1208
|
}
|
|
1202
1209
|
const resolved = spine.router.resolve(parsed.command);
|
|
1203
|
-
if (!resolved) {
|
|
1210
|
+
if (!resolved && !isWorkspaceBridgeCommand(parsed.command, parsed.input)) {
|
|
1204
1211
|
return {
|
|
1205
1212
|
text: JSON.stringify({
|
|
1206
1213
|
ok: false,
|
|
@@ -1219,7 +1226,7 @@ export default {
|
|
|
1219
1226
|
const spine = ensureActivationSpine();
|
|
1220
1227
|
syncWorkspaceRootFromTool(spine, params.workspaceRoot);
|
|
1221
1228
|
const resolved = spine.router.resolve(params.command);
|
|
1222
|
-
if (!resolved) {
|
|
1229
|
+
if (!resolved && !isWorkspaceBridgeCommand(params.command, params.args)) {
|
|
1223
1230
|
return {
|
|
1224
1231
|
content: [
|
|
1225
1232
|
{
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, narrative:diff, timeline, restore, runtime_secret_bootstrap.",
|
|
4
|
+
"version": "0.1.33",
|
|
5
|
+
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap.",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED
|
@@ -280,6 +280,7 @@ export function createCliCommands(deps) {
|
|
|
280
280
|
opsCommand("self_health", "T-ROS.C.1 — show v7 self-health snapshot and degraded dimensions"),
|
|
281
281
|
opsCommand("tool_affordance", "T-ROS.C.1 — show v7 tool affordance map or explicit unavailable state"),
|
|
282
282
|
opsCommand("heartbeat_digest", "T-ROS.C.1 — assemble v7 heartbeat digest for a day"),
|
|
283
|
+
opsCommand("snapshot:capture", "T-V7C.C.1 — capture restore snapshot and narrative timeline version"),
|
|
283
284
|
opsCommand("narrative:diff", "T-ROS.C.1 — compare two narrative timeline versions"),
|
|
284
285
|
opsCommand("timeline", "T-ROS.C.1 — query v7 narrative timeline with cursor pagination"),
|
|
285
286
|
opsCommand("restore", "T-ROS.C.1 — apply bounded restore and write restore audit"),
|
package/runtime/cli/index.js
CHANGED
|
@@ -9,6 +9,15 @@ import { resolvePackagedRuntime } from "./runtime/runtime-artifact-boundary.js";
|
|
|
9
9
|
import { createRestoreSnapshotStore, } from "../storage/services/restore-snapshot-store.js";
|
|
10
10
|
import { createRuntimeDecisionRecorder, } from "../observability/services/runtime-decision-recorder.js";
|
|
11
11
|
import { createConnectorExecutorAdapter, } from "../connectors/services/connector-executor-adapter.js";
|
|
12
|
+
import { CapabilityContractRegistry } from "../connectors/base/manifest.js";
|
|
13
|
+
import { CapabilityContractRegistryV7 } from "../connectors/base/manifest-v7.js";
|
|
14
|
+
import { moltbookManifest } from "../connectors/social-community/moltbook/manifest.js";
|
|
15
|
+
import { evomapManifest } from "../connectors/agent-network/evomap/manifest.js";
|
|
16
|
+
import { agentWorldManifest } from "../connectors/agent-network/agent-world/manifest.js";
|
|
17
|
+
import { instreetManifest } from "../connectors/social-community/instreet/manifest.js";
|
|
18
|
+
import { createAffordanceAssembler, } from "../core/second-nature/body/tool-affordance/affordance-assembler.js";
|
|
19
|
+
import { createHistoryDigestStore, } from "../storage/services/history-digest-store.js";
|
|
20
|
+
import { probeCredentialHealth, } from "../storage/services/credential-vault.js";
|
|
12
21
|
import { DynamicConnectorRegistry, createRegistrySnapshotStore, } from "../connectors/registry/index.js";
|
|
13
22
|
/** Built-in connector manifests for DynamicConnectorRegistry. */
|
|
14
23
|
const BUILT_IN_CONNECTOR_MANIFESTS = [
|
|
@@ -54,6 +63,154 @@ const BUILT_IN_CONNECTOR_MANIFESTS = [
|
|
|
54
63
|
sourceRefPolicy: { minSourceRefs: 1 },
|
|
55
64
|
},
|
|
56
65
|
];
|
|
66
|
+
const BUILT_IN_CAPABILITY_MANIFESTS = [
|
|
67
|
+
moltbookManifest,
|
|
68
|
+
evomapManifest,
|
|
69
|
+
agentWorldManifest,
|
|
70
|
+
instreetManifest,
|
|
71
|
+
];
|
|
72
|
+
function idempotencyClassForCapability(capabilityId) {
|
|
73
|
+
return capabilityId.includes("read") ||
|
|
74
|
+
capabilityId.includes("discover") ||
|
|
75
|
+
capabilityId.includes("list") ||
|
|
76
|
+
capabilityId.includes("heartbeat")
|
|
77
|
+
? "read_only"
|
|
78
|
+
: "idempotent_write";
|
|
79
|
+
}
|
|
80
|
+
function createCapabilityRegistry() {
|
|
81
|
+
const registry = new CapabilityContractRegistry();
|
|
82
|
+
for (const manifest of BUILT_IN_CAPABILITY_MANIFESTS) {
|
|
83
|
+
registry.register(manifest);
|
|
84
|
+
}
|
|
85
|
+
return registry;
|
|
86
|
+
}
|
|
87
|
+
function createAffordanceRegistry(registry, workspaceRoot) {
|
|
88
|
+
registry.reloadConnectors(workspaceRoot);
|
|
89
|
+
const v7 = new CapabilityContractRegistryV7();
|
|
90
|
+
for (const entry of registry.listConnectors()) {
|
|
91
|
+
v7.register({
|
|
92
|
+
platformId: entry.platformId,
|
|
93
|
+
capabilities: entry.capabilities.map((capabilityId) => ({
|
|
94
|
+
capabilityId,
|
|
95
|
+
intent: capabilityId,
|
|
96
|
+
probeConfig: {
|
|
97
|
+
safeEndpoint: `connector://${entry.platformId}/${capabilityId}`,
|
|
98
|
+
idempotencyClass: idempotencyClassForCapability(capabilityId),
|
|
99
|
+
},
|
|
100
|
+
})),
|
|
101
|
+
channelPriority: ["api_rest"],
|
|
102
|
+
credentialTypes: ["api_key"],
|
|
103
|
+
sourceRefPolicy: { minSourceRefs: 1 },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return v7;
|
|
107
|
+
}
|
|
108
|
+
function createWorkspaceAffordanceAssembler(registry, workspaceRoot) {
|
|
109
|
+
return createAffordanceAssembler({
|
|
110
|
+
registry: createAffordanceRegistry(registry, workspaceRoot),
|
|
111
|
+
probeReader: {
|
|
112
|
+
getLatestProbeResult() {
|
|
113
|
+
return undefined;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
credentialRequired: () => false,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function stringifyDeltaField(delta, key) {
|
|
120
|
+
const value = delta[key];
|
|
121
|
+
if (value === undefined || value === null)
|
|
122
|
+
return undefined;
|
|
123
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
124
|
+
}
|
|
125
|
+
function asStringArray(value) {
|
|
126
|
+
return Array.isArray(value)
|
|
127
|
+
? value.filter((item) => typeof item === "string")
|
|
128
|
+
: [];
|
|
129
|
+
}
|
|
130
|
+
function createNarrativeTimelineDeps(stateDb) {
|
|
131
|
+
const historyStore = createHistoryDigestStore(stateDb);
|
|
132
|
+
return {
|
|
133
|
+
stateMemoryPort: {
|
|
134
|
+
async listNarrativeTimeline(from, to, opts) {
|
|
135
|
+
const rows = await historyStore.listNarrativeTimeline({
|
|
136
|
+
limit: Math.max(opts?.limit ?? 100, 100),
|
|
137
|
+
});
|
|
138
|
+
return rows
|
|
139
|
+
.filter((row) => row.createdAt >= from && row.createdAt <= to)
|
|
140
|
+
.filter((row) => opts?.afterTimestamp ? row.createdAt > opts.afterTimestamp : true)
|
|
141
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
142
|
+
.slice(0, opts?.limit ?? rows.length)
|
|
143
|
+
.map((row) => ({
|
|
144
|
+
version: row.timelineId,
|
|
145
|
+
createdAt: row.createdAt,
|
|
146
|
+
triggerKind: row.entryType === "restore.applied" ||
|
|
147
|
+
row.entryType === "goal.transition" ||
|
|
148
|
+
row.entryType === "dream.projection" ||
|
|
149
|
+
row.entryType === "owner.override"
|
|
150
|
+
? row.entryType
|
|
151
|
+
: "heartbeat.decision",
|
|
152
|
+
sourceRefs: asStringArray(row.delta.sourceRefs),
|
|
153
|
+
reasonCode: stringifyDeltaField(row.delta, "reasonCode"),
|
|
154
|
+
summaryText: stringifyDeltaField(row.delta, "summaryText") ??
|
|
155
|
+
`${row.entryType}:${row.subjectId}`,
|
|
156
|
+
}));
|
|
157
|
+
},
|
|
158
|
+
async getNarrativeSnapshot(version) {
|
|
159
|
+
const rows = await historyStore.listNarrativeTimeline({ limit: 500 });
|
|
160
|
+
const row = rows.find((candidate) => candidate.timelineId === version || candidate.subjectId === version);
|
|
161
|
+
if (!row)
|
|
162
|
+
return null;
|
|
163
|
+
const delta = row.delta;
|
|
164
|
+
return {
|
|
165
|
+
version,
|
|
166
|
+
focus: delta.focus,
|
|
167
|
+
progress: delta.progress,
|
|
168
|
+
nextIntent: delta.nextIntent,
|
|
169
|
+
toneSignal: delta.toneSignal,
|
|
170
|
+
acceptedGoalId: delta.acceptedGoalId,
|
|
171
|
+
sourceRefs: asStringArray(delta.sourceRefs),
|
|
172
|
+
lastChangeReasonCode: stringifyDeltaField(delta, "reasonCode"),
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function createSecretAnchorDeps(stateDb) {
|
|
179
|
+
return {
|
|
180
|
+
runtimeOpsPort: {
|
|
181
|
+
getEncryptionKeyPath: () => "SECOND_NATURE_ENCRYPTION_KEY",
|
|
182
|
+
checkKeyPathExists: async () => {
|
|
183
|
+
const key = process.env.SECOND_NATURE_ENCRYPTION_KEY?.trim();
|
|
184
|
+
return Boolean(key && key.length >= 32);
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
credentialPort: {
|
|
188
|
+
verifySampleDecrypt: async () => {
|
|
189
|
+
const result = stateDb.sqlite.exec(`SELECT platform_id, encrypted_value
|
|
190
|
+
FROM credential_records
|
|
191
|
+
WHERE encrypted_value IS NOT NULL AND encrypted_value != ''
|
|
192
|
+
LIMIT 3`);
|
|
193
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
194
|
+
return { status: "ok", checkedIds: [] };
|
|
195
|
+
}
|
|
196
|
+
const checkedIds = [];
|
|
197
|
+
for (const row of result[0].values) {
|
|
198
|
+
const platformId = String(row[0] ?? "unknown");
|
|
199
|
+
const encryptedValue = typeof row[1] === "string" ? row[1] : "";
|
|
200
|
+
checkedIds.push(platformId);
|
|
201
|
+
const probe = probeCredentialHealth(platformId, encryptedValue, undefined);
|
|
202
|
+
if (probe.keyHealth === "wrong_key") {
|
|
203
|
+
return { status: "wrong_key", checkedIds };
|
|
204
|
+
}
|
|
205
|
+
if (probe.keyHealth !== "ok") {
|
|
206
|
+
return { status: "error", checkedIds };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { status: "ok", checkedIds };
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
57
214
|
export function createCliRuntimeDeps(overrides = {}) {
|
|
58
215
|
const workspaceRoot = overrides.workspaceRoot ?? process.cwd();
|
|
59
216
|
const stateDb = overrides.stateDb ?? createStateDatabase();
|
|
@@ -79,6 +236,11 @@ export function createCliRuntimeDeps(overrides = {}) {
|
|
|
79
236
|
builtInManifests: BUILT_IN_CONNECTOR_MANIFESTS,
|
|
80
237
|
snapshotStore: createRegistrySnapshotStore(),
|
|
81
238
|
});
|
|
239
|
+
const capabilityRegistry = overrides.capabilityRegistry ?? createCapabilityRegistry();
|
|
240
|
+
const affordanceAssembler = overrides.affordanceAssembler ??
|
|
241
|
+
createWorkspaceAffordanceAssembler(registry, workspaceRoot);
|
|
242
|
+
const narrativeTimelineDeps = overrides.narrativeTimelineDeps ?? createNarrativeTimelineDeps(stateDb);
|
|
243
|
+
const secretAnchorDeps = overrides.secretAnchorDeps ?? createSecretAnchorDeps(stateDb);
|
|
82
244
|
const restoreSnapshotStore = overrides.restoreSnapshotStore ?? createRestoreSnapshotStore(stateDb);
|
|
83
245
|
return {
|
|
84
246
|
stateDb,
|
|
@@ -88,7 +250,11 @@ export function createCliRuntimeDeps(overrides = {}) {
|
|
|
88
250
|
actionBridge,
|
|
89
251
|
runtimeRecorder,
|
|
90
252
|
connectorExecutor,
|
|
253
|
+
capabilityRegistry,
|
|
91
254
|
registry,
|
|
255
|
+
affordanceAssembler,
|
|
256
|
+
narrativeTimelineDeps,
|
|
257
|
+
secretAnchorDeps,
|
|
92
258
|
restoreSnapshotStore,
|
|
93
259
|
};
|
|
94
260
|
}
|
|
@@ -104,7 +270,11 @@ export function createCommandRouter(options = {}) {
|
|
|
104
270
|
workspaceRoot,
|
|
105
271
|
observabilityDb: runtime.observabilityDb,
|
|
106
272
|
connectorExecutor: runtime.connectorExecutor,
|
|
273
|
+
connectorRegistry: runtime.capabilityRegistry,
|
|
107
274
|
registry: runtime.registry,
|
|
275
|
+
toolAffordancePort: runtime.affordanceAssembler,
|
|
276
|
+
narrativeTimelineDeps: runtime.narrativeTimelineDeps,
|
|
277
|
+
secretAnchorDeps: runtime.secretAnchorDeps,
|
|
108
278
|
restoreSnapshotStore: runtime.restoreSnapshotStore,
|
|
109
279
|
});
|
|
110
280
|
const commands = createCliCommands({
|
|
@@ -10,6 +10,7 @@ import type { CliReadModels } from "../read-models/index.js";
|
|
|
10
10
|
import type { RuntimeDecisionRecorder } from "../../observability/services/runtime-decision-recorder.js";
|
|
11
11
|
import type { StateDatabase } from "../../storage/db/index.js";
|
|
12
12
|
import type { ConnectorExecutor } from "../../core/second-nature/orchestrator/effect-dispatcher.js";
|
|
13
|
+
import type { CapabilityContractRegistry } from "../../connectors/base/manifest.js";
|
|
13
14
|
export type HeartbeatSurfaceStatus = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
|
|
14
15
|
export interface HeartbeatSurfaceResult {
|
|
15
16
|
ok: boolean;
|
|
@@ -47,5 +48,7 @@ export interface HeartbeatCheckInput {
|
|
|
47
48
|
* connector-system instead of returning connector_dispatch_unwired.
|
|
48
49
|
*/
|
|
49
50
|
connectorExecutor?: ConnectorExecutor;
|
|
51
|
+
/** Capability registry used by planner to avoid platform/capability protocol mismatches. */
|
|
52
|
+
connectorRegistry?: CapabilityContractRegistry;
|
|
50
53
|
}
|
|
51
54
|
export declare function heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
|
|
@@ -74,6 +74,7 @@ export async function heartbeatCheck(input) {
|
|
|
74
74
|
state: input.state,
|
|
75
75
|
workspaceRoot: input.workspaceRoot ?? process.cwd(),
|
|
76
76
|
connectorExecutor: input.connectorExecutor,
|
|
77
|
+
connectorRegistry: input.connectorRegistry,
|
|
77
78
|
});
|
|
78
79
|
const cycle = await run(signal);
|
|
79
80
|
return mapCycleToSurface(cycle, "workspace_full_runtime");
|
|
@@ -10,6 +10,8 @@ import { type NarrativeTimelineDeps } from "../../observability/services/narrati
|
|
|
10
10
|
import { type SecretAnchorDeps } from "../../observability/services/runtime-secret-anchor-view.js";
|
|
11
11
|
import { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
12
12
|
import type { RestoreSnapshotStore } from "../../storage/services/restore-snapshot-store.js";
|
|
13
|
+
import type { CapabilityContractRegistry } from "../../connectors/base/manifest.js";
|
|
14
|
+
import type { AffordanceAssembler } from "../../core/second-nature/body/tool-affordance/affordance-assembler.js";
|
|
13
15
|
/** Unified response envelope for all v7 runtime-ops commands. */
|
|
14
16
|
export interface RuntimeOpsEnvelope<T = unknown> {
|
|
15
17
|
ok: boolean;
|
|
@@ -49,6 +51,8 @@ export interface OpsRouterDeps {
|
|
|
49
51
|
* connector-system instead of returning connector_dispatch_unwired.
|
|
50
52
|
*/
|
|
51
53
|
connectorExecutor?: ConnectorExecutor;
|
|
54
|
+
/** Capability registry used by heartbeat planner to avoid platform/capability mismatches. */
|
|
55
|
+
connectorRegistry?: CapabilityContractRegistry;
|
|
52
56
|
/**
|
|
53
57
|
* T1.2.3: DynamicConnectorRegistry for connector:status and connector:test commands.
|
|
54
58
|
*/
|
|
@@ -67,6 +71,8 @@ export interface OpsRouterDeps {
|
|
|
67
71
|
* Deps for narrative timeline (narrative:diff, timeline commands).
|
|
68
72
|
*/
|
|
69
73
|
narrativeTimelineDeps?: NarrativeTimelineDeps;
|
|
74
|
+
/** Port for tool_affordance command. */
|
|
75
|
+
toolAffordancePort?: AffordanceAssembler;
|
|
70
76
|
/**
|
|
71
77
|
* Deps for runtime_secret_bootstrap (key anchor health check).
|
|
72
78
|
*/
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* heartbeat_digest, narrative:diff, timeline, restore, runtime_secret_bootstrap.
|
|
6
6
|
* All commands return RuntimeOpsEnvelope.
|
|
7
7
|
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
9
11
|
import { heartbeatCheck, } from "./heartbeat-surface.js";
|
|
10
12
|
import { showOperatorFallback, OperatorFallbackNotFoundError, } from "./show-operator-fallback.js";
|
|
11
13
|
import { probeHostCapability } from "../host-capability/probe-host-capability.js";
|
|
@@ -21,16 +23,137 @@ import { generateHeartbeatDigest, } from "../../observability/services/heartbeat
|
|
|
21
23
|
import { queryNarrativeTimeline, queryNarrativeDiff, } from "../../observability/services/narrative-timeline-query-service.js";
|
|
22
24
|
import { viewSecretAnchor, } from "../../observability/services/runtime-secret-anchor-view.js";
|
|
23
25
|
import { writeRestoreAudit, } from "../../observability/services/restore-audit-service.js";
|
|
26
|
+
import { createHistoryDigestStore } from "../../storage/services/history-digest-store.js";
|
|
24
27
|
// T-ROS.C.3: ManualRunDispatcher and its deps
|
|
25
28
|
import { createManualRunDispatcher, } from "./manual-run-dispatcher.js";
|
|
26
29
|
import { createExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
|
|
27
|
-
import { createToolExperienceStore } from "../../storage/services/tool-experience-store.js";
|
|
30
|
+
import { createCapabilityProbeResultStore, createToolExperienceStore, } from "../../storage/services/tool-experience-store.js";
|
|
28
31
|
import { createWetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
|
|
29
32
|
import { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
|
|
30
33
|
function coerceProbeOnlyFlag(input) {
|
|
31
34
|
const v = input?.probeOnly;
|
|
32
35
|
return v === true || v === "true" || v === 1 || v === "1";
|
|
33
36
|
}
|
|
37
|
+
const SNAPSHOT_TABLE_BY_KIND = {
|
|
38
|
+
identity_profile: "identity_profile",
|
|
39
|
+
agent_goal: "agent_goal",
|
|
40
|
+
tool_experience: "tool_experience",
|
|
41
|
+
daily_diary: "daily_diary_index",
|
|
42
|
+
dream_output: "dream_output_index",
|
|
43
|
+
narrative_timeline: "narrative_timeline",
|
|
44
|
+
};
|
|
45
|
+
const DEFAULT_SNAPSHOT_KINDS = [
|
|
46
|
+
"identity_profile",
|
|
47
|
+
"agent_goal",
|
|
48
|
+
"tool_experience",
|
|
49
|
+
"daily_diary",
|
|
50
|
+
"dream_output",
|
|
51
|
+
"narrative_timeline",
|
|
52
|
+
];
|
|
53
|
+
function coerceRestorableKinds(value) {
|
|
54
|
+
if (!Array.isArray(value))
|
|
55
|
+
return undefined;
|
|
56
|
+
const valid = new Set(DEFAULT_SNAPSHOT_KINDS);
|
|
57
|
+
return value.filter((item) => typeof item === "string" && valid.has(item));
|
|
58
|
+
}
|
|
59
|
+
function tableExists(state, table) {
|
|
60
|
+
const result = state.sqlite.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`, [table]);
|
|
61
|
+
return result.length > 0 && result[0].values.length > 0;
|
|
62
|
+
}
|
|
63
|
+
function readRowsFromTable(state, table) {
|
|
64
|
+
const result = state.sqlite.exec(`SELECT * FROM ${table}`);
|
|
65
|
+
if (result.length === 0 || result[0].values.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const columns = result[0].columns;
|
|
68
|
+
return result[0].values.map((row) => {
|
|
69
|
+
const out = {};
|
|
70
|
+
columns.forEach((column, index) => {
|
|
71
|
+
out[column] = row[index];
|
|
72
|
+
});
|
|
73
|
+
return out;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function stringArray(value) {
|
|
77
|
+
return Array.isArray(value)
|
|
78
|
+
? value.filter((item) => typeof item === "string")
|
|
79
|
+
: [];
|
|
80
|
+
}
|
|
81
|
+
function textInput(input, key) {
|
|
82
|
+
const value = input?.[key];
|
|
83
|
+
if (typeof value !== "string")
|
|
84
|
+
return undefined;
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
87
|
+
}
|
|
88
|
+
function buildSnapshotNarrativeDelta(input, snapshotId, rowCounts) {
|
|
89
|
+
const explicit = input?.narrativeSnapshot &&
|
|
90
|
+
typeof input.narrativeSnapshot === "object" &&
|
|
91
|
+
!Array.isArray(input.narrativeSnapshot)
|
|
92
|
+
? input.narrativeSnapshot
|
|
93
|
+
: {};
|
|
94
|
+
const from = (key) => input?.[key] ?? explicit[key];
|
|
95
|
+
const sourceRefs = stringArray(from("sourceRefs"));
|
|
96
|
+
return {
|
|
97
|
+
focus: from("focus") ?? "workspace_state",
|
|
98
|
+
progress: from("progress") ??
|
|
99
|
+
`snapshot_captured:${Object.entries(rowCounts)
|
|
100
|
+
.map(([kind, count]) => `${kind}=${count}`)
|
|
101
|
+
.join(",")}`,
|
|
102
|
+
nextIntent: from("nextIntent") ?? "restore_ready",
|
|
103
|
+
toneSignal: from("toneSignal") ?? "system_maintenance",
|
|
104
|
+
acceptedGoalId: from("acceptedGoalId") ?? undefined,
|
|
105
|
+
sourceRefs: sourceRefs.length > 0
|
|
106
|
+
? sourceRefs
|
|
107
|
+
: [`restore_snapshot:${snapshotId}`, "runtime_ops:snapshot_capture"],
|
|
108
|
+
reasonCode: from("reasonCode") ?? "snapshot_captured",
|
|
109
|
+
summaryText: from("summaryText") ?? `Captured restore snapshot ${snapshotId}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function hashNarrativeSnapshot(input) {
|
|
113
|
+
return createHash("sha256")
|
|
114
|
+
.update(JSON.stringify({
|
|
115
|
+
previousHash: input.previousHash,
|
|
116
|
+
snapshotId: input.snapshotId,
|
|
117
|
+
delta: input.delta,
|
|
118
|
+
createdAt: input.createdAt,
|
|
119
|
+
}))
|
|
120
|
+
.digest("hex");
|
|
121
|
+
}
|
|
122
|
+
function resolveManifestPath(manifestPath, workspaceRoot) {
|
|
123
|
+
if (path.isAbsolute(manifestPath))
|
|
124
|
+
return manifestPath;
|
|
125
|
+
return path.join(workspaceRoot ?? process.cwd(), manifestPath);
|
|
126
|
+
}
|
|
127
|
+
function registerConnectorForWetProbe(input) {
|
|
128
|
+
if (input.entry.manifestPath) {
|
|
129
|
+
try {
|
|
130
|
+
const manifestText = fs.readFileSync(resolveManifestPath(input.entry.manifestPath, input.workspaceRoot), "utf-8");
|
|
131
|
+
const parsed = JSON.parse(manifestText);
|
|
132
|
+
const registered = input.registryV7.register(parsed);
|
|
133
|
+
if (registered.ok && input.registryV7.hasCapability(input.entry.platformId, input.selectedCapabilityId)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Non-v7 or YAML workspace manifests are projected below.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
input.registryV7.register({
|
|
142
|
+
platformId: input.entry.platformId,
|
|
143
|
+
capabilities: input.entry.capabilities.map((capabilityId) => ({
|
|
144
|
+
capabilityId,
|
|
145
|
+
intent: capabilityId,
|
|
146
|
+
probeConfig: capabilityId === input.selectedCapabilityId && input.safeEndpoint
|
|
147
|
+
? {
|
|
148
|
+
safeEndpoint: input.safeEndpoint,
|
|
149
|
+
idempotencyClass: "read_only",
|
|
150
|
+
}
|
|
151
|
+
: undefined,
|
|
152
|
+
})),
|
|
153
|
+
channelPriority: ["runtime_ops"],
|
|
154
|
+
credentialTypes: ["runtime_ops_probe"],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
34
157
|
/**
|
|
35
158
|
* T1.2.8 — static local adapter: all checks return `unknown` when no real host is available.
|
|
36
159
|
* Allows `capability_probe` to be called from CLI / workspace bridge without requiring a live host.
|
|
@@ -63,6 +186,8 @@ export function createOpsRouter(deps) {
|
|
|
63
186
|
state: input.state ?? deps.state,
|
|
64
187
|
workspaceRoot: input.workspaceRoot ?? deps.workspaceRoot,
|
|
65
188
|
connectorExecutor: input.connectorExecutor ?? deps.connectorExecutor,
|
|
189
|
+
connectorRegistry: input
|
|
190
|
+
?.connectorRegistry ?? deps.connectorRegistry,
|
|
66
191
|
}),
|
|
67
192
|
async dispatch(command, input) {
|
|
68
193
|
if (command === "heartbeat_check") {
|
|
@@ -91,6 +216,8 @@ export function createOpsRouter(deps) {
|
|
|
91
216
|
scopeHint: input?.scopeHint,
|
|
92
217
|
connectorExecutor: input
|
|
93
218
|
?.connectorExecutor ?? deps.connectorExecutor,
|
|
219
|
+
connectorRegistry: input
|
|
220
|
+
?.connectorRegistry ?? deps.connectorRegistry,
|
|
94
221
|
});
|
|
95
222
|
}
|
|
96
223
|
if (command === "fallback") {
|
|
@@ -244,8 +371,11 @@ export function createOpsRouter(deps) {
|
|
|
244
371
|
});
|
|
245
372
|
}
|
|
246
373
|
if (command === "connector_test") {
|
|
247
|
-
// v7 T-
|
|
248
|
-
const isWet = input?.wet === true ||
|
|
374
|
+
// v7 T-V7C.C.1: dryRun=false is the canonical wet probe switch.
|
|
375
|
+
const isWet = input?.wet === true ||
|
|
376
|
+
input?.wet === "true" ||
|
|
377
|
+
input?.dryRun === false ||
|
|
378
|
+
input?.dryRun === "false";
|
|
249
379
|
const result = await connectorTest(deps.registry, {
|
|
250
380
|
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
251
381
|
dryRun: isWet ? false : (input?.dryRun === false ? false : true),
|
|
@@ -253,12 +383,76 @@ export function createOpsRouter(deps) {
|
|
|
253
383
|
? input.workspaceRoot
|
|
254
384
|
: deps.workspaceRoot,
|
|
255
385
|
});
|
|
256
|
-
if (isWet
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
386
|
+
if (!isWet || !result.ok) {
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
const data = result.data && typeof result.data === "object"
|
|
390
|
+
? result.data
|
|
391
|
+
: {};
|
|
392
|
+
const capabilities = Array.isArray(data.capabilities)
|
|
393
|
+
? data.capabilities.filter((item) => typeof item === "string")
|
|
394
|
+
: [];
|
|
395
|
+
const capabilityId = textInput(input, "capabilityId") ?? capabilities[0] ?? "";
|
|
396
|
+
if (!capabilityId) {
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
command: "connector_test",
|
|
400
|
+
error: {
|
|
401
|
+
code: "MISSING_CAPABILITY_ID",
|
|
402
|
+
message: "wet connector_test requires capabilityId or at least one connector capability",
|
|
403
|
+
requiredUserInput: ["capabilityId"],
|
|
404
|
+
nextStep: "reinvoke_with_capability_id",
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const platformId = String(data.platformId ?? input?.platformId ?? "");
|
|
409
|
+
const registryEntry = deps.registry?.describeConnector(platformId);
|
|
410
|
+
if (!registryEntry) {
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
const registryV7 = new CapabilityContractRegistryV7();
|
|
414
|
+
registerConnectorForWetProbe({
|
|
415
|
+
registryV7,
|
|
416
|
+
entry: {
|
|
417
|
+
platformId: registryEntry.platformId,
|
|
418
|
+
capabilities: registryEntry.capabilities,
|
|
419
|
+
manifestPath: registryEntry.manifestPath,
|
|
420
|
+
},
|
|
421
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
422
|
+
? input.workspaceRoot
|
|
423
|
+
: deps.workspaceRoot,
|
|
424
|
+
selectedCapabilityId: capabilityId,
|
|
425
|
+
safeEndpoint: textInput(input, "safeEndpoint"),
|
|
426
|
+
});
|
|
427
|
+
const wetResult = await createWetProbeRunner().runWetProbe(platformId, capabilityId, registryV7);
|
|
428
|
+
const warnings = [];
|
|
429
|
+
let persistedProbeResult = false;
|
|
430
|
+
if (deps.state) {
|
|
431
|
+
await createCapabilityProbeResultStore(deps.state).appendProbeResult(wetResult.probeResult);
|
|
432
|
+
persistedProbeResult = true;
|
|
260
433
|
}
|
|
261
|
-
|
|
434
|
+
else {
|
|
435
|
+
warnings.push("state_db_unavailable:capability_probe_result_not_persisted");
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
ok: wetResult.probeResult.actualStatus !== "unavailable",
|
|
439
|
+
command: "connector_test",
|
|
440
|
+
data: {
|
|
441
|
+
...data,
|
|
442
|
+
dryRun: false,
|
|
443
|
+
capabilityId,
|
|
444
|
+
actualStatus: wetResult.probeResult.actualStatus,
|
|
445
|
+
httpStatus: wetResult.probeResult.httpStatus ?? wetResult.httpStatus,
|
|
446
|
+
probeResultId: wetResult.probeResult.probeResultId,
|
|
447
|
+
probeConfigRef: wetResult.probeResult.probeConfigRef,
|
|
448
|
+
sampleResponseRef: wetResult.probeResult.sampleResponseRef,
|
|
449
|
+
persistedProbeResult,
|
|
450
|
+
triggerSource: "manual_run",
|
|
451
|
+
affectsHeartbeatCadence: false,
|
|
452
|
+
note: "wet probe mode: executed safe probe endpoint and persisted capability_probe_result when state DB is available",
|
|
453
|
+
},
|
|
454
|
+
warnings,
|
|
455
|
+
};
|
|
262
456
|
}
|
|
263
457
|
if (command === "connector:run") {
|
|
264
458
|
// T-ROS.C.3: manual connector execution — isolated from heartbeat cadence
|
|
@@ -431,6 +625,38 @@ export function createOpsRouter(deps) {
|
|
|
431
625
|
*/
|
|
432
626
|
if (command === "tool_affordance") {
|
|
433
627
|
const generatedAt = new Date().toISOString();
|
|
628
|
+
if (deps.toolAffordancePort) {
|
|
629
|
+
const allStatuses = [
|
|
630
|
+
"safe",
|
|
631
|
+
"exploratory",
|
|
632
|
+
"needs_auth",
|
|
633
|
+
"painful",
|
|
634
|
+
"unavailable",
|
|
635
|
+
];
|
|
636
|
+
const platformIds = Array.isArray(input?.platformIds)
|
|
637
|
+
? input.platformIds.filter((item) => typeof item === "string")
|
|
638
|
+
: typeof input?.platformId === "string"
|
|
639
|
+
? [input.platformId]
|
|
640
|
+
: undefined;
|
|
641
|
+
const data = await deps.toolAffordancePort.assembleAffordanceMap({
|
|
642
|
+
platformIds,
|
|
643
|
+
allowedStatuses: allStatuses,
|
|
644
|
+
goalKind: typeof input?.goalKind === "string" ? input.goalKind : undefined,
|
|
645
|
+
});
|
|
646
|
+
const envelope = {
|
|
647
|
+
ok: true,
|
|
648
|
+
command: "tool_affordance",
|
|
649
|
+
runtimeMode: "workspace_full_runtime",
|
|
650
|
+
surfaceMode: "cli",
|
|
651
|
+
generatedAt,
|
|
652
|
+
data,
|
|
653
|
+
warnings: [],
|
|
654
|
+
sourceRefs: [
|
|
655
|
+
"core/second-nature/body/tool-affordance/affordance-assembler.ts",
|
|
656
|
+
],
|
|
657
|
+
};
|
|
658
|
+
return envelope;
|
|
659
|
+
}
|
|
434
660
|
const envelope = {
|
|
435
661
|
ok: false,
|
|
436
662
|
command: "tool_affordance",
|
|
@@ -506,6 +732,102 @@ export function createOpsRouter(deps) {
|
|
|
506
732
|
return envelope;
|
|
507
733
|
}
|
|
508
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* [G6] snapshot:capture — production capture path for RestoreSnapshot +
|
|
737
|
+
* NarrativeTimeline. This gives restore and narrative:diff real state to consume.
|
|
738
|
+
*/
|
|
739
|
+
if (command === "snapshot:capture") {
|
|
740
|
+
const generatedAt = new Date().toISOString();
|
|
741
|
+
if (!deps.state || !deps.restoreSnapshotStore) {
|
|
742
|
+
const envelope = {
|
|
743
|
+
ok: false,
|
|
744
|
+
command: "snapshot:capture",
|
|
745
|
+
runtimeMode: "unavailable",
|
|
746
|
+
surfaceMode: "cli",
|
|
747
|
+
generatedAt,
|
|
748
|
+
error: {
|
|
749
|
+
code: "SNAPSHOT_CAPTURE_DEPS_UNAVAILABLE",
|
|
750
|
+
message: "snapshot:capture requires state DB and RestoreSnapshotStore in OpsRouterDeps",
|
|
751
|
+
nextStep: "wire_state_and_restore_snapshot_store_into_ops_router",
|
|
752
|
+
},
|
|
753
|
+
warnings: [],
|
|
754
|
+
sourceRefs: [],
|
|
755
|
+
};
|
|
756
|
+
return envelope;
|
|
757
|
+
}
|
|
758
|
+
const snapshotId = textInput(input, "snapshotId") ??
|
|
759
|
+
`snapshot:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
760
|
+
const requestedKinds = coerceRestorableKinds(input?.entityWhitelist) ?? [...DEFAULT_SNAPSHOT_KINDS];
|
|
761
|
+
const rowCounts = {};
|
|
762
|
+
const warnings = [];
|
|
763
|
+
for (const kind of requestedKinds) {
|
|
764
|
+
const table = SNAPSHOT_TABLE_BY_KIND[kind];
|
|
765
|
+
if (!tableExists(deps.state, table)) {
|
|
766
|
+
rowCounts[kind] = 0;
|
|
767
|
+
warnings.push(`table_missing:${kind}:${table}`);
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
rowCounts[kind] = readRowsFromTable(deps.state, table).length;
|
|
771
|
+
}
|
|
772
|
+
const historyStore = createHistoryDigestStore(deps.state);
|
|
773
|
+
const previousHash = (await historyStore.listNarrativeTimeline({ limit: 1 }))[0]?.currentHash ?? "";
|
|
774
|
+
const delta = buildSnapshotNarrativeDelta(input, snapshotId, rowCounts);
|
|
775
|
+
const currentHash = hashNarrativeSnapshot({
|
|
776
|
+
previousHash,
|
|
777
|
+
snapshotId,
|
|
778
|
+
delta,
|
|
779
|
+
createdAt: generatedAt,
|
|
780
|
+
});
|
|
781
|
+
await historyStore.appendNarrativeTimeline({
|
|
782
|
+
timelineId: snapshotId,
|
|
783
|
+
entryType: "owner.override",
|
|
784
|
+
subjectId: textInput(input, "subjectId") ?? snapshotId,
|
|
785
|
+
delta,
|
|
786
|
+
previousHash,
|
|
787
|
+
currentHash,
|
|
788
|
+
createdAt: generatedAt,
|
|
789
|
+
});
|
|
790
|
+
const payload = {};
|
|
791
|
+
const capturedKinds = [];
|
|
792
|
+
for (const kind of requestedKinds) {
|
|
793
|
+
const table = SNAPSHOT_TABLE_BY_KIND[kind];
|
|
794
|
+
if (!tableExists(deps.state, table))
|
|
795
|
+
continue;
|
|
796
|
+
const rows = readRowsFromTable(deps.state, table);
|
|
797
|
+
rowCounts[kind] = rows.length;
|
|
798
|
+
if (rows.length > 0) {
|
|
799
|
+
payload[kind] = rows;
|
|
800
|
+
capturedKinds.push(kind);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const snapshot = await deps.restoreSnapshotStore.captureSnapshot({
|
|
804
|
+
snapshotId,
|
|
805
|
+
entityWhitelist: requestedKinds,
|
|
806
|
+
payload,
|
|
807
|
+
capturedAt: generatedAt,
|
|
808
|
+
});
|
|
809
|
+
const envelope = {
|
|
810
|
+
ok: true,
|
|
811
|
+
command: "snapshot:capture",
|
|
812
|
+
runtimeMode: "workspace_full_runtime",
|
|
813
|
+
surfaceMode: "cli",
|
|
814
|
+
generatedAt,
|
|
815
|
+
data: {
|
|
816
|
+
snapshotId: snapshot.snapshotId,
|
|
817
|
+
capturedAt: snapshot.capturedAt,
|
|
818
|
+
entityWhitelist: snapshot.entityWhitelist,
|
|
819
|
+
capturedKinds,
|
|
820
|
+
rowCounts,
|
|
821
|
+
narrativeVersion: snapshotId,
|
|
822
|
+
},
|
|
823
|
+
warnings,
|
|
824
|
+
sourceRefs: [
|
|
825
|
+
"storage/services/restore-snapshot-store.ts",
|
|
826
|
+
"storage/services/history-digest-store.ts",
|
|
827
|
+
],
|
|
828
|
+
};
|
|
829
|
+
return envelope;
|
|
830
|
+
}
|
|
509
831
|
/**
|
|
510
832
|
* [G6] narrative:diff — queryNarrativeDiff between two versions.
|
|
511
833
|
* Requires narrativeTimelineDeps in OpsRouterDeps.
|
|
@@ -32,6 +32,12 @@ export function createHistoryDigestStore(database) {
|
|
|
32
32
|
const { sqlite } = database;
|
|
33
33
|
return {
|
|
34
34
|
async appendNarrativeTimeline(entry) {
|
|
35
|
+
const deltaGate = validateWritePayload({
|
|
36
|
+
delta: entry.delta,
|
|
37
|
+
sourceRefs: ["narrative:append"],
|
|
38
|
+
});
|
|
39
|
+
if (!deltaGate.ok)
|
|
40
|
+
throw new Error(deltaGate.reason ?? "write_validation_failed");
|
|
35
41
|
const gate = validateWritePayload({
|
|
36
42
|
timelineId: entry.timelineId,
|
|
37
43
|
entryType: entry.entryType,
|
|
@@ -40,7 +46,7 @@ export function createHistoryDigestStore(database) {
|
|
|
40
46
|
previousHash: entry.previousHash,
|
|
41
47
|
currentHash: entry.currentHash,
|
|
42
48
|
sourceRefs: ["narrative:append"],
|
|
43
|
-
});
|
|
49
|
+
}, { runSensitivityScan: false });
|
|
44
50
|
if (!gate.ok)
|
|
45
51
|
throw new Error(gate.reason ?? "write_validation_failed");
|
|
46
52
|
sqlite.run(`INSERT INTO narrative_timeline
|
|
@@ -26,6 +26,14 @@ const ALL_RESTORABLE_KINDS = [
|
|
|
26
26
|
"dream_output",
|
|
27
27
|
"narrative_timeline",
|
|
28
28
|
];
|
|
29
|
+
const TABLE_BY_KIND = {
|
|
30
|
+
identity_profile: "identity_profile",
|
|
31
|
+
agent_goal: "agent_goal",
|
|
32
|
+
tool_experience: "tool_experience",
|
|
33
|
+
daily_diary: "daily_diary_index",
|
|
34
|
+
dream_output: "dream_output_index",
|
|
35
|
+
narrative_timeline: "narrative_timeline",
|
|
36
|
+
};
|
|
29
37
|
const DEFAULT_EXCLUDED_KINDS = [
|
|
30
38
|
"credential",
|
|
31
39
|
"raw_private_message",
|
|
@@ -172,7 +180,8 @@ export function createRestoreSnapshotStore(database, options = {}) {
|
|
|
172
180
|
const columns = keys.join(", ");
|
|
173
181
|
const placeholders = keys.map(() => "?").join(", ");
|
|
174
182
|
const values = keys.map((k) => row[k]);
|
|
175
|
-
|
|
183
|
+
const table = TABLE_BY_KIND[kind];
|
|
184
|
+
sqlite.run(`INSERT OR REPLACE INTO ${table} (${columns}) VALUES (${placeholders})`, values);
|
|
176
185
|
}
|
|
177
186
|
completedEntities.push(kind);
|
|
178
187
|
}
|
package/workspace-ops-bridge.js
CHANGED
|
@@ -71,9 +71,13 @@ export async function openWorkspaceOpsBridge(workspaceRoot) {
|
|
|
71
71
|
state: stateDb,
|
|
72
72
|
workspaceRoot: resolvedRoot,
|
|
73
73
|
connectorExecutor: deps.connectorExecutor,
|
|
74
|
+
connectorRegistry: deps.capabilityRegistry,
|
|
74
75
|
registry: deps.registry,
|
|
76
|
+
toolAffordancePort: deps.affordanceAssembler,
|
|
75
77
|
// v7 (T-ROS.C.2): in-memory audit store for heartbeat_digest / restore / self_health
|
|
76
78
|
auditStore,
|
|
79
|
+
narrativeTimelineDeps: deps.narrativeTimelineDeps,
|
|
80
|
+
secretAnchorDeps: deps.secretAnchorDeps,
|
|
77
81
|
restoreSnapshotStore: deps.restoreSnapshotStore,
|
|
78
82
|
});
|
|
79
83
|
const commands = commandsMod.createCliCommands({
|