@haaaiawd/second-nature 0.1.22 → 0.1.23
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/connector-init.d.ts +19 -0
- package/runtime/cli/commands/connector-init.js +168 -0
- package/runtime/cli/commands/connector-status.d.ts +12 -0
- package/runtime/cli/commands/connector-status.js +156 -0
- package/runtime/cli/commands/index.js +40 -0
- package/runtime/cli/ops/ops-router.d.ts +5 -0
- package/runtime/cli/ops/ops-router.js +34 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +22 -0
- package/runtime/connectors/base/manifest.d.ts +13 -0
- package/runtime/connectors/base/manifest.js +47 -0
- package/runtime/connectors/manifest/manifest-parser.d.ts +16 -0
- package/runtime/connectors/manifest/manifest-parser.js +35 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +145 -0
- package/runtime/connectors/manifest/manifest-schema.js +51 -0
- package/runtime/connectors/registry/dynamic-connector-registry.d.ts +29 -0
- package/runtime/connectors/registry/dynamic-connector-registry.js +123 -0
- package/runtime/connectors/registry/index.d.ts +3 -0
- package/runtime/connectors/registry/index.js +3 -0
- package/runtime/connectors/registry/manifest-scanner.d.ts +9 -0
- package/runtime/connectors/registry/manifest-scanner.js +29 -0
- package/runtime/connectors/registry/trust-policy.d.ts +13 -0
- package/runtime/connectors/registry/trust-policy.js +37 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +52 -1
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +19 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.js +67 -0
- package/runtime/core/second-nature/orchestrator/intent-planner.js +8 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.d.ts +27 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.js +107 -0
- package/runtime/core/second-nature/types.d.ts +4 -0
- package/runtime/guidance/draft-narrative-outreach.d.ts +36 -0
- package/runtime/guidance/draft-narrative-outreach.js +84 -0
- package/runtime/guidance/index.d.ts +1 -0
- package/runtime/guidance/index.js +1 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
- package/runtime/observability/connector-inventory-ledger.d.ts +45 -0
- package/runtime/observability/connector-inventory-ledger.js +72 -0
- package/runtime/observability/db/index.js +13 -0
- package/runtime/observability/db/schema/connector-inventory.d.ts +174 -0
- package/runtime/observability/db/schema/connector-inventory.js +15 -0
- package/runtime/observability/db/schema/index.d.ts +1 -0
- package/runtime/observability/db/schema/index.js +1 -0
- package/runtime/storage/chronicle/session-chronicle-store.d.ts +42 -0
- package/runtime/storage/chronicle/session-chronicle-store.js +66 -0
- package/runtime/storage/db/index.js +75 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +235 -0
- package/runtime/storage/db/schema/agent-goal.js +19 -0
- package/runtime/storage/db/schema/index.d.ts +5 -0
- package/runtime/storage/db/schema/index.js +5 -0
- package/runtime/storage/db/schema/memory-store.d.ts +199 -0
- package/runtime/storage/db/schema/memory-store.js +18 -0
- package/runtime/storage/db/schema/narrative-state.d.ts +195 -0
- package/runtime/storage/db/schema/narrative-state.js +16 -0
- package/runtime/storage/db/schema/relationship-memory.d.ts +174 -0
- package/runtime/storage/db/schema/relationship-memory.js +14 -0
- package/runtime/storage/db/schema/session-chronicle.d.ts +199 -0
- package/runtime/storage/db/schema/session-chronicle.js +18 -0
- package/runtime/storage/goal/agent-goal-store.d.ts +57 -0
- package/runtime/storage/goal/agent-goal-store.js +109 -0
- package/runtime/storage/index.d.ts +5 -0
- package/runtime/storage/index.js +5 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.d.ts +70 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.js +113 -0
- package/runtime/storage/narrative/narrative-state-store.d.ts +40 -0
- package/runtime/storage/narrative/narrative-state-store.js +79 -0
- package/runtime/storage/relationship/relationship-memory-store.d.ts +42 -0
- package/runtime/storage/relationship/relationship-memory-store.js +76 -0
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.23",
|
|
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 (see README / T1.1.4 ops norm).",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ConnectorInitInput {
|
|
2
|
+
platformId: string;
|
|
3
|
+
family?: "social_community" | "agent_network" | "work_platform" | "custom";
|
|
4
|
+
displayName?: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
runnerKind?: "declarative_http" | "declarative_a2a" | "declarative_mcp" | "cli_descriptor" | "custom_adapter" | "skill" | "browser";
|
|
7
|
+
force?: boolean;
|
|
8
|
+
workspaceRoot?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ConnectorInitResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
platformId: string;
|
|
13
|
+
manifestPath: string;
|
|
14
|
+
adapterPath: string;
|
|
15
|
+
typesPath: string;
|
|
16
|
+
created: boolean;
|
|
17
|
+
reason?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function connectorInit(input: ConnectorInitInput): Promise<ConnectorInitResult>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function resolveConnectorsDir(workspaceRoot) {
|
|
4
|
+
if (workspaceRoot) {
|
|
5
|
+
return path.resolve(workspaceRoot, ".second-nature", "connectors");
|
|
6
|
+
}
|
|
7
|
+
return path.resolve(process.cwd(), ".second-nature", "connectors");
|
|
8
|
+
}
|
|
9
|
+
function generateManifestYaml(input) {
|
|
10
|
+
const platformId = input.platformId;
|
|
11
|
+
const displayName = input.displayName ?? platformId;
|
|
12
|
+
const family = input.family ?? "custom";
|
|
13
|
+
const runnerKind = input.runnerKind ?? "declarative_http";
|
|
14
|
+
const baseUrlLine = input.baseUrl ? `\nbaseUrl: ${input.baseUrl}` : "";
|
|
15
|
+
return `schemaVersion: sn.connector.v1
|
|
16
|
+
platformId: ${platformId}
|
|
17
|
+
displayName: ${displayName}
|
|
18
|
+
family: ${family}${baseUrlLine}
|
|
19
|
+
capabilities:
|
|
20
|
+
- id: ${platformId}.placeholder
|
|
21
|
+
description: Placeholder capability — replace with real capability declarations
|
|
22
|
+
runner:
|
|
23
|
+
kind: ${runnerKind}
|
|
24
|
+
entrypoint: ""
|
|
25
|
+
credentials: []
|
|
26
|
+
sourceRefPolicy:
|
|
27
|
+
minSourceRefs: 1
|
|
28
|
+
rejectInlineSensitivePayload: true
|
|
29
|
+
trust:
|
|
30
|
+
status: custom_adapter_pending_trust
|
|
31
|
+
reason: generated_by_connector_init
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
function generateAdapterStub(platformId) {
|
|
35
|
+
const pascalId = platformId
|
|
36
|
+
.split(/[-_]/)
|
|
37
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
38
|
+
.join("");
|
|
39
|
+
return `/**
|
|
40
|
+
* ${pascalId} Connector Adapter Stub
|
|
41
|
+
*
|
|
42
|
+
* Generated by \`second-nature connector init\`.
|
|
43
|
+
* Replace this stub with real platform-specific execution logic.
|
|
44
|
+
*
|
|
45
|
+
* Runner kind: custom_adapter (pending trust until owner allowlists).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { ConnectorResult } from "@second-nature/connector-sdk";
|
|
49
|
+
|
|
50
|
+
export interface ${pascalId}AdapterConfig {
|
|
51
|
+
baseUrl: string;
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function execute${pascalId}Action(
|
|
56
|
+
config: ${pascalId}AdapterConfig,
|
|
57
|
+
action: string,
|
|
58
|
+
payload: unknown,
|
|
59
|
+
): Promise<ConnectorResult> {
|
|
60
|
+
// TODO: implement real platform call
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: {
|
|
64
|
+
code: "NOT_IMPLEMENTED",
|
|
65
|
+
message: \`${pascalId} adapter action '\${action}' is not yet implemented\`,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
function generateTypesStub(platformId) {
|
|
72
|
+
const pascalId = platformId
|
|
73
|
+
.split(/[-_]/)
|
|
74
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
75
|
+
.join("");
|
|
76
|
+
return `/**
|
|
77
|
+
* ${pascalId} Connector Type Stubs
|
|
78
|
+
*
|
|
79
|
+
* Generated by \`second-nature connector init\`.
|
|
80
|
+
* Expand these types as you add real capabilities.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
export interface ${pascalId}Capability {
|
|
84
|
+
id: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ${pascalId}Credential {
|
|
89
|
+
type: "api_key" | "oauth" | "session";
|
|
90
|
+
required: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ${pascalId}PlatformPayload {
|
|
94
|
+
// TODO: define real platform-specific payload shapes
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
export async function connectorInit(input) {
|
|
100
|
+
const { platformId, force = false } = input;
|
|
101
|
+
if (!platformId || typeof platformId !== "string" || platformId.trim().length === 0) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
platformId: "",
|
|
105
|
+
manifestPath: "",
|
|
106
|
+
adapterPath: "",
|
|
107
|
+
typesPath: "",
|
|
108
|
+
created: false,
|
|
109
|
+
reason: "platformId is required and must be a non-empty string",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const trimmed = platformId.trim();
|
|
113
|
+
if (trimmed === "." || trimmed === "..") {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
platformId: trimmed,
|
|
117
|
+
manifestPath: "",
|
|
118
|
+
adapterPath: "",
|
|
119
|
+
typesPath: "",
|
|
120
|
+
created: false,
|
|
121
|
+
reason: "platformId cannot be '.' or '..'",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const sanitized = trimmed.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
125
|
+
if (sanitized !== trimmed) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
platformId,
|
|
129
|
+
manifestPath: "",
|
|
130
|
+
adapterPath: "",
|
|
131
|
+
typesPath: "",
|
|
132
|
+
created: false,
|
|
133
|
+
reason: `platformId contains invalid characters. Suggested: ${sanitized}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const connectorsDir = resolveConnectorsDir(input.workspaceRoot);
|
|
137
|
+
const platformDir = path.join(connectorsDir, sanitized);
|
|
138
|
+
const manifestPath = path.join(platformDir, "manifest.yaml");
|
|
139
|
+
const adapterPath = path.join(platformDir, "adapter.ts");
|
|
140
|
+
const typesPath = path.join(platformDir, "types.ts");
|
|
141
|
+
// CR7-01: fail-closed when target already exists (no silent skip)
|
|
142
|
+
if (fs.existsSync(manifestPath) && !force) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
platformId: sanitized,
|
|
146
|
+
manifestPath,
|
|
147
|
+
adapterPath,
|
|
148
|
+
typesPath,
|
|
149
|
+
created: false,
|
|
150
|
+
reason: "manifest already exists; use force:true to overwrite",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
fs.mkdirSync(platformDir, { recursive: true });
|
|
154
|
+
const manifestContent = generateManifestYaml({ ...input, platformId: sanitized });
|
|
155
|
+
fs.writeFileSync(manifestPath, manifestContent, "utf-8");
|
|
156
|
+
const adapterContent = generateAdapterStub(sanitized);
|
|
157
|
+
fs.writeFileSync(adapterPath, adapterContent, "utf-8");
|
|
158
|
+
const typesContent = generateTypesStub(sanitized);
|
|
159
|
+
fs.writeFileSync(typesPath, typesContent, "utf-8");
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
platformId: sanitized,
|
|
163
|
+
manifestPath,
|
|
164
|
+
adapterPath,
|
|
165
|
+
typesPath,
|
|
166
|
+
created: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DynamicConnectorRegistry } from "../../connectors/registry/index.js";
|
|
2
|
+
import type { ConnectorInventoryLedger } from "../../observability/connector-inventory-ledger.js";
|
|
3
|
+
export interface ConnectorStatusInput {
|
|
4
|
+
includeHealth?: boolean;
|
|
5
|
+
workspaceRoot?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ConnectorTestInput {
|
|
8
|
+
platformId: string;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function connectorStatus(registry: DynamicConnectorRegistry | undefined, ledger: ConnectorInventoryLedger | undefined, input?: ConnectorStatusInput): Promise<Record<string, unknown>>;
|
|
12
|
+
export declare function connectorTest(registry: DynamicConnectorRegistry | undefined, input: ConnectorTestInput): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export async function connectorStatus(registry, ledger, input) {
|
|
2
|
+
if (!registry) {
|
|
3
|
+
return {
|
|
4
|
+
ok: false,
|
|
5
|
+
command: "connector_status",
|
|
6
|
+
error: {
|
|
7
|
+
code: "REGISTRY_UNAVAILABLE",
|
|
8
|
+
message: "connector:status requires DynamicConnectorRegistry to be wired into OpsRouterDeps",
|
|
9
|
+
nextStep: "wire_registry_into_ops_router",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// Ensure snapshot is loaded; if workspaceRoot provided, reload
|
|
14
|
+
if (input?.workspaceRoot) {
|
|
15
|
+
registry.reloadConnectors(input.workspaceRoot);
|
|
16
|
+
}
|
|
17
|
+
const snapshot = registry.getActiveRegistrySnapshot();
|
|
18
|
+
const entries = [...snapshot.entries.values()];
|
|
19
|
+
const conflicts = snapshot.conflicts.map((c) => ({
|
|
20
|
+
platformId: c.platformId,
|
|
21
|
+
existingSource: c.existingSource,
|
|
22
|
+
attemptedSource: c.attemptedSource,
|
|
23
|
+
reason: c.reason,
|
|
24
|
+
}));
|
|
25
|
+
const validationErrors = snapshot.validationErrors.map((e) => ({
|
|
26
|
+
platformId: e.platformId,
|
|
27
|
+
path: e.path,
|
|
28
|
+
message: e.message,
|
|
29
|
+
}));
|
|
30
|
+
const summary = {
|
|
31
|
+
total: entries.length,
|
|
32
|
+
builtIn: entries.filter((e) => e.source === "built_in").length,
|
|
33
|
+
workspace: entries.filter((e) => e.source === "workspace").length,
|
|
34
|
+
executable: entries.filter((e) => e.executable).length,
|
|
35
|
+
pendingTrust: entries.filter((e) => !e.executable).length,
|
|
36
|
+
};
|
|
37
|
+
// Optionally record audit
|
|
38
|
+
if (ledger) {
|
|
39
|
+
await ledger.recordAudit({
|
|
40
|
+
snapshotId: snapshot.createdAt,
|
|
41
|
+
scanned: entries.length + conflicts.length + validationErrors.length,
|
|
42
|
+
registered: entries.length,
|
|
43
|
+
skipped: conflicts.length + validationErrors.length,
|
|
44
|
+
conflicts: conflicts.map((c) => ({ connectorId: c.platformId, reason: c.reason })),
|
|
45
|
+
validationErrors: validationErrors.map((e) => ({
|
|
46
|
+
connectorId: e.platformId ?? "unknown",
|
|
47
|
+
errors: [e.message],
|
|
48
|
+
})),
|
|
49
|
+
trustSummary: {
|
|
50
|
+
executable: summary.executable,
|
|
51
|
+
pendingTrust: summary.pendingTrust,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
command: "connector_status",
|
|
58
|
+
data: {
|
|
59
|
+
summary,
|
|
60
|
+
connectors: entries.map((e) => ({
|
|
61
|
+
platformId: e.platformId,
|
|
62
|
+
source: e.source,
|
|
63
|
+
trustStatus: e.trustStatus,
|
|
64
|
+
executable: e.executable,
|
|
65
|
+
capabilities: e.capabilities,
|
|
66
|
+
validationErrors: e.validationErrors,
|
|
67
|
+
manifestPath: e.manifestPath,
|
|
68
|
+
})),
|
|
69
|
+
conflicts,
|
|
70
|
+
validationErrors,
|
|
71
|
+
snapshotCreatedAt: snapshot.createdAt,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function connectorTest(registry, input) {
|
|
76
|
+
if (!registry) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
command: "connector_test",
|
|
80
|
+
error: {
|
|
81
|
+
code: "REGISTRY_UNAVAILABLE",
|
|
82
|
+
message: "connector:test requires DynamicConnectorRegistry to be wired into OpsRouterDeps",
|
|
83
|
+
nextStep: "wire_registry_into_ops_router",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const platformId = input.platformId.trim();
|
|
88
|
+
if (!platformId) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
command: "connector_test",
|
|
92
|
+
error: {
|
|
93
|
+
code: "MISSING_PLATFORM_ID",
|
|
94
|
+
message: "connector:test requires platformId",
|
|
95
|
+
requiredUserInput: ["platformId"],
|
|
96
|
+
nextStep: "reinvoke_with_platform_id",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const entry = registry.describeConnector(platformId);
|
|
101
|
+
if (!entry) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
command: "connector_test",
|
|
105
|
+
error: {
|
|
106
|
+
code: "CONNECTOR_NOT_FOUND",
|
|
107
|
+
message: `No connector found for platformId: ${platformId}`,
|
|
108
|
+
requiredUserInput: ["platformId"],
|
|
109
|
+
nextStep: "verify_platform_id_or_run_connector_status",
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const dryRun = input.dryRun !== false; // default dry-run
|
|
114
|
+
// CR7-02: pending-trust / non-executable connectors must fail-closed
|
|
115
|
+
if (!entry.executable) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
command: "connector_test",
|
|
119
|
+
error: {
|
|
120
|
+
code: "PENDING_TRUST_DENIED",
|
|
121
|
+
message: `Connector '${entry.platformId}' is not executable: trustStatus=${entry.trustStatus}. Use connector:status to review trust policy or owner allowlist to enable.`,
|
|
122
|
+
trustStatus: entry.trustStatus,
|
|
123
|
+
platformId: entry.platformId,
|
|
124
|
+
nextStep: "review_trust_policy_or_owner_allowlist",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const healthChecks = [];
|
|
129
|
+
if (entry.validationErrors.length > 0) {
|
|
130
|
+
healthChecks.push(`validation_errors: ${entry.validationErrors.join("; ")}`);
|
|
131
|
+
}
|
|
132
|
+
if (entry.capabilities.length === 0) {
|
|
133
|
+
healthChecks.push("no_capabilities_declared");
|
|
134
|
+
}
|
|
135
|
+
if (healthChecks.length === 0) {
|
|
136
|
+
healthChecks.push("ok");
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
command: "connector_test",
|
|
141
|
+
data: {
|
|
142
|
+
platformId: entry.platformId,
|
|
143
|
+
source: entry.source,
|
|
144
|
+
trustStatus: entry.trustStatus,
|
|
145
|
+
executable: entry.executable,
|
|
146
|
+
capabilities: entry.capabilities,
|
|
147
|
+
validationErrors: entry.validationErrors,
|
|
148
|
+
manifestPath: entry.manifestPath,
|
|
149
|
+
dryRun,
|
|
150
|
+
healthChecks,
|
|
151
|
+
note: dryRun
|
|
152
|
+
? "dry-run mode: no side effects were attempted"
|
|
153
|
+
: "live test mode: side effects may have been attempted (use with caution)",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { credentialVerify } from "./credential.js";
|
|
2
|
+
import { connectorInit } from "./connector-init.js";
|
|
2
3
|
import { formatExplanation } from "../explain/format-explanation.js";
|
|
3
4
|
import { explainSurfaceSubject } from "../explain/explain-surface-subject.js";
|
|
4
5
|
import { showOperatorFallback, OperatorFallbackNotFoundError, } from "../ops/show-operator-fallback.js";
|
|
@@ -220,5 +221,44 @@ export function createCliCommands(deps) {
|
|
|
220
221
|
return surface;
|
|
221
222
|
},
|
|
222
223
|
},
|
|
224
|
+
{
|
|
225
|
+
name: "connector_init",
|
|
226
|
+
description: "T1.3.1 — generate connector manifest stub under .second-nature/connectors/{platformId}/",
|
|
227
|
+
execute: async (input) => {
|
|
228
|
+
const result = await connectorInit({
|
|
229
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
230
|
+
family: typeof input?.family === "string"
|
|
231
|
+
? input.family
|
|
232
|
+
: undefined,
|
|
233
|
+
displayName: typeof input?.displayName === "string"
|
|
234
|
+
? input.displayName
|
|
235
|
+
: undefined,
|
|
236
|
+
runnerKind: typeof input?.runnerKind === "string"
|
|
237
|
+
? input.runnerKind
|
|
238
|
+
: undefined,
|
|
239
|
+
force: Boolean(input?.force),
|
|
240
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
241
|
+
? input.workspaceRoot
|
|
242
|
+
: undefined,
|
|
243
|
+
});
|
|
244
|
+
return result;
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "connector_status",
|
|
249
|
+
description: "T1.2.3 — show connector inventory, trust/executable/conflict summary",
|
|
250
|
+
execute: async (input) => {
|
|
251
|
+
const surface = await Promise.resolve(opsRouter.dispatch("connector_status", input));
|
|
252
|
+
return surface;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "connector_test",
|
|
257
|
+
description: "T1.2.3 — dry-run test a connector by platformId (default dry-run)",
|
|
258
|
+
execute: async (input) => {
|
|
259
|
+
const surface = await Promise.resolve(opsRouter.dispatch("connector_test", input));
|
|
260
|
+
return surface;
|
|
261
|
+
},
|
|
262
|
+
},
|
|
223
263
|
];
|
|
224
264
|
}
|
|
@@ -7,6 +7,7 @@ import type { RuntimeDecisionRecorder } from "../../observability/services/runti
|
|
|
7
7
|
import type { StateDatabase } from "../../storage/db/index.js";
|
|
8
8
|
import type { ObservabilityDatabase } from "../../observability/db/index.js";
|
|
9
9
|
import type { ConnectorExecutor } from "../../core/second-nature/orchestrator/effect-dispatcher.js";
|
|
10
|
+
import type { DynamicConnectorRegistry } from "../../connectors/registry/index.js";
|
|
10
11
|
export interface OpsRouterDeps {
|
|
11
12
|
/** When true, packaged runtime artifacts resolved and full graph is loadable */
|
|
12
13
|
runtimeAvailable: boolean;
|
|
@@ -30,6 +31,10 @@ export interface OpsRouterDeps {
|
|
|
30
31
|
* connector-system instead of returning connector_dispatch_unwired.
|
|
31
32
|
*/
|
|
32
33
|
connectorExecutor?: ConnectorExecutor;
|
|
34
|
+
/**
|
|
35
|
+
* T1.2.3: DynamicConnectorRegistry for connector:status and connector:test commands.
|
|
36
|
+
*/
|
|
37
|
+
registry?: DynamicConnectorRegistry;
|
|
33
38
|
}
|
|
34
39
|
export interface OpsRouter {
|
|
35
40
|
heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
|
|
@@ -6,6 +6,8 @@ import { showOperatorFallback, OperatorFallbackNotFoundError, } from "./show-ope
|
|
|
6
6
|
import { probeHostCapability } from "../host-capability/probe-host-capability.js";
|
|
7
7
|
import { recordHostCapability } from "../host-capability/record-host-capability.js";
|
|
8
8
|
import { runNearRealConnectorSmoke } from "../../connectors/near-real/near-real-connector-smoke.js";
|
|
9
|
+
import { connectorInit } from "../commands/connector-init.js";
|
|
10
|
+
import { connectorStatus, connectorTest } from "../commands/connector-status.js";
|
|
9
11
|
function coerceProbeOnlyFlag(input) {
|
|
10
12
|
const v = input?.probeOnly;
|
|
11
13
|
return v === true || v === "true" || v === 1 || v === "1";
|
|
@@ -179,6 +181,38 @@ export function createOpsRouter(deps) {
|
|
|
179
181
|
};
|
|
180
182
|
})();
|
|
181
183
|
}
|
|
184
|
+
if (command === "connector_init") {
|
|
185
|
+
// T1.3.1 (SN-CODE-06): generate connector manifest stub.
|
|
186
|
+
return (async () => {
|
|
187
|
+
const result = await connectorInit({
|
|
188
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
189
|
+
family: typeof input?.family === "string"
|
|
190
|
+
? input.family
|
|
191
|
+
: undefined,
|
|
192
|
+
displayName: typeof input?.displayName === "string" ? input.displayName : undefined,
|
|
193
|
+
runnerKind: typeof input?.runnerKind === "string"
|
|
194
|
+
? input.runnerKind
|
|
195
|
+
: undefined,
|
|
196
|
+
force: Boolean(input?.force),
|
|
197
|
+
workspaceRoot: deps.workspaceRoot,
|
|
198
|
+
});
|
|
199
|
+
return result;
|
|
200
|
+
})();
|
|
201
|
+
}
|
|
202
|
+
if (command === "connector_status") {
|
|
203
|
+
return connectorStatus(deps.registry, undefined, {
|
|
204
|
+
includeHealth: Boolean(input?.includeHealth),
|
|
205
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
206
|
+
? input.workspaceRoot
|
|
207
|
+
: deps.workspaceRoot,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (command === "connector_test") {
|
|
211
|
+
return connectorTest(deps.registry, {
|
|
212
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
213
|
+
dryRun: input?.dryRun === false ? false : true, // default dry-run
|
|
214
|
+
});
|
|
215
|
+
}
|
|
182
216
|
return {
|
|
183
217
|
ok: false,
|
|
184
218
|
error: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { runHeartbeatCycle } from "../../core/second-nature/heartbeat/run-heartbeat-cycle.js";
|
|
2
2
|
import { loadLifeEvidenceSnapshot } from "../../storage/snapshots/life-evidence-snapshot.js";
|
|
3
|
+
import { createAgentGoalStore } from "../../storage/goal/agent-goal-store.js";
|
|
4
|
+
import { createNarrativeStateStore } from "../../storage/narrative/narrative-state-store.js";
|
|
3
5
|
export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, options = {}) {
|
|
4
6
|
const status = await readModels.loadStatus();
|
|
5
7
|
const mode = status.rhythm.mode === "unknown" ? "active" : status.rhythm.mode;
|
|
@@ -43,6 +45,20 @@ export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, option
|
|
|
43
45
|
// No state wired — record that life evidence wasn't loaded so guards can reason honestly.
|
|
44
46
|
lifeEvidenceEmptyReason = "state_unavailable";
|
|
45
47
|
}
|
|
48
|
+
// T2.1.4: Load accepted goals from state DB when available.
|
|
49
|
+
let acceptedGoals;
|
|
50
|
+
if (options.state) {
|
|
51
|
+
try {
|
|
52
|
+
const goalStore = createAgentGoalStore(options.state);
|
|
53
|
+
acceptedGoals = await goalStore.listAgentGoals({
|
|
54
|
+
statuses: ["accepted"],
|
|
55
|
+
limit: 20,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
acceptedGoals = undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
46
62
|
return {
|
|
47
63
|
mode,
|
|
48
64
|
currentWindowId: status.rhythm.windowId ?? "workspace-default",
|
|
@@ -57,12 +73,17 @@ export async function loadSnapshotInputsForWorkspaceHeartbeat(readModels, option
|
|
|
57
73
|
platformEventCount,
|
|
58
74
|
workEventCount,
|
|
59
75
|
lifeEvidenceEmptyReason,
|
|
76
|
+
acceptedGoals,
|
|
60
77
|
};
|
|
61
78
|
}
|
|
62
79
|
export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
|
|
63
80
|
// T1.2.4: inject quietWorkflow dep when workspaceRoot is set so quiet/reflection intents
|
|
64
81
|
// can trigger runSourceBackedQuiet and persist artifacts to disk.
|
|
65
82
|
const quietEnabled = options.workspaceRoot && options.enableQuietWorkflow !== false;
|
|
83
|
+
// T2.1.5: when state DB is wired, create a NarrativeStateStore for heartbeat updates.
|
|
84
|
+
const narrativeStateStore = options.state
|
|
85
|
+
? createNarrativeStateStore(options.state)
|
|
86
|
+
: undefined;
|
|
66
87
|
return async (signal) => {
|
|
67
88
|
const cycle = await runHeartbeatCycle({
|
|
68
89
|
signal,
|
|
@@ -77,6 +98,7 @@ export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
|
|
|
77
98
|
? { workspaceRoot: options.workspaceRoot }
|
|
78
99
|
: undefined,
|
|
79
100
|
connectorExecutor: options.connectorExecutor,
|
|
101
|
+
narrativeStateStore,
|
|
80
102
|
},
|
|
81
103
|
});
|
|
82
104
|
if (options.runtimeRecorder) {
|
|
@@ -38,6 +38,11 @@ declare const connectorManifestSchema: z.ZodObject<{
|
|
|
38
38
|
}, z.core.$strip>>;
|
|
39
39
|
}, z.core.$strip>;
|
|
40
40
|
export type ConnectorManifest = z.infer<typeof connectorManifestSchema>;
|
|
41
|
+
export interface ResolvedConnectorCapability {
|
|
42
|
+
platformId: string;
|
|
43
|
+
intent: CapabilityIntent;
|
|
44
|
+
source: "namespace" | "v5_explicit" | "unambiguous_default";
|
|
45
|
+
}
|
|
41
46
|
export declare class CapabilityContractRegistry {
|
|
42
47
|
private readonly byPlatform;
|
|
43
48
|
register(manifest: ConnectorManifest): void;
|
|
@@ -46,6 +51,14 @@ export declare class CapabilityContractRegistry {
|
|
|
46
51
|
hasCapability(platformId: string, intent: CapabilityIntent): boolean;
|
|
47
52
|
listCapabilities(platformId: string): CapabilityIntent[];
|
|
48
53
|
listChannels(platformId: string): ChannelType[];
|
|
54
|
+
/**
|
|
55
|
+
* Resolve a capability string that may be namespaced (`platformId:capability`)
|
|
56
|
+
* or a bare v5 capability. Returns the platform + intent pair.
|
|
57
|
+
* If bare capability and no explicit platform is provided, only succeeds when
|
|
58
|
+
* exactly one registered platform supports it (unambiguous_default).
|
|
59
|
+
*/
|
|
60
|
+
resolveCapability(intentWithNamespace: string, explicitPlatformId?: string): ResolvedConnectorCapability;
|
|
61
|
+
findPlatformsForIntent(intent: CapabilityIntent): string[];
|
|
49
62
|
}
|
|
50
63
|
/** T3.1.1 contract name for manifest-first registry. */
|
|
51
64
|
export declare const ConnectorManifestRegistry: typeof CapabilityContractRegistry;
|
|
@@ -40,6 +40,53 @@ export class CapabilityContractRegistry {
|
|
|
40
40
|
listChannels(platformId) {
|
|
41
41
|
return [...this.loadManifest(platformId).channelPriority];
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a capability string that may be namespaced (`platformId:capability`)
|
|
45
|
+
* or a bare v5 capability. Returns the platform + intent pair.
|
|
46
|
+
* If bare capability and no explicit platform is provided, only succeeds when
|
|
47
|
+
* exactly one registered platform supports it (unambiguous_default).
|
|
48
|
+
*/
|
|
49
|
+
resolveCapability(intentWithNamespace, explicitPlatformId) {
|
|
50
|
+
const colonIndex = intentWithNamespace.indexOf(":");
|
|
51
|
+
if (colonIndex >= 0) {
|
|
52
|
+
const platformId = intentWithNamespace.slice(0, colonIndex);
|
|
53
|
+
const intent = intentWithNamespace.slice(colonIndex + 1);
|
|
54
|
+
if (!CAPABILITY_INTENTS.includes(intent)) {
|
|
55
|
+
throw new Error(`capability_not_recognized:${intent}`);
|
|
56
|
+
}
|
|
57
|
+
if (!this.byPlatform.has(platformId)) {
|
|
58
|
+
throw new Error(`platform_not_found:${platformId}`);
|
|
59
|
+
}
|
|
60
|
+
return { platformId, intent, source: "namespace" };
|
|
61
|
+
}
|
|
62
|
+
const intent = intentWithNamespace;
|
|
63
|
+
if (!CAPABILITY_INTENTS.includes(intent)) {
|
|
64
|
+
throw new Error(`capability_not_recognized:${intent}`);
|
|
65
|
+
}
|
|
66
|
+
if (explicitPlatformId) {
|
|
67
|
+
if (!this.byPlatform.has(explicitPlatformId)) {
|
|
68
|
+
throw new Error(`platform_not_found:${explicitPlatformId}`);
|
|
69
|
+
}
|
|
70
|
+
return { platformId: explicitPlatformId, intent, source: "v5_explicit" };
|
|
71
|
+
}
|
|
72
|
+
const platforms = this.findPlatformsForIntent(intent);
|
|
73
|
+
if (platforms.length === 0) {
|
|
74
|
+
throw new Error(`no_platform_supports_capability:${intent}`);
|
|
75
|
+
}
|
|
76
|
+
if (platforms.length > 1) {
|
|
77
|
+
throw new Error(`ambiguous_capability:${intent}:${platforms.join(",")}`);
|
|
78
|
+
}
|
|
79
|
+
return { platformId: platforms[0], intent, source: "unambiguous_default" };
|
|
80
|
+
}
|
|
81
|
+
findPlatformsForIntent(intent) {
|
|
82
|
+
const result = [];
|
|
83
|
+
for (const [platformId, manifest] of this.byPlatform) {
|
|
84
|
+
if (manifest.supportedCapabilities.includes(intent)) {
|
|
85
|
+
result.push(platformId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
43
90
|
}
|
|
44
91
|
/** T3.1.1 contract name for manifest-first registry. */
|
|
45
92
|
export const ConnectorManifestRegistry = CapabilityContractRegistry;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ConnectorManifestV6, type ConnectorManifestValidationError } from "./manifest-schema.js";
|
|
2
|
+
export interface ParseManifestResult {
|
|
3
|
+
ok: true;
|
|
4
|
+
manifest: ConnectorManifestV6;
|
|
5
|
+
}
|
|
6
|
+
export interface ParseManifestFailure {
|
|
7
|
+
ok: false;
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
export type ParseManifestOutput = ParseManifestResult | ParseManifestFailure;
|
|
11
|
+
/**
|
|
12
|
+
* Safe YAML parse + zod validation for connector manifest v6.
|
|
13
|
+
* Uses JSON_SCHEMA to block custom YAML tags/object constructors.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseConnectorManifestV6(yamlText: string, path?: string): ParseManifestOutput;
|
|
16
|
+
export declare function toValidationError(path: string, output: ParseManifestFailure): ConnectorManifestValidationError[];
|