@haaaiawd/second-nature 0.1.41 → 0.1.43

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "second-nature",
3
3
  "name": "Second Nature",
4
- "version": "0.1.41",
4
+ "version": "0.1.43",
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, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
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.41",
3
+ "version": "0.1.43",
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",
@@ -285,6 +285,7 @@ export function createCliCommands(deps) {
285
285
  opsCommand("timeline", "T-ROS.C.1 — query v7 narrative timeline with cursor pagination"),
286
286
  opsCommand("restore", "T-ROS.C.1 — apply bounded restore and write restore audit"),
287
287
  opsCommand("runtime_secret_bootstrap", "T-ROS.C.1 — inspect runtime secret anchor health without exposing plaintext"),
288
+ opsCommand("guidance_payload", "T-V7C.C.4R — assemble impulse + atmosphere for a scene context"),
288
289
  {
289
290
  name: "goal",
290
291
  description: "T1.2.4 — owner-governed goal operations: set, list, accept, reject",
@@ -13,6 +13,8 @@ import { showOperatorFallback, OperatorFallbackNotFoundError, } from "./show-ope
13
13
  import { probeHostCapability } from "../host-capability/probe-host-capability.js";
14
14
  import { recordHostCapability } from "../host-capability/record-host-capability.js";
15
15
  import { runNearRealConnectorSmoke } from "../../connectors/near-real/near-real-connector-smoke.js";
16
+ import { scanConnectorManifests } from "../../connectors/registry/manifest-scanner.js";
17
+ import { parseConnectorManifestV6 } from "../../connectors/manifest/manifest-parser.js";
16
18
  import { connectorInit } from "../commands/connector-init.js";
17
19
  import { connectorBehaviorAdd } from "../commands/connector-behavior.js";
18
20
  import { connectorStatus, connectorTest } from "../commands/connector-status.js";
@@ -292,7 +294,7 @@ async function captureRuntimeSnapshot(deps, input) {
292
294
  * T1.2.8 — static local adapter: all checks return `unknown` when no real host is available.
293
295
  * Allows `capability_probe` to be called from CLI / workspace bridge without requiring a live host.
294
296
  */
295
- function createStaticUnknownAdapter() {
297
+ function createStaticUnknownAdapter(workspaceRoot) {
296
298
  const now = new Date().toISOString();
297
299
  const unknownResult = (name) => ({
298
300
  name,
@@ -301,11 +303,35 @@ function createStaticUnknownAdapter() {
301
303
  reason: "static_local_probe_no_host_context",
302
304
  evidenceRefs: [],
303
305
  });
306
+ function checkDeliveryTarget() {
307
+ if (!workspaceRoot) {
308
+ return { status: "target_none", evidenceRefs: [], reason: "no_workspace_root_provided" };
309
+ }
310
+ const deliveryCapabilities = ["message.send", "comment.reply"];
311
+ const scanned = scanConnectorManifests(workspaceRoot);
312
+ for (const manifestFile of scanned) {
313
+ const parsed = parseConnectorManifestV6(manifestFile.content, manifestFile.path);
314
+ if (parsed.ok && parsed.manifest.capabilities.some((cap) => deliveryCapabilities.includes(cap.id))) {
315
+ return {
316
+ status: "target_available",
317
+ evidenceRefs: [
318
+ {
319
+ id: `delivery:${parsed.manifest.platformId}`,
320
+ kind: "workspace_artifact",
321
+ uri: `workspace://connectors/${parsed.manifest.platformId}/manifest.yaml`,
322
+ observedAt: now,
323
+ },
324
+ ],
325
+ };
326
+ }
327
+ }
328
+ return { status: "target_none", evidenceRefs: [], reason: "no_delivery_connector_found_in_workspace" };
329
+ }
304
330
  return {
305
331
  checkPluginLoad: () => unknownResult("plugin_load"),
306
332
  checkHeartbeatBridge: () => unknownResult("heartbeat_bridge"),
307
333
  checkHeartbeatToolInvocation: () => unknownResult("heartbeat_tool_invocation"),
308
- checkDeliveryTarget: () => ({ status: "unknown", evidenceRefs: [] }),
334
+ checkDeliveryTarget,
309
335
  checkAckDropBehavior: () => unknownResult("ack_drop"),
310
336
  checkHookSupport: () => [],
311
337
  };
@@ -487,7 +513,7 @@ export function createOpsRouter(deps) {
487
513
  // T1.2.8 (SN-CODE-03): run host capability probe with static unknown adapter (CLI context).
488
514
  // Persists report when observabilityDb is available; returns safe JSON subset.
489
515
  return (async () => {
490
- const adapter = createStaticUnknownAdapter();
516
+ const adapter = createStaticUnknownAdapter(deps.workspaceRoot);
491
517
  const docCheckedAt = new Date().toISOString();
492
518
  const report = probeHostCapability({
493
519
  adapter,
@@ -44,6 +44,7 @@ export interface ConnectorResult<T> {
44
44
  channel: ChannelType;
45
45
  latencyMs: number;
46
46
  degraded?: boolean;
47
+ detail?: string;
47
48
  };
48
49
  }
49
50
  export interface RawAttempt {
@@ -1,4 +1,4 @@
1
- export declare const FAILURE_CLASSES: readonly ["transport_failure", "auth_failure", "credential_expired", "verification_required", "rate_limited", "cooldown_blocked", "parse_failure", "protocol_mismatch", "semantic_rejection", "idempotency_conflict", "concurrency_conflict", "permanent_input_error", "unknown_platform_change"];
1
+ export declare const FAILURE_CLASSES: readonly ["transport_failure", "auth_failure", "credential_expired", "verification_required", "rate_limited", "cooldown_blocked", "parse_failure", "protocol_mismatch", "semantic_rejection", "idempotency_conflict", "concurrency_conflict", "permanent_input_error", "platform_unavailable", "configuration_missing", "script_error", "timeout", "unknown_platform_change"];
2
2
  export type FailureClass = (typeof FAILURE_CLASSES)[number];
3
3
  export interface FailureClassification {
4
4
  class: FailureClass;
@@ -11,6 +11,10 @@ export const FAILURE_CLASSES = [
11
11
  "idempotency_conflict",
12
12
  "concurrency_conflict",
13
13
  "permanent_input_error",
14
+ "platform_unavailable",
15
+ "configuration_missing",
16
+ "script_error",
17
+ "timeout",
14
18
  "unknown_platform_change",
15
19
  ];
16
20
  const RETRYABLE_BY_CLASS = {
@@ -26,6 +30,10 @@ const RETRYABLE_BY_CLASS = {
26
30
  idempotency_conflict: false,
27
31
  concurrency_conflict: true,
28
32
  permanent_input_error: false,
33
+ platform_unavailable: false,
34
+ configuration_missing: false,
35
+ script_error: false,
36
+ timeout: true,
29
37
  unknown_platform_change: false,
30
38
  };
31
39
  export class ConnectorPolicyError extends Error {
@@ -118,6 +126,26 @@ export function classifyFailure(error) {
118
126
  class: "permanent_input_error",
119
127
  retryable: RETRYABLE_BY_CLASS.permanent_input_error,
120
128
  };
129
+ if (code === "platform_unavailable")
130
+ return {
131
+ class: "platform_unavailable",
132
+ retryable: RETRYABLE_BY_CLASS.platform_unavailable,
133
+ };
134
+ if (code === "configuration_missing")
135
+ return {
136
+ class: "configuration_missing",
137
+ retryable: RETRYABLE_BY_CLASS.configuration_missing,
138
+ };
139
+ if (code === "script_error")
140
+ return {
141
+ class: "script_error",
142
+ retryable: RETRYABLE_BY_CLASS.script_error,
143
+ };
144
+ if (code === "timeout")
145
+ return {
146
+ class: "timeout",
147
+ retryable: RETRYABLE_BY_CLASS.timeout,
148
+ };
121
149
  if (code === "unknown_platform" || code === "unknown_platform_change")
122
150
  return {
123
151
  class: "unknown_platform_change",
@@ -1,3 +1,53 @@
1
+ const PLATFORM_ARRAY_KEYS = [
2
+ "posts",
3
+ "nodes",
4
+ "agents",
5
+ "edges",
6
+ "results",
7
+ "entries",
8
+ ];
9
+ function tryExtractId(item) {
10
+ if (item && typeof item === "object" && "id" in item) {
11
+ const id = item.id;
12
+ if (id !== undefined && id !== null)
13
+ return String(id);
14
+ }
15
+ return undefined;
16
+ }
17
+ function tryExtractUri(item, platformId, fallbackId) {
18
+ if (item && typeof item === "object") {
19
+ const record = item;
20
+ for (const key of ["url", "uri", "link"]) {
21
+ const value = record[key];
22
+ if (typeof value === "string" && value.trim().length > 0) {
23
+ return value;
24
+ }
25
+ }
26
+ }
27
+ return `platform://${platformId}/item/${encodeURIComponent(fallbackId)}`;
28
+ }
29
+ function extractFromPlatformArray(platformId, record, observedAt) {
30
+ for (const key of PLATFORM_ARRAY_KEYS) {
31
+ const arr = record[key];
32
+ if (Array.isArray(arr) && arr.length > 0) {
33
+ const out = [];
34
+ for (let index = 0; index < arr.length; index += 1) {
35
+ const item = arr[index];
36
+ const id = tryExtractId(item) ?? `${platformId}-${key}-${index}`;
37
+ const uri = tryExtractUri(item, platformId, id);
38
+ out.push({
39
+ id,
40
+ kind: "platform_item",
41
+ uri,
42
+ observedAt,
43
+ });
44
+ }
45
+ if (out.length > 0)
46
+ return out;
47
+ }
48
+ }
49
+ return undefined;
50
+ }
1
51
  function extractSourceRefs(platformId, data, observedAt) {
2
52
  if (data && typeof data === "object") {
3
53
  const record = data;
@@ -36,6 +86,9 @@ function extractSourceRefs(platformId, data, observedAt) {
36
86
  };
37
87
  });
38
88
  }
89
+ const platformRefs = extractFromPlatformArray(platformId, record, observedAt);
90
+ if (platformRefs)
91
+ return platformRefs;
39
92
  }
40
93
  return [];
41
94
  }
@@ -166,6 +166,9 @@ export function createConnectorPolicyLayer(ctx) {
166
166
  await ctx.cooldownPort.markFailure(request.platformId, intent, classified.class, classified.retryAfterMs);
167
167
  }
168
168
  const isRetryable = classified.retryable;
169
+ const errorDetail = raw.error && typeof raw.error === "object" && "detail" in raw.error
170
+ ? String(raw.error.detail)
171
+ : undefined;
169
172
  if (!isRetryable || attempt >= retryPolicy.maxRetries) {
170
173
  return {
171
174
  status: "terminal_failure",
@@ -176,6 +179,7 @@ export function createConnectorPolicyLayer(ctx) {
176
179
  channel: raw.channel,
177
180
  latencyMs: raw.latencyMs,
178
181
  degraded: raw.degraded,
182
+ detail: errorDetail,
179
183
  },
180
184
  };
181
185
  }
@@ -7,6 +7,7 @@ export declare const connectorRunnerKindSchema: z.ZodEnum<{
7
7
  declarative_mcp: "declarative_mcp";
8
8
  cli_descriptor: "cli_descriptor";
9
9
  custom_adapter: "custom_adapter";
10
+ scriptable_node: "scriptable_node";
10
11
  }>;
11
12
  export type ConnectorRunnerKind = z.infer<typeof connectorRunnerKindSchema>;
12
13
  export declare const connectorTrustStatusSchema: z.ZodEnum<{
@@ -33,6 +34,7 @@ export declare const runnerDeclarationSchema: z.ZodObject<{
33
34
  declarative_mcp: "declarative_mcp";
34
35
  cli_descriptor: "cli_descriptor";
35
36
  custom_adapter: "custom_adapter";
37
+ scriptable_node: "scriptable_node";
36
38
  }>;
37
39
  entrypoint: z.ZodOptional<z.ZodString>;
38
40
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -86,6 +88,7 @@ export declare const connectorManifestV6Schema: z.ZodObject<{
86
88
  declarative_mcp: "declarative_mcp";
87
89
  cli_descriptor: "cli_descriptor";
88
90
  custom_adapter: "custom_adapter";
91
+ scriptable_node: "scriptable_node";
89
92
  }>;
90
93
  entrypoint: z.ZodOptional<z.ZodString>;
91
94
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -7,6 +7,7 @@ export const connectorRunnerKindSchema = z.enum([
7
7
  "custom_adapter",
8
8
  "skill",
9
9
  "browser",
10
+ "scriptable_node",
10
11
  ]);
11
12
  export const connectorTrustStatusSchema = z.enum([
12
13
  "declarative_trusted",
@@ -7,11 +7,14 @@
7
7
  */
8
8
  import type { ConnectorExecutor } from "../base/contract.js";
9
9
  export type { ConnectorExecutor } from "../base/contract.js";
10
+ import { type EvoMapSecretPort } from "../agent-network/evomap/adapter.js";
10
11
  import type { ObservabilityDatabase } from "../../observability/db/index.js";
11
12
  import type { StateDatabase } from "../../storage/db/index.js";
13
+ import { createCredentialVault } from "../../storage/services/credential-vault.js";
12
14
  export interface ConnectorExecutorAdapterOptions {
13
15
  stateDb: StateDatabase;
14
16
  observabilityDb: ObservabilityDatabase;
15
17
  workspaceRoot?: string;
16
18
  }
19
+ export declare function createEvoMapSecretPort(vault: ReturnType<typeof createCredentialVault>): EvoMapSecretPort;
17
20
  export declare function createConnectorExecutorAdapter(options: ConnectorExecutorAdapterOptions): ConnectorExecutor;
@@ -4,11 +4,13 @@ import { ChannelHealthStore } from "../base/channel-health.js";
4
4
  import { createConnectorPolicyLayer } from "../base/policy-layer.js";
5
5
  import { InMemoryEffectCommitLedger } from "../base/execution-policy.js";
6
6
  import { moltbookManifest } from "../social-community/moltbook/manifest.js";
7
+ import { instreetManifest } from "../social-community/instreet/manifest.js";
7
8
  import { evomapManifest } from "../agent-network/evomap/manifest.js";
8
9
  import { agentWorldManifest } from "../agent-network/agent-world/manifest.js";
9
10
  import { createMoltbookApiClient } from "../social-community/moltbook/api-client.js";
10
11
  import { createMoltbookRunner } from "../social-community/moltbook/adapter.js";
11
12
  import { createAgentWorldRunner } from "../agent-network/agent-world/adapter.js";
13
+ import { createEvoMapRunner } from "../agent-network/evomap/adapter.js";
12
14
  import { ExecutionTelemetry } from "../../observability/services/execution-telemetry.js";
13
15
  import { createCredentialVault } from "../../storage/services/credential-vault.js";
14
16
  import { createCredentialRouteContextPort } from "./credential-route-context.js";
@@ -16,6 +18,7 @@ import { scanConnectorManifests } from "../registry/manifest-scanner.js";
16
18
  import { parseConnectorManifestV6 } from "../manifest/manifest-parser.js";
17
19
  import fs from "node:fs";
18
20
  import path from "node:path";
21
+ import { pathToFileURL } from "node:url";
19
22
  const DEFAULT_AGENT_WORLD_USERNAME = "nyx_ha";
20
23
  const DEFAULT_AGENT_WORLD_PROFILE_PATH_TEMPLATE = "/api/agents/profile/{username}";
21
24
  function readString(value) {
@@ -60,7 +63,9 @@ function registerWorkspaceManifests(registry, workspaceRoot) {
60
63
  platformId: manifest.platformId,
61
64
  supportedCapabilities: manifest.capabilities.map((capability) => capability.id),
62
65
  channelPriority: channelPriorityForRunner(manifest),
63
- credentialTypes: manifest.credentials.map((credential) => credential.type),
66
+ credentialTypes: manifest.credentials
67
+ .filter((credential) => credential.required !== false)
68
+ .map((credential) => credential.type),
64
69
  sourceRefPolicy: manifest.sourceRefPolicy,
65
70
  });
66
71
  }
@@ -103,6 +108,49 @@ async function fetchAgentWorldJson(input) {
103
108
  }
104
109
  return resp.json();
105
110
  }
111
+ export function createEvoMapSecretPort(vault) {
112
+ const NODE_SECRET_KEY = "evomap_node_secret";
113
+ return {
114
+ async loadNodeSecret(_platformId) {
115
+ const ctx = await vault.loadCredentialContext(NODE_SECRET_KEY);
116
+ if (!ctx || ctx.status !== "active" || !ctx.encryptedValue)
117
+ return null;
118
+ // CredentialVault.loadCredentialContext already decrypts at rest;
119
+ // encryptedValue here is the plaintext.
120
+ return ctx.encryptedValue;
121
+ },
122
+ async saveNodeSecret(_platformId, nodeSecret) {
123
+ await vault.saveCredentialContext({
124
+ platformId: NODE_SECRET_KEY,
125
+ credentialType: "node_secret",
126
+ encryptedValue: nodeSecret,
127
+ status: "active",
128
+ });
129
+ },
130
+ };
131
+ }
132
+ function joinEvoMapUrl(baseUrl, path) {
133
+ if (/^https?:\/\//i.test(path))
134
+ return path;
135
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
136
+ }
137
+ async function fetchEvoMapJson(input) {
138
+ const headers = {
139
+ "Content-Type": "application/json",
140
+ };
141
+ if (input.nodeSecret) {
142
+ headers["Authorization"] = `Bearer ${input.nodeSecret}`;
143
+ }
144
+ const resp = await fetch(joinEvoMapUrl(input.baseUrl, input.path), {
145
+ method: input.method ?? "GET",
146
+ headers,
147
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
148
+ });
149
+ if (!resp.ok) {
150
+ throw { code: "api_error", detail: `evomap ${input.label}: ${resp.status}` };
151
+ }
152
+ return resp.json();
153
+ }
106
154
  function createMoltbookMockRunner(workspaceRoot) {
107
155
  return {
108
156
  async run(_plan, request) {
@@ -162,7 +210,7 @@ function findWorkspaceManifest(platformId, workspaceRoot) {
162
210
  for (const file of scanConnectorManifests(workspaceRoot)) {
163
211
  const parsed = parseConnectorManifestV6(file.content, file.path);
164
212
  if (parsed.ok && parsed.manifest.platformId === platformId) {
165
- return parsed.manifest;
213
+ return { manifest: parsed.manifest, manifestDir: path.dirname(file.path) };
166
214
  }
167
215
  }
168
216
  return undefined;
@@ -247,12 +295,125 @@ function createDeclarativeHttpRunner(manifest, credential) {
247
295
  },
248
296
  };
249
297
  }
298
+ /**
299
+ * Scriptable Node Runner — workspace connector execution via dynamic ES Module import.
300
+ *
301
+ * Contract (runner.mjs default export):
302
+ * Input: { intent: string, payload: unknown, credential?: string }
303
+ * Output: { success: boolean, data?: unknown, error?: { code: string, detail: string } }
304
+ *
305
+ * Timeout: default 10s, overridable via manifest.runner.config.timeoutMs.
306
+ * Credential: passed as plain string when manifest.credentials required and vault has active entry.
307
+ * Error mapping:
308
+ * - missing entrypoint file → configuration_missing
309
+ * - default export is not function → script_error
310
+ * - runner throws → script_error (detail includes error message)
311
+ * - Promise.race timeout → timeout
312
+ */
313
+ function createScriptableNodeRunner(manifest, manifestDir, activeCredential) {
314
+ const entryPath = manifest.runner.entrypoint ?? "runner.mjs";
315
+ const absoluteEntryPath = path.resolve(manifestDir, entryPath);
316
+ const DEFAULT_TIMEOUT_MS = 10000;
317
+ const timeoutMs = typeof manifest.runner.config?.timeoutMs === "number" &&
318
+ Number.isFinite(manifest.runner.config.timeoutMs) &&
319
+ manifest.runner.config.timeoutMs > 0
320
+ ? manifest.runner.config.timeoutMs
321
+ : DEFAULT_TIMEOUT_MS;
322
+ return {
323
+ async run(_plan, request) {
324
+ const started = Date.now();
325
+ if (!fs.existsSync(absoluteEntryPath)) {
326
+ return {
327
+ platformId: request.platformId,
328
+ channel: request.preferredChannel ?? "api_rest",
329
+ latencyMs: Date.now() - started,
330
+ success: false,
331
+ error: {
332
+ code: "configuration_missing",
333
+ detail: `scriptable_node runner not found: ${absoluteEntryPath}`,
334
+ },
335
+ };
336
+ }
337
+ try {
338
+ const module = await import(pathToFileURL(absoluteEntryPath).href);
339
+ const handler = module.default;
340
+ if (typeof handler !== "function") {
341
+ return {
342
+ platformId: request.platformId,
343
+ channel: request.preferredChannel ?? "api_rest",
344
+ latencyMs: Date.now() - started,
345
+ success: false,
346
+ error: {
347
+ code: "script_error",
348
+ detail: `scriptable_node runner must export a default function from ${absoluteEntryPath}`,
349
+ },
350
+ };
351
+ }
352
+ const result = await Promise.race([
353
+ handler({
354
+ intent: request.intent,
355
+ payload: request.payload,
356
+ credential: activeCredential?.encryptedValue,
357
+ }),
358
+ new Promise((_, reject) => setTimeout(() => reject(new Error("scriptable_node_timeout")), timeoutMs)),
359
+ ]);
360
+ if (result && typeof result === "object" && "success" in result) {
361
+ return {
362
+ platformId: request.platformId,
363
+ channel: request.preferredChannel ?? "api_rest",
364
+ latencyMs: Date.now() - started,
365
+ success: Boolean(result.success),
366
+ payload: result.success
367
+ ? {
368
+ capability: request.intent,
369
+ channel: request.preferredChannel ?? "api_rest",
370
+ data: result.data,
371
+ }
372
+ : undefined,
373
+ error: !result.success
374
+ ? {
375
+ code: result.error?.code ?? "script_error",
376
+ detail: result.error?.detail ?? "scriptable_node_runner_returned_failure",
377
+ }
378
+ : undefined,
379
+ };
380
+ }
381
+ return {
382
+ platformId: request.platformId,
383
+ channel: request.preferredChannel ?? "api_rest",
384
+ latencyMs: Date.now() - started,
385
+ success: false,
386
+ error: {
387
+ code: "script_error",
388
+ detail: `scriptable_node runner returned invalid shape from ${absoluteEntryPath}`,
389
+ },
390
+ };
391
+ }
392
+ catch (err) {
393
+ const isTimeout = err instanceof Error && err.message === "scriptable_node_timeout";
394
+ return {
395
+ platformId: request.platformId,
396
+ channel: request.preferredChannel ?? "api_rest",
397
+ latencyMs: Date.now() - started,
398
+ success: false,
399
+ error: {
400
+ code: isTimeout ? "timeout" : "script_error",
401
+ detail: isTimeout
402
+ ? `scriptable_node runner exceeded ${timeoutMs}ms timeout`
403
+ : String(err),
404
+ },
405
+ };
406
+ }
407
+ },
408
+ };
409
+ }
250
410
  function createAdaptiveExecutionRunner(vault, workspaceRoot) {
251
411
  return {
252
412
  async run(_plan, request) {
253
413
  const platformId = request.platformId;
254
414
  const started = Date.now();
255
- const workspaceManifest = findWorkspaceManifest(platformId, workspaceRoot);
415
+ const workspaceManifestResult = findWorkspaceManifest(platformId, workspaceRoot);
416
+ const workspaceManifest = workspaceManifestResult?.manifest;
256
417
  const isBuiltInPlatform = platformId === "moltbook" ||
257
418
  platformId === "evomap" ||
258
419
  platformId === "agent-world";
@@ -305,16 +466,44 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
305
466
  return mockRunner.run(_plan, request);
306
467
  }
307
468
  if (platformId === "evomap") {
308
- return {
309
- platformId,
310
- channel: request.preferredChannel ?? "api_rest",
311
- latencyMs: Date.now() - started,
312
- success: false,
313
- error: {
314
- code: "not_implemented",
315
- detail: "evomap_execution_runner_not_yet_implemented",
469
+ const baseUrl = process.env.SECOND_NATURE_EVOMAP_BASE_URL;
470
+ if (!baseUrl) {
471
+ return {
472
+ platformId,
473
+ channel: request.preferredChannel ?? "api_rest",
474
+ latencyMs: Date.now() - started,
475
+ success: false,
476
+ error: {
477
+ code: "configuration_missing",
478
+ detail: "SECOND_NATURE_EVOMAP_BASE_URL not set. This connector requires the evomap node base URL to be configured via environment variable.",
479
+ },
480
+ };
481
+ }
482
+ const secretPort = createEvoMapSecretPort(vault);
483
+ const runner = createEvoMapRunner({
484
+ apiClient: {
485
+ async heartbeat(payload, nodeSecret) {
486
+ const path = readString(payload.heartbeatPath) ?? "/api/heartbeat";
487
+ return fetchEvoMapJson({ baseUrl, path, nodeSecret, method: "POST", body: payload, label: "heartbeat" });
488
+ },
489
+ async claimTask(payload, nodeSecret) {
490
+ const path = readString(payload.claimPath) ?? "/api/tasks/claim";
491
+ return fetchEvoMapJson({ baseUrl, path, nodeSecret, method: "POST", body: payload, label: "claim" });
492
+ },
316
493
  },
317
- };
494
+ a2aClient: {
495
+ async helloOrRegister(payload) {
496
+ const path = readString(payload.registerPath) ?? "/a2a/hello";
497
+ return fetchEvoMapJson({ baseUrl, path, method: "POST", body: payload, label: "register" });
498
+ },
499
+ async discoverWork(payload, nodeSecret) {
500
+ const path = readString(payload.discoverPath) ?? "/a2a/discover";
501
+ return fetchEvoMapJson({ baseUrl, path, nodeSecret, method: "POST", body: payload, label: "discover" });
502
+ },
503
+ },
504
+ secretPort,
505
+ });
506
+ return runner.run(_plan, request);
318
507
  }
319
508
  if (platformId === "agent-world") {
320
509
  const baseUrl = process.env.SECOND_NATURE_AGENT_WORLD_BASE_URL;
@@ -326,7 +515,7 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
326
515
  success: false,
327
516
  error: {
328
517
  code: "configuration_missing",
329
- detail: "SECOND_NATURE_AGENT_WORLD_BASE_URL not set",
518
+ detail: "SECOND_NATURE_AGENT_WORLD_BASE_URL not set. This connector requires the agent-world node base URL to be configured via environment variable.",
330
519
  },
331
520
  };
332
521
  }
@@ -372,11 +561,42 @@ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
372
561
  });
373
562
  return runner.run(_plan, request);
374
563
  }
564
+ // T-CS.C.9: instreet is registered but requires skill/browser channel;
565
+ // pure api_rest execution returns platform_unavailable.
566
+ if (platformId === "instreet") {
567
+ return {
568
+ platformId,
569
+ channel: request.preferredChannel ?? "api_rest",
570
+ latencyMs: Date.now() - started,
571
+ success: false,
572
+ error: {
573
+ code: "platform_unavailable",
574
+ detail: "instreet_requires_skill_browser_channel",
575
+ },
576
+ };
577
+ }
375
578
  // Wave 83: workspace declarative_http connector fallback
376
579
  if (workspaceManifest && workspaceManifest.runner.kind === "declarative_http") {
377
580
  const httpRunner = createDeclarativeHttpRunner(workspaceManifest, activeCredential);
378
581
  return httpRunner.run(_plan, request);
379
582
  }
583
+ // Wave 90: workspace scriptable_node connector
584
+ if (workspaceManifest && workspaceManifest.runner.kind === "scriptable_node") {
585
+ if (!workspaceManifestResult) {
586
+ return {
587
+ platformId,
588
+ channel: request.preferredChannel ?? "api_rest",
589
+ latencyMs: Date.now() - started,
590
+ success: false,
591
+ error: {
592
+ code: "configuration_missing",
593
+ detail: "scriptable_node requires workspace manifest with manifestDir",
594
+ },
595
+ };
596
+ }
597
+ const scriptRunner = createScriptableNodeRunner(workspaceManifest, workspaceManifestResult.manifestDir, activeCredential);
598
+ return scriptRunner.run(_plan, request);
599
+ }
380
600
  return {
381
601
  platformId,
382
602
  channel: request.preferredChannel ?? "api_rest",
@@ -396,6 +616,7 @@ export function createConnectorExecutorAdapter(options) {
396
616
  registry.register({ ...moltbookManifest });
397
617
  registry.register({ ...evomapManifest });
398
618
  registry.register({ ...agentWorldManifest });
619
+ registry.register({ ...instreetManifest });
399
620
  registerWorkspaceManifests(registry, options.workspaceRoot);
400
621
  const routeContextPort = createCredentialRouteContextPort(vault);
401
622
  const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());