@haaaiawd/second-nature 0.1.9 → 0.1.10
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 +208 -68
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/runtime/cli/ops/heartbeat-surface.js +2 -2
- package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
- package/runtime/observability/services/lived-experience-audit.js +2 -1
- package/runtime/storage/repositories/credential-repository.js +12 -1
- package/runtime/storage/services/credential-vault.js +4 -3
- package/workspace-ops-bridge.js +78 -0
package/index.js
CHANGED
|
@@ -19,12 +19,102 @@
|
|
|
19
19
|
* Test coverage:
|
|
20
20
|
* - tests/integration/cli/plugin-runtime-registration.test.ts
|
|
21
21
|
* - tests/integration/cli/plugin-packaging-walkthrough.test.ts
|
|
22
|
+
* - tests/integration/cli/plugin-workspace-ops-bridge.test.ts (T1.1.4)
|
|
22
23
|
*/
|
|
23
24
|
import { startRuntimeService, } from "./runtime/core/second-nature/runtime/service-entry.js";
|
|
24
25
|
import { getLifecycleState, recordRegistration, } from "./runtime/core/second-nature/runtime/lifecycle-service.js";
|
|
26
|
+
import { openWorkspaceOpsBridge } from "./workspace-ops-bridge.js";
|
|
25
27
|
const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
|
|
26
28
|
const HOST_SAFE_LIMITATION_MESSAGE = "Host-safe plugin package keeps synchronous register/load semantics, but mutating workspace runtime flows remain unavailable here.";
|
|
27
29
|
let activationSpine = null;
|
|
30
|
+
/** T1.1.4 — lazily opened full read bridge; closed when workspace root / resolution changes. */
|
|
31
|
+
let workspaceOpsBridge = null;
|
|
32
|
+
function disposeWorkspaceOpsBridge() {
|
|
33
|
+
if (workspaceOpsBridge) {
|
|
34
|
+
workspaceOpsBridge.close();
|
|
35
|
+
workspaceOpsBridge = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
39
|
+
"status",
|
|
40
|
+
"quiet",
|
|
41
|
+
"report",
|
|
42
|
+
"session",
|
|
43
|
+
"explain",
|
|
44
|
+
"heartbeat_check",
|
|
45
|
+
"fallback",
|
|
46
|
+
"storage_smoke",
|
|
47
|
+
]);
|
|
48
|
+
function isWorkspaceBridgeCommand(command, input) {
|
|
49
|
+
if (command === "credential") {
|
|
50
|
+
const action = typeof input?.action === "string" ? input.action : "show";
|
|
51
|
+
return action !== "verify";
|
|
52
|
+
}
|
|
53
|
+
return WORKSPACE_BRIDGE_COMMANDS.has(command);
|
|
54
|
+
}
|
|
55
|
+
async function ensureWorkspaceOpsBridge(spine) {
|
|
56
|
+
const root = spine.workspaceRootContext.runtimeRoot;
|
|
57
|
+
if (workspaceOpsBridge?.root === root) {
|
|
58
|
+
return { ok: true, dispatch: workspaceOpsBridge.dispatch };
|
|
59
|
+
}
|
|
60
|
+
disposeWorkspaceOpsBridge();
|
|
61
|
+
const opened = await openWorkspaceOpsBridge(root);
|
|
62
|
+
if (!opened.ok) {
|
|
63
|
+
return opened;
|
|
64
|
+
}
|
|
65
|
+
workspaceOpsBridge = { root, close: opened.close, dispatch: opened.dispatch };
|
|
66
|
+
return { ok: true, dispatch: opened.dispatch };
|
|
67
|
+
}
|
|
68
|
+
async function routeSecondNatureCommand(spine, command, input) {
|
|
69
|
+
const wr = spine.workspaceRootContext;
|
|
70
|
+
const useBridge = wr.resolution !== "unknown" && isWorkspaceBridgeCommand(command, input);
|
|
71
|
+
if (useBridge) {
|
|
72
|
+
const bridge = await ensureWorkspaceOpsBridge(spine);
|
|
73
|
+
if (!bridge.ok) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
surfaceMode: "host_safe_carrier",
|
|
77
|
+
workspaceReadModelsEvaluated: false,
|
|
78
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
79
|
+
error: bridge.error,
|
|
80
|
+
data: {
|
|
81
|
+
workspaceRootResolution: wr.resolution,
|
|
82
|
+
bridgeAttempted: true,
|
|
83
|
+
declaredRoot: wr.declaredRoot,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return (await bridge.dispatch(command, input));
|
|
88
|
+
}
|
|
89
|
+
const def = spine.router.resolve(command);
|
|
90
|
+
if (!def) {
|
|
91
|
+
return { ok: false, message: `Unknown Second Nature command: ${command}` };
|
|
92
|
+
}
|
|
93
|
+
return def.execute(input);
|
|
94
|
+
}
|
|
95
|
+
function resolveWorkspaceRoot(toolWorkspaceRoot) {
|
|
96
|
+
const env = process.env.SECOND_NATURE_WORKSPACE_ROOT?.trim();
|
|
97
|
+
if (env) {
|
|
98
|
+
return { resolution: "env", declaredRoot: env, runtimeRoot: env };
|
|
99
|
+
}
|
|
100
|
+
const tool = toolWorkspaceRoot?.trim();
|
|
101
|
+
if (tool) {
|
|
102
|
+
return { resolution: "tool_args", declaredRoot: tool, runtimeRoot: tool };
|
|
103
|
+
}
|
|
104
|
+
return { resolution: "unknown", declaredRoot: undefined, runtimeRoot: process.cwd() };
|
|
105
|
+
}
|
|
106
|
+
function syncWorkspaceRootFromTool(spine, toolWorkspaceRoot) {
|
|
107
|
+
const next = resolveWorkspaceRoot(toolWorkspaceRoot);
|
|
108
|
+
const prev = spine.workspaceRootContext;
|
|
109
|
+
const changed = next.runtimeRoot !== prev.runtimeRoot || next.resolution !== prev.resolution;
|
|
110
|
+
if (changed) {
|
|
111
|
+
disposeWorkspaceOpsBridge();
|
|
112
|
+
}
|
|
113
|
+
spine.workspaceRootContext = next;
|
|
114
|
+
if (changed) {
|
|
115
|
+
spine.runtimeHandle = startRuntimeService({ workspaceRoot: next.runtimeRoot });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
28
118
|
function trimRuntimeEvidence(spine) {
|
|
29
119
|
if (spine.runtimeEvidence.length > 12) {
|
|
30
120
|
spine.runtimeEvidence.splice(0, spine.runtimeEvidence.length - 12);
|
|
@@ -88,56 +178,73 @@ function parseExplainSubject(subjectRaw) {
|
|
|
88
178
|
function buildStatusPayload(spine) {
|
|
89
179
|
const runtimeEvidence = latestRuntimeEvidence(spine);
|
|
90
180
|
const updatedAt = runtimeEvidence?.createdAt ?? new Date(spine.lifecycleState.lastChangedAt).toISOString();
|
|
181
|
+
const wr = spine.workspaceRootContext;
|
|
182
|
+
const needsRootHint = wr.resolution === "unknown";
|
|
91
183
|
return {
|
|
92
|
-
ok:
|
|
184
|
+
ok: false,
|
|
185
|
+
surfaceMode: "host_safe_carrier",
|
|
186
|
+
workspaceReadModelsEvaluated: false,
|
|
187
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
188
|
+
error: {
|
|
189
|
+
code: "WORKSPACE_READ_SURFACE_UNAVAILABLE",
|
|
190
|
+
message: "Aggregated status requires workspace state; the host-safe plugin does not load persisted read models on this surface.",
|
|
191
|
+
requiredUserInput: needsRootHint ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
192
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
193
|
+
},
|
|
93
194
|
data: {
|
|
94
|
-
|
|
195
|
+
workspaceRootResolution: wr.resolution,
|
|
196
|
+
carrier: {
|
|
95
197
|
host: "openclaw-plugin",
|
|
96
198
|
serviceStatus: spine.runtimeHandle.ready ? "running" : "idle",
|
|
97
199
|
updatedAt,
|
|
98
|
-
|
|
99
|
-
rhythm: {
|
|
100
|
-
mode: "active",
|
|
101
|
-
windowId: undefined,
|
|
102
|
-
},
|
|
103
|
-
quiet: {
|
|
104
|
-
mode: "unknown",
|
|
105
|
-
lastEvent: runtimeEvidence?.traceId,
|
|
106
|
-
interrupted: undefined,
|
|
107
|
-
},
|
|
108
|
-
connectors: [],
|
|
109
|
-
credentials: [],
|
|
110
|
-
risk: {
|
|
111
|
-
level: "low",
|
|
112
|
-
flags: [],
|
|
200
|
+
lastRuntimeTraceId: runtimeEvidence?.traceId,
|
|
113
201
|
},
|
|
114
202
|
},
|
|
115
203
|
};
|
|
116
204
|
}
|
|
117
|
-
function buildQuietPayload(scope) {
|
|
205
|
+
function buildQuietPayload(spine, scope) {
|
|
206
|
+
const wr = spine.workspaceRootContext;
|
|
118
207
|
return {
|
|
119
|
-
ok:
|
|
208
|
+
ok: false,
|
|
209
|
+
surfaceMode: "host_safe_carrier",
|
|
210
|
+
workspaceReadModelsEvaluated: false,
|
|
211
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
212
|
+
error: {
|
|
213
|
+
code: "QUIET_READ_SURFACE_UNAVAILABLE",
|
|
214
|
+
message: "Quiet read surface requires workspace runtime; not evaluated in host-safe carrier mode.",
|
|
215
|
+
requiredUserInput: wr.resolution === "unknown" ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
216
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
217
|
+
},
|
|
120
218
|
data: {
|
|
121
219
|
scope,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
recentJournalCount: 0,
|
|
220
|
+
evaluated: false,
|
|
221
|
+
unavailableReason: "host_safe_carrier_no_workspace_db",
|
|
222
|
+
workspaceRootResolution: wr.resolution,
|
|
126
223
|
},
|
|
127
224
|
};
|
|
128
225
|
}
|
|
129
|
-
function buildReportPayload(day) {
|
|
226
|
+
function buildReportPayload(spine, day) {
|
|
227
|
+
const wr = spine.workspaceRootContext;
|
|
130
228
|
return {
|
|
131
|
-
ok:
|
|
229
|
+
ok: false,
|
|
230
|
+
surfaceMode: "host_safe_carrier",
|
|
231
|
+
workspaceReadModelsEvaluated: false,
|
|
232
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
233
|
+
error: {
|
|
234
|
+
code: "REPORT_READ_SURFACE_UNAVAILABLE",
|
|
235
|
+
message: "Daily report artifacts require workspace runtime.",
|
|
236
|
+
requiredUserInput: wr.resolution === "unknown" ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
237
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
238
|
+
},
|
|
132
239
|
data: {
|
|
240
|
+
evaluated: false,
|
|
241
|
+
unavailableReason: "host_safe_carrier_no_workspace_db",
|
|
133
242
|
day: day && day.trim() ? day : new Date().toISOString().slice(0, 10),
|
|
134
|
-
|
|
135
|
-
highlights: [],
|
|
136
|
-
sourceRefs: [],
|
|
243
|
+
workspaceRootResolution: wr.resolution,
|
|
137
244
|
},
|
|
138
245
|
};
|
|
139
246
|
}
|
|
140
|
-
function buildSessionPayload(sessionId) {
|
|
247
|
+
function buildSessionPayload(spine, sessionId) {
|
|
141
248
|
if (!sessionId) {
|
|
142
249
|
return {
|
|
143
250
|
ok: false,
|
|
@@ -149,26 +256,44 @@ function buildSessionPayload(sessionId) {
|
|
|
149
256
|
},
|
|
150
257
|
};
|
|
151
258
|
}
|
|
259
|
+
const wr = spine.workspaceRootContext;
|
|
152
260
|
return {
|
|
153
|
-
ok:
|
|
261
|
+
ok: false,
|
|
262
|
+
surfaceMode: "host_safe_carrier",
|
|
263
|
+
workspaceReadModelsEvaluated: false,
|
|
264
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
265
|
+
error: {
|
|
266
|
+
code: "SESSION_READ_SURFACE_UNAVAILABLE",
|
|
267
|
+
message: "Session analytics require workspace state database.",
|
|
268
|
+
requiredUserInput: wr.resolution === "unknown" ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
269
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
270
|
+
},
|
|
154
271
|
data: {
|
|
155
272
|
requestedSessionId: sessionId,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
governanceCount: 0,
|
|
160
|
-
keyFactors: [],
|
|
161
|
-
evidenceRefs: [],
|
|
273
|
+
evaluated: false,
|
|
274
|
+
unavailableReason: "host_safe_carrier_no_workspace_db",
|
|
275
|
+
workspaceRootResolution: wr.resolution,
|
|
162
276
|
},
|
|
163
277
|
};
|
|
164
278
|
}
|
|
165
|
-
function buildCredentialPayload(platformId) {
|
|
279
|
+
function buildCredentialPayload(spine, platformId) {
|
|
280
|
+
const wr = spine.workspaceRootContext;
|
|
166
281
|
return {
|
|
167
|
-
ok:
|
|
282
|
+
ok: false,
|
|
283
|
+
surfaceMode: "host_safe_carrier",
|
|
284
|
+
workspaceReadModelsEvaluated: false,
|
|
285
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
286
|
+
error: {
|
|
287
|
+
code: "CREDENTIAL_READ_SURFACE_UNAVAILABLE",
|
|
288
|
+
message: "Credential inspection requires workspace runtime on this surface.",
|
|
289
|
+
requiredUserInput: wr.resolution === "unknown" ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
290
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
291
|
+
},
|
|
168
292
|
data: {
|
|
169
|
-
platformId: platformId && platformId.trim() ? platformId :
|
|
170
|
-
|
|
171
|
-
|
|
293
|
+
platformId: platformId && platformId.trim() ? platformId : undefined,
|
|
294
|
+
evaluated: false,
|
|
295
|
+
unavailableReason: "host_safe_carrier_no_workspace_db",
|
|
296
|
+
workspaceRootResolution: wr.resolution,
|
|
172
297
|
},
|
|
173
298
|
};
|
|
174
299
|
}
|
|
@@ -198,23 +323,22 @@ function buildExplainPayload(spine, subjectRaw) {
|
|
|
198
323
|
}
|
|
199
324
|
return createUnavailableActionError("EXPLAIN_SUBJECT_INVALID", "invalid explain subject", ["subject"], "reinvoke_explain_with_supported_subject");
|
|
200
325
|
}
|
|
201
|
-
const
|
|
326
|
+
const wr = spine.workspaceRootContext;
|
|
202
327
|
return {
|
|
203
|
-
ok:
|
|
328
|
+
ok: false,
|
|
329
|
+
surfaceMode: "host_safe_carrier",
|
|
330
|
+
workspaceReadModelsEvaluated: false,
|
|
331
|
+
message: HOST_SAFE_LIMITATION_MESSAGE,
|
|
332
|
+
error: {
|
|
333
|
+
code: "EXPLAIN_READ_SURFACE_UNAVAILABLE",
|
|
334
|
+
message: "Evidence-backed explain requires persisted workspace read models; host-safe carrier did not evaluate operator explain (CH-11-02).",
|
|
335
|
+
requiredUserInput: wr.resolution === "unknown" ? ["SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot"] : [],
|
|
336
|
+
nextStep: "run_workspace_second_nature_cli_or_full_runtime_package",
|
|
337
|
+
},
|
|
204
338
|
data: {
|
|
205
339
|
subjectType: subject.subjectType,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"synchronous_register",
|
|
209
|
-
`subject:${subject.subjectId}`,
|
|
210
|
-
runtimeEvidence?.capability ?? "runtime.activate",
|
|
211
|
-
],
|
|
212
|
-
evidenceRefs: [
|
|
213
|
-
runtimeEvidence?.traceId ?? `${INTERNAL_RUNTIME_TRACE_PREFIX}none`,
|
|
214
|
-
`subject:${subjectRaw.trim()}`,
|
|
215
|
-
"host_safe_mode",
|
|
216
|
-
],
|
|
217
|
-
nextStep: "use full workspace runtime for evidence-backed explain details",
|
|
340
|
+
evaluated: false,
|
|
341
|
+
workspaceRootResolution: wr.resolution,
|
|
218
342
|
},
|
|
219
343
|
};
|
|
220
344
|
}
|
|
@@ -256,16 +380,18 @@ function buildHeartbeatCheckPayload(spine, input) {
|
|
|
256
380
|
const runtimeEvidence = latestRuntimeEvidence(spine);
|
|
257
381
|
const updatedAt = runtimeEvidence?.createdAt ?? new Date(spine.lifecycleState.lastChangedAt).toISOString();
|
|
258
382
|
const timestamp = typeof input?.timestamp === "string" && input.timestamp.trim().length > 0 ? input.timestamp : updatedAt;
|
|
383
|
+
const wr = spine.workspaceRootContext;
|
|
259
384
|
return {
|
|
260
385
|
ok: true,
|
|
261
|
-
status: "
|
|
262
|
-
|
|
386
|
+
status: "runtime_carrier_only",
|
|
387
|
+
livedExperienceLoopClaimed: false,
|
|
263
388
|
scope: "rhythm",
|
|
264
389
|
trigger: "heartbeat_bridge",
|
|
265
|
-
reasons: ["
|
|
266
|
-
nextAction: "
|
|
267
|
-
message: "
|
|
390
|
+
reasons: ["runtime_carrier_only", "host_safe_bridge_ack"],
|
|
391
|
+
nextAction: "continue_carrier_surface_only",
|
|
392
|
+
message: "Packaged carrier acknowledged this heartbeat round. This is not a full lived-experience decision loop; use the workspace CLI when read models are required.",
|
|
268
393
|
data: {
|
|
394
|
+
workspaceRootResolution: wr.resolution,
|
|
269
395
|
runtime: {
|
|
270
396
|
host: "openclaw-plugin",
|
|
271
397
|
serviceStatus: spine.runtimeHandle.ready ? "running" : "idle",
|
|
@@ -316,7 +442,7 @@ function createHostSafeRouter(spine) {
|
|
|
316
442
|
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");
|
|
317
443
|
}
|
|
318
444
|
const platformId = typeof input?.platformId === "string" ? input.platformId : undefined;
|
|
319
|
-
return buildCredentialPayload(platformId);
|
|
445
|
+
return buildCredentialPayload(spine, platformId);
|
|
320
446
|
},
|
|
321
447
|
},
|
|
322
448
|
{
|
|
@@ -324,7 +450,7 @@ function createHostSafeRouter(spine) {
|
|
|
324
450
|
description: "Inspect Quiet lifecycle state",
|
|
325
451
|
execute: async (input) => {
|
|
326
452
|
const scope = typeof input?.scope === "string" ? input.scope : undefined;
|
|
327
|
-
return buildQuietPayload(scope);
|
|
453
|
+
return buildQuietPayload(spine, scope);
|
|
328
454
|
},
|
|
329
455
|
},
|
|
330
456
|
{
|
|
@@ -332,7 +458,7 @@ function createHostSafeRouter(spine) {
|
|
|
332
458
|
description: "Show daily report artifacts",
|
|
333
459
|
execute: async (input) => {
|
|
334
460
|
const day = typeof input?.day === "string" ? input.day : undefined;
|
|
335
|
-
return buildReportPayload(day);
|
|
461
|
+
return buildReportPayload(spine, day);
|
|
336
462
|
},
|
|
337
463
|
},
|
|
338
464
|
{
|
|
@@ -340,7 +466,7 @@ function createHostSafeRouter(spine) {
|
|
|
340
466
|
description: "Inspect continuity session details",
|
|
341
467
|
execute: async (input) => {
|
|
342
468
|
const sessionId = typeof input?.sessionId === "string" ? input.sessionId : undefined;
|
|
343
|
-
return buildSessionPayload(sessionId);
|
|
469
|
+
return buildSessionPayload(spine, sessionId);
|
|
344
470
|
},
|
|
345
471
|
},
|
|
346
472
|
{
|
|
@@ -383,12 +509,14 @@ function createHostSafeRouter(spine) {
|
|
|
383
509
|
};
|
|
384
510
|
}
|
|
385
511
|
function createActivationSpine() {
|
|
512
|
+
const workspaceRootContext = resolveWorkspaceRoot(undefined);
|
|
386
513
|
const spine = {
|
|
387
514
|
router: undefined,
|
|
388
|
-
runtimeHandle: startRuntimeService({ workspaceRoot:
|
|
515
|
+
runtimeHandle: startRuntimeService({ workspaceRoot: workspaceRootContext.runtimeRoot }),
|
|
389
516
|
lifecycleState: getLifecycleState(),
|
|
390
517
|
serviceStartRecorded: false,
|
|
391
518
|
runtimeEvidence: [],
|
|
519
|
+
workspaceRootContext,
|
|
392
520
|
};
|
|
393
521
|
spine.router = createHostSafeRouter(spine);
|
|
394
522
|
return spine;
|
|
@@ -422,7 +550,14 @@ function recordRuntimeEvidence(spine, origin) {
|
|
|
422
550
|
}
|
|
423
551
|
function refreshRegistrationState() {
|
|
424
552
|
const spine = ensureActivationSpine();
|
|
425
|
-
|
|
553
|
+
const workspaceRootContext = resolveWorkspaceRoot(undefined);
|
|
554
|
+
const prev = spine.workspaceRootContext;
|
|
555
|
+
const changed = workspaceRootContext.runtimeRoot !== prev.runtimeRoot || workspaceRootContext.resolution !== prev.resolution;
|
|
556
|
+
if (changed) {
|
|
557
|
+
disposeWorkspaceOpsBridge();
|
|
558
|
+
}
|
|
559
|
+
spine.workspaceRootContext = workspaceRootContext;
|
|
560
|
+
spine.runtimeHandle = startRuntimeService({ workspaceRoot: workspaceRootContext.runtimeRoot });
|
|
426
561
|
spine.lifecycleState = recordRegistration();
|
|
427
562
|
spine.serviceStartRecorded = false;
|
|
428
563
|
recordRuntimeEvidence(spine, "register");
|
|
@@ -575,7 +710,7 @@ export default {
|
|
|
575
710
|
text: JSON.stringify({ ok: false, command: parsed.command, message: "Unknown Second Nature command." }),
|
|
576
711
|
};
|
|
577
712
|
}
|
|
578
|
-
const result = await
|
|
713
|
+
const result = await routeSecondNatureCommand(spine, parsed.command, parsed.input);
|
|
579
714
|
return {
|
|
580
715
|
text: JSON.stringify(result),
|
|
581
716
|
};
|
|
@@ -590,11 +725,16 @@ export default {
|
|
|
590
725
|
properties: {
|
|
591
726
|
command: { type: "string" },
|
|
592
727
|
args: { type: "object", additionalProperties: true },
|
|
728
|
+
workspaceRoot: {
|
|
729
|
+
type: "string",
|
|
730
|
+
description: "Workspace root for packaged smoke/runtime alignment (optional; prefer SECOND_NATURE_WORKSPACE_ROOT).",
|
|
731
|
+
},
|
|
593
732
|
},
|
|
594
733
|
required: ["command"],
|
|
595
734
|
},
|
|
596
735
|
async execute(_id, params) {
|
|
597
736
|
const spine = ensureActivationSpine();
|
|
737
|
+
syncWorkspaceRootFromTool(spine, params.workspaceRoot);
|
|
598
738
|
const resolved = spine.router.resolve(params.command);
|
|
599
739
|
if (!resolved) {
|
|
600
740
|
return {
|
|
@@ -606,7 +746,7 @@ export default {
|
|
|
606
746
|
],
|
|
607
747
|
};
|
|
608
748
|
}
|
|
609
|
-
const result = await
|
|
749
|
+
const result = await routeSecondNatureCommand(spine, params.command, params.args);
|
|
610
750
|
return {
|
|
611
751
|
content: [
|
|
612
752
|
{
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/second-nature",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "OpenClaw native plugin with synchronous registration, a packaged runtime artifact, and operator-facing status/explain flows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"main": "./index.js",
|
|
18
18
|
"files": [
|
|
19
19
|
"index.js",
|
|
20
|
+
"workspace-ops-bridge.js",
|
|
20
21
|
"openclaw.plugin.json",
|
|
21
22
|
"runtime/"
|
|
22
23
|
],
|
|
@@ -48,8 +48,8 @@ export async function heartbeatCheck(input) {
|
|
|
48
48
|
if (!input.readModels) {
|
|
49
49
|
return {
|
|
50
50
|
ok: true,
|
|
51
|
-
status: "
|
|
52
|
-
surfaceMode: "
|
|
51
|
+
status: "runtime_carrier_only",
|
|
52
|
+
surfaceMode: "host_safe_carrier",
|
|
53
53
|
reasons: ["heartbeat_read_models_unavailable"],
|
|
54
54
|
livedExperienceLoopClaimed: false,
|
|
55
55
|
};
|
|
@@ -59,8 +59,8 @@ export declare const sceneGuidanceRequestSchema: z.ZodObject<{
|
|
|
59
59
|
maintenance: "maintenance";
|
|
60
60
|
}>>;
|
|
61
61
|
riskLevel: z.ZodEnum<{
|
|
62
|
-
low: "low";
|
|
63
62
|
medium: "medium";
|
|
63
|
+
low: "low";
|
|
64
64
|
high: "high";
|
|
65
65
|
}>;
|
|
66
66
|
sourceRefs: z.ZodArray<z.ZodObject<{
|
|
@@ -113,8 +113,8 @@ export declare const outreachDraftRequestSchema: z.ZodObject<{
|
|
|
113
113
|
maintenance: "maintenance";
|
|
114
114
|
}>>;
|
|
115
115
|
riskLevel: z.ZodEnum<{
|
|
116
|
-
low: "low";
|
|
117
116
|
medium: "medium";
|
|
117
|
+
low: "low";
|
|
118
118
|
high: "high";
|
|
119
119
|
}>;
|
|
120
120
|
sourceRefs: z.ZodArray<z.ZodObject<{
|
|
@@ -185,7 +185,7 @@ export declare function parseOutreachDraftRequest(input: unknown): OutreachDraft
|
|
|
185
185
|
export declare function safeParseOutreachDraftRequest(input: unknown): z.ZodSafeParseResult<{
|
|
186
186
|
requestId: string;
|
|
187
187
|
runtimeScope: "rhythm" | "user_reply" | "user_task";
|
|
188
|
-
riskLevel: "
|
|
188
|
+
riskLevel: "medium" | "low" | "high";
|
|
189
189
|
sourceRefs: {
|
|
190
190
|
id: string;
|
|
191
191
|
kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
|
|
@@ -91,7 +91,8 @@ export class LivedExperienceAuditRecorder {
|
|
|
91
91
|
payload.status === "not_sent_fallback" ||
|
|
92
92
|
payload.status === "channel_missing" ||
|
|
93
93
|
payload.status === "host_unsupported" ||
|
|
94
|
-
payload.status === "failed"
|
|
94
|
+
payload.status === "failed" ||
|
|
95
|
+
payload.status === "ack_dropped") {
|
|
95
96
|
entry.noUserVisibleContact = true;
|
|
96
97
|
}
|
|
97
98
|
return { eventId: envelope.eventId };
|
|
@@ -12,8 +12,19 @@ export class CredentialRepository {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
async findByPlatformId(platformId) {
|
|
15
|
-
|
|
15
|
+
const row = await this.database.db.query.credentialRecords.findFirst({
|
|
16
16
|
where: eq(credentialRecords.platformId, platformId),
|
|
17
17
|
});
|
|
18
|
+
if (row == null) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const r = row;
|
|
22
|
+
const pid = (r.platformId ?? r.platform_id);
|
|
23
|
+
const enc = (r.encryptedValue ?? r.encrypted_value);
|
|
24
|
+
// sql.js + Drizzle: no-match can still return a "shell" row (keys present, values undefined).
|
|
25
|
+
if (pid == null || pid === "" || enc == null || enc === "") {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return row;
|
|
18
29
|
}
|
|
19
30
|
}
|
|
@@ -90,9 +90,10 @@ export function createCredentialVault(db) {
|
|
|
90
90
|
return null;
|
|
91
91
|
let plain;
|
|
92
92
|
if (record.encryptedValue) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (!isCredentialCiphertext(record.encryptedValue)) {
|
|
94
|
+
throw new Error("credential_store_plaintext_or_invalid_legacy_record");
|
|
95
|
+
}
|
|
96
|
+
plain = decryptCredentialAtRest(record.encryptedValue);
|
|
96
97
|
}
|
|
97
98
|
return {
|
|
98
99
|
platformId: record.platformId,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T1.1.4 — Lazy workspace full-ops bridge (OpenClaw plugin).
|
|
3
|
+
*
|
|
4
|
+
* Core logic: dynamic-import packaged `runtime/` + open workspace `data/*.db` with the same
|
|
5
|
+
* `createCliRuntimeDeps` + `createOpsRouter` + `createCliCommands` path as the workspace CLI.
|
|
6
|
+
* `process.chdir(workspaceRoot)` during dispatch so `memory/workspace` paths match CLI cwd semantics.
|
|
7
|
+
*
|
|
8
|
+
* Boundaries: no static imports from `./runtime/*` (sql.js top-level await stays out of register() graph).
|
|
9
|
+
*
|
|
10
|
+
* Plan B (CH-11-01): if the host VM blocks dynamic import + sql.js, fall back to a subprocess invoking
|
|
11
|
+
* the workspace `second-nature` CLI — not implemented here; bridge failures surface as explicit errors.
|
|
12
|
+
*
|
|
13
|
+
* Test coverage: tests/integration/cli/plugin-workspace-ops-bridge.test.ts
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
const PLUGIN_PACKAGE_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
export async function openWorkspaceOpsBridge(workspaceRoot) {
|
|
20
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
21
|
+
try {
|
|
22
|
+
// Packaged `plugin/runtime` is emitted JS without sibling `.d.ts` in this repo layout.
|
|
23
|
+
// @ts-expect-error TS7016 — intentional dynamic import of artifact bundle
|
|
24
|
+
const cliIndex = (await import("./runtime/cli/index.js"));
|
|
25
|
+
const commandsMod = (await import("./runtime/cli/commands/index.js"));
|
|
26
|
+
const storageDb = (await import("./runtime/storage/db/index.js"));
|
|
27
|
+
const obsDb = (await import("./runtime/observability/db/index.js"));
|
|
28
|
+
const boundary = (await import("./runtime/cli/runtime/runtime-artifact-boundary.js"));
|
|
29
|
+
const dataDir = path.join(resolvedRoot, "data");
|
|
30
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
31
|
+
const statePath = path.join(dataDir, "state.db");
|
|
32
|
+
const obsPath = path.join(dataDir, "observability.db");
|
|
33
|
+
const stateDb = storageDb.createStateDatabase(statePath);
|
|
34
|
+
const observabilityDb = obsDb.createObservabilityDatabase(obsPath);
|
|
35
|
+
const deps = cliIndex.createCliRuntimeDeps({ stateDb, observabilityDb });
|
|
36
|
+
const runtimeResolved = boundary.resolvePackagedRuntime(PLUGIN_PACKAGE_ROOT);
|
|
37
|
+
const opsRouter = cliIndex.createOpsRouter({
|
|
38
|
+
runtimeAvailable: runtimeResolved.ok,
|
|
39
|
+
readModels: deps.readModels,
|
|
40
|
+
});
|
|
41
|
+
const commands = commandsMod.createCliCommands({
|
|
42
|
+
readModels: deps.readModels,
|
|
43
|
+
actionBridge: deps.actionBridge,
|
|
44
|
+
opsRouter,
|
|
45
|
+
});
|
|
46
|
+
const dispatch = async (command, input) => {
|
|
47
|
+
const def = commands.find((c) => c.name === command);
|
|
48
|
+
if (!def) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { code: "unknown_command", message: `Unknown Second Nature command: ${command}` },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const prevCwd = process.cwd();
|
|
55
|
+
try {
|
|
56
|
+
process.chdir(resolvedRoot);
|
|
57
|
+
return await def.execute(input);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
process.chdir(prevCwd);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const close = () => {
|
|
64
|
+
cliIndex.closeCliRuntimeDeps(deps);
|
|
65
|
+
};
|
|
66
|
+
return { ok: true, workspaceRoot: resolvedRoot, dispatch, close };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: {
|
|
72
|
+
code: "WORKSPACE_FULL_OPS_BRIDGE_FAILED",
|
|
73
|
+
message: error instanceof Error ? error.message : String(error),
|
|
74
|
+
nextStep: "Confirm the packaged plugin includes plugin/runtime and that the host allows dynamic import of sql.js. If the VM forbids it, use a subprocess workspace CLI (Plan B) or run outside the sandbox.",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|