@haaaiawd/second-nature 0.1.6 → 0.1.7
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 +195 -58
- package/openclaw.plugin.json +12 -5
- package/package.json +2 -2
- package/runtime/cli/read-models/index.js +67 -22
- package/runtime/observability/db/index.js +71 -0
- 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,3 +1,13 @@
|
|
|
1
|
+
// @ts-ignore packaged runtime declarations are generated under plugin/runtime
|
|
2
|
+
import { createCliRuntimeDeps, createCommandRouter } from "./runtime/cli/index.js";
|
|
3
|
+
import { DecisionLedger } from "./runtime/observability/services/decision-ledger.js";
|
|
4
|
+
import { ExecutionTelemetry } from "./runtime/observability/services/execution-telemetry.js";
|
|
5
|
+
import { startRuntimeService, } from "./runtime/core/second-nature/runtime/service-entry.js";
|
|
6
|
+
import { getLifecycleState, recordRegistration, } from "./runtime/core/second-nature/runtime/lifecycle-service.js";
|
|
7
|
+
const INTERNAL_RUNTIME_PLATFORM_ID = "second-nature-runtime";
|
|
8
|
+
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
9
|
+
const INTERNAL_RUNTIME_CHANNEL = "plugin_host";
|
|
10
|
+
let activationSpine = null;
|
|
1
11
|
function createFallbackCommands() {
|
|
2
12
|
const commandNames = ["status", "policy", "credential", "quiet", "report", "session", "audit", "explain"];
|
|
3
13
|
return commandNames.map((name) => ({
|
|
@@ -10,16 +20,7 @@ function createFallbackCommands() {
|
|
|
10
20
|
}),
|
|
11
21
|
}));
|
|
12
22
|
}
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const mod = await import("./runtime/cli/index.js");
|
|
16
|
-
if (mod?.createCommandRouter) {
|
|
17
|
-
return mod.createCommandRouter();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// fall through to fallback router
|
|
22
|
-
}
|
|
23
|
+
function createFallbackRouter() {
|
|
23
24
|
const commands = createFallbackCommands();
|
|
24
25
|
return {
|
|
25
26
|
commands,
|
|
@@ -28,51 +29,190 @@ async function resolveCommandRouterSafe() {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
function ensureActivationSpine() {
|
|
33
|
+
if (activationSpine) {
|
|
34
|
+
return activationSpine;
|
|
35
|
+
}
|
|
32
36
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const runtimeDeps = createCliRuntimeDeps();
|
|
38
|
+
const router = createCommandRouter({ deps: runtimeDeps });
|
|
39
|
+
activationSpine = {
|
|
40
|
+
runtimeDeps,
|
|
41
|
+
router,
|
|
42
|
+
decisionLedger: new DecisionLedger(runtimeDeps.observabilityDb),
|
|
43
|
+
executionTelemetry: new ExecutionTelemetry(runtimeDeps.observabilityDb),
|
|
44
|
+
runtimeHandle: startRuntimeService({ workspaceRoot: process.cwd() }),
|
|
45
|
+
lifecycleState: getLifecycleState(),
|
|
46
|
+
serviceStartRecorded: false,
|
|
47
|
+
};
|
|
48
|
+
return activationSpine;
|
|
43
49
|
}
|
|
44
50
|
catch {
|
|
45
|
-
|
|
51
|
+
activationSpine = {
|
|
52
|
+
router: createFallbackRouter(),
|
|
53
|
+
runtimeHandle: { ready: true, version: "0.1.7-minimal", close() { } },
|
|
54
|
+
lifecycleState: getLifecycleState(),
|
|
55
|
+
serviceStartRecorded: false,
|
|
56
|
+
};
|
|
57
|
+
return activationSpine;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function refreshRegistrationState() {
|
|
61
|
+
const spine = ensureActivationSpine();
|
|
62
|
+
spine.runtimeHandle = startRuntimeService({ workspaceRoot: process.cwd() });
|
|
63
|
+
spine.lifecycleState = recordRegistration();
|
|
64
|
+
spine.serviceStartRecorded = false;
|
|
65
|
+
recordRuntimeEvidence(spine, "register");
|
|
66
|
+
return spine;
|
|
67
|
+
}
|
|
68
|
+
function recordRuntimeEvidence(spine, origin) {
|
|
69
|
+
const decisionLedger = spine.decisionLedger;
|
|
70
|
+
const executionTelemetry = spine.executionTelemetry;
|
|
71
|
+
if (!decisionLedger || !executionTelemetry) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (origin === "service_start" && spine.serviceStartRecorded) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (origin === "service_start") {
|
|
78
|
+
spine.serviceStartRecorded = true;
|
|
79
|
+
}
|
|
80
|
+
const now = new Date().toISOString();
|
|
81
|
+
const traceId = `${INTERNAL_RUNTIME_TRACE_PREFIX}${origin}-${spine.lifecycleState.registerCount}-${Date.now()}`;
|
|
82
|
+
const decisionId = `decision-${traceId}`;
|
|
83
|
+
const tickId = `tick-${traceId}`;
|
|
84
|
+
const intentId = `intent-${traceId}`;
|
|
85
|
+
const capability = origin === "register"
|
|
86
|
+
? spine.lifecycleState.registerCount === 1
|
|
87
|
+
? "runtime.activate"
|
|
88
|
+
: "runtime.reload"
|
|
89
|
+
: "runtime.heartbeat";
|
|
90
|
+
void (async () => {
|
|
91
|
+
await decisionLedger.recordHeartbeatDecision({
|
|
92
|
+
id: decisionId,
|
|
93
|
+
tickId,
|
|
94
|
+
traceId,
|
|
95
|
+
intentId,
|
|
96
|
+
runtimeScope: "rhythm",
|
|
97
|
+
triggerSource: "heartbeat_bridge",
|
|
98
|
+
decisionStatus: "heartbeat_ok",
|
|
99
|
+
reasons: [
|
|
100
|
+
`origin:${origin}`,
|
|
101
|
+
`phase:${spine.lifecycleState.phase}`,
|
|
102
|
+
`registrations:${spine.lifecycleState.registerCount}`,
|
|
103
|
+
],
|
|
104
|
+
mode: "active",
|
|
105
|
+
createdAt: now,
|
|
106
|
+
});
|
|
107
|
+
await executionTelemetry.startAttempt({
|
|
108
|
+
traceId,
|
|
109
|
+
decisionId,
|
|
110
|
+
intentId,
|
|
111
|
+
platformId: INTERNAL_RUNTIME_PLATFORM_ID,
|
|
112
|
+
capability,
|
|
113
|
+
channel: INTERNAL_RUNTIME_CHANNEL,
|
|
114
|
+
status: "started",
|
|
115
|
+
startedAt: now,
|
|
116
|
+
});
|
|
117
|
+
await executionTelemetry.completeAttempt(traceId, "succeeded", "committed");
|
|
118
|
+
})().catch(() => {
|
|
119
|
+
if (origin === "service_start") {
|
|
120
|
+
spine.serviceStartRecorded = false;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function parseCommandInput(rawArgs) {
|
|
125
|
+
const tokens = rawArgs?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
126
|
+
if (tokens.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
result: { ok: false, message: "Missing command argument." },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const [command, ...rest] = tokens;
|
|
133
|
+
if (command === "policy" && rest[0] === "set") {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
result: {
|
|
137
|
+
ok: false,
|
|
138
|
+
command,
|
|
139
|
+
message: "policy set requires structured args; use second_nature_ops instead.",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (command === "credential" && rest[0] === "verify") {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
result: {
|
|
147
|
+
ok: false,
|
|
148
|
+
command,
|
|
149
|
+
message: "credential verify requires structured args; use second_nature_ops instead.",
|
|
150
|
+
},
|
|
151
|
+
};
|
|
46
152
|
}
|
|
153
|
+
switch (command) {
|
|
154
|
+
case "status":
|
|
155
|
+
case "quiet":
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
command,
|
|
159
|
+
input: rest.length > 0 ? { scope: rest.join(" ") } : undefined,
|
|
160
|
+
};
|
|
161
|
+
case "report":
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
command,
|
|
165
|
+
input: rest[0] ? { day: rest[0] } : undefined,
|
|
166
|
+
};
|
|
167
|
+
case "session":
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
command,
|
|
171
|
+
input: rest[0] ? { sessionId: rest[0] } : undefined,
|
|
172
|
+
};
|
|
173
|
+
case "credential":
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
command,
|
|
177
|
+
input: rest[0] ? { platformId: rest[0] } : undefined,
|
|
178
|
+
};
|
|
179
|
+
case "explain":
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
command,
|
|
183
|
+
input: rest.length > 0 ? { subject: rest.join(" ") } : undefined,
|
|
184
|
+
};
|
|
185
|
+
default:
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
command,
|
|
189
|
+
input: undefined,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function createRuntimeService() {
|
|
47
194
|
return {
|
|
48
195
|
id: "second-nature-runtime",
|
|
49
196
|
start() {
|
|
50
|
-
|
|
197
|
+
const spine = ensureActivationSpine();
|
|
198
|
+
recordRuntimeEvidence(spine, "service_start");
|
|
199
|
+
return {
|
|
200
|
+
ready: spine.runtimeHandle.ready,
|
|
201
|
+
version: spine.runtimeHandle.version,
|
|
202
|
+
};
|
|
51
203
|
},
|
|
52
204
|
};
|
|
53
205
|
}
|
|
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;
|
|
206
|
+
function createLifecycleService() {
|
|
71
207
|
return {
|
|
72
208
|
id: "second-nature-lifecycle",
|
|
73
209
|
start() {
|
|
74
|
-
|
|
75
|
-
return {
|
|
210
|
+
const spine = ensureActivationSpine();
|
|
211
|
+
return {
|
|
212
|
+
phase: spine.lifecycleState.phase,
|
|
213
|
+
registerCount: spine.lifecycleState.registerCount,
|
|
214
|
+
lastChangedAt: spine.lifecycleState.lastChangedAt,
|
|
215
|
+
};
|
|
76
216
|
},
|
|
77
217
|
};
|
|
78
218
|
}
|
|
@@ -80,33 +220,30 @@ export default {
|
|
|
80
220
|
id: "second-nature",
|
|
81
221
|
name: "Second Nature",
|
|
82
222
|
description: "Registers command/tool/service surface with load-reload lifecycle semantics.",
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const runtimeService =
|
|
86
|
-
const lifecycleService =
|
|
223
|
+
register(api) {
|
|
224
|
+
const spine = refreshRegistrationState();
|
|
225
|
+
const runtimeService = createRuntimeService();
|
|
226
|
+
const lifecycleService = createLifecycleService();
|
|
87
227
|
api.registerService(runtimeService);
|
|
88
228
|
api.registerService(lifecycleService);
|
|
89
|
-
api.registerCli(({ program }) => {
|
|
90
|
-
void program;
|
|
91
|
-
}, { commands: ["second-nature"] });
|
|
92
229
|
api.registerCommand({
|
|
93
230
|
name: "second-nature",
|
|
94
231
|
description: "Route Agent-facing operational commands for Second Nature.",
|
|
95
232
|
acceptsArgs: true,
|
|
96
233
|
handler: async (ctx) => {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
234
|
+
const parsed = parseCommandInput(ctx.args);
|
|
235
|
+
if (!parsed.ok) {
|
|
99
236
|
return {
|
|
100
|
-
text: JSON.stringify(
|
|
237
|
+
text: JSON.stringify(parsed.result),
|
|
101
238
|
};
|
|
102
239
|
}
|
|
103
|
-
const resolved = router.resolve(command);
|
|
240
|
+
const resolved = spine.router.resolve(parsed.command);
|
|
104
241
|
if (!resolved) {
|
|
105
242
|
return {
|
|
106
|
-
text: JSON.stringify({ ok: false, command, message: "Unknown Second Nature command." }),
|
|
243
|
+
text: JSON.stringify({ ok: false, command: parsed.command, message: "Unknown Second Nature command." }),
|
|
107
244
|
};
|
|
108
245
|
}
|
|
109
|
-
const result = await resolved.execute();
|
|
246
|
+
const result = await resolved.execute(parsed.input);
|
|
110
247
|
return {
|
|
111
248
|
text: JSON.stringify(result),
|
|
112
249
|
};
|
|
@@ -120,12 +257,12 @@ export default {
|
|
|
120
257
|
additionalProperties: false,
|
|
121
258
|
properties: {
|
|
122
259
|
command: { type: "string" },
|
|
123
|
-
args: { type: "object", additionalProperties: true }
|
|
260
|
+
args: { type: "object", additionalProperties: true },
|
|
124
261
|
},
|
|
125
|
-
required: ["command"]
|
|
262
|
+
required: ["command"],
|
|
126
263
|
},
|
|
127
264
|
async execute(_id, params) {
|
|
128
|
-
const resolved = router.resolve(params.command);
|
|
265
|
+
const resolved = spine.router.resolve(params.command);
|
|
129
266
|
if (!resolved) {
|
|
130
267
|
return {
|
|
131
268
|
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.7",
|
|
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.7",
|
|
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
|
},
|
|
@@ -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;
|