@haaaiawd/second-nature 0.1.30 → 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 +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/connector-status.d.ts +1 -0
- package/runtime/cli/commands/connector-status.js +5 -0
- package/runtime/cli/commands/index.js +16 -0
- package/runtime/cli/index.js +170 -0
- package/runtime/cli/ops/heartbeat-surface.d.ts +3 -0
- package/runtime/cli/ops/heartbeat-surface.js +1 -0
- package/runtime/cli/ops/ops-router.d.ts +6 -0
- package/runtime/cli/ops/ops-router.js +39 -0
- package/runtime/core/second-nature/orchestrator/intent-planner.js +3 -0
- package/runtime/core/second-nature/orchestrator/platform-capability-router.js +18 -2
- package/workspace-ops-bridge.js +17 -1
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.
|
|
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
|
{
|
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.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
|
@@ -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",
|
package/runtime/cli/index.js
CHANGED
|
@@ -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") {
|
|
@@ -249,6 +253,9 @@ export function createOpsRouter(deps) {
|
|
|
249
253
|
const result = await connectorTest(deps.registry, {
|
|
250
254
|
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
251
255
|
dryRun: isWet ? false : (input?.dryRun === false ? false : true),
|
|
256
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
257
|
+
? input.workspaceRoot
|
|
258
|
+
: deps.workspaceRoot,
|
|
252
259
|
});
|
|
253
260
|
if (isWet && result.ok) {
|
|
254
261
|
// Annotate result with manual trigger context (DR-038 / T-ROS.C.3)
|
|
@@ -428,6 +435,38 @@ export function createOpsRouter(deps) {
|
|
|
428
435
|
*/
|
|
429
436
|
if (command === "tool_affordance") {
|
|
430
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
|
+
}
|
|
431
470
|
const envelope = {
|
|
432
471
|
ok: false,
|
|
433
472
|
command: "tool_affordance",
|
|
@@ -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:
|
|
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
|
}
|
package/workspace-ops-bridge.js
CHANGED
|
@@ -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
|
|
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.
|
|
@@ -59,9 +71,13 @@ export async function openWorkspaceOpsBridge(workspaceRoot) {
|
|
|
59
71
|
state: stateDb,
|
|
60
72
|
workspaceRoot: resolvedRoot,
|
|
61
73
|
connectorExecutor: deps.connectorExecutor,
|
|
74
|
+
connectorRegistry: deps.capabilityRegistry,
|
|
62
75
|
registry: deps.registry,
|
|
76
|
+
toolAffordancePort: deps.affordanceAssembler,
|
|
63
77
|
// v7 (T-ROS.C.2): in-memory audit store for heartbeat_digest / restore / self_health
|
|
64
78
|
auditStore,
|
|
79
|
+
narrativeTimelineDeps: deps.narrativeTimelineDeps,
|
|
80
|
+
secretAnchorDeps: deps.secretAnchorDeps,
|
|
65
81
|
restoreSnapshotStore: deps.restoreSnapshotStore,
|
|
66
82
|
});
|
|
67
83
|
const commands = commandsMod.createCliCommands({
|