@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.
Files changed (70) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/runtime/cli/commands/connector-init.d.ts +19 -0
  4. package/runtime/cli/commands/connector-init.js +168 -0
  5. package/runtime/cli/commands/connector-status.d.ts +12 -0
  6. package/runtime/cli/commands/connector-status.js +156 -0
  7. package/runtime/cli/commands/index.js +40 -0
  8. package/runtime/cli/ops/ops-router.d.ts +5 -0
  9. package/runtime/cli/ops/ops-router.js +34 -0
  10. package/runtime/cli/ops/workspace-heartbeat-runner.js +22 -0
  11. package/runtime/connectors/base/manifest.d.ts +13 -0
  12. package/runtime/connectors/base/manifest.js +47 -0
  13. package/runtime/connectors/manifest/manifest-parser.d.ts +16 -0
  14. package/runtime/connectors/manifest/manifest-parser.js +35 -0
  15. package/runtime/connectors/manifest/manifest-schema.d.ts +145 -0
  16. package/runtime/connectors/manifest/manifest-schema.js +51 -0
  17. package/runtime/connectors/registry/dynamic-connector-registry.d.ts +29 -0
  18. package/runtime/connectors/registry/dynamic-connector-registry.js +123 -0
  19. package/runtime/connectors/registry/index.d.ts +3 -0
  20. package/runtime/connectors/registry/index.js +3 -0
  21. package/runtime/connectors/registry/manifest-scanner.d.ts +9 -0
  22. package/runtime/connectors/registry/manifest-scanner.js +29 -0
  23. package/runtime/connectors/registry/trust-policy.d.ts +13 -0
  24. package/runtime/connectors/registry/trust-policy.js +37 -0
  25. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
  26. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +52 -1
  27. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -0
  28. package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +19 -0
  29. package/runtime/core/second-nature/orchestrator/goal-priority.js +67 -0
  30. package/runtime/core/second-nature/orchestrator/intent-planner.js +8 -0
  31. package/runtime/core/second-nature/orchestrator/narrative-update.d.ts +27 -0
  32. package/runtime/core/second-nature/orchestrator/narrative-update.js +107 -0
  33. package/runtime/core/second-nature/types.d.ts +4 -0
  34. package/runtime/guidance/draft-narrative-outreach.d.ts +36 -0
  35. package/runtime/guidance/draft-narrative-outreach.js +84 -0
  36. package/runtime/guidance/index.d.ts +1 -0
  37. package/runtime/guidance/index.js +1 -0
  38. package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
  39. package/runtime/observability/connector-inventory-ledger.d.ts +45 -0
  40. package/runtime/observability/connector-inventory-ledger.js +72 -0
  41. package/runtime/observability/db/index.js +13 -0
  42. package/runtime/observability/db/schema/connector-inventory.d.ts +174 -0
  43. package/runtime/observability/db/schema/connector-inventory.js +15 -0
  44. package/runtime/observability/db/schema/index.d.ts +1 -0
  45. package/runtime/observability/db/schema/index.js +1 -0
  46. package/runtime/storage/chronicle/session-chronicle-store.d.ts +42 -0
  47. package/runtime/storage/chronicle/session-chronicle-store.js +66 -0
  48. package/runtime/storage/db/index.js +75 -0
  49. package/runtime/storage/db/schema/agent-goal.d.ts +235 -0
  50. package/runtime/storage/db/schema/agent-goal.js +19 -0
  51. package/runtime/storage/db/schema/index.d.ts +5 -0
  52. package/runtime/storage/db/schema/index.js +5 -0
  53. package/runtime/storage/db/schema/memory-store.d.ts +199 -0
  54. package/runtime/storage/db/schema/memory-store.js +18 -0
  55. package/runtime/storage/db/schema/narrative-state.d.ts +195 -0
  56. package/runtime/storage/db/schema/narrative-state.js +16 -0
  57. package/runtime/storage/db/schema/relationship-memory.d.ts +174 -0
  58. package/runtime/storage/db/schema/relationship-memory.js +14 -0
  59. package/runtime/storage/db/schema/session-chronicle.d.ts +199 -0
  60. package/runtime/storage/db/schema/session-chronicle.js +18 -0
  61. package/runtime/storage/goal/agent-goal-store.d.ts +57 -0
  62. package/runtime/storage/goal/agent-goal-store.js +109 -0
  63. package/runtime/storage/index.d.ts +5 -0
  64. package/runtime/storage/index.js +5 -0
  65. package/runtime/storage/memory-store/memory-store-lifecycle.d.ts +70 -0
  66. package/runtime/storage/memory-store/memory-store-lifecycle.js +113 -0
  67. package/runtime/storage/narrative/narrative-state-store.d.ts +40 -0
  68. package/runtime/storage/narrative/narrative-state-store.js +79 -0
  69. package/runtime/storage/relationship/relationship-memory-store.d.ts +42 -0
  70. package/runtime/storage/relationship/relationship-memory-store.js +76 -0
