@haaaiawd/second-nature 0.1.31 → 0.1.32

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 CHANGED
@@ -71,7 +71,7 @@ process.stderr.write("[second-nature] module evaluated\n");
71
71
  const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
72
72
  const HOST_SAFE_LIMITATION_MESSAGE = "Host-safe plugin package keeps synchronous register/load semantics, but mutating workspace runtime flows remain unavailable here.";
73
73
  const SETUP_MARKER_RELATIVE_PATH = path.join(".second-nature", "setup", "agent-inner-guide-ack.json");
74
- const SETUP_GUIDE_VERSION = "0.1.31";
74
+ const SETUP_GUIDE_VERSION = "0.1.32";
75
75
  const SETUP_COMMANDS = new Set(["setup_hint", "setup_ack"]);
76
76
  let activationSpine = null;
77
77
  /** T1.1.4 — lazily opened full read bridge; closed when workspace root / resolution changes. */
@@ -1200,7 +1200,7 @@ export default {
1200
1200
  };
1201
1201
  }
1202
1202
  const resolved = spine.router.resolve(parsed.command);
1203
- if (!resolved) {
1203
+ if (!resolved && !isWorkspaceBridgeCommand(parsed.command, parsed.input)) {
1204
1204
  return {
1205
1205
  text: JSON.stringify({
1206
1206
  ok: false,
@@ -1219,7 +1219,7 @@ export default {
1219
1219
  const spine = ensureActivationSpine();
1220
1220
  syncWorkspaceRootFromTool(spine, params.workspaceRoot);
1221
1221
  const resolved = spine.router.resolve(params.command);
1222
- if (!resolved) {
1222
+ if (!resolved && !isWorkspaceBridgeCommand(params.command, params.args)) {
1223
1223
  return {
1224
1224
  content: [
1225
1225
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "second-nature",
3
3
  "name": "Second Nature",
4
- "version": "0.1.31",
4
+ "version": "0.1.32",
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. v7 ops surface: self_health, tool_affordance, heartbeat_digest, narrative:diff, timeline, restore, runtime_secret_bootstrap.",
6
6
  "activation": {
7
7
  "onStartup": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haaaiawd/second-nature",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
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",
@@ -9,6 +9,15 @@ import { resolvePackagedRuntime } from "./runtime/runtime-artifact-boundary.js";
9
9
  import { createRestoreSnapshotStore, } from "../storage/services/restore-snapshot-store.js";
10
10
  import { createRuntimeDecisionRecorder, } from "../observability/services/runtime-decision-recorder.js";
11
11
  import { createConnectorExecutorAdapter, } from "../connectors/services/connector-executor-adapter.js";
12
+ import { CapabilityContractRegistry } from "../connectors/base/manifest.js";
13
+ import { CapabilityContractRegistryV7 } from "../connectors/base/manifest-v7.js";
14
+ import { moltbookManifest } from "../connectors/social-community/moltbook/manifest.js";
15
+ import { evomapManifest } from "../connectors/agent-network/evomap/manifest.js";
16
+ import { agentWorldManifest } from "../connectors/agent-network/agent-world/manifest.js";
17
+ import { instreetManifest } from "../connectors/social-community/instreet/manifest.js";
18
+ import { createAffordanceAssembler, } from "../core/second-nature/body/tool-affordance/affordance-assembler.js";
19
+ import { createHistoryDigestStore, } from "../storage/services/history-digest-store.js";
20
+ import { probeCredentialHealth, } from "../storage/services/credential-vault.js";
12
21
  import { DynamicConnectorRegistry, createRegistrySnapshotStore, } from "../connectors/registry/index.js";
13
22
  /** Built-in connector manifests for DynamicConnectorRegistry. */
14
23
  const BUILT_IN_CONNECTOR_MANIFESTS = [
@@ -54,6 +63,154 @@ const BUILT_IN_CONNECTOR_MANIFESTS = [
54
63
  sourceRefPolicy: { minSourceRefs: 1 },
55
64
  },
56
65
  ];
66
+ const BUILT_IN_CAPABILITY_MANIFESTS = [
67
+ moltbookManifest,
68
+ evomapManifest,
69
+ agentWorldManifest,
70
+ instreetManifest,
71
+ ];
72
+ function idempotencyClassForCapability(capabilityId) {
73
+ return capabilityId.includes("read") ||
74
+ capabilityId.includes("discover") ||
75
+ capabilityId.includes("list") ||
76
+ capabilityId.includes("heartbeat")
77
+ ? "read_only"
78
+ : "idempotent_write";
79
+ }
80
+ function createCapabilityRegistry() {
81
+ const registry = new CapabilityContractRegistry();
82
+ for (const manifest of BUILT_IN_CAPABILITY_MANIFESTS) {
83
+ registry.register(manifest);
84
+ }
85
+ return registry;
86
+ }
87
+ function createAffordanceRegistry(registry, workspaceRoot) {
88
+ registry.reloadConnectors(workspaceRoot);
89
+ const v7 = new CapabilityContractRegistryV7();
90
+ for (const entry of registry.listConnectors()) {
91
+ v7.register({
92
+ platformId: entry.platformId,
93
+ capabilities: entry.capabilities.map((capabilityId) => ({
94
+ capabilityId,
95
+ intent: capabilityId,
96
+ probeConfig: {
97
+ safeEndpoint: `connector://${entry.platformId}/${capabilityId}`,
98
+ idempotencyClass: idempotencyClassForCapability(capabilityId),
99
+ },
100
+ })),
101
+ channelPriority: ["api_rest"],
102
+ credentialTypes: ["api_key"],
103
+ sourceRefPolicy: { minSourceRefs: 1 },
104
+ });
105
+ }
106
+ return v7;
107
+ }
108
+ function createWorkspaceAffordanceAssembler(registry, workspaceRoot) {
109
+ return createAffordanceAssembler({
110
+ registry: createAffordanceRegistry(registry, workspaceRoot),
111
+ probeReader: {
112
+ getLatestProbeResult() {
113
+ return undefined;
114
+ },
115
+ },
116
+ credentialRequired: () => false,
117
+ });
118
+ }
119
+ function stringifyDeltaField(delta, key) {
120
+ const value = delta[key];
121
+ if (value === undefined || value === null)
122
+ return undefined;
123
+ return typeof value === "string" ? value : JSON.stringify(value);
124
+ }
125
+ function asStringArray(value) {
126
+ return Array.isArray(value)
127
+ ? value.filter((item) => typeof item === "string")
128
+ : [];
129
+ }
130
+ function createNarrativeTimelineDeps(stateDb) {
131
+ const historyStore = createHistoryDigestStore(stateDb);
132
+ return {
133
+ stateMemoryPort: {
134
+ async listNarrativeTimeline(from, to, opts) {
135
+ const rows = await historyStore.listNarrativeTimeline({
136
+ limit: Math.max(opts?.limit ?? 100, 100),
137
+ });
138
+ return rows
139
+ .filter((row) => row.createdAt >= from && row.createdAt <= to)
140
+ .filter((row) => opts?.afterTimestamp ? row.createdAt > opts.afterTimestamp : true)
141
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt))
142
+ .slice(0, opts?.limit ?? rows.length)
143
+ .map((row) => ({
144
+ version: row.timelineId,
145
+ createdAt: row.createdAt,
146
+ triggerKind: row.entryType === "restore.applied" ||
147
+ row.entryType === "goal.transition" ||
148
+ row.entryType === "dream.projection" ||
149
+ row.entryType === "owner.override"
150
+ ? row.entryType
151
+ : "heartbeat.decision",
152
+ sourceRefs: asStringArray(row.delta.sourceRefs),
153
+ reasonCode: stringifyDeltaField(row.delta, "reasonCode"),
154
+ summaryText: stringifyDeltaField(row.delta, "summaryText") ??
155
+ `${row.entryType}:${row.subjectId}`,
156
+ }));
157
+ },
158
+ async getNarrativeSnapshot(version) {
159
+ const rows = await historyStore.listNarrativeTimeline({ limit: 500 });
160
+ const row = rows.find((candidate) => candidate.timelineId === version || candidate.subjectId === version);
161
+ if (!row)
162
+ return null;
163
+ const delta = row.delta;
164
+ return {
165
+ version,
166
+ focus: delta.focus,
167
+ progress: delta.progress,
168
+ nextIntent: delta.nextIntent,
169
+ toneSignal: delta.toneSignal,
170
+ acceptedGoalId: delta.acceptedGoalId,
171
+ sourceRefs: asStringArray(delta.sourceRefs),
172
+ lastChangeReasonCode: stringifyDeltaField(delta, "reasonCode"),
173
+ };
174
+ },
175
+ },
176
+ };
177
+ }
178
+ function createSecretAnchorDeps(stateDb) {
179
+ return {
180
+ runtimeOpsPort: {
181
+ getEncryptionKeyPath: () => "SECOND_NATURE_ENCRYPTION_KEY",
182
+ checkKeyPathExists: async () => {
183
+ const key = process.env.SECOND_NATURE_ENCRYPTION_KEY?.trim();
184
+ return Boolean(key && key.length >= 32);
185
+ },
186
+ },
187
+ credentialPort: {
188
+ verifySampleDecrypt: async () => {
189
+ const result = stateDb.sqlite.exec(`SELECT platform_id, encrypted_value
190
+ FROM credential_records
191
+ WHERE encrypted_value IS NOT NULL AND encrypted_value != ''
192
+ LIMIT 3`);
193
+ if (result.length === 0 || result[0].values.length === 0) {
194
+ return { status: "ok", checkedIds: [] };
195
+ }
196
+ const checkedIds = [];
197
+ for (const row of result[0].values) {
198
+ const platformId = String(row[0] ?? "unknown");
199
+ const encryptedValue = typeof row[1] === "string" ? row[1] : "";
200
+ checkedIds.push(platformId);
201
+ const probe = probeCredentialHealth(platformId, encryptedValue, undefined);
202
+ if (probe.keyHealth === "wrong_key") {
203
+ return { status: "wrong_key", checkedIds };
204
+ }
205
+ if (probe.keyHealth !== "ok") {
206
+ return { status: "error", checkedIds };
207
+ }
208
+ }
209
+ return { status: "ok", checkedIds };
210
+ },
211
+ },
212
+ };
213
+ }
57
214
  export function createCliRuntimeDeps(overrides = {}) {
58
215
  const workspaceRoot = overrides.workspaceRoot ?? process.cwd();
59
216
  const stateDb = overrides.stateDb ?? createStateDatabase();
@@ -79,6 +236,11 @@ export function createCliRuntimeDeps(overrides = {}) {
79
236
  builtInManifests: BUILT_IN_CONNECTOR_MANIFESTS,
80
237
  snapshotStore: createRegistrySnapshotStore(),
81
238
  });
239
+ const capabilityRegistry = overrides.capabilityRegistry ?? createCapabilityRegistry();
240
+ const affordanceAssembler = overrides.affordanceAssembler ??
241
+ createWorkspaceAffordanceAssembler(registry, workspaceRoot);
242
+ const narrativeTimelineDeps = overrides.narrativeTimelineDeps ?? createNarrativeTimelineDeps(stateDb);
243
+ const secretAnchorDeps = overrides.secretAnchorDeps ?? createSecretAnchorDeps(stateDb);
82
244
  const restoreSnapshotStore = overrides.restoreSnapshotStore ?? createRestoreSnapshotStore(stateDb);
83
245
  return {
84
246
  stateDb,
@@ -88,7 +250,11 @@ export function createCliRuntimeDeps(overrides = {}) {
88
250
  actionBridge,
89
251
  runtimeRecorder,
90
252
  connectorExecutor,
253
+ capabilityRegistry,
91
254
  registry,
255
+ affordanceAssembler,
256
+ narrativeTimelineDeps,
257
+ secretAnchorDeps,
92
258
  restoreSnapshotStore,
93
259
  };
94
260
  }
@@ -104,7 +270,11 @@ export function createCommandRouter(options = {}) {
104
270
  workspaceRoot,
105
271
  observabilityDb: runtime.observabilityDb,
106
272
  connectorExecutor: runtime.connectorExecutor,
273
+ connectorRegistry: runtime.capabilityRegistry,
107
274
  registry: runtime.registry,
275
+ toolAffordancePort: runtime.affordanceAssembler,
276
+ narrativeTimelineDeps: runtime.narrativeTimelineDeps,
277
+ secretAnchorDeps: runtime.secretAnchorDeps,
108
278
  restoreSnapshotStore: runtime.restoreSnapshotStore,
109
279
  });
110
280
  const commands = createCliCommands({
@@ -10,6 +10,7 @@ import type { CliReadModels } from "../read-models/index.js";
10
10
  import type { RuntimeDecisionRecorder } from "../../observability/services/runtime-decision-recorder.js";
11
11
  import type { StateDatabase } from "../../storage/db/index.js";
12
12
  import type { ConnectorExecutor } from "../../core/second-nature/orchestrator/effect-dispatcher.js";
13
+ import type { CapabilityContractRegistry } from "../../connectors/base/manifest.js";
13
14
  export type HeartbeatSurfaceStatus = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
14
15
  export interface HeartbeatSurfaceResult {
15
16
  ok: boolean;
@@ -47,5 +48,7 @@ export interface HeartbeatCheckInput {
47
48
  * connector-system instead of returning connector_dispatch_unwired.
48
49
  */
49
50
  connectorExecutor?: ConnectorExecutor;
51
+ /** Capability registry used by planner to avoid platform/capability protocol mismatches. */
52
+ connectorRegistry?: CapabilityContractRegistry;
50
53
  }
51
54
  export declare function heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
@@ -74,6 +74,7 @@ export async function heartbeatCheck(input) {
74
74
  state: input.state,
75
75
  workspaceRoot: input.workspaceRoot ?? process.cwd(),
76
76
  connectorExecutor: input.connectorExecutor,
77
+ connectorRegistry: input.connectorRegistry,
77
78
  });
78
79
  const cycle = await run(signal);
79
80
  return mapCycleToSurface(cycle, "workspace_full_runtime");
@@ -10,6 +10,8 @@ import { type NarrativeTimelineDeps } from "../../observability/services/narrati
10
10
  import { type SecretAnchorDeps } from "../../observability/services/runtime-secret-anchor-view.js";
11
11
  import { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
12
12
  import type { RestoreSnapshotStore } from "../../storage/services/restore-snapshot-store.js";
13
+ import type { CapabilityContractRegistry } from "../../connectors/base/manifest.js";
14
+ import type { AffordanceAssembler } from "../../core/second-nature/body/tool-affordance/affordance-assembler.js";
13
15
  /** Unified response envelope for all v7 runtime-ops commands. */
14
16
  export interface RuntimeOpsEnvelope<T = unknown> {
15
17
  ok: boolean;
@@ -49,6 +51,8 @@ export interface OpsRouterDeps {
49
51
  * connector-system instead of returning connector_dispatch_unwired.
50
52
  */
51
53
  connectorExecutor?: ConnectorExecutor;
54
+ /** Capability registry used by heartbeat planner to avoid platform/capability mismatches. */
55
+ connectorRegistry?: CapabilityContractRegistry;
52
56
  /**
53
57
  * T1.2.3: DynamicConnectorRegistry for connector:status and connector:test commands.
54
58
  */
@@ -67,6 +71,8 @@ export interface OpsRouterDeps {
67
71
  * Deps for narrative timeline (narrative:diff, timeline commands).
68
72
  */
69
73
  narrativeTimelineDeps?: NarrativeTimelineDeps;
74
+ /** Port for tool_affordance command. */
75
+ toolAffordancePort?: AffordanceAssembler;
70
76
  /**
71
77
  * Deps for runtime_secret_bootstrap (key anchor health check).
72
78
  */
@@ -63,6 +63,8 @@ export function createOpsRouter(deps) {
63
63
  state: input.state ?? deps.state,
64
64
  workspaceRoot: input.workspaceRoot ?? deps.workspaceRoot,
65
65
  connectorExecutor: input.connectorExecutor ?? deps.connectorExecutor,
66
+ connectorRegistry: input
67
+ ?.connectorRegistry ?? deps.connectorRegistry,
66
68
  }),
67
69
  async dispatch(command, input) {
68
70
  if (command === "heartbeat_check") {
@@ -91,6 +93,8 @@ export function createOpsRouter(deps) {
91
93
  scopeHint: input?.scopeHint,
92
94
  connectorExecutor: input
93
95
  ?.connectorExecutor ?? deps.connectorExecutor,
96
+ connectorRegistry: input
97
+ ?.connectorRegistry ?? deps.connectorRegistry,
94
98
  });
95
99
  }
96
100
  if (command === "fallback") {
@@ -431,6 +435,38 @@ export function createOpsRouter(deps) {
431
435
  */
432
436
  if (command === "tool_affordance") {
433
437
  const generatedAt = new Date().toISOString();
438
+ if (deps.toolAffordancePort) {
439
+ const allStatuses = [
440
+ "safe",
441
+ "exploratory",
442
+ "needs_auth",
443
+ "painful",
444
+ "unavailable",
445
+ ];
446
+ const platformIds = Array.isArray(input?.platformIds)
447
+ ? input.platformIds.filter((item) => typeof item === "string")
448
+ : typeof input?.platformId === "string"
449
+ ? [input.platformId]
450
+ : undefined;
451
+ const data = await deps.toolAffordancePort.assembleAffordanceMap({
452
+ platformIds,
453
+ allowedStatuses: allStatuses,
454
+ goalKind: typeof input?.goalKind === "string" ? input.goalKind : undefined,
455
+ });
456
+ const envelope = {
457
+ ok: true,
458
+ command: "tool_affordance",
459
+ runtimeMode: "workspace_full_runtime",
460
+ surfaceMode: "cli",
461
+ generatedAt,
462
+ data,
463
+ warnings: [],
464
+ sourceRefs: [
465
+ "core/second-nature/body/tool-affordance/affordance-assembler.ts",
466
+ ],
467
+ };
468
+ return envelope;
469
+ }
434
470
  const envelope = {
435
471
  ok: false,
436
472
  command: "tool_affordance",
@@ -71,9 +71,13 @@ export async function openWorkspaceOpsBridge(workspaceRoot) {
71
71
  state: stateDb,
72
72
  workspaceRoot: resolvedRoot,
73
73
  connectorExecutor: deps.connectorExecutor,
74
+ connectorRegistry: deps.capabilityRegistry,
74
75
  registry: deps.registry,
76
+ toolAffordancePort: deps.affordanceAssembler,
75
77
  // v7 (T-ROS.C.2): in-memory audit store for heartbeat_digest / restore / self_health
76
78
  auditStore,
79
+ narrativeTimelineDeps: deps.narrativeTimelineDeps,
80
+ secretAnchorDeps: deps.secretAnchorDeps,
77
81
  restoreSnapshotStore: deps.restoreSnapshotStore,
78
82
  });
79
83
  const commands = commandsMod.createCliCommands({