@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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/index.js +1 -0
- package/runtime/cli/ops/ops-router.js +29 -3
- package/runtime/connectors/base/contract.d.ts +1 -0
- package/runtime/connectors/base/failure-taxonomy.d.ts +1 -1
- package/runtime/connectors/base/failure-taxonomy.js +28 -0
- package/runtime/connectors/base/map-life-evidence.js +53 -0
- package/runtime/connectors/base/policy-layer.js +4 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +3 -0
- package/runtime/connectors/manifest/manifest-schema.js +1 -0
- package/runtime/connectors/services/connector-executor-adapter.d.ts +3 -0
- package/runtime/connectors/services/connector-executor-adapter.js +234 -13
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.1.
|
|
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
|
@@ -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
|
|
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,
|
|
@@ -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,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
|
|
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
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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());
|