@@ -0,0 +1,35 @@
1
+ import yaml from "js-yaml";
2
+ import { connectorManifestV6Schema, } from "./manifest-schema.js";
3
+ /**
4
+ * Safe YAML parse + zod validation for connector manifest v6.
5
+ * Uses JSON_SCHEMA to block custom YAML tags/object constructors.
6
+ */
7
+ export function parseConnectorManifestV6(yamlText, path) {
8
+ let raw;
9
+ try {
10
+ raw = yaml.load(yamlText, { schema: yaml.JSON_SCHEMA });
11
+ }
12
+ catch (error) {
13
+ return {
14
+ ok: false,
15
+ errors: [
16
+ error instanceof Error ? `yaml_parse_error:${error.message}` : `yaml_parse_error:${String(error)}`,
17
+ ],
18
+ };
19
+ }
20
+ if (raw === null || typeof raw !== "object") {
21
+ return { ok: false, errors: ["yaml_parse_error:parsed_content_is_null_or_not_object"] };
22
+ }
23
+ const parsed = connectorManifestV6Schema.safeParse(raw);
24
+ if (!parsed.success) {
25
+ const errors = parsed.error.issues.map((issue) => {
26
+ const pathStr = issue.path.join(".");
27
+ return `schema_validation_error:${pathStr}:${issue.message}`;
28
+ });
29
+ return { ok: false, errors };
30
+ }
31
+ return { ok: true, manifest: parsed.data };
32
+ }
33
+ export function toValidationError(path, output) {
34
+ return output.errors.map((message) => ({ path, message }));
35
+ }
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ export declare const connectorRunnerKindSchema: z.ZodEnum<{
3
+ skill: "skill";
4
+ browser: "browser";
5
+ declarative_http: "declarative_http";
6
+ declarative_a2a: "declarative_a2a";
7
+ declarative_mcp: "declarative_mcp";
8
+ cli_descriptor: "cli_descriptor";
9
+ custom_adapter: "custom_adapter";
10
+ }>;
11
+ export type ConnectorRunnerKind = z.infer<typeof connectorRunnerKindSchema>;
12
+ export declare const connectorTrustStatusSchema: z.ZodEnum<{
13
+ blocked: "blocked";
14
+ declarative_trusted: "declarative_trusted";
15
+ custom_adapter_pending_trust: "custom_adapter_pending_trust";
16
+ trusted_custom_adapter: "trusted_custom_adapter";
17
+ }>;
18
+ export type ConnectorTrustStatus = z.infer<typeof connectorTrustStatusSchema>;
19
+ export declare const capabilityDeclarationSchema: z.ZodObject<{
20
+ id: z.ZodString;
21
+ channel: z.ZodOptional<z.ZodString>;
22
+ description: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>;
24
+ export type ConnectorCapabilityDeclaration = z.infer<typeof capabilityDeclarationSchema>;
25
+ export declare const runnerDeclarationSchema: z.ZodObject<{
26
+ kind: z.ZodEnum<{
27
+ skill: "skill";
28
+ browser: "browser";
29
+ declarative_http: "declarative_http";
30
+ declarative_a2a: "declarative_a2a";
31
+ declarative_mcp: "declarative_mcp";
32
+ cli_descriptor: "cli_descriptor";
33
+ custom_adapter: "custom_adapter";
34
+ }>;
35
+ entrypoint: z.ZodOptional<z.ZodString>;
36
+ config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
37
+ }, z.core.$strip>;
38
+ export type ConnectorRunnerDeclaration = z.infer<typeof runnerDeclarationSchema>;
39
+ export declare const credentialRequirementSchema: z.ZodObject<{
40
+ type: z.ZodString;
41
+ required: z.ZodDefault<z.ZodBoolean>;
42
+ description: z.ZodOptional<z.ZodString>;
43
+ }, z.core.$strip>;
44
+ export type CredentialRequirementDeclaration = z.infer<typeof credentialRequirementSchema>;
45
+ export declare const sourceRefPolicySchema: z.ZodObject<{
46
+ minSourceRefs: z.ZodDefault<z.ZodNumber>;
47
+ rejectInlineSensitivePayload: z.ZodOptional<z.ZodBoolean>;
48
+ }, z.core.$strip>;
49
+ export type SourceRefPolicyDeclaration = z.infer<typeof sourceRefPolicySchema>;
50
+ export declare const trustDeclarationSchema: z.ZodObject<{
51
+ status: z.ZodOptional<z.ZodEnum<{
52
+ blocked: "blocked";
53
+ declarative_trusted: "declarative_trusted";
54
+ custom_adapter_pending_trust: "custom_adapter_pending_trust";
55
+ trusted_custom_adapter: "trusted_custom_adapter";
56
+ }>>;
57
+ override: z.ZodOptional<z.ZodBoolean>;
58
+ reason: z.ZodOptional<z.ZodString>;
59
+ }, z.core.$strip>;
60
+ export type ConnectorTrustDeclaration = z.infer<typeof trustDeclarationSchema>;
61
+ export declare const connectorManifestV6Schema: z.ZodObject<{
62
+ schemaVersion: z.ZodLiteral<"sn.connector.v1">;
63
+ platformId: z.ZodString;
64
+ displayName: z.ZodString;
65
+ family: z.ZodEnum<{
66
+ custom: "custom";
67
+ social_community: "social_community";
68
+ agent_network: "agent_network";
69
+ work_platform: "work_platform";
70
+ }>;
71
+ capabilities: z.ZodArray<z.ZodObject<{
72
+ id: z.ZodString;
73
+ channel: z.ZodOptional<z.ZodString>;
74
+ description: z.ZodOptional<z.ZodString>;
75
+ }, z.core.$strip>>;
76
+ runner: z.ZodObject<{
77
+ kind: z.ZodEnum<{
78
+ skill: "skill";
79
+ browser: "browser";
80
+ declarative_http: "declarative_http";
81
+ declarative_a2a: "declarative_a2a";
82
+ declarative_mcp: "declarative_mcp";
83
+ cli_descriptor: "cli_descriptor";
84
+ custom_adapter: "custom_adapter";
85
+ }>;
86
+ entrypoint: z.ZodOptional<z.ZodString>;
87
+ config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
88
+ }, z.core.$strip>;
89
+ credentials: z.ZodArray<z.ZodObject<{
90
+ type: z.ZodString;
91
+ required: z.ZodDefault<z.ZodBoolean>;
92
+ description: z.ZodOptional<z.ZodString>;
93
+ }, z.core.$strip>>;
94
+ sourceRefPolicy: z.ZodObject<{
95
+ minSourceRefs: z.ZodDefault<z.ZodNumber>;
96
+ rejectInlineSensitivePayload: z.ZodOptional<z.ZodBoolean>;
97
+ }, z.core.$strip>;
98
+ trust: z.ZodOptional<z.ZodObject<{
99
+ status: z.ZodOptional<z.ZodEnum<{
100
+ blocked: "blocked";
101
+ declarative_trusted: "declarative_trusted";
102
+ custom_adapter_pending_trust: "custom_adapter_pending_trust";
103
+ trusted_custom_adapter: "trusted_custom_adapter";
104
+ }>>;
105
+ override: z.ZodOptional<z.ZodBoolean>;
106
+ reason: z.ZodOptional<z.ZodString>;
107
+ }, z.core.$strip>>;
108
+ }, z.core.$strip>;
109
+ export type ConnectorManifestV6 = z.infer<typeof connectorManifestV6Schema>;
110
+ export interface ConnectorConflict {
111
+ platformId: string;
112
+ existingSource: "built_in" | "workspace";
113
+ attemptedSource: "built_in" | "workspace";
114
+ reason: string;
115
+ }
116
+ export interface ConnectorManifestValidationError {
117
+ platformId?: string;
118
+ path: string;
119
+ message: string;
120
+ }
121
+ export interface ConnectorInventoryEntry {
122
+ platformId: string;
123
+ source: "built_in" | "workspace";
124
+ manifestPath?: string;
125
+ trustStatus: ConnectorTrustStatus;
126
+ executable: boolean;
127
+ capabilities: string[];
128
+ validationErrors: string[];
129
+ conflict?: ConnectorConflict;
130
+ }
131
+ export interface ConnectorReloadResult {
132
+ scanned: number;
133
+ registered: number;
134
+ skipped: number;
135
+ conflicts: ConnectorConflict[];
136
+ validationErrors: ConnectorManifestValidationError[];
137
+ }
138
+ export interface ConnectorRegistrySnapshot {
139
+ readonly entries: ReadonlyMap<string, ConnectorInventoryEntry>;
140
+ readonly builtInEntries: ReadonlyMap<string, ConnectorInventoryEntry>;
141
+ readonly dynamicEntries: ReadonlyMap<string, ConnectorInventoryEntry>;
142
+ readonly conflicts: readonly ConnectorConflict[];
143
+ readonly validationErrors: readonly ConnectorManifestValidationError[];
144
+ readonly createdAt: string;
145
+ }
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ export const connectorRunnerKindSchema = z.enum([
3
+ "declarative_http",
4
+ "declarative_a2a",
5
+ "declarative_mcp",
6
+ "cli_descriptor",
7
+ "custom_adapter",
8
+ "skill",
9
+ "browser",
10
+ ]);
11
+ export const connectorTrustStatusSchema = z.enum([
12
+ "declarative_trusted",
13
+ "custom_adapter_pending_trust",
14
+ "trusted_custom_adapter",
15
+ "blocked",
16
+ ]);
17
+ export const capabilityDeclarationSchema = z.object({
18
+ id: z.string().min(1),
19
+ channel: z.string().optional(),
20
+ description: z.string().optional(),
21
+ });
22
+ export const runnerDeclarationSchema = z.object({
23
+ kind: connectorRunnerKindSchema,
24
+ entrypoint: z.string().optional(),
25
+ config: z.record(z.string(), z.unknown()).optional(),
26
+ });
27
+ export const credentialRequirementSchema = z.object({
28
+ type: z.string().min(1),
29
+ required: z.boolean().default(true),
30
+ description: z.string().optional(),
31
+ });
32
+ export const sourceRefPolicySchema = z.object({
33
+ minSourceRefs: z.number().int().min(0).default(1),
34
+ rejectInlineSensitivePayload: z.boolean().optional(),
35
+ });
36
+ export const trustDeclarationSchema = z.object({
37
+ status: connectorTrustStatusSchema.optional(),
38
+ override: z.boolean().optional(),
39
+ reason: z.string().optional(),
40
+ });
41
+ export const connectorManifestV6Schema = z.object({
42
+ schemaVersion: z.literal("sn.connector.v1"),
43
+ platformId: z.string().min(1),
44
+ displayName: z.string().min(1),
45
+ family: z.enum(["social_community", "agent_network", "work_platform", "custom"]),
46
+ capabilities: z.array(capabilityDeclarationSchema).min(1),
47
+ runner: runnerDeclarationSchema,
48
+ credentials: z.array(credentialRequirementSchema),
49
+ sourceRefPolicy: sourceRefPolicySchema,
50
+ trust: trustDeclarationSchema.optional(),
51
+ });
@@ -0,0 +1,29 @@
1
+ import { type ConnectorManifestV6, type ConnectorInventoryEntry, type ConnectorReloadResult, type ConnectorRegistrySnapshot } from "../manifest/manifest-schema.js";
2
+ export interface RegistrySnapshotStore {
3
+ getActive(): ConnectorRegistrySnapshot;
4
+ swap(snapshot: ConnectorRegistrySnapshot): void;
5
+ }
6
+ export declare function createRegistrySnapshotStore(initial?: ConnectorRegistrySnapshot): RegistrySnapshotStore;
7
+ export interface DynamicConnectorRegistryOptions {
8
+ builtInManifests?: ConnectorManifestV6[];
9
+ snapshotStore: RegistrySnapshotStore;
10
+ }
11
+ /**
12
+ * DynamicConnectorRegistry scans workspace manifests, validates, classifies trust,
13
+ * merges built-in entries, applies fail-closed conflict policy, and publishes
14
+ * immutable registry snapshots.
15
+ */
16
+ export declare class DynamicConnectorRegistry {
17
+ private readonly builtInManifests;
18
+ private readonly snapshotStore;
19
+ constructor(options: DynamicConnectorRegistryOptions);
20
+ /**
21
+ * Reload connectors from workspace root.
22
+ * Scans `.second-nature/connectors/{platformId}/manifest.yaml`, validates,
23
+ * classifies trust, and atomically swaps the active registry snapshot.
24
+ */
25
+ reloadConnectors(workspaceRoot: string): ConnectorReloadResult;
26
+ getActiveRegistrySnapshot(): ConnectorRegistrySnapshot;
27
+ listConnectors(): ConnectorInventoryEntry[];
28
+ describeConnector(platformId: string): ConnectorInventoryEntry | undefined;
29
+ }
@@ -0,0 +1,123 @@
1
+ import path from "node:path";
2
+ import { parseConnectorManifestV6, toValidationError } from "../manifest/manifest-parser.js";
3
+ import { classifyTrust, isExecutable } from "./trust-policy.js";
4
+ import { scanConnectorManifests } from "./manifest-scanner.js";
5
+ export function createRegistrySnapshotStore(initial) {
6
+ let active = initial ??
7
+ buildSnapshot(new Map(), new Map(), [], [], new Date().toISOString());
8
+ return {
9
+ getActive() {
10
+ return active;
11
+ },
12
+ swap(snapshot) {
13
+ active = snapshot;
14
+ },
15
+ };
16
+ }
17
+ function buildSnapshot(builtIn, dynamic, conflicts, validationErrors, createdAt) {
18
+ const entries = new Map();
19
+ for (const [k, v] of builtIn)
20
+ entries.set(k, v);
21
+ for (const [k, v] of dynamic)
22
+ entries.set(k, v);
23
+ return Object.freeze({
24
+ entries,
25
+ builtInEntries: builtIn,
26
+ dynamicEntries: dynamic,
27
+ conflicts: Object.freeze([...conflicts]),
28
+ validationErrors: Object.freeze([...validationErrors]),
29
+ createdAt,
30
+ });
31
+ }
32
+ function manifestToInventoryEntry(manifest, source, manifestPath) {
33
+ const trustStatus = classifyTrust(manifest);
34
+ return {
35
+ platformId: manifest.platformId,
36
+ source,
37
+ manifestPath,
38
+ trustStatus,
39
+ executable: isExecutable(trustStatus),
40
+ capabilities: manifest.capabilities.map((c) => c.id),
41
+ validationErrors: [],
42
+ };
43
+ }
44
+ /**
45
+ * DynamicConnectorRegistry scans workspace manifests, validates, classifies trust,
46
+ * merges built-in entries, applies fail-closed conflict policy, and publishes
47
+ * immutable registry snapshots.
48
+ */
49
+ export class DynamicConnectorRegistry {
50
+ builtInManifests;
51
+ snapshotStore;
52
+ constructor(options) {
53
+ this.builtInManifests = options.builtInManifests ?? [];
54
+ this.snapshotStore = options.snapshotStore;
55
+ }
56
+ /**
57
+ * Reload connectors from workspace root.
58
+ * Scans `.second-nature/connectors/{platformId}/manifest.yaml`, validates,
59
+ * classifies trust, and atomically swaps the active registry snapshot.
60
+ */
61
+ reloadConnectors(workspaceRoot) {
62
+ const scannedFiles = scanConnectorManifests(workspaceRoot);
63
+ const scanned = scannedFiles.length;
64
+ const builtInMap = new Map();
65
+ for (const manifest of this.builtInManifests) {
66
+ builtInMap.set(manifest.platformId, manifestToInventoryEntry(manifest, "built_in"));
67
+ }
68
+ const dynamicMap = new Map();
69
+ const conflicts = [];
70
+ const validationErrors = [];
71
+ let registered = 0;
72
+ let skipped = 0;
73
+ for (const file of scannedFiles) {
74
+ const parseResult = parseConnectorManifestV6(file.content, file.path);
75
+ if (!parseResult.ok) {
76
+ validationErrors.push(...toValidationError(file.path, parseResult));
77
+ skipped++;
78
+ continue;
79
+ }
80
+ const manifest = parseResult.manifest;
81
+ const manifestPath = path.relative(workspaceRoot, file.path);
82
+ // Duplicate platformId without override -> fail-closed
83
+ if (builtInMap.has(manifest.platformId) || dynamicMap.has(manifest.platformId)) {
84
+ const existing = builtInMap.get(manifest.platformId) ?? dynamicMap.get(manifest.platformId);
85
+ const allowOverride = manifest.trust?.override === true;
86
+ const isTrustedSource = existing.source === "built_in";
87
+ if (!allowOverride || isTrustedSource) {
88
+ conflicts.push({
89
+ platformId: manifest.platformId,
90
+ existingSource: existing.source,
91
+ attemptedSource: "workspace",
92
+ reason: allowOverride
93
+ ? "override_rejected_trusted_source"
94
+ : "duplicate_platform_id_without_override",
95
+ });
96
+ skipped++;
97
+ continue;
98
+ }
99
+ }
100
+ const entry = manifestToInventoryEntry(manifest, "workspace", manifestPath);
101
+ dynamicMap.set(manifest.platformId, entry);
102
+ registered++;
103
+ }
104
+ const snapshot = buildSnapshot(builtInMap, dynamicMap, conflicts, validationErrors, new Date().toISOString());
105
+ this.snapshotStore.swap(snapshot);
106
+ return {
107
+ scanned,
108
+ registered,
109
+ skipped,
110
+ conflicts,
111
+ validationErrors,
112
+ };
113
+ }
114
+ getActiveRegistrySnapshot() {
115
+ return this.snapshotStore.getActive();
116
+ }
117
+ listConnectors() {
118
+ return [...this.snapshotStore.getActive().entries.values()];
119
+ }
120
+ describeConnector(platformId) {
121
+ return this.snapshotStore.getActive().entries.get(platformId);
122
+ }
123
+ }
@@ -0,0 +1,3 @@
1
+ export { DynamicConnectorRegistry, createRegistrySnapshotStore, type RegistrySnapshotStore, type DynamicConnectorRegistryOptions, } from "./dynamic-connector-registry.js";
2
+ export { scanConnectorManifests, type ManifestScanResult } from "./manifest-scanner.js";
3
+ export { classifyTrust, isExecutable } from "./trust-policy.js";
@@ -0,0 +1,3 @@
1
+ export { DynamicConnectorRegistry, createRegistrySnapshotStore, } from "./dynamic-connector-registry.js";
2
+ export { scanConnectorManifests } from "./manifest-scanner.js";
3
+ export { classifyTrust, isExecutable } from "./trust-policy.js";
@@ -0,0 +1,9 @@
1
+ export interface ManifestScanResult {
2
+ path: string;
3
+ content: string;
4
+ }
5
+ /**
6
+ * Enumerate `.second-nature/connectors/{platformId}/manifest.yaml` under workspace root.
7
+ * Does not execute any code; only reads file paths and contents.
8
+ */
9
+ export declare function scanConnectorManifests(workspaceRoot: string): ManifestScanResult[];
@@ -0,0 +1,29 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * Enumerate `.second-nature/connectors/{platformId}/manifest.yaml` under workspace root.
5
+ * Does not execute any code; only reads file paths and contents.
6
+ */
7
+ export function scanConnectorManifests(workspaceRoot) {
8
+ const connectorsDir = path.join(workspaceRoot, ".second-nature", "connectors");
9
+ if (!fs.existsSync(connectorsDir)) {
10
+ return [];
11
+ }
12
+ const results = [];
13
+ const entries = fs.readdirSync(connectorsDir, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ if (!entry.isDirectory())
16
+ continue;
17
+ const manifestPath = path.join(connectorsDir, entry.name, "manifest.yaml");
18
+ if (!fs.existsSync(manifestPath))
19
+ continue;
20
+ try {
21
+ const content = fs.readFileSync(manifestPath, "utf-8");
22
+ results.push({ path: manifestPath, content });
23
+ }
24
+ catch {
25
+ // Skip unreadable files; validation layer will not see them
26
+ }
27
+ }
28
+ return results;
29
+ }
@@ -0,0 +1,13 @@
1
+ import { type ConnectorManifestV6, type ConnectorTrustStatus } from "../manifest/manifest-schema.js";
2
+ /**
3
+ * Classify manifest runner into trust status per v6 trust decision tree.
4
+ * declarative_http/a2a/mcp -> declarative_trusted
5
+ * cli_descriptor -> declarative_trusted (P0 conditional; dry-run/read path优先)
6
+ * custom_adapter/skill/browser -> custom_adapter_pending_trust
7
+ * explicit blocked/trusted_custom_adapter in manifest.trust respected.
8
+ */
9
+ export declare function classifyTrust(manifest: ConnectorManifestV6): ConnectorTrustStatus;
10
+ /**
11
+ * Determine whether a connector entry is executable based on trust status.
12
+ */
13
+ export declare function isExecutable(trustStatus: ConnectorTrustStatus): boolean;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Classify manifest runner into trust status per v6 trust decision tree.
3
+ * declarative_http/a2a/mcp -> declarative_trusted
4
+ * cli_descriptor -> declarative_trusted (P0 conditional; dry-run/read path优先)
5
+ * custom_adapter/skill/browser -> custom_adapter_pending_trust
6
+ * explicit blocked/trusted_custom_adapter in manifest.trust respected.
7
+ */
8
+ export function classifyTrust(manifest) {
9
+ if (manifest.trust?.status === "blocked") {
10
+ return "blocked";
11
+ }
12
+ if (manifest.trust?.status === "trusted_custom_adapter") {
13
+ return "trusted_custom_adapter";
14
+ }
15
+ const kind = manifest.runner.kind;
16
+ switch (kind) {
17
+ case "declarative_http":
18
+ case "declarative_a2a":
19
+ case "declarative_mcp":
20
+ case "cli_descriptor":
21
+ return "declarative_trusted";
22
+ case "custom_adapter":
23
+ case "skill":
24
+ case "browser":
25
+ return "custom_adapter_pending_trust";
26
+ default: {
27
+ // Exhaustive check; unknown runner kind is blocked for safety
28
+ return "blocked";
29
+ }
30
+ }
31
+ }
32
+ /**
33
+ * Determine whether a connector entry is executable based on trust status.
34
+ */
35
+ export function isExecutable(trustStatus) {
36
+ return trustStatus === "declarative_trusted" || trustStatus === "trusted_custom_adapter";
37
+ }
@@ -20,6 +20,7 @@ import type { GuidanceDraftPort } from "../../../guidance/outreach-draft-schema.
20
20
  import type { StateDatabase } from "../../../storage/db/index.js";
21
21
  import { type OpenClawDeliveryPort } from "../outreach/dispatch-user-outreach.js";
22
22
  import type { ConnectorExecutor } from "../../../connectors/base/contract.js";
23
+ import type { NarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
23
24
  export interface HeartbeatDecisionTracePayload {
24
25
  scope: RuntimeScope;
25
26
  status: HeartbeatCycleStatus;
@@ -58,6 +59,8 @@ export interface HeartbeatDeps {
58
59
  * through the connector-system instead of returning connector_dispatch_unwired.
59
60
  */
60
61
  connectorExecutor?: ConnectorExecutor;
62
+ /** T2.1.5: when present, heartbeat writes a source-backed NarrativeState revision after each cycle. */
63
+ narrativeStateStore?: NarrativeStateStore;
61
64
  }
62
65
  /**
63
66
  * Ingest a heartbeat rhythm signal and drive one full decision round.
@@ -1,11 +1,13 @@
1
1
  import { buildContinuitySnapshot, } from "./snapshot-builder.js";
2
2
  import { buildHeartbeatRuntimeSnapshot, } from "./runtime-snapshot.js";
3
3
  import { planCandidateIntents } from "../orchestrator/intent-planner.js";
4
+ import { applyGoalPriority } from "../orchestrator/goal-priority.js";
4
5
  import { evaluateHardGuards } from "../orchestrator/guard-layer.js";
5
6
  import { dispatchUserOutreachIntent, } from "../outreach/dispatch-user-outreach.js";
6
7
  import { buildJudgeOutreachInputFromSnapshot } from "../outreach/judge-input-from-snapshot.js";
7
8
  import { runSourceBackedQuiet } from "../quiet/run-source-backed-quiet.js";
8
9
  import { toCapabilityIntent } from "../orchestrator/effect-dispatcher.js";
10
+ import { updateNarrativeAfterEffect } from "../orchestrator/narrative-update.js";
9
11
  /**
10
12
  * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
11
13
  * Exported for unit tests (CR-M1 wiring).
@@ -83,6 +85,49 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
83
85
  reasons,
84
86
  };
85
87
  }
88
+ /**
89
+ * T2.1.5: after the cycle result is known, write a narrative revision when
90
+ * a NarrativeStateStore is wired. Errors are swallowed so the cycle result
91
+ * is never blocked by a store failure. Store failures are optionally traced
92
+ * via recordDecisionTrace so operators can monitor store health.
93
+ */
94
+ async function maybeUpdateNarrativeState(result, selectedIntent, runtime, store, recordTrace, signal) {
95
+ if (!store)
96
+ return;
97
+ try {
98
+ const prior = await store.loadNarrativeState();
99
+ const update = updateNarrativeAfterEffect({
100
+ result,
101
+ selectedIntent,
102
+ lifeEvidence: runtime.lifeEvidence,
103
+ priorNarrative: prior,
104
+ });
105
+ await store.updateNarrativeState(update);
106
+ }
107
+ catch {
108
+ // degrade silently; narrative update is best-effort
109
+ if (recordTrace && signal) {
110
+ try {
111
+ await recordTrace({
112
+ scope: result.scope,
113
+ status: result.status,
114
+ reasons: ["narrative_update_failed"],
115
+ selectedIntentId: selectedIntent?.id,
116
+ rhythmWindowId: runtime.rhythmWindow.windowId,
117
+ allowedIntentKinds: [...runtime.rhythmWindow.allowedIntentKinds],
118
+ candidateCount: 0,
119
+ lifeEvidenceEmpty: runtime.lifeEvidence.evidenceRefs.length === 0 &&
120
+ runtime.lifeEvidence.platformEventCount === 0 &&
121
+ runtime.lifeEvidence.workEventCount === 0,
122
+ trigger: signal.trigger,
123
+ });
124
+ }
125
+ catch {
126
+ // trace emission must also not block the cycle
127
+ }
128
+ }
129
+ }
130
+ }
86
131
  /**
87
132
  * Ingest a heartbeat rhythm signal and drive one full decision round.
88
133
  */
@@ -91,7 +136,8 @@ export async function ingestRhythmSignal(signal, deps) {
91
136
  const snapshot = buildContinuitySnapshot(inputs);
92
137
  const timestamp = signal.payload.timestamp;
93
138
  const runtime = buildHeartbeatRuntimeSnapshot(timestamp, inputs, snapshot);
94
- const candidates = planCandidateIntents(runtime);
139
+ const rawCandidates = planCandidateIntents(runtime);
140
+ const { candidates } = applyGoalPriority(rawCandidates, inputs.acceptedGoals);
95
141
  const emitTrace = async (result) => {
96
142
  if (!deps.recordDecisionTrace)
97
143
  return;
@@ -132,6 +178,7 @@ export async function ingestRhythmSignal(signal, deps) {
132
178
  ? { ...resolved, reasons: evaluation.reasons }
133
179
  : resolved;
134
180
  await emitTrace(result);
181
+ await maybeUpdateNarrativeState(result, intent, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
135
182
  return result;
136
183
  }
137
184
  if (evaluation.verdict === "defer") {
@@ -149,6 +196,7 @@ export async function ingestRhythmSignal(signal, deps) {
149
196
  reasons: ["silent_no_candidates"],
150
197
  };
151
198
  await emitTrace(result);
199
+ await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
152
200
  return result;
153
201
  }
154
202
  if (!anyAllow && anyDefer && !anyDeny) {
@@ -158,6 +206,7 @@ export async function ingestRhythmSignal(signal, deps) {
158
206
  reasons: denyReasons.length > 0 ? denyReasons : ["all_candidates_deferred"],
159
207
  };
160
208
  await emitTrace(result);
209
+ await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
161
210
  return result;
162
211
  }
163
212
  if (!anyAllow && denyReasons.length > 0) {
@@ -167,6 +216,7 @@ export async function ingestRhythmSignal(signal, deps) {
167
216
  reasons: denyReasons,
168
217
  };
169
218
  await emitTrace(result);
219
+ await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
170
220
  return result;
171
221
  }
172
222
  const result = {
@@ -175,6 +225,7 @@ export async function ingestRhythmSignal(signal, deps) {
175
225
  reasons: ["no_allow_verdict"],
176
226
  };
177
227
  await emitTrace(result);
228
+ await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
178
229
  return result;
179
230
  }
180
231
  /**
@@ -10,6 +10,7 @@ import type { ContinuitySnapshot, ControlPlaneSourceRef, TopLevelMode } from "..
10
10
  import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
11
11
  import type { DeliveryCapabilitySnapshot } from "../outreach/delivery-target.js";
12
12
  import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
13
+ import type { AgentGoal } from "../../../storage/goal/agent-goal-store.js";
13
14
  export interface SnapshotInputs {
14
15
  mode: TopLevelMode;
15
16
  currentWindowId: string;
@@ -41,6 +42,8 @@ export interface SnapshotInputs {
41
42
  deliveryCapability?: DeliveryCapabilitySnapshot;
42
43
  /** When present, outreach judgment uses this user-interest read model (T4.2.2). */
43
44
  userInterestSnapshot?: UserInterestSnapshot;
45
+ /** T2.1.4: accepted goals to influence candidate intent priority. */
46
+ acceptedGoals?: AgentGoal[];
44
47
  }
45
48
  /**
46
49
  * Build a ContinuitySnapshot from loaded inputs.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * T2.1.4 — Goal-Directed Intent Priority.
3
+ *
4
+ * `applyGoalPriority` adjusts candidate intent priorities based on accepted AgentGoals.
5
+ * Priority order: user_task > accepted_goal > rhythm.
6
+ * Only goals with status === "accepted" and origin !== "agent_proposed" are considered.
7
+ * All other statuses (proposal / rejected / completed / paused) are implicitly excluded.
8
+ */
9
+ import type { CandidateIntent } from "../types.js";
10
+ import type { AgentGoal } from "../../../storage/goal/agent-goal-store.js";
11
+ export interface ApplyGoalPriorityResult {
12
+ candidates: CandidateIntent[];
13
+ goalInfluences: Array<{
14
+ candidateId: string;
15
+ goalIds: string[];
16
+ boost: number;
17
+ }>;
18
+ }
19
+ export declare function applyGoalPriority(candidates: CandidateIntent[], goals: AgentGoal[] | undefined): ApplyGoalPriorityResult;