@haaaiawd/second-nature 0.2.9 → 0.2.12
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 +6 -0
- package/openclaw.plugin.json +2 -5
- package/package.json +1 -1
- package/runtime/cli/host-capability/probe-host-capability.js +1 -1
- package/runtime/cli/host-capability/types.d.ts +2 -7
- package/runtime/cli/host-capability/types.js +0 -5
- package/runtime/cli/ops/heartbeat-surface.d.ts +2 -0
- package/runtime/cli/ops/heartbeat-surface.js +32 -3
- package/runtime/cli/ops/ops-router.js +3 -3
- package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
- package/runtime/connectors/manifest/manifest-schema.d.ts +2 -2
- package/runtime/connectors/registry/dynamic-connector-registry.js +16 -1
- package/runtime/connectors/services/connector-executor-adapter.js +54 -35
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +2 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +2 -4
- package/runtime/core/second-nature/action/action-proposal-builder.js +5 -32
- package/runtime/core/second-nature/action/policy-bound-dispatch.js +4 -3
- package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +1 -11
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +1 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +10 -6
- package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
- package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +4 -3
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +3 -2
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -2
- package/runtime/core/second-nature/orchestrator/intent-planner.js +4 -2
- package/runtime/core/second-nature/orchestrator/narrative-update.js +1 -2
- package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +2 -2
- package/runtime/core/second-nature/orchestrator/platform-capability-router.js +1 -1
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +2 -3
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +2 -2
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +6 -1
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +3 -14
- package/runtime/core/second-nature/outreach/judge-outreach.d.ts +6 -5
- package/runtime/core/second-nature/perception/judgment-engine.js +2 -12
- package/runtime/core/second-nature/perception/perception-builder.js +1 -9
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +13 -15
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +10 -13
- package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +0 -2
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +0 -12
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +1 -11
- package/runtime/core/second-nature/types.d.ts +2 -9
- package/runtime/dream/dream-engine.js +11 -4
- package/runtime/guidance/outreach-draft-schema.d.ts +12 -12
- package/runtime/guidance/persona-selection.js +5 -0
- package/runtime/guidance/template-registry.js +6 -1
- package/runtime/guidance/types.d.ts +2 -2
- package/runtime/observability/living-loop-health-gate.js +2 -8
- package/runtime/observability/loop-stage-event-sink.js +0 -1
- package/runtime/observability/services/lived-experience-audit.d.ts +7 -7
- package/runtime/shared/serialization.d.ts +17 -0
- package/runtime/shared/serialization.js +27 -0
- package/runtime/shared/source-ref-compat.d.ts +26 -0
- package/runtime/shared/source-ref-compat.js +61 -0
- package/runtime/shared/types/goal.d.ts +4 -4
- package/runtime/shared/types/goal.js +1 -1
- package/runtime/shared/types/index.d.ts +1 -0
- package/runtime/shared/types/index.js +1 -3
- package/runtime/shared/types/source-ref.d.ts +2 -2
- package/runtime/shared/types/v7-entities.d.ts +5 -5
- package/runtime/shared/types/v7-entities.js +1 -1
- package/runtime/shared/types/v8-contracts.d.ts +1 -1
- package/runtime/storage/db/index.js +31 -26
- package/runtime/storage/db/migrations/index.js +4 -0
- package/runtime/storage/db/migrations/v8-004-schema-closure.d.ts +19 -0
- package/runtime/storage/db/migrations/v8-004-schema-closure.js +74 -0
- package/runtime/storage/db/migrations/v8-005-single-status-schema.d.ts +11 -0
- package/runtime/storage/db/migrations/v8-005-single-status-schema.js +16 -0
- package/runtime/storage/db/schema/v8-entities.d.ts +0 -95
- package/runtime/storage/db/schema/v8-entities.js +4 -7
- package/runtime/storage/delivery/types.d.ts +2 -2
- package/runtime/storage/fallback/load-operator-fallback.d.ts +2 -2
- package/runtime/storage/fallback/operator-fallback-types.d.ts +2 -2
- package/runtime/storage/fallback/operator-fallback-view.d.ts +2 -2
- package/runtime/storage/index.d.ts +1 -1
- package/runtime/storage/life-evidence/types.d.ts +5 -5
- package/runtime/storage/quiet/quiet-artifact-types.d.ts +4 -4
- package/runtime/storage/quiet/quiet-artifact-writer.d.ts +2 -2
- package/runtime/storage/services/write-validation-gate.d.ts +1 -1
- package/runtime/storage/services/write-validation-gate.js +16 -4
- package/runtime/storage/snapshots/types.d.ts +8 -8
- package/runtime/storage/user-interest/types.d.ts +3 -3
- package/runtime/storage/v8-state-stores.d.ts +8 -1
- package/runtime/storage/v8-state-stores.js +23 -20
package/index.js
CHANGED
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
* illusion of a working plugin while agent sessions see no tool. We hit
|
|
32
32
|
* exactly that on 2026-05-06; the fix lives in plugin/openclaw.plugin.json
|
|
33
33
|
* under the `activation` block.
|
|
34
|
+
* - Cloud Feishu/OpenClaw sessions can report `capabilities=none`. In that
|
|
35
|
+
* mode, binding activation to `onCapabilities:["tool"]` prevents the agent
|
|
36
|
+
* tool from entering the session even though the plugin loaded. Therefore
|
|
37
|
+
* the manifest keeps `activation.onStartup === true` for daemon loading and
|
|
38
|
+
* declares the tool through `contracts.tools`, but does not require a session
|
|
39
|
+
* capability to activate `second_nature_ops`.
|
|
34
40
|
* - `Shape: non-capability` reported by `openclaw plugins info` is EXPECTED
|
|
35
41
|
* for this plugin. OpenClaw counts capabilities only across cli-backend /
|
|
36
42
|
* text-inference / speech / realtime-* / media-understanding /
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.12",
|
|
5
5
|
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. Ops surface: loop_status, self_health, tool_affordance, heartbeat_check, heartbeat_run, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
|
|
6
6
|
"activation": {
|
|
7
|
-
"onStartup": true
|
|
8
|
-
"onCapabilities": [
|
|
9
|
-
"tool"
|
|
10
|
-
]
|
|
7
|
+
"onStartup": true
|
|
11
8
|
},
|
|
12
9
|
"contracts": {
|
|
13
10
|
"commands": [
|
package/package.json
CHANGED
|
@@ -3,15 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Test coverage: tests/unit/cli/host-capability.test.ts, tests/integration/cli/host-capability-probe.test.ts
|
|
5
5
|
*/
|
|
6
|
+
import type { SourceRef } from "../../shared/types/v8-contracts.js";
|
|
7
|
+
export type { SourceRef } from "../../shared/types/v8-contracts.js";
|
|
6
8
|
export type DeliveryCapabilityStatus = "target_available" | "target_none" | "channel_missing" | "host_api_unavailable" | "host_unsupported" | "unknown";
|
|
7
9
|
export type CapabilityVerdict = "pass" | "fail" | "unknown" | "not_applicable";
|
|
8
|
-
export interface SourceRef {
|
|
9
|
-
id: string;
|
|
10
|
-
kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
|
|
11
|
-
uri: string;
|
|
12
|
-
excerptHash?: string;
|
|
13
|
-
observedAt?: string;
|
|
14
|
-
}
|
|
15
10
|
export interface HostCapabilityDocReference {
|
|
16
11
|
title: string;
|
|
17
12
|
url: string;
|
|
@@ -45,6 +45,8 @@ export interface HeartbeatSurfaceResult {
|
|
|
45
45
|
capabilityClass?: string | null;
|
|
46
46
|
impulseText?: string | null;
|
|
47
47
|
atmosphereText?: string | null;
|
|
48
|
+
expressionBoundaryConstraints?: string[] | null;
|
|
49
|
+
expressionBoundaryStyle?: string | null;
|
|
48
50
|
freshnessMs?: number;
|
|
49
51
|
missingReason?: string;
|
|
50
52
|
};
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { createWorkspaceHeartbeatRunner } from "./workspace-heartbeat-runner.js";
|
|
2
2
|
// T-CP.R.2: v8 real runtime spine bridge
|
|
3
3
|
import { runRealRuntimeHeartbeatCycle, } from "../../core/second-nature/control-plane/real-runtime-spine.js";
|
|
4
|
+
async function refreshHeartbeatImpulseContext(state, now) {
|
|
5
|
+
const { assembleImpulseSync } = await import("../../guidance/impulse-assembler.js");
|
|
6
|
+
const { buildExpressionBoundary } = await import("../../guidance/output-guard.js");
|
|
7
|
+
const { getShortAtmosphereTemplate } = await import("../../guidance/template-registry.js");
|
|
8
|
+
const { writeImpulseContext } = await import("../../core/second-nature/guidance/impulse-context-writer.js");
|
|
9
|
+
const impulseResult = assembleImpulseSync({ sceneType: "heartbeat" });
|
|
10
|
+
const atmosphere = getShortAtmosphereTemplate("active", "low");
|
|
11
|
+
const expressionBoundary = buildExpressionBoundary("heartbeat");
|
|
12
|
+
const result = await writeImpulseContext(state, {
|
|
13
|
+
sceneType: "heartbeat",
|
|
14
|
+
impulseResult,
|
|
15
|
+
atmosphereText: atmosphere.text,
|
|
16
|
+
expressionBoundaryConstraints: expressionBoundary.constraints,
|
|
17
|
+
expressionBoundaryStyle: expressionBoundary.style,
|
|
18
|
+
}, { now });
|
|
19
|
+
return "id" in result ? result.id : undefined;
|
|
20
|
+
}
|
|
4
21
|
function mapCycleToSurface(cycle, surfaceMode) {
|
|
5
22
|
const status = cycle.status === "runtime_carrier_only"
|
|
6
23
|
? "runtime_carrier_only"
|
|
@@ -111,6 +128,11 @@ export async function heartbeatCheck(input) {
|
|
|
111
128
|
}
|
|
112
129
|
else {
|
|
113
130
|
const spine = v8Result;
|
|
131
|
+
const artifactId = await refreshHeartbeatImpulseContext(input.state, timestamp);
|
|
132
|
+
if (artifactId) {
|
|
133
|
+
spine.impulseContextArtifactId = artifactId;
|
|
134
|
+
surfaceResult.reasons.push(`impulse_context_refreshed:${artifactId}`);
|
|
135
|
+
}
|
|
114
136
|
surfaceResult.v8Spine = spine;
|
|
115
137
|
surfaceResult.livedExperienceLoopClaimed = Boolean(spine.cycleId && (spine.closureRef || spine.noActionReason));
|
|
116
138
|
surfaceResult.reasons = [
|
|
@@ -134,7 +156,7 @@ export async function heartbeatCheck(input) {
|
|
|
134
156
|
if (input.state) {
|
|
135
157
|
try {
|
|
136
158
|
const { readImpulseContext } = await import("../../core/second-nature/guidance/impulse-context-reader.js");
|
|
137
|
-
const ctx = await readImpulseContext(input.state, "
|
|
159
|
+
const ctx = await readImpulseContext(input.state, "heartbeat");
|
|
138
160
|
if (ctx.available) {
|
|
139
161
|
surfaceResult.impulseContext = {
|
|
140
162
|
available: true,
|
|
@@ -142,6 +164,8 @@ export async function heartbeatCheck(input) {
|
|
|
142
164
|
capabilityClass: ctx.artifact.capabilityClass,
|
|
143
165
|
impulseText: ctx.artifact.impulseText,
|
|
144
166
|
atmosphereText: ctx.artifact.atmosphereText,
|
|
167
|
+
expressionBoundaryConstraints: ctx.artifact.expressionBoundaryConstraints,
|
|
168
|
+
expressionBoundaryStyle: ctx.artifact.expressionBoundaryStyle,
|
|
145
169
|
freshnessMs: ctx.freshnessMs,
|
|
146
170
|
};
|
|
147
171
|
surfaceResult.reasons.push(`impulse_context:${ctx.artifact.id}`);
|
|
@@ -154,8 +178,13 @@ export async function heartbeatCheck(input) {
|
|
|
154
178
|
surfaceResult.reasons.push(`impulse_context_missing:${ctx.reason}`);
|
|
155
179
|
}
|
|
156
180
|
}
|
|
157
|
-
catch {
|
|
158
|
-
|
|
181
|
+
catch (readErr) {
|
|
182
|
+
const msg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
183
|
+
surfaceResult.impulseContext = {
|
|
184
|
+
available: false,
|
|
185
|
+
missingReason: `read_exception:${msg.slice(0, 120)}`,
|
|
186
|
+
};
|
|
187
|
+
surfaceResult.reasons.push(`impulse_context_read_failed:${msg.slice(0, 120)}`);
|
|
159
188
|
}
|
|
160
189
|
}
|
|
161
190
|
return surfaceResult;
|
|
@@ -325,9 +325,9 @@ function createStaticUnknownAdapter(workspaceRoot) {
|
|
|
325
325
|
evidenceRefs: [
|
|
326
326
|
{
|
|
327
327
|
id: `delivery:${parsed.manifest.platformId}`,
|
|
328
|
-
|
|
328
|
+
family: "audit",
|
|
329
329
|
uri: `workspace://connectors/${parsed.manifest.platformId}/manifest.yaml`,
|
|
330
|
-
|
|
330
|
+
redactionClass: "none",
|
|
331
331
|
},
|
|
332
332
|
],
|
|
333
333
|
};
|
|
@@ -1527,7 +1527,7 @@ export function createOpsRouter(deps) {
|
|
|
1527
1527
|
const platformId = typeof input?.platformId === "string"
|
|
1528
1528
|
? input.platformId
|
|
1529
1529
|
: undefined;
|
|
1530
|
-
const validSceneTypes = ["social", "reply", "outreach", "quiet", "explain", "user_reply"];
|
|
1530
|
+
const validSceneTypes = ["social", "reply", "outreach", "quiet", "heartbeat", "explain", "user_reply"];
|
|
1531
1531
|
if (!validSceneTypes.includes(sceneType)) {
|
|
1532
1532
|
const envelope = {
|
|
1533
1533
|
ok: false,
|
|
@@ -4,6 +4,7 @@ import { createAgentGoalStore } from "../../storage/goal/agent-goal-store.js";
|
|
|
4
4
|
import { createNarrativeStateStore } from "../../storage/narrative/narrative-state-store.js";
|
|
5
5
|
import { createRelationshipMemoryStore } from "../../storage/relationship/relationship-memory-store.js";
|
|
6
6
|
import { createIdentityProfileStore } from "../../storage/services/identity-profile-store.js";
|
|
7
|
+
import { toCanonicalSourceRef } from "../../shared/source-ref-compat.js";
|
|
7
8
|
import { generateHeartbeatDigest, } from "../../observability/services/heartbeat-digest-assembler.js";
|
|
8
9
|
import { createHistoryDigestStore } from "../../storage/services/history-digest-store.js";
|
|
9
10
|
export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, options = {}) {
|
|
@@ -26,11 +27,7 @@ export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, option
|
|
|
26
27
|
const snapshot = await loadLifeEvidenceSnapshot(options.state, options.workspaceRoot, { limit: 50 },
|
|
27
28
|
// Skip repair gate here — runner is called inside a live cycle; gate ran at startup.
|
|
28
29
|
{ runRepairGate: false });
|
|
29
|
-
lifeEvidenceRefs = snapshot.evidenceRefs.map((ref) => (
|
|
30
|
-
id: ref.id,
|
|
31
|
-
kind: ref.kind,
|
|
32
|
-
uri: ref.uri,
|
|
33
|
-
}));
|
|
30
|
+
lifeEvidenceRefs = snapshot.evidenceRefs.map((ref) => toCanonicalSourceRef(ref));
|
|
34
31
|
platformEventCount = snapshot.platformEvents.length;
|
|
35
32
|
workEventCount = snapshot.workEvents.length;
|
|
36
33
|
if (snapshot.empty) {
|
|
@@ -116,7 +116,7 @@ export declare const connectorManifestV6Schema: z.ZodObject<{
|
|
|
116
116
|
export type ConnectorManifestV6 = z.infer<typeof connectorManifestV6Schema>;
|
|
117
117
|
export interface ConnectorConflict {
|
|
118
118
|
platformId: string;
|
|
119
|
-
existingSource: "built_in" | "workspace";
|
|
119
|
+
existingSource: "built_in" | "workspace" | "workspace_shadow";
|
|
120
120
|
attemptedSource: "built_in" | "workspace";
|
|
121
121
|
reason: string;
|
|
122
122
|
}
|
|
@@ -127,7 +127,7 @@ export interface ConnectorManifestValidationError {
|
|
|
127
127
|
}
|
|
128
128
|
export interface ConnectorInventoryEntry {
|
|
129
129
|
platformId: string;
|
|
130
|
-
source: "built_in" | "workspace";
|
|
130
|
+
source: "built_in" | "workspace" | "workspace_shadow";
|
|
131
131
|
manifestPath?: string;
|
|
132
132
|
trustStatus: ConnectorTrustStatus;
|
|
133
133
|
executable: boolean;
|
|
@@ -41,6 +41,12 @@ function manifestToInventoryEntry(manifest, source, manifestPath) {
|
|
|
41
41
|
validationErrors: [],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
function isSafeBuiltInShadow(manifest) {
|
|
45
|
+
const reason = manifest.trust?.reason?.trim();
|
|
46
|
+
if (!manifest.trust?.override || !reason)
|
|
47
|
+
return false;
|
|
48
|
+
return manifest.runner.kind === "declarative_http" || manifest.runner.kind === "scriptable_node";
|
|
49
|
+
}
|
|
44
50
|
/**
|
|
45
51
|
* DynamicConnectorRegistry scans workspace manifests, validates, classifies trust,
|
|
46
52
|
* merges built-in entries, applies fail-closed conflict policy, and publishes
|
|
@@ -79,11 +85,20 @@ export class DynamicConnectorRegistry {
|
|
|
79
85
|
}
|
|
80
86
|
const manifest = parseResult.manifest;
|
|
81
87
|
const manifestPath = path.relative(workspaceRoot, file.path);
|
|
82
|
-
// Duplicate platformId without override -> fail-closed
|
|
88
|
+
// Duplicate platformId without explicit safe override -> fail-closed.
|
|
89
|
+
// Built-ins may be shadowed only by an auditable workspace manifest with
|
|
90
|
+
// a trusted runner kind, so operators can repair endpoint config without
|
|
91
|
+
// silently replacing native code.
|
|
83
92
|
if (builtInMap.has(manifest.platformId) || dynamicMap.has(manifest.platformId)) {
|
|
84
93
|
const existing = builtInMap.get(manifest.platformId) ?? dynamicMap.get(manifest.platformId);
|
|
85
94
|
const allowOverride = manifest.trust?.override === true;
|
|
86
95
|
const isTrustedSource = existing.source === "built_in";
|
|
96
|
+
if (isTrustedSource && isSafeBuiltInShadow(manifest)) {
|
|
97
|
+
const entry = manifestToInventoryEntry(manifest, "workspace_shadow", manifestPath);
|
|
98
|
+
dynamicMap.set(manifest.platformId, entry);
|
|
99
|
+
registered++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
87
102
|
if (!allowOverride || isTrustedSource) {
|
|
88
103
|
conflicts.push({
|
|
89
104
|
platformId: manifest.platformId,
|
|
@@ -51,7 +51,13 @@ function channelPriorityForRunner(manifest) {
|
|
|
51
51
|
return ["browser"];
|
|
52
52
|
return ["api_rest"];
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function isSafeBuiltInShadow(manifest) {
|
|
55
|
+
const reason = manifest.trust?.reason?.trim();
|
|
56
|
+
if (!manifest.trust?.override || !reason)
|
|
57
|
+
return false;
|
|
58
|
+
return manifest.runner.kind === "declarative_http" || manifest.runner.kind === "scriptable_node";
|
|
59
|
+
}
|
|
60
|
+
function registerWorkspaceManifests(registry, builtInPlatformIds, workspaceRoot) {
|
|
55
61
|
if (!workspaceRoot)
|
|
56
62
|
return;
|
|
57
63
|
for (const file of scanConnectorManifests(workspaceRoot)) {
|
|
@@ -59,6 +65,12 @@ function registerWorkspaceManifests(registry, workspaceRoot) {
|
|
|
59
65
|
if (!parsed.ok)
|
|
60
66
|
continue;
|
|
61
67
|
const manifest = parsed.manifest;
|
|
68
|
+
// Built-in platforms may only be shadowed by auditable, trusted-runner manifests.
|
|
69
|
+
// Unsafe shadows are ignored by execution registry; they remain visible in
|
|
70
|
+
// DynamicConnectorRegistry conflicts for diagnostics.
|
|
71
|
+
if (builtInPlatformIds.has(manifest.platformId) && !isSafeBuiltInShadow(manifest)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
62
74
|
try {
|
|
63
75
|
registry.register({
|
|
64
76
|
platformId: manifest.platformId,
|
|
@@ -201,8 +213,8 @@ function createMoltbookMockRunner(workspaceRoot) {
|
|
|
201
213
|
latencyMs: Date.now() - started,
|
|
202
214
|
success: false,
|
|
203
215
|
error: {
|
|
204
|
-
code: "
|
|
205
|
-
detail: String(err)
|
|
216
|
+
code: "configuration_missing",
|
|
217
|
+
detail: `moltbook mock unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
206
218
|
},
|
|
207
219
|
};
|
|
208
220
|
}
|
|
@@ -424,9 +436,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
424
436
|
const workspaceManifest = workspaceManifestResult?.manifest;
|
|
425
437
|
const isBuiltInPlatform = platformId === "moltbook" ||
|
|
426
438
|
platformId === "evomap" ||
|
|
427
|
-
platformId === "agent-world"
|
|
439
|
+
platformId === "agent-world" ||
|
|
440
|
+
platformId === "instreet";
|
|
441
|
+
const effectiveWorkspaceManifest = workspaceManifest && (!isBuiltInPlatform || isSafeBuiltInShadow(workspaceManifest))
|
|
442
|
+
? workspaceManifest
|
|
443
|
+
: undefined;
|
|
428
444
|
const requiresCredential = isBuiltInPlatform ||
|
|
429
|
-
Boolean(
|
|
445
|
+
Boolean(effectiveWorkspaceManifest?.credentials.some((credential) => credential.required !== false));
|
|
430
446
|
const credential = requiresCredential
|
|
431
447
|
? await vault.loadCredentialContext(platformId)
|
|
432
448
|
: undefined;
|
|
@@ -448,9 +464,33 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
448
464
|
const activeCredential = credential?.status === "active" && credential.encryptedValue
|
|
449
465
|
? { encryptedValue: credential.encryptedValue }
|
|
450
466
|
: undefined;
|
|
467
|
+
// Safe workspace shadows of built-in platforms take precedence over built-in runners.
|
|
468
|
+
if (effectiveWorkspaceManifest && effectiveWorkspaceManifest.runner.kind === "declarative_http") {
|
|
469
|
+
const httpRunner = createDeclarativeHttpRunner(effectiveWorkspaceManifest, activeCredential);
|
|
470
|
+
return httpRunner.run(_plan, request);
|
|
471
|
+
}
|
|
472
|
+
if (effectiveWorkspaceManifest && effectiveWorkspaceManifest.runner.kind === "scriptable_node") {
|
|
473
|
+
if (!workspaceManifestResult) {
|
|
474
|
+
return {
|
|
475
|
+
platformId,
|
|
476
|
+
channel: request.preferredChannel ?? "api_rest",
|
|
477
|
+
latencyMs: Date.now() - started,
|
|
478
|
+
success: false,
|
|
479
|
+
error: {
|
|
480
|
+
code: "configuration_missing",
|
|
481
|
+
detail: "scriptable_node requires workspace manifest with manifestDir",
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
const scriptRunner = createScriptableNodeRunner(effectiveWorkspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
|
|
486
|
+
return scriptRunner.run(_plan, request);
|
|
487
|
+
}
|
|
451
488
|
if (platformId === "moltbook") {
|
|
452
489
|
const baseUrl = process.env.SECOND_NATURE_MOLTBOOK_BASE_URL;
|
|
453
490
|
if (baseUrl) {
|
|
491
|
+
const effectivePlan = request.intent === "feed.read" && _plan.channel !== "api_rest"
|
|
492
|
+
? { ..._plan, channel: "api_rest", endpointMode: "rest_json", degraded: false }
|
|
493
|
+
: _plan;
|
|
454
494
|
const apiClient = createMoltbookApiClient({
|
|
455
495
|
baseUrl,
|
|
456
496
|
accessToken: activeCredential.encryptedValue,
|
|
@@ -461,13 +501,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
461
501
|
skillRunner: {
|
|
462
502
|
run: async () => {
|
|
463
503
|
throw {
|
|
464
|
-
code: "
|
|
504
|
+
code: "configuration_missing",
|
|
465
505
|
detail: "moltbook_skill_runner_not_configured",
|
|
466
506
|
};
|
|
467
507
|
},
|
|
468
508
|
},
|
|
469
509
|
});
|
|
470
|
-
return runner.run(
|
|
510
|
+
return runner.run(effectivePlan, request);
|
|
471
511
|
}
|
|
472
512
|
// Mock fallback when real API is not configured
|
|
473
513
|
const mockRunner = createMoltbookMockRunner(workspaceRoot);
|
|
@@ -583,28 +623,6 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
583
623
|
},
|
|
584
624
|
};
|
|
585
625
|
}
|
|
586
|
-
// Wave 83: workspace declarative_http connector fallback
|
|
587
|
-
if (workspaceManifest && workspaceManifest.runner.kind === "declarative_http") {
|
|
588
|
-
const httpRunner = createDeclarativeHttpRunner(workspaceManifest, activeCredential);
|
|
589
|
-
return httpRunner.run(_plan, request);
|
|
590
|
-
}
|
|
591
|
-
// Wave 90: workspace scriptable_node connector
|
|
592
|
-
if (workspaceManifest && workspaceManifest.runner.kind === "scriptable_node") {
|
|
593
|
-
if (!workspaceManifestResult) {
|
|
594
|
-
return {
|
|
595
|
-
platformId,
|
|
596
|
-
channel: request.preferredChannel ?? "api_rest",
|
|
597
|
-
latencyMs: Date.now() - started,
|
|
598
|
-
success: false,
|
|
599
|
-
error: {
|
|
600
|
-
code: "configuration_missing",
|
|
601
|
-
detail: "scriptable_node requires workspace manifest with manifestDir",
|
|
602
|
-
},
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
const scriptRunner = createScriptableNodeRunner(workspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
|
|
606
|
-
return scriptRunner.run(_plan, request);
|
|
607
|
-
}
|
|
608
626
|
return {
|
|
609
627
|
platformId,
|
|
610
628
|
channel: request.preferredChannel ?? "api_rest",
|
|
@@ -621,11 +639,12 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
621
639
|
export function createConnectorExecutorAdapter(options) {
|
|
622
640
|
const vault = createCredentialVault(options.stateDb.db);
|
|
623
641
|
const registry = new CapabilityContractRegistry();
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
642
|
+
const builtInManifests = [moltbookManifest, evomapManifest, agentWorldManifest, instreetManifest];
|
|
643
|
+
const builtInPlatformIds = new Set(builtInManifests.map((m) => m.platformId));
|
|
644
|
+
for (const manifest of builtInManifests) {
|
|
645
|
+
registry.register({ ...manifest });
|
|
646
|
+
}
|
|
647
|
+
registerWorkspaceManifests(registry, builtInPlatformIds, options.workspaceRoot);
|
|
629
648
|
const cooldownPort = createConnectorCooldownPort(options.stateDb);
|
|
630
649
|
const routeContextPort = createCredentialRouteContextPort(vault, options.stateDb);
|
|
631
650
|
const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
|
|
@@ -641,7 +660,7 @@ export function createConnectorExecutorAdapter(options) {
|
|
|
641
660
|
});
|
|
642
661
|
return {
|
|
643
662
|
async executeEffect(input) {
|
|
644
|
-
registerWorkspaceManifests(registry, options.workspaceRoot);
|
|
663
|
+
registerWorkspaceManifests(registry, builtInPlatformIds, options.workspaceRoot);
|
|
645
664
|
return policy.executeWithPolicy(input.intent, {
|
|
646
665
|
platformId: input.platformId,
|
|
647
666
|
intent: input.intent,
|
|
@@ -60,7 +60,6 @@ export async function recordNoActionClosure(db, cycleId, noActionReason, options
|
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
redactionClass: "none",
|
|
63
|
-
lifecycleStatus: "closed",
|
|
64
63
|
payloadJson: JSON.stringify({ dispatchAttempt: 0, inputSummary: "no-action" }),
|
|
65
64
|
});
|
|
66
65
|
if ("reason" in result) {
|
|
@@ -78,12 +77,13 @@ export async function recordRememberClosure(db, cycleId, memoryReviewCandidate,
|
|
|
78
77
|
id: closureId,
|
|
79
78
|
createdAt: now,
|
|
80
79
|
cycleId,
|
|
80
|
+
platformId: options?.platformId ?? "heartbeat",
|
|
81
|
+
capabilityId: options?.capabilityId,
|
|
81
82
|
status: "completed",
|
|
82
83
|
reason: "remember_for_review",
|
|
83
84
|
nextState: "pending_daily_review",
|
|
84
85
|
sourceRefs: memoryReviewCandidate.sourceRefs,
|
|
85
86
|
redactionClass: "none",
|
|
86
|
-
lifecycleStatus: "closed",
|
|
87
87
|
payloadJson: JSON.stringify({
|
|
88
88
|
memoryReviewCandidate,
|
|
89
89
|
dispatchAttempt: 1,
|
|
@@ -132,7 +132,6 @@ export async function recordPolicyOutcomeClosure(db, cycleId, closureStatus, rea
|
|
|
132
132
|
nextState: params.nextState ?? "await_next_cycle",
|
|
133
133
|
sourceRefs,
|
|
134
134
|
redactionClass: "none",
|
|
135
|
-
lifecycleStatus: "closed",
|
|
136
135
|
payloadJson: JSON.stringify({
|
|
137
136
|
dispatchAttempt: 1,
|
|
138
137
|
inputSummary: buildInputSummary(params.proposalId, params.decisionId),
|
|
@@ -173,7 +172,6 @@ export async function recordExecutionClosure(db, cycleId, closureStatus, reason,
|
|
|
173
172
|
nextState: params.nextState ?? (closureStatus === "completed" ? "await_next_cycle" : "retryable"),
|
|
174
173
|
sourceRefs,
|
|
175
174
|
redactionClass: "none",
|
|
176
|
-
lifecycleStatus: "closed",
|
|
177
175
|
payloadJson: JSON.stringify({
|
|
178
176
|
dispatchAttempt: 1,
|
|
179
177
|
executionResultRef: params.executionResultRef,
|
|
@@ -22,22 +22,12 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Test coverage: tests/unit/action/action-proposal-builder.test.ts
|
|
24
24
|
*/
|
|
25
|
-
import { readJudgmentVerdictById,
|
|
25
|
+
import { readJudgmentVerdictById, } from "../../../storage/v8-state-stores.js";
|
|
26
|
+
import { parseSourceRefs } from "../../../shared/serialization.js";
|
|
26
27
|
import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
|
|
27
28
|
// ───────────────────────────────────────────────────────────────
|
|
28
29
|
// Helpers
|
|
29
30
|
// ───────────────────────────────────────────────────────────────
|
|
30
|
-
function parseVerdictSourceRefs(json) {
|
|
31
|
-
if (!json)
|
|
32
|
-
return [];
|
|
33
|
-
try {
|
|
34
|
-
const parsed = JSON.parse(json);
|
|
35
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
31
|
function buildExpectedOutput(actionKind) {
|
|
42
32
|
switch (actionKind) {
|
|
43
33
|
case "ignore":
|
|
@@ -99,8 +89,8 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
|
|
|
99
89
|
judgmentVerdictId,
|
|
100
90
|
};
|
|
101
91
|
}
|
|
102
|
-
const sourceRefs =
|
|
103
|
-
// remember → memory review candidate
|
|
92
|
+
const sourceRefs = parseSourceRefs(verdict.sourceRefsJson);
|
|
93
|
+
// remember → memory review candidate (no direct projection; orchestrator writes closure)
|
|
104
94
|
if (actionKind === "remember") {
|
|
105
95
|
const candidate = {
|
|
106
96
|
closureSubtype: "remember_for_review",
|
|
@@ -131,27 +121,10 @@ export async function buildActionProposal(db, judgmentVerdictId, options) {
|
|
|
131
121
|
},
|
|
132
122
|
]),
|
|
133
123
|
};
|
|
134
|
-
const closureId = `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`;
|
|
135
|
-
const writeResult = await writeActionClosureRecord(db, {
|
|
136
|
-
id: closureId,
|
|
137
|
-
createdAt: now,
|
|
138
|
-
cycleId,
|
|
139
|
-
platformId: "heartbeat",
|
|
140
|
-
status: "completed",
|
|
141
|
-
reason: "remember_for_review",
|
|
142
|
-
nextState: "pending_daily_review",
|
|
143
|
-
sourceRefs: candidate.sourceRefs,
|
|
144
|
-
redactionClass: "none",
|
|
145
|
-
lifecycleStatus: "closed",
|
|
146
|
-
payloadJson: JSON.stringify({ memoryReviewCandidate: candidate }),
|
|
147
|
-
});
|
|
148
|
-
if ("reason" in writeResult) {
|
|
149
|
-
return writeResult;
|
|
150
|
-
}
|
|
151
124
|
return {
|
|
152
125
|
status: "remember_for_review",
|
|
153
126
|
memoryReviewCandidate: candidate,
|
|
154
|
-
closureId,
|
|
127
|
+
closureId: `cls_remember_${judgmentVerdictId}_${now.replace(/[:.]/g, "")}`,
|
|
155
128
|
};
|
|
156
129
|
}
|
|
157
130
|
// Actionable verdict → build proposal
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Test coverage: tests/unit/action/policy-bound-dispatch.test.ts
|
|
22
22
|
*/
|
|
23
|
+
import { serializeSourceRefs } from "../../../shared/serialization.js";
|
|
23
24
|
// ───────────────────────────────────────────────────────────────
|
|
24
25
|
// Helpers
|
|
25
26
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -60,7 +61,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
60
61
|
actionKind: target,
|
|
61
62
|
draftType,
|
|
62
63
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
63
|
-
sourceRefs:
|
|
64
|
+
sourceRefs: serializeSourceRefs(decision.proofRefs),
|
|
64
65
|
},
|
|
65
66
|
};
|
|
66
67
|
}
|
|
@@ -75,7 +76,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
75
76
|
capabilityId: proposal.targetCapabilityId ?? "run_connector",
|
|
76
77
|
idempotencyKey: proposal.idempotencyKey,
|
|
77
78
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
78
|
-
sourceRefs:
|
|
79
|
+
sourceRefs: serializeSourceRefs(proposal.sourceRefs),
|
|
79
80
|
},
|
|
80
81
|
};
|
|
81
82
|
}
|
|
@@ -91,7 +92,7 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
91
92
|
actionKind: proposal.actionKind,
|
|
92
93
|
draftType,
|
|
93
94
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
94
|
-
sourceRefs:
|
|
95
|
+
sourceRefs: serializeSourceRefs(proposal.sourceRefs),
|
|
95
96
|
},
|
|
96
97
|
};
|
|
97
98
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* Test coverage: tests/unit/control-plane/accepted-projection-loader.test.ts
|
|
21
21
|
*/
|
|
22
22
|
import { readMemoryProjectionsByStatus, } from "../../../storage/v8-state-stores.js";
|
|
23
|
+
import { parseSourceRefs } from "../../../shared/serialization.js";
|
|
23
24
|
// ───────────────────────────────────────────────────────────────
|
|
24
25
|
// Helpers
|
|
25
26
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -33,17 +34,6 @@ function parsePayloadJson(json) {
|
|
|
33
34
|
return {};
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
function parseSourceRefs(json) {
|
|
37
|
-
if (!json)
|
|
38
|
-
return [];
|
|
39
|
-
try {
|
|
40
|
-
const parsed = JSON.parse(json);
|
|
41
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
37
|
// ───────────────────────────────────────────────────────────────
|
|
48
38
|
// Public API
|
|
49
39
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -35,6 +35,7 @@ export interface HeartbeatOrchestrationResult {
|
|
|
35
35
|
closureRef?: SourceRef;
|
|
36
36
|
noActionReason?: V8ReasonCode;
|
|
37
37
|
degraded?: DegradedOperationResult;
|
|
38
|
+
rhythmDegraded?: DegradedOperationResult;
|
|
38
39
|
rhythmState?: DailyRhythmState;
|
|
39
40
|
}
|
|
40
41
|
export declare function runHeartbeatCycle(db: StateDatabase, request: HeartbeatOrchestrationRequest): Promise<HeartbeatOrchestrationResult | DegradedOperationResult>;
|