@haaaiawd/second-nature 0.1.29 → 0.1.31

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
@@ -58,23 +58,11 @@ import { fileURLToPath } from "node:url";
58
58
  import { startRuntimeService, } from "./runtime/core/second-nature/runtime/service-entry.js";
59
59
  import { getLifecycleState, recordRegistration, } from "./runtime/core/second-nature/runtime/lifecycle-service.js";
60
60
  import { openWorkspaceOpsBridge } from "./workspace-ops-bridge.js";
61
- // definePluginEntry is OpenClaw's canonical factory for non-channel plugins
62
- // (provider/tool/command/service/memory/context-engine). At runtime it returns
63
- // a plain options object; it does NOT add a brand symbol earlier debugging
64
- // rounds wrongly assumed the factory was the "plain-capability" marker. The
65
- // real classification happens via manifest fields (see file header). We still
66
- // use the factory because it is the documented, supported entry shape, and
67
- // keeping it future-proof against SDK option-processing changes.
68
- //
69
- // IMPORTANT — keep this a STATIC import. The packaged runtime is loaded inside
70
- // OpenClaw's vm sandbox, which rejects top-level await (manifests as
71
- // "SyntaxError: Unexpected identifier 'Promise'" at host load time). The same
72
- // constraint applies to the sql.js async bootstrap noted in the file header.
73
- // In production the host always provides `openclaw` as a sibling module under
74
- // ~/.openclaw/npm/node_modules/, so this resolves synchronously. Locally,
75
- // `openclaw` is declared as a devDependency so build and tests resolve via
76
- // the same import path.
77
- import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
61
+ // Keep the entry as a plain object instead of importing OpenClaw's
62
+ // SDK entry helper. Upload/package validators may import this module
63
+ // before the host SDK is installed; a static SDK import turns a valid package
64
+ // into ERR_MODULE_NOT_FOUND. OpenClaw classifies this plugin from the manifest
65
+ // fields, and the helper returns this same object shape at runtime.
78
66
  // Stderr sentinels make daemon load-path observable in `gateway.log`. Three
79
67
  // lines should appear at startup: "module evaluated", "register() entered ...",
80
68
  // "register() completed". Their absence after `openclaw gateway run` proves
@@ -83,7 +71,7 @@ process.stderr.write("[second-nature] module evaluated\n");
83
71
  const INTERNAL_RUNTIME_TRACE_PREFIX = "sn-runtime-";
84
72
  const HOST_SAFE_LIMITATION_MESSAGE = "Host-safe plugin package keeps synchronous register/load semantics, but mutating workspace runtime flows remain unavailable here.";
85
73
  const SETUP_MARKER_RELATIVE_PATH = path.join(".second-nature", "setup", "agent-inner-guide-ack.json");
86
- const SETUP_GUIDE_VERSION = "0.1.28";
74
+ const SETUP_GUIDE_VERSION = "0.1.31";
87
75
  const SETUP_COMMANDS = new Set(["setup_hint", "setup_ack"]);
88
76
  let activationSpine = null;
89
77
  /** T1.1.4 — lazily opened full read bridge; closed when workspace root / resolution changes. */
@@ -1189,7 +1177,7 @@ const SECOND_NATURE_TOOL_SCHEMA = {
1189
1177
  },
1190
1178
  required: ["command"],
1191
1179
  };
1192
- export default definePluginEntry({
1180
+ export default {
1193
1181
  id: "second-nature",
1194
1182
  name: "Second Nature",
1195
1183
  description: "Registers command/tool/service surface with load-reload lifecycle semantics.",
@@ -1264,4 +1252,4 @@ export default definePluginEntry({
1264
1252
  });
1265
1253
  process.stderr.write("[second-nature] register() completed\n");
1266
1254
  },
1267
- });
1255
+ };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "second-nature",
3
3
  "name": "Second Nature",
4
- "version": "0.1.29",
4
+ "version": "0.1.31",
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.29",
3
+ "version": "0.1.31",
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",
@@ -7,6 +7,7 @@ export interface ConnectorStatusInput {
7
7
  export interface ConnectorTestInput {
8
8
  platformId: string;
9
9
  dryRun?: boolean;
10
+ workspaceRoot?: string;
10
11
  }
11
12
  export declare function connectorStatus(registry: DynamicConnectorRegistry | undefined, ledger: ConnectorInventoryLedger | undefined, input?: ConnectorStatusInput): Promise<Record<string, unknown>>;
12
13
  export declare function connectorTest(registry: DynamicConnectorRegistry | undefined, input: ConnectorTestInput): Promise<Record<string, unknown>>;
@@ -97,6 +97,11 @@ export async function connectorTest(registry, input) {
97
97
  },
98
98
  };
99
99
  }
100
+ // Workspace bridge calls are often process-isolated per tool invocation.
101
+ // Reload here as well as in connector_status so dry-run tests see the same inventory.
102
+ if (input.workspaceRoot) {
103
+ registry.reloadConnectors(input.workspaceRoot);
104
+ }
100
105
  const entry = registry.describeConnector(platformId);
