@haaaiawd/second-nature 0.1.6 → 0.1.8
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 +465 -67
- package/openclaw.plugin.json +12 -5
- package/package.json +2 -2
- package/runtime/cli/read-models/index.js +67 -22
- package/runtime/connectors/base/policy-layer.js +4 -35
- package/runtime/observability/db/index.js +71 -0
- package/runtime/observability/services/execution-telemetry.d.ts +0 -1
- package/runtime/storage/db/index.js +63 -0
- package/runtime/cli/action-bridge.d.ts +0 -11
- package/runtime/cli/index.d.ts +0 -25
package/index.js
CHANGED
|
@@ -1,26 +1,322 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Host-safe Second Nature plugin surface.
|
|
3
|
+
*
|
|
4
|
+
* Core logic:
|
|
5
|
+
* - keep register(api) synchronous so OpenClaw captures services/command/tool before return
|
|
6
|
+
* - avoid importing CLI/runtime DB modules at module-evaluation time because the packaged
|
|
7
|
+
* runtime graph currently contains async sql.js bootstrap that breaks vm sandbox loading
|
|
8
|
+
* - expose a minimal in-memory activation spine so status/lifecycle stay truthful even when
|
|
9
|
+
* the full workspace runtime is not loaded inside the host
|
|
10
|
+
*
|
|
11
|
+
* Dependencies:
|
|
12
|
+
* - only imports runtime lifecycle/service modules that are synchronous at load time
|
|
13
|
+
*
|
|
14
|
+
* Boundaries:
|
|
15
|
+
* - read-only operator flows stay available through command/tool surface
|
|
16
|
+
* - structured mutating flows such as policy set / credential verify remain unavailable here
|
|
17
|
+
* - full evidence-backed workspace runtime can be reintroduced later behind a host-safe boundary
|
|
18
|
+
*
|
|
19
|
+
* Test coverage:
|
|
20
|
+
* - tests/integration/cli/plugin-runtime-registration.test.ts
|
|
21
|
+
* - tests/integration/cli/plugin-packaging-walkthrough.test.ts
|
|
22
|
+
*/
|
|
23
|
+
import { startRuntimeService, } from "./runtime/core/second-nature/runtime/service-entry.js";
|
|
24
|
+
import { getLifecycleState, recordRegistration, } from "./runtime/core/second-nature/runtime/lifecycle-service.js";
|
|
25
|
+
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
26
|
+
const HOST_SAFE_LIMITATION_MESSAGE = "Host-safe plugin package keeps synchronous register/load semantics, but mutating workspace runtime flows remain unavailable here.";
|
|
27
|
+
let activationSpine = null;
|
|
28
|
+
function trimRuntimeEvidence(spine) {
|
|
29
|
+
if (spine.runtimeEvidence.length > 12) {
|
|
30
|
+
spine.runtimeEvidence.splice(0, spine.runtimeEvidence.length - 12);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function latestRuntimeEvidence(spine) {
|
|
34
|
+
return spine.runtimeEvidence[spine.runtimeEvidence.length - 1];
|
|
35
|
+
}
|
|
36
|
+
function createUnavailableActionError(code, message, requiredUserInput, nextStep) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
error: {
|
|
40
|
+
code,
|
|
41
|
+
message,
|
|
42
|
+
requiredUserInput,
|
|
43
|
+
nextStep,
|
|
44
|
+
},
|
|
45
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function parseExplainSubject(subjectRaw) {
|
|
49
|
+
const trimmed = subjectRaw.trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
throw new Error("explain_subject_invalid");
|
|
52
|
+
}
|
|
53
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
54
|
+
if (separatorIndex === -1) {
|
|
55
|
+
throw new Error("explain_subject_requires_id");
|
|
56
|
+
}
|
|
57
|
+
const kind = trimmed.slice(0, separatorIndex).trim();
|
|
58
|
+
const id = trimmed.slice(separatorIndex + 1).trim();
|
|
59
|
+
if (!id) {
|
|
60
|
+
throw new Error("explain_subject_requires_id");
|
|
61
|
+
}
|
|
62
|
+
switch (kind) {
|
|
63
|
+
case "decision":
|
|
64
|
+
return { subjectType: "decision", subjectId: id };
|
|
65
|
+
case "platform":
|
|
66
|
+
case "platform-selection":
|
|
67
|
+
return { subjectType: "platform-selection", subjectId: id };
|
|
68
|
+
case "outreach":
|
|
69
|
+
return { subjectType: "outreach", subjectId: id };
|
|
70
|
+
case "soul":
|
|
71
|
+
case "soul-change":
|
|
72
|
+
return { subjectType: "soul-change", subjectId: id };
|
|
73
|
+
default:
|
|
74
|
+
throw new Error("explain_subject_unsupported");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function buildStatusPayload(spine) {
|
|
78
|
+
const runtimeEvidence = latestRuntimeEvidence(spine);
|
|
79
|
+
const updatedAt = runtimeEvidence?.createdAt ?? new Date(spine.lifecycleState.lastChangedAt).toISOString();
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
data: {
|
|
83
|
+
runtime: {
|
|
84
|
+
host: "openclaw-plugin",
|
|
85
|
+
serviceStatus: spine.runtimeHandle.ready ? "running" : "idle",
|
|
86
|
+
updatedAt,
|
|
87
|
+
},
|
|
88
|
+
rhythm: {
|
|
89
|
+
mode: "active",
|
|
90
|
+
windowId: undefined,
|
|
91
|
+
},
|
|
92
|
+
quiet: {
|
|
93
|
+
mode: "unknown",
|
|
94
|
+
lastEvent: runtimeEvidence?.traceId,
|
|
95
|
+
interrupted: undefined,
|
|
96
|
+
},
|
|
97
|
+
connectors: [],
|
|
98
|
+
credentials: [],
|
|
99
|
+
risk: {
|
|
100
|
+
level: "low",
|
|
101
|
+
flags: [],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function buildQuietPayload(scope) {
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
data: {
|
|
110
|
+
scope,
|
|
111
|
+
mode: "unknown",
|
|
112
|
+
sourceCount: 0,
|
|
113
|
+
reportCount: 0,
|
|
114
|
+
recentJournalCount: 0,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function buildReportPayload(day) {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
data: {
|
|
122
|
+
day: day && day.trim() ? day : new Date().toISOString().slice(0, 10),
|
|
123
|
+
summary: "",
|
|
124
|
+
highlights: [],
|
|
125
|
+
sourceRefs: [],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function buildSessionPayload(sessionId) {
|
|
130
|
+
if (!sessionId) {
|
|
131
|
+
return {
|
|
7
132
|
ok: false,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
133
|
+
error: {
|
|
134
|
+
code: "MISSING_SESSION_ID",
|
|
135
|
+
message: "session show requires sessionId",
|
|
136
|
+
requiredUserInput: ["session_id"],
|
|
137
|
+
nextStep: "reinvoke_session_with_session_id",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
data: {
|
|
144
|
+
requestedSessionId: sessionId,
|
|
145
|
+
traceId: sessionId,
|
|
146
|
+
decisionCount: 0,
|
|
147
|
+
attemptCount: 0,
|
|
148
|
+
governanceCount: 0,
|
|
149
|
+
keyFactors: [],
|
|
150
|
+
evidenceRefs: [],
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function buildCredentialPayload(platformId) {
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
data: {
|
|
158
|
+
platformId: platformId && platformId.trim() ? platformId : "unknown",
|
|
159
|
+
status: "missing",
|
|
160
|
+
nextStep: "provide_credential_context",
|
|
161
|
+
},
|
|
162
|
+
};
|
|
12
163
|
}
|
|
13
|
-
|
|
164
|
+
function buildExplainPayload(spine, subjectRaw) {
|
|
165
|
+
if (!subjectRaw?.trim()) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
error: {
|
|
169
|
+
code: "MISSING_EXPLAIN_SUBJECT",
|
|
170
|
+
message: "explain requires subject",
|
|
171
|
+
requiredUserInput: ["subject"],
|
|
172
|
+
nextStep: "reinvoke_explain_with_subject",
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
let subject;
|
|
14
177
|
try {
|
|
15
|
-
|
|
16
|
-
if (mod?.createCommandRouter) {
|
|
17
|
-
return mod.createCommandRouter();
|
|
18
|
-
}
|
|
178
|
+
subject = parseExplainSubject(subjectRaw);
|
|
19
179
|
}
|
|
20
|
-
catch {
|
|
21
|
-
|
|
180
|
+
catch (error) {
|
|
181
|
+
const code = error.message;
|
|
182
|
+
if (code === "explain_subject_requires_id") {
|
|
183
|
+
return createUnavailableActionError("EXPLAIN_SUBJECT_REQUIRES_ID", "subject must include identifier", ["subject"], "reinvoke_explain_with_supported_subject");
|
|
184
|
+
}
|
|
185
|
+
if (code === "explain_subject_unsupported") {
|
|
186
|
+
return createUnavailableActionError("EXPLAIN_SUBJECT_UNSUPPORTED", "supported subjects are decision:<id>, platform:<id>, outreach:<id>, soul:<id>", ["subject"], "reinvoke_explain_with_supported_subject");
|
|
187
|
+
}
|
|
188
|
+
return createUnavailableActionError("EXPLAIN_SUBJECT_INVALID", "invalid explain subject", ["subject"], "reinvoke_explain_with_supported_subject");
|
|
22
189
|
}
|
|
23
|
-
const
|
|
190
|
+
const runtimeEvidence = latestRuntimeEvidence(spine);
|
|
191
|
+
return {
|
|
192
|
+
ok: true,
|
|
193
|
+
data: {
|
|
194
|
+
subjectType: subject.subjectType,
|
|
195
|
+
conclusion: "Plugin surface is loaded in host-safe mode with a minimal activation spine.",
|
|
196
|
+
keyFactors: [
|
|
197
|
+
"synchronous_register",
|
|
198
|
+
`subject:${subject.subjectId}`,
|
|
199
|
+
runtimeEvidence?.capability ?? "runtime.activate",
|
|
200
|
+
],
|
|
201
|
+
evidenceRefs: [
|
|
202
|
+
runtimeEvidence?.traceId ?? `${INTERNAL_RUNTIME_TRACE_PREFIX}none`,
|
|
203
|
+
`subject:${subjectRaw.trim()}`,
|
|
204
|
+
"host_safe_mode",
|
|
205
|
+
],
|
|
206
|
+
nextStep: "use full workspace runtime for evidence-backed explain details",
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function buildHeartbeatCheckPayload(spine, input) {
|
|
211
|
+
const runtimeEvidence = latestRuntimeEvidence(spine);
|
|
212
|
+
const updatedAt = runtimeEvidence?.createdAt ?? new Date(spine.lifecycleState.lastChangedAt).toISOString();
|
|
213
|
+
const timestamp = typeof input?.timestamp === "string" && input.timestamp.trim().length > 0 ? input.timestamp : updatedAt;
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
status: "heartbeat_ok",
|
|
217
|
+
heartbeat: "HEARTBEAT_OK",
|
|
218
|
+
scope: "rhythm",
|
|
219
|
+
trigger: "heartbeat_bridge",
|
|
220
|
+
reasons: ["host_safe_bridge_ready"],
|
|
221
|
+
nextAction: "continue",
|
|
222
|
+
message: "Host-safe heartbeat bridge acknowledged the round. No additional action is required from this surface.",
|
|
223
|
+
data: {
|
|
224
|
+
runtime: {
|
|
225
|
+
host: "openclaw-plugin",
|
|
226
|
+
serviceStatus: spine.runtimeHandle.ready ? "running" : "idle",
|
|
227
|
+
updatedAt,
|
|
228
|
+
},
|
|
229
|
+
surface: {
|
|
230
|
+
tool: "second_nature_ops",
|
|
231
|
+
command: "second-nature heartbeat_check",
|
|
232
|
+
},
|
|
233
|
+
bridge: {
|
|
234
|
+
timestamp,
|
|
235
|
+
sessionContextProvided: typeof input?.sessionContext === "string" && input.sessionContext.trim().length > 0,
|
|
236
|
+
heartbeatChecklistProvided: typeof input?.heartbeatChecklist === "string" && input.heartbeatChecklist.trim().length > 0,
|
|
237
|
+
serviceEntryMode: "runtime_carrier_only",
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function createHostSafeRouter(spine) {
|
|
243
|
+
const notImplemented = async (command) => ({
|
|
244
|
+
ok: false,
|
|
245
|
+
command,
|
|
246
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
247
|
+
});
|
|
248
|
+
const commands = [
|
|
249
|
+
{
|
|
250
|
+
name: "status",
|
|
251
|
+
description: "Show aggregated Second Nature status",
|
|
252
|
+
execute: async () => buildStatusPayload(spine),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "policy",
|
|
256
|
+
description: "Write or inspect policy state",
|
|
257
|
+
execute: async (input) => {
|
|
258
|
+
const action = typeof input?.action === "string" ? input.action : "show";
|
|
259
|
+
if (action === "set") {
|
|
260
|
+
return createUnavailableActionError("HOST_SAFE_POLICY_SET_UNAVAILABLE", "policy set is unavailable in the host-safe plugin package", ["social_daily_limit", "quiet_enabled"], "run_workspace_runtime_or_reinstall_full_build");
|
|
261
|
+
}
|
|
262
|
+
return notImplemented("policy");
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "credential",
|
|
267
|
+
description: "Inspect or recover credential state",
|
|
268
|
+
execute: async (input) => {
|
|
269
|
+
const action = typeof input?.action === "string" ? input.action : "show";
|
|
270
|
+
if (action === "verify") {
|
|
271
|
+
return createUnavailableActionError("HOST_SAFE_CREDENTIAL_VERIFY_UNAVAILABLE", "credential verify is unavailable in the host-safe plugin package", ["verification_answer"], "run_workspace_runtime_or_reinstall_full_build");
|
|
272
|
+
}
|
|
273
|
+
const platformId = typeof input?.platformId === "string" ? input.platformId : undefined;
|
|
274
|
+
return buildCredentialPayload(platformId);
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "quiet",
|
|
279
|
+
description: "Inspect Quiet lifecycle state",
|
|
280
|
+
execute: async (input) => {
|
|
281
|
+
const scope = typeof input?.scope === "string" ? input.scope : undefined;
|
|
282
|
+
return buildQuietPayload(scope);
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "report",
|
|
287
|
+
description: "Show daily report artifacts",
|
|
288
|
+
execute: async (input) => {
|
|
289
|
+
const day = typeof input?.day === "string" ? input.day : undefined;
|
|
290
|
+
return buildReportPayload(day);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "session",
|
|
295
|
+
description: "Inspect continuity session details",
|
|
296
|
+
execute: async (input) => {
|
|
297
|
+
const sessionId = typeof input?.sessionId === "string" ? input.sessionId : undefined;
|
|
298
|
+
return buildSessionPayload(sessionId);
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "audit",
|
|
303
|
+
description: "Inspect audit and evidence views",
|
|
304
|
+
execute: async () => notImplemented("audit"),
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "explain",
|
|
308
|
+
description: "Answer why-question explain requests",
|
|
309
|
+
execute: async (input) => {
|
|
310
|
+
const subject = typeof input?.subject === "string" ? input.subject : undefined;
|
|
311
|
+
return buildExplainPayload(spine, subject);
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "heartbeat_check",
|
|
316
|
+
description: "Acknowledge the shipping heartbeat bridge round",
|
|
317
|
+
execute: async (input) => buildHeartbeatCheckPayload(spine, input),
|
|
318
|
+
},
|
|
319
|
+
];
|
|
24
320
|
return {
|
|
25
321
|
commands,
|
|
26
322
|
resolve(name) {
|
|
@@ -28,51 +324,155 @@ async function resolveCommandRouterSafe() {
|
|
|
28
324
|
},
|
|
29
325
|
};
|
|
30
326
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
327
|
+
function createActivationSpine() {
|
|
328
|
+
const spine = {
|
|
329
|
+
router: undefined,
|
|
330
|
+
runtimeHandle: startRuntimeService({ workspaceRoot: process.cwd() }),
|
|
331
|
+
lifecycleState: getLifecycleState(),
|
|
332
|
+
serviceStartRecorded: false,
|
|
333
|
+
runtimeEvidence: [],
|
|
334
|
+
};
|
|
335
|
+
spine.router = createHostSafeRouter(spine);
|
|
336
|
+
return spine;
|
|
337
|
+
}
|
|
338
|
+
function ensureActivationSpine() {
|
|
339
|
+
if (activationSpine) {
|
|
340
|
+
return activationSpine;
|
|
341
|
+
}
|
|
342
|
+
activationSpine = createActivationSpine();
|
|
343
|
+
return activationSpine;
|
|
344
|
+
}
|
|
345
|
+
function recordRuntimeEvidence(spine, origin) {
|
|
346
|
+
if (origin === "service_start" && spine.serviceStartRecorded) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (origin === "service_start") {
|
|
350
|
+
spine.serviceStartRecorded = true;
|
|
351
|
+
}
|
|
352
|
+
spine.runtimeEvidence.push({
|
|
353
|
+
traceId: `${INTERNAL_RUNTIME_TRACE_PREFIX}${origin}-${spine.lifecycleState.registerCount}-${Date.now()}`,
|
|
354
|
+
capability: origin === "register"
|
|
355
|
+
? spine.lifecycleState.registerCount === 1
|
|
356
|
+
? "runtime.activate"
|
|
357
|
+
: "runtime.reload"
|
|
358
|
+
: "runtime.heartbeat",
|
|
359
|
+
origin,
|
|
360
|
+
createdAt: new Date().toISOString(),
|
|
361
|
+
status: "succeeded",
|
|
362
|
+
});
|
|
363
|
+
trimRuntimeEvidence(spine);
|
|
364
|
+
}
|
|
365
|
+
function refreshRegistrationState() {
|
|
366
|
+
const spine = ensureActivationSpine();
|
|
367
|
+
spine.runtimeHandle = startRuntimeService({ workspaceRoot: process.cwd() });
|
|
368
|
+
spine.lifecycleState = recordRegistration();
|
|
369
|
+
spine.serviceStartRecorded = false;
|
|
370
|
+
recordRuntimeEvidence(spine, "register");
|
|
371
|
+
return spine;
|
|
372
|
+
}
|
|
373
|
+
function parseCommandInput(rawArgs) {
|
|
374
|
+
const tokens = rawArgs?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
375
|
+
if (tokens.length === 0) {
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
result: { ok: false, message: "Missing command argument." },
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const [command, ...rest] = tokens;
|
|
382
|
+
if (command === "policy" && rest[0] === "set") {
|
|
383
|
+
return {
|
|
384
|
+
ok: false,
|
|
385
|
+
result: {
|
|
386
|
+
ok: false,
|
|
387
|
+
command,
|
|
388
|
+
message: "policy set requires structured args; use second_nature_ops instead.",
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (command === "credential" && rest[0] === "verify") {
|
|
393
|
+
return {
|
|
394
|
+
ok: false,
|
|
395
|
+
result: {
|
|
396
|
+
ok: false,
|
|
397
|
+
command,
|
|
398
|
+
message: "credential verify requires structured args; use second_nature_ops instead.",
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
switch (command) {
|
|
403
|
+
case "status":
|
|
404
|
+
case "quiet":
|
|
36
405
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
406
|
+
ok: true,
|
|
407
|
+
command,
|
|
408
|
+
input: rest.length > 0 ? { scope: rest.join(" ") } : undefined,
|
|
409
|
+
};
|
|
410
|
+
case "report":
|
|
411
|
+
return {
|
|
412
|
+
ok: true,
|
|
413
|
+
command,
|
|
414
|
+
input: rest[0] ? { day: rest[0] } : undefined,
|
|
415
|
+
};
|
|
416
|
+
case "session":
|
|
417
|
+
return {
|
|
418
|
+
ok: true,
|
|
419
|
+
command,
|
|
420
|
+
input: rest[0] ? { sessionId: rest[0] } : undefined,
|
|
421
|
+
};
|
|
422
|
+
case "credential":
|
|
423
|
+
return {
|
|
424
|
+
ok: true,
|
|
425
|
+
command,
|
|
426
|
+
input: rest[0] ? { platformId: rest[0] } : undefined,
|
|
427
|
+
};
|
|
428
|
+
case "heartbeat_check":
|
|
429
|
+
return {
|
|
430
|
+
ok: true,
|
|
431
|
+
command,
|
|
432
|
+
input: rest.length > 0
|
|
433
|
+
? {
|
|
434
|
+
timestamp: rest[0],
|
|
435
|
+
sessionContext: rest.length > 1 ? rest.slice(1).join(" ") : undefined,
|
|
436
|
+
}
|
|
437
|
+
: undefined,
|
|
438
|
+
};
|
|
439
|
+
case "explain":
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
command,
|
|
443
|
+
input: rest.length > 0 ? { subject: rest.join(" ") } : undefined,
|
|
444
|
+
};
|
|
445
|
+
default:
|
|
446
|
+
return {
|
|
447
|
+
ok: true,
|
|
448
|
+
command,
|
|
449
|
+
input: undefined,
|
|
41
450
|
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// fall through to minimal service shell
|
|
46
451
|
}
|
|
452
|
+
}
|
|
453
|
+
function createRuntimeService() {
|
|
47
454
|
return {
|
|
48
455
|
id: "second-nature-runtime",
|
|
49
456
|
start() {
|
|
50
|
-
|
|
457
|
+
const spine = ensureActivationSpine();
|
|
458
|
+
recordRuntimeEvidence(spine, "service_start");
|
|
459
|
+
return {
|
|
460
|
+
ready: spine.runtimeHandle.ready,
|
|
461
|
+
version: spine.runtimeHandle.version,
|
|
462
|
+
};
|
|
51
463
|
},
|
|
52
464
|
};
|
|
53
465
|
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const lifecycleMod = await import("./runtime/core/second-nature/runtime/lifecycle-service.js");
|
|
57
|
-
if (lifecycleMod?.recordRegistration) {
|
|
58
|
-
return {
|
|
59
|
-
id: "second-nature-lifecycle",
|
|
60
|
-
start() {
|
|
61
|
-
const state = lifecycleMod.recordRegistration();
|
|
62
|
-
return { phase: state.phase, registerCount: state.registerCount };
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
// fall through to minimal lifecycle shell
|
|
69
|
-
}
|
|
70
|
-
let registerCount = 0;
|
|
466
|
+
function createLifecycleService() {
|
|
71
467
|
return {
|
|
72
468
|
id: "second-nature-lifecycle",
|
|
73
469
|
start() {
|
|
74
|
-
|
|
75
|
-
return {
|
|
470
|
+
const spine = ensureActivationSpine();
|
|
471
|
+
return {
|
|
472
|
+
phase: spine.lifecycleState.phase,
|
|
473
|
+
registerCount: spine.lifecycleState.registerCount,
|
|
474
|
+
lastChangedAt: spine.lifecycleState.lastChangedAt,
|
|
475
|
+
};
|
|
76
476
|
},
|
|
77
477
|
};
|
|
78
478
|
}
|
|
@@ -80,33 +480,30 @@ export default {
|
|
|
80
480
|
id: "second-nature",
|
|
81
481
|
name: "Second Nature",
|
|
82
482
|
description: "Registers command/tool/service surface with load-reload lifecycle semantics.",
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const lifecycleService = await createLifecycleService();
|
|
483
|
+
register(api) {
|
|
484
|
+
const runtimeService = createRuntimeService();
|
|
485
|
+
const lifecycleService = createLifecycleService();
|
|
87
486
|
api.registerService(runtimeService);
|
|
88
487
|
api.registerService(lifecycleService);
|
|
89
|
-
api.registerCli(({ program }) => {
|
|
90
|
-
void program;
|
|
91
|
-
}, { commands: ["second-nature"] });
|
|
92
488
|
api.registerCommand({
|
|
93
489
|
name: "second-nature",
|
|
94
490
|
description: "Route Agent-facing operational commands for Second Nature.",
|
|
95
491
|
acceptsArgs: true,
|
|
96
492
|
handler: async (ctx) => {
|
|
97
|
-
const
|
|
98
|
-
|
|
493
|
+
const spine = ensureActivationSpine();
|
|
494
|
+
const parsed = parseCommandInput(ctx.args);
|
|
495
|
+
if (!parsed.ok) {
|
|
99
496
|
return {
|
|
100
|
-
text: JSON.stringify(
|
|
497
|
+
text: JSON.stringify(parsed.result),
|
|
101
498
|
};
|
|
102
499
|
}
|
|
103
|
-
const resolved = router.resolve(command);
|
|
500
|
+
const resolved = spine.router.resolve(parsed.command);
|
|
104
501
|
if (!resolved) {
|
|
105
502
|
return {
|
|
106
|
-
text: JSON.stringify({ ok: false, command, message: "Unknown Second Nature command." }),
|
|
503
|
+
text: JSON.stringify({ ok: false, command: parsed.command, message: "Unknown Second Nature command." }),
|
|
107
504
|
};
|
|
108
505
|
}
|
|
109
|
-
const result = await resolved.execute();
|
|
506
|
+
const result = await resolved.execute(parsed.input);
|
|
110
507
|
return {
|
|
111
508
|
text: JSON.stringify(result),
|
|
112
509
|
};
|
|
@@ -120,12 +517,13 @@ export default {
|
|
|
120
517
|
additionalProperties: false,
|
|
121
518
|
properties: {
|
|
122
519
|
command: { type: "string" },
|
|
123
|
-
args: { type: "object", additionalProperties: true }
|
|
520
|
+
args: { type: "object", additionalProperties: true },
|
|
124
521
|
},
|
|
125
|
-
required: ["command"]
|
|
522
|
+
required: ["command"],
|
|
126
523
|
},
|
|
127
524
|
async execute(_id, params) {
|
|
128
|
-
const
|
|
525
|
+
const spine = ensureActivationSpine();
|
|
526
|
+
const resolved = spine.router.resolve(params.command);
|
|
129
527
|
if (!resolved) {
|
|
130
528
|
return {
|
|
131
529
|
content: [
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.8",
|
|
5
5
|
"entry": "./index.js",
|
|
6
|
-
"description": "OpenClaw native plugin package
|
|
6
|
+
"description": "OpenClaw native plugin package with synchronous surface registration and a bundled runtime spine.",
|
|
7
7
|
"capabilities": {
|
|
8
|
-
"commands": [
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
"commands": [
|
|
9
|
+
"second-nature"
|
|
10
|
+
],
|
|
11
|
+
"tools": [
|
|
12
|
+
"second_nature_ops"
|
|
13
|
+
],
|
|
14
|
+
"services": [
|
|
15
|
+
"second-nature-runtime",
|
|
16
|
+
"second-nature-lifecycle"
|
|
17
|
+
]
|
|
11
18
|
},
|
|
12
19
|
"configSchema": {
|
|
13
20
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/second-nature",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "OpenClaw native plugin
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "OpenClaw native plugin with synchronous registration, a packaged runtime artifact, and operator-facing status/explain flows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
7
7
|
"plugin",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { desc } from "drizzle-orm";
|
|
1
2
|
import { createQuietInputLoader } from "../../storage/services/quiet-input-loader.js";
|
|
2
3
|
import { AssetRepository } from "../../storage/repositories/asset-repository.js";
|
|
3
4
|
import { CredentialRepository } from "../../storage/repositories/credential-repository.js";
|
|
4
5
|
import { EvidenceQueryEngine } from "../../observability/query/evidence-query-engine.js";
|
|
5
|
-
import { executionAttempts } from "../../observability/db/schema/index.js";
|
|
6
|
-
|
|
6
|
+
import { decisionLedger, executionAttempts } from "../../observability/db/schema/index.js";
|
|
7
|
+
const INTERNAL_RUNTIME_PLATFORM_ID = "second-nature-runtime";
|
|
8
|
+
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
7
9
|
function buildCredentialNextStep(status) {
|
|
8
10
|
if (status === "pending_verification")
|
|
9
11
|
return "submit_verification_answer";
|
|
@@ -11,6 +13,24 @@ function buildCredentialNextStep(status) {
|
|
|
11
13
|
return "refresh_credential_context";
|
|
12
14
|
return undefined;
|
|
13
15
|
}
|
|
16
|
+
function mapRuntimeStatus(attempt) {
|
|
17
|
+
if (!attempt) {
|
|
18
|
+
return "unknown";
|
|
19
|
+
}
|
|
20
|
+
if (attempt.failureClass || attempt.status === "failed") {
|
|
21
|
+
return "degraded";
|
|
22
|
+
}
|
|
23
|
+
return "running";
|
|
24
|
+
}
|
|
25
|
+
function mapConnectorStatus(attempt) {
|
|
26
|
+
if (!attempt) {
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
if (attempt.failureClass || attempt.status === "failed") {
|
|
30
|
+
return "degraded";
|
|
31
|
+
}
|
|
32
|
+
return "healthy";
|
|
33
|
+
}
|
|
14
34
|
export function createCliReadModels(deps) {
|
|
15
35
|
const assetRepository = new AssetRepository(deps.stateDb);
|
|
16
36
|
const credentialRepository = new CredentialRepository(deps.stateDb);
|
|
@@ -18,15 +38,28 @@ export function createCliReadModels(deps) {
|
|
|
18
38
|
const evidenceQuery = new EvidenceQueryEngine(deps.observabilityDb);
|
|
19
39
|
return {
|
|
20
40
|
async loadStatus(_scope) {
|
|
21
|
-
let
|
|
41
|
+
let recentAttempts = [];
|
|
42
|
+
let recentDecisions = [];
|
|
22
43
|
let credentials = [];
|
|
23
44
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
recentAttempts = await deps.observabilityDb.db
|
|
46
|
+
.select()
|
|
47
|
+
.from(executionAttempts)
|
|
48
|
+
.orderBy(desc(executionAttempts.startedAt), desc(executionAttempts.finishedAt))
|
|
49
|
+
.limit(50);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
recentAttempts = [];
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
recentDecisions = await deps.observabilityDb.db
|
|
56
|
+
.select()
|
|
57
|
+
.from(decisionLedger)
|
|
58
|
+
.orderBy(desc(decisionLedger.createdAt))
|
|
59
|
+
.limit(50);
|
|
27
60
|
}
|
|
28
61
|
catch {
|
|
29
|
-
|
|
62
|
+
recentDecisions = [];
|
|
30
63
|
}
|
|
31
64
|
try {
|
|
32
65
|
credentials = await deps.stateDb.db.query.credentialRecords.findMany();
|
|
@@ -34,28 +67,40 @@ export function createCliReadModels(deps) {
|
|
|
34
67
|
catch {
|
|
35
68
|
credentials = [];
|
|
36
69
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
const latestRuntimeAttempt = recentAttempts.find((attempt) => attempt.platformId === INTERNAL_RUNTIME_PLATFORM_ID);
|
|
71
|
+
const latestConnectorAttempt = recentAttempts.find((attempt) => attempt.platformId !== INTERNAL_RUNTIME_PLATFORM_ID);
|
|
72
|
+
const latestRuntimeDecision = recentDecisions.find((decision) => decision.traceId.startsWith(INTERNAL_RUNTIME_TRACE_PREFIX));
|
|
73
|
+
const runtimeUpdatedAt = latestRuntimeAttempt?.finishedAt ?? latestRuntimeAttempt?.startedAt ?? latestRuntimeDecision?.createdAt ?? "";
|
|
74
|
+
const quietMode = latestRuntimeDecision?.mode === "quiet" ||
|
|
75
|
+
latestRuntimeDecision?.mode === "maintenance_only" ||
|
|
76
|
+
latestRuntimeDecision?.mode === "paused_for_interrupt"
|
|
77
|
+
? latestRuntimeDecision.mode
|
|
78
|
+
: "unknown";
|
|
79
|
+
const riskFlags = [latestRuntimeAttempt?.failureClass, latestConnectorAttempt?.failureClass].filter((value) => Boolean(value));
|
|
80
|
+
const connectorSummary = latestConnectorAttempt
|
|
81
|
+
? [
|
|
82
|
+
{
|
|
83
|
+
platformId: latestConnectorAttempt.platformId,
|
|
84
|
+
status: mapConnectorStatus(latestConnectorAttempt),
|
|
85
|
+
channel: latestConnectorAttempt.channel,
|
|
86
|
+
failureClass: latestConnectorAttempt.failureClass ?? undefined,
|
|
87
|
+
},
|
|
88
|
+
]
|
|
44
89
|
: [];
|
|
45
90
|
return {
|
|
46
91
|
runtime: {
|
|
47
92
|
host: "openclaw-plugin",
|
|
48
|
-
serviceStatus:
|
|
49
|
-
updatedAt:
|
|
93
|
+
serviceStatus: mapRuntimeStatus(latestRuntimeAttempt),
|
|
94
|
+
updatedAt: runtimeUpdatedAt,
|
|
50
95
|
},
|
|
51
96
|
rhythm: {
|
|
52
|
-
mode: "unknown",
|
|
97
|
+
mode: latestRuntimeDecision?.mode ?? "unknown",
|
|
53
98
|
windowId: undefined,
|
|
54
99
|
},
|
|
55
100
|
quiet: {
|
|
56
|
-
mode:
|
|
57
|
-
lastEvent:
|
|
58
|
-
interrupted: undefined,
|
|
101
|
+
mode: quietMode,
|
|
102
|
+
lastEvent: latestRuntimeDecision?.traceId,
|
|
103
|
+
interrupted: latestRuntimeDecision?.mode === "paused_for_interrupt" ? true : undefined,
|
|
59
104
|
},
|
|
60
105
|
connectors: connectorSummary,
|
|
61
106
|
credentials: credentials.map((item) => ({
|
|
@@ -64,8 +109,8 @@ export function createCliReadModels(deps) {
|
|
|
64
109
|
nextStep: buildCredentialNextStep(item.status),
|
|
65
110
|
})),
|
|
66
111
|
risk: {
|
|
67
|
-
level:
|
|
68
|
-
flags:
|
|
112
|
+
level: riskFlags.length > 0 ? "medium" : "low",
|
|
113
|
+
flags: riskFlags,
|
|
69
114
|
},
|
|
70
115
|
};
|
|
71
116
|
},
|
|
@@ -108,40 +108,23 @@ export function createConnectorPolicyLayer(ctx) {
|
|
|
108
108
|
}
|
|
109
109
|
let lastFailure;
|
|
110
110
|
for (let attempt = 1; attempt <= retryPolicy.maxRetries; attempt += 1) {
|
|
111
|
-
const traceId = makeTraceId(request, plan)
|
|
112
|
-
const attemptId = `attempt-${traceId}-${attempt}`;
|
|
111
|
+
const traceId = `${makeTraceId(request, plan)}:${attempt}`;
|
|
113
112
|
if (ctx.telemetry) {
|
|
114
|
-
await ctx.telemetry.
|
|
115
|
-
id: attemptId,
|
|
113
|
+
await ctx.telemetry.startAttempt({
|
|
116
114
|
traceId,
|
|
117
115
|
decisionId: identity.decisionId,
|
|
118
116
|
intentId: identity.intentId,
|
|
119
117
|
platformId: request.platformId,
|
|
120
118
|
capability: request.intent,
|
|
121
119
|
channel: plan.channel,
|
|
122
|
-
status: "started",
|
|
123
120
|
retryPolicy: JSON.stringify(retryPolicy),
|
|
124
121
|
idempotencyKey: request.idempotencyKey,
|
|
125
|
-
startedAt: new Date().toISOString(),
|
|
126
122
|
});
|
|
127
123
|
}
|
|
128
124
|
const raw = await ctx.executionRunner.run(plan, request);
|
|
129
125
|
if (raw.success) {
|
|
130
126
|
if (ctx.telemetry) {
|
|
131
|
-
await ctx.telemetry.
|
|
132
|
-
id: `${attemptId}-done`,
|
|
133
|
-
traceId,
|
|
134
|
-
decisionId: identity.decisionId,
|
|
135
|
-
intentId: identity.intentId,
|
|
136
|
-
platformId: request.platformId,
|
|
137
|
-
capability: request.intent,
|
|
138
|
-
channel: plan.channel,
|
|
139
|
-
status: "succeeded",
|
|
140
|
-
retryPolicy: JSON.stringify(retryPolicy),
|
|
141
|
-
idempotencyKey: request.idempotencyKey,
|
|
142
|
-
startedAt: new Date().toISOString(),
|
|
143
|
-
finishedAt: new Date().toISOString(),
|
|
144
|
-
});
|
|
127
|
+
await ctx.telemetry.completeAttempt(traceId, "succeeded");
|
|
145
128
|
}
|
|
146
129
|
return {
|
|
147
130
|
status: "success",
|
|
@@ -161,21 +144,7 @@ export function createConnectorPolicyLayer(ctx) {
|
|
|
161
144
|
channel: raw.channel,
|
|
162
145
|
};
|
|
163
146
|
if (ctx.telemetry) {
|
|
164
|
-
await ctx.telemetry.
|
|
165
|
-
id: `${attemptId}-failed`,
|
|
166
|
-
traceId,
|
|
167
|
-
decisionId: identity.decisionId,
|
|
168
|
-
intentId: identity.intentId,
|
|
169
|
-
platformId: request.platformId,
|
|
170
|
-
capability: request.intent,
|
|
171
|
-
channel: plan.channel,
|
|
172
|
-
status: "failed",
|
|
173
|
-
failureClass: classified.class,
|
|
174
|
-
retryPolicy: JSON.stringify(retryPolicy),
|
|
175
|
-
idempotencyKey: request.idempotencyKey,
|
|
176
|
-
startedAt: new Date().toISOString(),
|
|
177
|
-
finishedAt: new Date().toISOString(),
|
|
178
|
-
});
|
|
147
|
+
await ctx.telemetry.completeAttempt(traceId, "failed", undefined, classified.class);
|
|
179
148
|
}
|
|
180
149
|
if (ctx.cooldownPort) {
|
|
181
150
|
await ctx.cooldownPort.markFailure(request.platformId, intent, classified.class, classified.retryAfterMs);
|
|
@@ -6,6 +6,73 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import * as schema from "./schema/index.js";
|
|
7
7
|
// Pre-initialize sql.js WASM at module load time
|
|
8
8
|
const SQL = await initSqlJs();
|
|
9
|
+
const OBSERVABILITY_SCHEMA_SQL = `
|
|
10
|
+
CREATE TABLE IF NOT EXISTS decision_ledger (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
tick_id TEXT NOT NULL,
|
|
13
|
+
trace_id TEXT NOT NULL,
|
|
14
|
+
intent_id TEXT,
|
|
15
|
+
platform_id TEXT,
|
|
16
|
+
verdict TEXT NOT NULL,
|
|
17
|
+
mode TEXT NOT NULL,
|
|
18
|
+
reasons TEXT NOT NULL,
|
|
19
|
+
reason_codes TEXT NOT NULL,
|
|
20
|
+
decision_basis TEXT NOT NULL,
|
|
21
|
+
evidence_refs TEXT NOT NULL,
|
|
22
|
+
model_eval_ref TEXT,
|
|
23
|
+
created_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS decision_trace_idx ON decision_ledger(trace_id);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS decision_tick_idx ON decision_ledger(tick_id);
|
|
27
|
+
CREATE TABLE IF NOT EXISTS execution_attempts (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
trace_id TEXT NOT NULL,
|
|
30
|
+
decision_id TEXT NOT NULL,
|
|
31
|
+
intent_id TEXT NOT NULL,
|
|
32
|
+
platform_id TEXT NOT NULL,
|
|
33
|
+
capability TEXT NOT NULL,
|
|
34
|
+
channel TEXT NOT NULL,
|
|
35
|
+
status TEXT NOT NULL,
|
|
36
|
+
commit_state TEXT,
|
|
37
|
+
failure_class TEXT,
|
|
38
|
+
retry_policy TEXT,
|
|
39
|
+
idempotency_key TEXT,
|
|
40
|
+
started_at TEXT,
|
|
41
|
+
finished_at TEXT
|
|
42
|
+
);
|
|
43
|
+
CREATE UNIQUE INDEX IF NOT EXISTS attempt_trace_idx ON execution_attempts(trace_id);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS attempt_decision_idx ON execution_attempts(decision_id);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS attempt_platform_idx ON execution_attempts(platform_id);
|
|
46
|
+
CREATE TABLE IF NOT EXISTS governance_audit (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
event_type TEXT NOT NULL,
|
|
49
|
+
proposal_id TEXT,
|
|
50
|
+
target_asset_id TEXT,
|
|
51
|
+
asset_path TEXT,
|
|
52
|
+
status_from TEXT,
|
|
53
|
+
status_to TEXT NOT NULL,
|
|
54
|
+
before_hash TEXT,
|
|
55
|
+
after_hash TEXT,
|
|
56
|
+
supporting_sources TEXT,
|
|
57
|
+
reason TEXT,
|
|
58
|
+
verification_deadline TEXT,
|
|
59
|
+
attempts_remaining INTEGER,
|
|
60
|
+
created_at TEXT NOT NULL
|
|
61
|
+
);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS audit_proposal_idx ON governance_audit(proposal_id);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS audit_asset_idx ON governance_audit(target_asset_id);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS audit_event_idx ON governance_audit(event_type);
|
|
65
|
+
CREATE TABLE IF NOT EXISTS redaction_manifest (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
event_id TEXT NOT NULL,
|
|
68
|
+
event_type TEXT NOT NULL,
|
|
69
|
+
field_name TEXT NOT NULL,
|
|
70
|
+
action TEXT NOT NULL,
|
|
71
|
+
original_value_hash TEXT,
|
|
72
|
+
created_at TEXT NOT NULL
|
|
73
|
+
);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS redact_event_idx ON redaction_manifest(event_id);
|
|
75
|
+
`;
|
|
9
76
|
function resolveDbPath(filename) {
|
|
10
77
|
if (path.isAbsolute(filename) || filename === ":memory:") {
|
|
11
78
|
return filename;
|
|
@@ -18,6 +85,9 @@ function resolveDbPath(filename) {
|
|
|
18
85
|
}
|
|
19
86
|
return path.join(dataDir, filename);
|
|
20
87
|
}
|
|
88
|
+
function bootstrapObservabilitySchema(sqlite) {
|
|
89
|
+
sqlite.exec(OBSERVABILITY_SCHEMA_SQL);
|
|
90
|
+
}
|
|
21
91
|
export function createObservabilityDatabase(filename = "observability.db") {
|
|
22
92
|
const dbPath = resolveDbPath(filename);
|
|
23
93
|
const isMemory = filename === ":memory:";
|
|
@@ -26,6 +96,7 @@ export function createObservabilityDatabase(filename = "observability.db") {
|
|
|
26
96
|
dbBuffer = fs.readFileSync(dbPath);
|
|
27
97
|
}
|
|
28
98
|
const sqlite = new SQL.Database(dbBuffer);
|
|
99
|
+
bootstrapObservabilitySchema(sqlite);
|
|
29
100
|
const db = drizzle(sqlite, { schema });
|
|
30
101
|
return {
|
|
31
102
|
sqlite,
|
|
@@ -6,6 +6,65 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import * as schema from "./schema/index.js";
|
|
7
7
|
// Pre-initialize sql.js WASM at module load time
|
|
8
8
|
const SQL = await initSqlJs();
|
|
9
|
+
const STATE_SCHEMA_SQL = `
|
|
10
|
+
CREATE TABLE IF NOT EXISTS credential_records (
|
|
11
|
+
platform_id TEXT PRIMARY KEY,
|
|
12
|
+
credential_type TEXT NOT NULL,
|
|
13
|
+
encrypted_value TEXT NOT NULL,
|
|
14
|
+
status TEXT NOT NULL,
|
|
15
|
+
verification_code TEXT,
|
|
16
|
+
challenge_text TEXT,
|
|
17
|
+
expires_at TEXT,
|
|
18
|
+
attempts_remaining INTEGER,
|
|
19
|
+
updated_at TEXT NOT NULL
|
|
20
|
+
);
|
|
21
|
+
CREATE TABLE IF NOT EXISTS policy_records (
|
|
22
|
+
platform_id TEXT PRIMARY KEY,
|
|
23
|
+
social_daily_limit INTEGER NOT NULL,
|
|
24
|
+
quiet_enabled INTEGER NOT NULL,
|
|
25
|
+
updated_at TEXT NOT NULL
|
|
26
|
+
);
|
|
27
|
+
CREATE TABLE IF NOT EXISTS asset_registry (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
kind TEXT NOT NULL,
|
|
30
|
+
path TEXT NOT NULL,
|
|
31
|
+
hash TEXT NOT NULL,
|
|
32
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
33
|
+
layer TEXT NOT NULL,
|
|
34
|
+
last_indexed_at TEXT NOT NULL
|
|
35
|
+
);
|
|
36
|
+
CREATE UNIQUE INDEX IF NOT EXISTS asset_registry_path_idx ON asset_registry(path);
|
|
37
|
+
CREATE TABLE IF NOT EXISTS intent_commit_records (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
intent_id TEXT NOT NULL,
|
|
40
|
+
decision_id TEXT NOT NULL,
|
|
41
|
+
checkpoint_id TEXT,
|
|
42
|
+
state TEXT NOT NULL,
|
|
43
|
+
outcome_ref TEXT,
|
|
44
|
+
metadata_json TEXT,
|
|
45
|
+
updated_at TEXT NOT NULL
|
|
46
|
+
);
|
|
47
|
+
CREATE TABLE IF NOT EXISTS proposal_records (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
target_asset_id TEXT NOT NULL,
|
|
50
|
+
before_hash TEXT,
|
|
51
|
+
after_hash TEXT,
|
|
52
|
+
status TEXT NOT NULL,
|
|
53
|
+
proposed_diff TEXT NOT NULL,
|
|
54
|
+
reason TEXT NOT NULL,
|
|
55
|
+
supporting_sources TEXT NOT NULL,
|
|
56
|
+
confidence REAL NOT NULL,
|
|
57
|
+
created_at TEXT NOT NULL,
|
|
58
|
+
applied_at TEXT
|
|
59
|
+
);
|
|
60
|
+
CREATE TABLE IF NOT EXISTS provenance_edges (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
from_id TEXT NOT NULL,
|
|
63
|
+
to_id TEXT NOT NULL,
|
|
64
|
+
kind TEXT NOT NULL,
|
|
65
|
+
created_at TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
`;
|
|
9
68
|
function resolveDbPath(filename) {
|
|
10
69
|
if (path.isAbsolute(filename) || filename === ":memory:") {
|
|
11
70
|
return filename;
|
|
@@ -18,6 +77,9 @@ function resolveDbPath(filename) {
|
|
|
18
77
|
}
|
|
19
78
|
return path.join(dataDir, filename);
|
|
20
79
|
}
|
|
80
|
+
function bootstrapStateSchema(sqlite) {
|
|
81
|
+
sqlite.exec(STATE_SCHEMA_SQL);
|
|
82
|
+
}
|
|
21
83
|
export function createStateDatabase(filename = "state.db") {
|
|
22
84
|
const dbPath = resolveDbPath(filename);
|
|
23
85
|
const isMemory = filename === ":memory:";
|
|
@@ -26,6 +88,7 @@ export function createStateDatabase(filename = "state.db") {
|
|
|
26
88
|
dbBuffer = fs.readFileSync(dbPath);
|
|
27
89
|
}
|
|
28
90
|
const sqlite = new SQL.Database(dbBuffer);
|
|
91
|
+
bootstrapStateSchema(sqlite);
|
|
29
92
|
const db = drizzle(sqlite, { schema });
|
|
30
93
|
return {
|
|
31
94
|
sqlite,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { StateAPI } from "../storage/state-api.js";
|
|
2
|
-
export interface PolicyWriteInput {
|
|
3
|
-
platformId: string;
|
|
4
|
-
socialDailyLimit: number;
|
|
5
|
-
quietEnabled: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface ActionBridge {
|
|
8
|
-
savePolicy(input: PolicyWriteInput): Promise<void>;
|
|
9
|
-
verifyCredential(platformId: string, answer: string): Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
export declare function createActionBridge(stateApi: StateAPI): ActionBridge;
|
package/runtime/cli/index.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type ObservabilityDatabase } from "../observability/db/index.js";
|
|
2
|
-
import { type StateDatabase, type StateAPI } from "../storage/index.js";
|
|
3
|
-
import { type ActionBridge } from "./action-bridge.js";
|
|
4
|
-
import { type CliCommandDefinition } from "./commands/index.js";
|
|
5
|
-
import { type CliReadModels } from "./read-models/index.js";
|
|
6
|
-
export interface CommandRouter {
|
|
7
|
-
commands: CliCommandDefinition[];
|
|
8
|
-
resolve(name: string): CliCommandDefinition | undefined;
|
|
9
|
-
}
|
|
10
|
-
export interface CommandRouterDeps {
|
|
11
|
-
commands: CliCommandDefinition[];
|
|
12
|
-
}
|
|
13
|
-
export interface CliRuntimeDeps {
|
|
14
|
-
stateDb: StateDatabase;
|
|
15
|
-
observabilityDb: ObservabilityDatabase;
|
|
16
|
-
stateApi: StateAPI;
|
|
17
|
-
readModels: CliReadModels;
|
|
18
|
-
actionBridge: ActionBridge;
|
|
19
|
-
}
|
|
20
|
-
export interface CreateCommandRouterOptions {
|
|
21
|
-
deps?: Partial<CliRuntimeDeps>;
|
|
22
|
-
}
|
|
23
|
-
export declare function createCliRuntimeDeps(overrides?: Partial<CliRuntimeDeps>): CliRuntimeDeps;
|
|
24
|
-
export declare function createCommandRouter(options?: CreateCommandRouterOptions): CommandRouter;
|
|
25
|
-
export declare function closeCliRuntimeDeps(deps: Pick<CliRuntimeDeps, "stateDb" | "observabilityDb">): void;
|