@haaaiawd/second-nature 0.2.8 → 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 +12 -3
- package/openclaw.plugin.json +3 -6
- 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/manual-run-dispatcher.d.ts +10 -0
- package/runtime/cli/ops/manual-run-dispatcher.js +49 -0
- package/runtime/cli/ops/ops-router.js +5 -3
- package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
- package/runtime/connectors/base/normalized-evidence-content.js +15 -2
- 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 +58 -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-consolidation-runner.d.ts +1 -0
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +11 -19
- 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/workspace-ops-bridge.js +29 -1
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 /
|
|
@@ -91,6 +97,7 @@ const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
|
91
97
|
"session",
|
|
92
98
|
"explain",
|
|
93
99
|
"heartbeat_check",
|
|
100
|
+
"heartbeat_run",
|
|
94
101
|
"fallback",
|
|
95
102
|
"storage_smoke",
|
|
96
103
|
// T1.2.8 (SN-CODE-03): capability probe surface via workspace bridge
|
|
@@ -146,8 +153,10 @@ async function ensureWorkspaceOpsBridge(spine) {
|
|
|
146
153
|
return { ok: true, dispatch: opened.dispatch };
|
|
147
154
|
}
|
|
148
155
|
async function routeSecondNatureCommand(spine, command, input) {
|
|
156
|
+
// T-ROS.C.1-followup: heartbeat_run is an alias for heartbeat_check on the plugin surface.
|
|
157
|
+
const normalizedCommand = command === "heartbeat_run" ? "heartbeat_check" : command;
|
|
149
158
|
const wr = spine.workspaceRootContext;
|
|
150
|
-
const useBridge = wr.resolution !== "unknown" && isWorkspaceBridgeCommand(
|
|
159
|
+
const useBridge = wr.resolution !== "unknown" && isWorkspaceBridgeCommand(normalizedCommand, input);
|
|
151
160
|
if (useBridge) {
|
|
152
161
|
const bridge = await ensureWorkspaceOpsBridge(spine);
|
|
153
162
|
if (!bridge.ok) {
|
|
@@ -164,10 +173,10 @@ async function routeSecondNatureCommand(spine, command, input) {
|
|
|
164
173
|
},
|
|
165
174
|
};
|
|
166
175
|
}
|
|
167
|
-
const payload = (await bridge.dispatch(
|
|
176
|
+
const payload = (await bridge.dispatch(normalizedCommand, input));
|
|
168
177
|
return withSetupNudge(spine, command, payload);
|
|
169
178
|
}
|
|
170
|
-
const def = spine.router.resolve(
|
|
179
|
+
const def = spine.router.resolve(normalizedCommand);
|
|
171
180
|
if (!def) {
|
|
172
181
|
return { ok: false, message: `Unknown Second Nature command: ${command}` };
|
|
173
182
|
}
|
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.
|
|
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_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
|
|
4
|
+
"version": "0.2.12",
|
|
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;
|
|
@@ -29,6 +29,7 @@ import type { CapabilityContractRegistryV7 } from "../../connectors/base/manifes
|
|
|
29
29
|
import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
30
30
|
import { type HeartbeatCheckInput, type HeartbeatSurfaceResult } from "./heartbeat-surface.js";
|
|
31
31
|
import type { RuntimeOpsEnvelope } from "./ops-router.js";
|
|
32
|
+
import type { StateDatabase } from "../../storage/db/index.js";
|
|
32
33
|
export interface ManualTriggerContext {
|
|
33
34
|
triggerSource: "manual_run";
|
|
34
35
|
affectsHeartbeatCadence: false;
|
|
@@ -47,6 +48,11 @@ export interface ConnectorRunResult {
|
|
|
47
48
|
experienceId: string;
|
|
48
49
|
triggerSource: "manual_run";
|
|
49
50
|
affectsHeartbeatCadence: false;
|
|
51
|
+
evidence?: {
|
|
52
|
+
v7EvidenceId?: string;
|
|
53
|
+
v8EvidenceIds: string[];
|
|
54
|
+
emptyReason?: string;
|
|
55
|
+
};
|
|
50
56
|
}
|
|
51
57
|
export interface WetProbeRunInput {
|
|
52
58
|
platformId: string;
|
|
@@ -77,5 +83,9 @@ export interface ManualRunDispatcherDeps {
|
|
|
77
83
|
wetProbeRunner: WetProbeRunner;
|
|
78
84
|
registryV7: CapabilityContractRegistryV7;
|
|
79
85
|
auditStore?: AppendOnlyAuditStore;
|
|
86
|
+
/** Workspace state database for evidence persistence (v7 life_evidence + v8 EvidenceItem). */
|
|
87
|
+
state?: StateDatabase;
|
|
88
|
+
/** Workspace root required for v7 life evidence JSON artifacts. */
|
|
89
|
+
workspaceRoot?: string;
|
|
80
90
|
}
|
|
81
91
|
export declare function createManualRunDispatcher(deps: ManualRunDispatcherDeps): ManualRunDispatcher;
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
import * as crypto from "node:crypto";
|
|
26
26
|
import { recordConnectorAttemptAudit } from "../../observability/services/audit-closure-recorders.js";
|
|
27
27
|
import { heartbeatCheck, } from "./heartbeat-surface.js";
|
|
28
|
+
import { appendLifeEvidence } from "../../storage/life-evidence/append-life-evidence.js";
|
|
29
|
+
import { mapLifeEvidence } from "../../connectors/base/map-life-evidence.js";
|
|
30
|
+
import { normalizeConnectorEvidence } from "../../connectors/evidence-normalizer.js";
|
|
28
31
|
function buildManualContext(input) {
|
|
29
32
|
return {
|
|
30
33
|
triggerSource: "manual_run",
|
|
@@ -40,6 +43,7 @@ export function createManualRunDispatcher(deps) {
|
|
|
40
43
|
const decisionId = `manual:${crypto.randomUUID()}`;
|
|
41
44
|
const intentId = `manual-run:${input.platformId}:${input.capabilityId}`;
|
|
42
45
|
const idempotencyKey = `idem:manual:${crypto.randomUUID()}`;
|
|
46
|
+
const now = new Date().toISOString();
|
|
43
47
|
const connectorResult = await deps.connectorExecutor.executeEffect({
|
|
44
48
|
platformId: input.platformId,
|
|
45
49
|
intent: input.capabilityId,
|
|
@@ -64,11 +68,56 @@ export function createManualRunDispatcher(deps) {
|
|
|
64
68
|
decisionId,
|
|
65
69
|
intentId,
|
|
66
70
|
});
|
|
71
|
+
const evidenceSummary = {
|
|
72
|
+
v8EvidenceIds: [],
|
|
73
|
+
};
|
|
74
|
+
if (connectorResult.status === "success" && deps.state) {
|
|
75
|
+
const capabilityIntent = input.capabilityId;
|
|
76
|
+
// v7 life evidence double-write (parity with heartbeat loop)
|
|
77
|
+
try {
|
|
78
|
+
if (deps.workspaceRoot) {
|
|
79
|
+
const lifeCandidate = mapLifeEvidence({
|
|
80
|
+
platformId: input.platformId,
|
|
81
|
+
intent: capabilityIntent,
|
|
82
|
+
result: connectorResult,
|
|
83
|
+
observedAt: now,
|
|
84
|
+
});
|
|
85
|
+
if (lifeCandidate) {
|
|
86
|
+
const lifeAck = await appendLifeEvidence(deps.state, deps.workspaceRoot, lifeCandidate);
|
|
87
|
+
evidenceSummary.v7EvidenceId = lifeAck.evidenceId;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (v7Err) {
|
|
92
|
+
const msg = v7Err instanceof Error ? v7Err.message : String(v7Err);
|
|
93
|
+
console.warn(`[connector:run] v7 life evidence append failed for ${input.platformId}: ${msg}`);
|
|
94
|
+
}
|
|
95
|
+
// v8 EvidenceItem content-bearing write
|
|
96
|
+
try {
|
|
97
|
+
const v8Result = await normalizeConnectorEvidence(deps.state, {
|
|
98
|
+
status: "success",
|
|
99
|
+
platformId: input.platformId,
|
|
100
|
+
capabilityId: input.capabilityId,
|
|
101
|
+
data: connectorResult.data,
|
|
102
|
+
observedAt: now,
|
|
103
|
+
});
|
|
104
|
+
evidenceSummary.v8EvidenceIds = v8Result.evidenceIds;
|
|
105
|
+
evidenceSummary.emptyReason = v8Result.emptyReason;
|
|
106
|
+
if (v8Result.degraded) {
|
|
107
|
+
console.warn(`[connector:run] v8 evidence normalization degraded for ${input.platformId}: ${v8Result.degraded.reason}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (v8Err) {
|
|
111
|
+
const msg = v8Err instanceof Error ? v8Err.message : String(v8Err);
|
|
112
|
+
console.warn(`[connector:run] v8 evidence normalization failed for ${input.platformId}: ${msg}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
67
115
|
const runResult = {
|
|
68
116
|
connectorResult,
|
|
69
117
|
experienceId,
|
|
70
118
|
triggerSource: ctx.triggerSource,
|
|
71
119
|
affectsHeartbeatCadence: ctx.affectsHeartbeatCadence,
|
|
120
|
+
evidence: evidenceSummary,
|
|
72
121
|
};
|
|
73
122
|
return {
|
|
74
123
|
ok: connectorResult.status === "success",
|
|
@@ -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
|
};
|
|
@@ -785,6 +785,8 @@ export function createOpsRouter(deps) {
|
|
|
785
785
|
wetProbeRunner,
|
|
786
786
|
registryV7,
|
|
787
787
|
auditStore: deps.auditStore,
|
|
788
|
+
state: deps.state,
|
|
789
|
+
workspaceRoot: typeof input?.workspaceRoot === "string" ? input.workspaceRoot : process.cwd(),
|
|
788
790
|
});
|
|
789
791
|
return dispatcher.runConnector({
|
|
790
792
|
platformId,
|
|
@@ -1525,7 +1527,7 @@ export function createOpsRouter(deps) {
|
|
|
1525
1527
|
const platformId = typeof input?.platformId === "string"
|
|
1526
1528
|
? input.platformId
|
|
1527
1529
|
: undefined;
|
|
1528
|
-
const validSceneTypes = ["social", "reply", "outreach", "quiet", "explain", "user_reply"];
|
|
1530
|
+
const validSceneTypes = ["social", "reply", "outreach", "quiet", "heartbeat", "explain", "user_reply"];
|
|
1529
1531
|
if (!validSceneTypes.includes(sceneType)) {
|
|
1530
1532
|
const envelope = {
|
|
1531
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) {
|
|
@@ -185,12 +185,15 @@ function extractMetrics(item) {
|
|
|
185
185
|
// ───────────────────────────────────────────────────────────────
|
|
186
186
|
// Item extraction from connector payload
|
|
187
187
|
// ───────────────────────────────────────────────────────────────
|
|
188
|
-
function extractItems(data) {
|
|
188
|
+
function extractItems(data, depth = 4) {
|
|
189
|
+
if (depth <= 0)
|
|
190
|
+
return [];
|
|
189
191
|
if (Array.isArray(data))
|
|
190
192
|
return data;
|
|
191
193
|
if (!isRecord(data))
|
|
192
194
|
return [];
|
|
193
|
-
|
|
195
|
+
const ARRAY_KEYS = ["items", "data", "results", "posts", "nodes", "agents", "edges", "entries", "feed"];
|
|
196
|
+
for (const key of ARRAY_KEYS) {
|
|
194
197
|
const candidate = data[key];
|
|
195
198
|
if (Array.isArray(candidate))
|
|
196
199
|
return candidate;
|
|
@@ -198,6 +201,16 @@ function extractItems(data) {
|
|
|
198
201
|
// If the payload itself looks like a single item, treat it as one-item array.
|
|
199
202
|
if ("id" in data || "title" in data || "content" in data)
|
|
200
203
|
return [data];
|
|
204
|
+
// Recurse into nested record-valued fields (e.g. real connector runners wrap
|
|
205
|
+
// the platform response in { capability, channel, data: apiResponse }).
|
|
206
|
+
for (const key of ARRAY_KEYS) {
|
|
207
|
+
const candidate = data[key];
|
|
208
|
+
if (isRecord(candidate)) {
|
|
209
|
+
const nested = extractItems(candidate, depth - 1);
|
|
210
|
+
if (nested.length > 0)
|
|
211
|
+
return nested;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
201
214
|
return [];
|
|
202
215
|
}
|
|
203
216
|
function normalizeSingleItem(item, options) {
|
|
@@ -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,
|
|
@@ -187,6 +199,10 @@ function createMoltbookMockRunner(workspaceRoot) {
|
|
|
187
199
|
source: "mock",
|
|
188
200
|
items: Array.isArray(data.items) ? data.items : [],
|
|
189
201
|
},
|
|
202
|
+
// Duplicate items at payload top-level so v8 evidence normalizer
|
|
203
|
+
// can extract content-bearing evidence without re-implementing
|
|
204
|
+
// the legacy v7 nested shape.
|
|
205
|
+
items: Array.isArray(data.items) ? data.items : [],
|
|
190
206
|
},
|
|
191
207
|
};
|
|
192
208
|
}
|
|
@@ -197,8 +213,8 @@ function createMoltbookMockRunner(workspaceRoot) {
|
|
|
197
213
|
latencyMs: Date.now() - started,
|
|
198
214
|
success: false,
|
|
199
215
|
error: {
|
|
200
|
-
code: "
|
|
201
|
-
detail: String(err)
|
|
216
|
+
code: "configuration_missing",
|
|
217
|
+
detail: `moltbook mock unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
202
218
|
},
|
|
203
219
|
};
|
|
204
220
|
}
|
|
@@ -420,9 +436,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
420
436
|
const workspaceManifest = workspaceManifestResult?.manifest;
|
|
421
437
|
const isBuiltInPlatform = platformId === "moltbook" ||
|
|
422
438
|
platformId === "evomap" ||
|
|
423
|
-
platformId === "agent-world"
|
|
439
|
+
platformId === "agent-world" ||
|
|
440
|
+
platformId === "instreet";
|
|
441
|
+
const effectiveWorkspaceManifest = workspaceManifest && (!isBuiltInPlatform || isSafeBuiltInShadow(workspaceManifest))
|
|
442
|
+
? workspaceManifest
|
|
443
|
+
: undefined;
|
|
424
444
|
const requiresCredential = isBuiltInPlatform ||
|
|
425
|
-
Boolean(
|
|
445
|
+
Boolean(effectiveWorkspaceManifest?.credentials.some((credential) => credential.required !== false));
|
|
426
446
|
const credential = requiresCredential
|
|
427
447
|
? await vault.loadCredentialContext(platformId)
|
|
428
448
|
: undefined;
|
|
@@ -444,9 +464,33 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
444
464
|
const activeCredential = credential?.status === "active" && credential.encryptedValue
|
|
445
465
|
? { encryptedValue: credential.encryptedValue }
|
|
446
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
|
+
}
|
|
447
488
|
if (platformId === "moltbook") {
|
|
448
489
|
const baseUrl = process.env.SECOND_NATURE_MOLTBOOK_BASE_URL;
|
|
449
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;
|
|
450
494
|
const apiClient = createMoltbookApiClient({
|
|
451
495
|
baseUrl,
|
|
452
496
|
accessToken: activeCredential.encryptedValue,
|
|
@@ -457,13 +501,13 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
457
501
|
skillRunner: {
|
|
458
502
|
run: async () => {
|
|
459
503
|
throw {
|
|
460
|
-
code: "
|
|
504
|
+
code: "configuration_missing",
|
|
461
505
|
detail: "moltbook_skill_runner_not_configured",
|
|
462
506
|
};
|
|
463
507
|
},
|
|
464
508
|
},
|
|
465
509
|
});
|
|
466
|
-
return runner.run(
|
|
510
|
+
return runner.run(effectivePlan, request);
|
|
467
511
|
}
|
|
468
512
|
// Mock fallback when real API is not configured
|
|
469
513
|
const mockRunner = createMoltbookMockRunner(workspaceRoot);
|
|
@@ -579,28 +623,6 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
579
623
|
},
|
|
580
624
|
};
|
|
581
625
|
}
|
|
582
|
-
// Wave 83: workspace declarative_http connector fallback
|
|
583
|
-
if (workspaceManifest && workspaceManifest.runner.kind === "declarative_http") {
|
|
584
|
-
const httpRunner = createDeclarativeHttpRunner(workspaceManifest, activeCredential);
|
|
585
|
-
return httpRunner.run(_plan, request);
|
|
586
|
-
}
|
|
587
|
-
// Wave 90: workspace scriptable_node connector
|
|
588
|
-
if (workspaceManifest && workspaceManifest.runner.kind === "scriptable_node") {
|
|
589
|
-
if (!workspaceManifestResult) {
|
|
590
|
-
return {
|
|
591
|
-
platformId,
|
|
592
|
-
channel: request.preferredChannel ?? "api_rest",
|
|
593
|
-
latencyMs: Date.now() - started,
|
|
594
|
-
success: false,
|
|
595
|
-
error: {
|
|
596
|
-
code: "configuration_missing",
|
|
597
|
-
detail: "scriptable_node requires workspace manifest with manifestDir",
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
const scriptRunner = createScriptableNodeRunner(workspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
|
|
602
|
-
return scriptRunner.run(_plan, request);
|
|
603
|
-
}
|
|
604
626
|
return {
|
|
605
627
|
platformId,
|
|
606
628
|
channel: request.preferredChannel ?? "api_rest",
|
|
@@ -617,11 +639,12 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
|
|
|
617
639
|
export function createConnectorExecutorAdapter(options) {
|
|
618
640
|
const vault = createCredentialVault(options.stateDb.db);
|
|
619
641
|
const registry = new CapabilityContractRegistry();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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);
|
|
625
648
|
const cooldownPort = createConnectorCooldownPort(options.stateDb);
|
|
626
649
|
const routeContextPort = createCredentialRouteContextPort(vault, options.stateDb);
|
|
627
650
|
const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
|
|
@@ -637,7 +660,7 @@ export function createConnectorExecutorAdapter(options) {
|
|
|
637
660
|
});
|
|
638
661
|
return {
|
|
639
662
|
async executeEffect(input) {
|
|
640
|
-
registerWorkspaceManifests(registry, options.workspaceRoot);
|
|
663
|
+
registerWorkspaceManifests(registry, builtInPlatformIds, options.workspaceRoot);
|
|
641
664
|
return policy.executeWithPolicy(input.intent, {
|
|
642
665
|
platformId: input.platformId,
|
|
643
666
|
intent: input.intent,
|