101
106
  if (!entry) {
102
107
  return {
@@ -23,6 +23,14 @@ function explainSubjectError(code, message) {
23
23
  }
24
24
  export function createCliCommands(deps) {
25
25
  const { readModels, actionBridge, opsRouter } = deps;
26
+ const opsCommand = (name, description) => ({
27
+ name,
28
+ description,
29
+ execute: async (input) => {
30
+ const surface = await Promise.resolve(opsRouter.dispatch(name, input));
31
+ return surface;
32
+ },
33
+ });
26
34
  return [
27
35
  {
28
36
  name: "status",
@@ -268,6 +276,14 @@ export function createCliCommands(deps) {
268
276
  return surface;
269
277
  },
270
278
  },
279
+ opsCommand("connector:run", "T-ROS.C.3 — manually execute a connector capability outside heartbeat cadence"),
280
+ opsCommand("self_health", "T-ROS.C.1 — show v7 self-health snapshot and degraded dimensions"),
281
+ opsCommand("tool_affordance", "T-ROS.C.1 — show v7 tool affordance map or explicit unavailable state"),
282
+ opsCommand("heartbeat_digest", "T-ROS.C.1 — assemble v7 heartbeat digest for a day"),
283
+ opsCommand("narrative:diff", "T-ROS.C.1 — compare two narrative timeline versions"),
284
+ opsCommand("timeline", "T-ROS.C.1 — query v7 narrative timeline with cursor pagination"),
285
+ opsCommand("restore", "T-ROS.C.1 — apply bounded restore and write restore audit"),
286
+ opsCommand("runtime_secret_bootstrap", "T-ROS.C.1 — inspect runtime secret anchor health without exposing plaintext"),
271
287
  {
272
288
  name: "goal",
273
289
  description: "T1.2.4 — owner-governed goal operations: set, list, accept, reject",
@@ -249,6 +249,9 @@ export function createOpsRouter(deps) {
249
249
  const result = await connectorTest(deps.registry, {
250
250
  platformId: typeof input?.platformId === "string" ? input.platformId : "",
251
251
  dryRun: isWet ? false : (input?.dryRun === false ? false : true),
252
+ workspaceRoot: typeof input?.workspaceRoot === "string"
253
+ ? input.workspaceRoot
254
+ : deps.workspaceRoot,
252
255
  });
253
256
  if (isWet && result.ok) {
254
257
  // Annotate result with manual trigger context (DR-038 / T-ROS.C.3)
@@ -79,6 +79,9 @@ export function planIntentWithKind(kind, basePriority, runtime, context, registr
79
79
  return [];
80
80
  const config = INTENT_CONFIGS[kind];
81
81
  const platformId = resolvePlatformForIntent(kind, context ?? {}, registry);
82
+ if (kind === "work" && !options?.multiSource && !platformId) {
83
+ return [];
84
+ }
82
85
  let priority = basePriority;
83
86
  // Social budget exhaustion → cap priority.
84
87
  if (kind === "social" &&
@@ -14,7 +14,19 @@ function getPlatformIds(registry) {
14
14
  return registry.listRegisteredPlatformIds();
15
15
  }
16
16
  // Fallback: built-in platforms when registry is absent (backward compat)
17
- return ["moltbook", "instreet", "evomap"];
17
+ return ["moltbook", "instreet", "evomap", "agent-world"];
18
+ }
19
+ const FALLBACK_PLATFORM_CAPABILITIES = {
20
+ "moltbook": ["feed.read", "post.publish", "comment.reply", "message.send"],
21
+ "instreet": ["notification.list", "message.send", "comment.reply", "agent.heartbeat"],
22
+ "evomap": ["agent.register", "agent.heartbeat", "work.discover", "task.claim"],
23
+ "agent-world": ["feed.read", "work.discover", "task.claim"],
24
+ };
25
+ function fallbackPlatformSupportsCapability(platformId, kind) {
26
+ const capability = kindToCapability(kind);
27
+ if (!capability)
28
+ return false;
29
+ return (FALLBACK_PLATFORM_CAPABILITIES[platformId] ?? []).includes(capability);
18
30
  }
19
31
  function extractPlatformIdsFromGoals(goals, kind, platformIds) {
20
32
  const capability = kindToCapability(kind);
@@ -110,6 +122,10 @@ export function resolvePlatformForIntent(kind, context, registry) {
110
122
  // Registry says unsupported → undefined (guard layer will deny)
111
123
  return undefined;
112
124
  }
113
- // No registry: best-effort return the single candidate (backward compat)
125
+ // No registry: keep legacy platform-name fallback, but do not invent an
126
+ // unsupported platform/capability pair that later fails as protocol_mismatch.
127
+ if (!fallbackPlatformSupportsCapability(single, kind)) {
128
+ return undefined;
129
+ }
114
130
  return single;
115
131
  }
@@ -23,7 +23,19 @@ import fs from "node:fs";
23
23
  import path from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
25
  export async function openWorkspaceOpsBridge(workspaceRoot) {
26
- const resolvedRoot = path.resolve(workspaceRoot);
26
+ const declaredRoot = typeof workspaceRoot === "string" ? workspaceRoot.trim() : "";
27
+ if (!declaredRoot) {
28
+ return {
29
+ ok: false,
30
+ error: {
31
+ code: "WORKSPACE_ROOT_REQUIRED",
32
+ message: "openWorkspaceOpsBridge requires a workspaceRoot path. Set SECOND_NATURE_WORKSPACE_ROOT or pass tool workspaceRoot.",
33
+ nextStep: "reinvoke_with_workspaceRoot_or_set_SECOND_NATURE_WORKSPACE_ROOT",
34
+ requiredUserInput: ["workspaceRoot"],
35
+ },
36
+ };
37
+ }
38
+ const resolvedRoot = path.resolve(declaredRoot);
27
39
  try {
28
40
  const pluginPackageRoot = path.dirname(fileURLToPath(import.meta.url));
29
41
  // Packaged `plugin/runtime` is emitted JS without sibling `.d.ts` in this repo layout.