@haaaiawd/second-nature 0.1.26 → 0.1.29

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 (158) hide show
  1. package/SKILL.md +35 -0
  2. package/agent-inner-guide.md +144 -0
  3. package/index.js +280 -2
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +4 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +3 -13
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/credential-vault.js +31 -17
  138. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  139. package/runtime/storage/services/diary-dream-store.js +165 -0
  140. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  141. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  142. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  143. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  144. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  145. package/runtime/storage/services/history-digest-store.js +140 -0
  146. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  147. package/runtime/storage/services/identity-profile-store.js +81 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  149. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  150. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  151. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  153. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  154. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  155. package/runtime/storage/services/tool-experience-store.js +116 -0
  156. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  157. package/runtime/storage/services/write-validation-gate.js +200 -0
  158. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Workspace connector behavior authoring.
3
+ *
4
+ * Adds a declarative capability stub to `.second-nature/connectors/{platformId}/manifest.yaml`.
5
+ * This records a behavior the agent discovered without granting executable custom code.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import yaml from "js-yaml";
10
+ import { connectorManifestV6Schema } from "../../connectors/manifest/manifest-schema.js";
11
+ const PLATFORM_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
12
+ const BEHAVIOR_ID_PATTERN = /^[a-zA-Z0-9_.:-]+$/;
13
+ function resolveManifestPath(workspaceRoot, platformId) {
14
+ const root = workspaceRoot ? path.resolve(workspaceRoot) : process.cwd();
15
+ return path.join(root, ".second-nature", "connectors", platformId, "manifest.yaml");
16
+ }
17
+ function asRecord(value) {
18
+ return value && typeof value === "object" && !Array.isArray(value)
19
+ ? value
20
+ : {};
21
+ }
22
+ function sanitizeText(value, max = 500) {
23
+ if (typeof value !== "string")
24
+ return undefined;
25
+ const trimmed = value.trim();
26
+ return trimmed.length > 0 ? trimmed.slice(0, max) : undefined;
27
+ }
28
+ function sanitizeSourceRefs(value) {
29
+ if (!Array.isArray(value))
30
+ return [];
31
+ return value
32
+ .map((entry) => {
33
+ if (typeof entry === "string")
34
+ return sanitizeText(entry, 300);
35
+ if (entry && typeof entry === "object" && "id" in entry) {
36
+ return sanitizeText(entry.id, 300);
37
+ }
38
+ return undefined;
39
+ })
40
+ .filter((entry) => !!entry);
41
+ }
42
+ export async function connectorBehaviorAdd(input) {
43
+ const platformId = sanitizeText(input.platformId, 128) ?? "";
44
+ const behaviorId = sanitizeText(input.behaviorId, 160) ?? "";
45
+ const description = sanitizeText(input.description);
46
+ const sourceRefs = sanitizeSourceRefs(input.sourceRefs);
47
+ const observedCount = Number.isInteger(input.observedCount) && input.observedCount > 0
48
+ ? input.observedCount
49
+ : undefined;
50
+ const manifestPath = resolveManifestPath(input.workspaceRoot, platformId || "_missing");
51
+ if (!platformId || platformId === "." || platformId === ".." || !PLATFORM_ID_PATTERN.test(platformId)) {
52
+ return {
53
+ ok: false,
54
+ command: "connector_behavior_add",
55
+ platformId,
56
+ behaviorId,
57
+ manifestPath,
58
+ added: false,
59
+ reason: "platformId is required and must use only letters, numbers, _, or -",
60
+ };
61
+ }
62
+ if (!behaviorId || !BEHAVIOR_ID_PATTERN.test(behaviorId)) {
63
+ return {
64
+ ok: false,
65
+ command: "connector_behavior_add",
66
+ platformId,
67
+ behaviorId,
68
+ manifestPath,
69
+ added: false,
70
+ reason: "behaviorId is required and must use only letters, numbers, _, -, ., or :",
71
+ };
72
+ }
73
+ if (!fs.existsSync(manifestPath)) {
74
+ return {
75
+ ok: false,
76
+ command: "connector_behavior_add",
77
+ platformId,
78
+ behaviorId,
79
+ manifestPath,
80
+ added: false,
81
+ reason: "connector manifest not found",
82
+ nextStep: "run connector_init for this platform first",
83
+ };
84
+ }
85
+ const raw = fs.readFileSync(manifestPath, "utf-8");
86
+ const manifest = asRecord(yaml.load(raw, { schema: yaml.JSON_SCHEMA }));
87
+ const parsedBefore = connectorManifestV6Schema.safeParse(manifest);
88
+ if (!parsedBefore.success) {
89
+ return {
90
+ ok: false,
91
+ command: "connector_behavior_add",
92
+ platformId,
93
+ behaviorId,
94
+ manifestPath,
95
+ added: false,
96
+ reason: `manifest schema validation failed: ${parsedBefore.error.issues.map((issue) => issue.path.join(".")).join(",")}`,
97
+ };
98
+ }
99
+ const capabilities = Array.isArray(manifest.capabilities)
100
+ ? [...manifest.capabilities]
101
+ : [];
102
+ const exists = capabilities.some((entry) => asRecord(entry).id === behaviorId);
103
+ if (exists) {
104
+ return {
105
+ ok: true,
106
+ command: "connector_behavior_add",
107
+ platformId,
108
+ behaviorId,
109
+ manifestPath,
110
+ added: false,
111
+ reason: "behavior already exists",
112
+ };
113
+ }
114
+ if (!description && sourceRefs.length === 0) {
115
+ return {
116
+ ok: false,
117
+ command: "connector_behavior_add",
118
+ platformId,
119
+ behaviorId,
120
+ manifestPath,
121
+ added: false,
122
+ reason: "behavior description or sourceRefs is required so the new action has a reviewable motive",
123
+ };
124
+ }
125
+ capabilities.push({
126
+ id: behaviorId,
127
+ ...(description ? { description } : {}),
128
+ ...(sanitizeText(input.channel, 64) ? { channel: sanitizeText(input.channel, 64) } : {}),
129
+ ...(sourceRefs.length > 0 ? { sourceRefs } : {}),
130
+ ...(observedCount ? { observedCount } : {}),
131
+ });
132
+ manifest.capabilities = capabilities;
133
+ const parsedAfter = connectorManifestV6Schema.safeParse(manifest);
134
+ if (!parsedAfter.success) {
135
+ return {
136
+ ok: false,
137
+ command: "connector_behavior_add",
138
+ platformId,
139
+ behaviorId,
140
+ manifestPath,
141
+ added: false,
142
+ reason: `manifest schema validation failed after behavior add: ${parsedAfter.error.issues.map((issue) => issue.path.join(".")).join(",")}`,
143
+ };
144
+ }
145
+ const nextYaml = yaml.dump(parsedAfter.data, {
146
+ lineWidth: 100,
147
+ noRefs: true,
148
+ sortKeys: false,
149
+ });
150
+ fs.writeFileSync(manifestPath, nextYaml, "utf-8");
151
+ return {
152
+ ok: true,
153
+ command: "connector_behavior_add",
154
+ platformId,
155
+ behaviorId,
156
+ manifestPath,
157
+ added: true,
158
+ nextStep: "run connector_status or reload the runtime registry before executing this behavior",
159
+ };
160
+ }
@@ -244,6 +244,14 @@ export function createCliCommands(deps) {
244
244
  return result;
245
245
  },
246
246
  },
247
+ {
248
+ name: "connector_behavior_add",
249
+ description: "Add a workspace-defined connector behavior to an existing manifest without executing custom code",
250
+ execute: async (input) => {
251
+ const surface = await Promise.resolve(opsRouter.dispatch("connector_behavior_add", input));
252
+ return surface;
253
+ },
254
+ },
247
255
  {
248
256
  name: "connector_status",
249
257
  description: "T1.2.3 — show connector inventory, trust/executable/conflict summary",
@@ -6,6 +6,7 @@ import { createCliCommands, } from "./commands/index.js";
6
6
  import { createOpsRouter } from "./ops/ops-router.js";
7
7
  import { createCliReadModels, } from "./read-models/index.js";
8
8
  import { resolvePackagedRuntime } from "./runtime/runtime-artifact-boundary.js";
9
+ import { createRestoreSnapshotStore, } from "../storage/services/restore-snapshot-store.js";
9
10
  import { createRuntimeDecisionRecorder, } from "../observability/services/runtime-decision-recorder.js";
10
11
  import { createConnectorExecutorAdapter, } from "../connectors/services/connector-executor-adapter.js";
11
12
  import { DynamicConnectorRegistry, createRegistrySnapshotStore, } from "../connectors/registry/index.js";
@@ -54,6 +55,7 @@ const BUILT_IN_CONNECTOR_MANIFESTS = [
54
55
  },
55
56
  ];
56
57
  export function createCliRuntimeDeps(overrides = {}) {
58
+ const workspaceRoot = overrides.workspaceRoot ?? process.cwd();
57
59
  const stateDb = overrides.stateDb ?? createStateDatabase();
58
60
  const observabilityDb = overrides.observabilityDb ?? createObservabilityDatabase();
59
61
  const stateApi = overrides.stateApi ?? createStateAPI(stateDb);
@@ -61,7 +63,7 @@ export function createCliRuntimeDeps(overrides = {}) {
61
63
  createCliReadModels({
62
64
  stateDb,
63
65
  observabilityDb,
64
- workspaceRoot: process.cwd(),
66
+ workspaceRoot,
65
67
  livedExperienceAuditStore: overrides.livedExperienceAuditStore,
66
68
  });
67
69
  const actionBridge = overrides.actionBridge ?? createActionBridge(stateApi);
@@ -70,12 +72,14 @@ export function createCliRuntimeDeps(overrides = {}) {
70
72
  createConnectorExecutorAdapter({
71
73
  stateDb,
72
74
  observabilityDb,
75
+ workspaceRoot,
73
76
  });
74
77
  const registry = overrides.registry ??
75
78
  new DynamicConnectorRegistry({
76
79
  builtInManifests: BUILT_IN_CONNECTOR_MANIFESTS,
77
80
  snapshotStore: createRegistrySnapshotStore(),
78
81
  });
82
+ const restoreSnapshotStore = overrides.restoreSnapshotStore ?? createRestoreSnapshotStore(stateDb);
79
83
  return {
80
84
  stateDb,
81
85
  observabilityDb,
@@ -85,9 +89,11 @@ export function createCliRuntimeDeps(overrides = {}) {
85
89
  runtimeRecorder,
86
90
  connectorExecutor,
87
91
  registry,
92
+ restoreSnapshotStore,
88
93
  };
89
94
  }
90
95
  export function createCommandRouter(options = {}) {
96
+ const workspaceRoot = process.cwd();
91
97
  const runtime = createCliRuntimeDeps(options.deps);
92
98
  const pluginRoot = path.join(process.cwd(), "plugin");
93
99
  const opsRouter = createOpsRouter({
@@ -95,10 +101,11 @@ export function createCommandRouter(options = {}) {
95
101
  readModels: runtime.readModels,
96
102
  runtimeRecorder: runtime.runtimeRecorder,
97
103
  state: runtime.stateDb,
98
- workspaceRoot: process.cwd(),
104
+ workspaceRoot,
99
105
  observabilityDb: runtime.observabilityDb,
100
106
  connectorExecutor: runtime.connectorExecutor,
101
107
  registry: runtime.registry,
108
+ restoreSnapshotStore: runtime.restoreSnapshotStore,
102
109
  });
103
110
  const commands = createCliCommands({
104
111
  readModels: runtime.readModels,
@@ -0,0 +1,79 @@
1
+ /**
2
+ * ManualRunDispatcher — T-ROS.C.3 (DR-038)
3
+ *
4
+ * Core logic: Isolated entry points for manual connector execution, wet probe,
5
+ * and heartbeat probe. All paths enforce:
6
+ * - triggerSource: "manual_run" on downstream records
7
+ * - affectsHeartbeatCadence: false (does not advance cron cadence)
8
+ *
9
+ * Writes are serialized through the same WriteQueue as cron heartbeat,
10
+ * preventing concurrent DB conflicts without blocking either path.
11
+ *
12
+ * Dependencies:
13
+ * - ConnectorExecutor (from connector-system)
14
+ * - ExperienceWriter (from body-tool-system)
15
+ * - WetProbeRunner (from connector-system)
16
+ * - heartbeatCheck (from heartbeat-surface)
17
+ *
18
+ * Boundary:
19
+ * - Does NOT implement connector adapters; delegates to ConnectorExecutor.
20
+ * - Does NOT persist raw payloads; redacted samples only.
21
+ * - Caller provides all ports; no module-scope state.
22
+ *
23
+ * Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts
24
+ */
25
+ import type { ConnectorExecutor, ConnectorResult } from "../../connectors/base/contract.js";
26
+ import type { ExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
27
+ import type { WetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
28
+ import type { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
29
+ import { type HeartbeatCheckInput, type HeartbeatSurfaceResult } from "./heartbeat-surface.js";
30
+ import type { RuntimeOpsEnvelope } from "./ops-router.js";
31
+ export interface ManualTriggerContext {
32
+ triggerSource: "manual_run";
33
+ affectsHeartbeatCadence: false;
34
+ caller?: string;
35
+ reason?: string;
36
+ }
37
+ export interface ConnectorRunInput {
38
+ platformId: string;
39
+ capabilityId: string;
40
+ payload?: Record<string, unknown>;
41
+ caller?: string;
42
+ reason?: string;
43
+ }
44
+ export interface ConnectorRunResult {
45
+ connectorResult: ConnectorResult<unknown>;
46
+ experienceId: string;
47
+ triggerSource: "manual_run";
48
+ affectsHeartbeatCadence: false;
49
+ }
50
+ export interface WetProbeRunInput {
51
+ platformId: string;
52
+ capabilityId: string;
53
+ }
54
+ export interface WetProbeManualResult {
55
+ probeResultId: string;
56
+ platformId: string;
57
+ capabilityId: string;
58
+ actualStatus: string;
59
+ httpStatus: number;
60
+ triggerSource: "manual_run";
61
+ affectsHeartbeatCadence: false;
62
+ }
63
+ export interface HeartbeatProbeInput {
64
+ deps: HeartbeatCheckInput;
65
+ caller?: string;
66
+ reason?: string;
67
+ }
68
+ export interface ManualRunDispatcher {
69
+ runConnector(input: ConnectorRunInput): Promise<RuntimeOpsEnvelope<ConnectorRunResult>>;
70
+ runWetProbe(input: WetProbeRunInput): Promise<RuntimeOpsEnvelope<WetProbeManualResult>>;
71
+ runHeartbeatProbe(input: HeartbeatProbeInput): Promise<HeartbeatSurfaceResult & ManualTriggerContext>;
72
+ }
73
+ export interface ManualRunDispatcherDeps {
74
+ connectorExecutor: ConnectorExecutor;
75
+ experienceWriter: ExperienceWriter;
76
+ wetProbeRunner: WetProbeRunner;
77
+ registryV7: CapabilityContractRegistryV7;
78
+ }
79
+ export declare function createManualRunDispatcher(deps: ManualRunDispatcherDeps): ManualRunDispatcher;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * ManualRunDispatcher — T-ROS.C.3 (DR-038)
3
+ *
4
+ * Core logic: Isolated entry points for manual connector execution, wet probe,
5
+ * and heartbeat probe. All paths enforce:
6
+ * - triggerSource: "manual_run" on downstream records
7
+ * - affectsHeartbeatCadence: false (does not advance cron cadence)
8
+ *
9
+ * Writes are serialized through the same WriteQueue as cron heartbeat,
10
+ * preventing concurrent DB conflicts without blocking either path.
11
+ *
12
+ * Dependencies:
13
+ * - ConnectorExecutor (from connector-system)
14
+ * - ExperienceWriter (from body-tool-system)
15
+ * - WetProbeRunner (from connector-system)
16
+ * - heartbeatCheck (from heartbeat-surface)
17
+ *
18
+ * Boundary:
19
+ * - Does NOT implement connector adapters; delegates to ConnectorExecutor.
20
+ * - Does NOT persist raw payloads; redacted samples only.
21
+ * - Caller provides all ports; no module-scope state.
22
+ *
23
+ * Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts
24
+ */
25
+ import * as crypto from "node:crypto";
26
+ import { heartbeatCheck, } from "./heartbeat-surface.js";
27
+ function buildManualContext(input) {
28
+ return {
29
+ triggerSource: "manual_run",
30
+ affectsHeartbeatCadence: false,
31
+ caller: input.caller,
32
+ reason: input.reason ?? "manual_run_dispatcher",
33
+ };
34
+ }
35
+ export function createManualRunDispatcher(deps) {
36
+ return {
37
+ async runConnector(input) {
38
+ const ctx = buildManualContext(input);
39
+ const decisionId = `manual:${crypto.randomUUID()}`;
40
+ const intentId = `manual-run:${input.platformId}:${input.capabilityId}`;
41
+ const idempotencyKey = `idem:manual:${crypto.randomUUID()}`;
42
+ const connectorResult = await deps.connectorExecutor.executeEffect({
43
+ platformId: input.platformId,
44
+ intent: input.capabilityId,
45
+ payload: input.payload ?? {},
46
+ decisionId,
47
+ intentId,
48
+ idempotencyKey,
49
+ });
50
+ const experienceId = crypto.randomUUID();
51
+ await deps.experienceWriter.recordExperience({
52
+ connectorId: input.platformId,
53
+ capabilityId: input.capabilityId,
54
+ result: connectorResult,
55
+ triggerSource: ctx.triggerSource,
56
+ });
57
+ const runResult = {
58
+ connectorResult,
59
+ experienceId,
60
+ triggerSource: ctx.triggerSource,
61
+ affectsHeartbeatCadence: ctx.affectsHeartbeatCadence,
62
+ };
63
+ return {
64
+ ok: connectorResult.status === "success",
65
+ command: "connector:run",
66
+ data: runResult,
67
+ runtimeMode: "workspace_full_runtime",
68
+ surfaceMode: "cli",
69
+ generatedAt: new Date().toISOString(),
70
+ warnings: [],
71
+ sourceRefs: ["manual-run-dispatcher:runConnector"],
72
+ };
73
+ },
74
+ async runWetProbe(input) {
75
+ const ctx = buildManualContext({});
76
+ // WetProbeRunner requires a CapabilityContractRegistryV7.
77
+ // In the full runtime this is available via deps.registry; here we
78
+ // delegate to the caller-provided wetProbeRunner which already has
79
+ // the registry wired. If the runner is not available, degrade.
80
+ const wetResult = await deps.wetProbeRunner.runWetProbe(input.platformId, input.capabilityId, deps.registryV7);
81
+ const result = {
82
+ probeResultId: wetResult.probeResult.probeResultId,
83
+ platformId: wetResult.probeResult.connectorId,
84
+ capabilityId: wetResult.probeResult.capabilityId,
85
+ actualStatus: wetResult.probeResult.actualStatus,
86
+ httpStatus: wetResult.httpStatus,
87
+ triggerSource: ctx.triggerSource,
88
+ affectsHeartbeatCadence: ctx.affectsHeartbeatCadence,
89
+ };
90
+ return {
91
+ ok: wetResult.probeResult.actualStatus === "available",
92
+ command: "connector_test",
93
+ data: result,
94
+ runtimeMode: "workspace_full_runtime",
95
+ surfaceMode: "cli",
96
+ generatedAt: new Date().toISOString(),
97
+ warnings: [],
98
+ sourceRefs: ["manual-run-dispatcher:runWetProbe"],
99
+ };
100
+ },
101
+ async runHeartbeatProbe(input) {
102
+ const ctx = buildManualContext(input);
103
+ const heartbeatResult = await heartbeatCheck(input.deps);
104
+ return {
105
+ ...heartbeatResult,
106
+ ...ctx,
107
+ };
108
+ },
109
+ };
110
+ }
@@ -1,6 +1,3 @@
1
- /**
2
- * Shared ops command dispatch for CLI + tool surfaces (T1.1.3, T1.2.2).
3
- */
4
1
  import { type HeartbeatCheckInput, type HeartbeatSurfaceResult } from "./heartbeat-surface.js";
5
2
  import type { CliReadModels } from "../read-models/index.js";
6
3
  import type { RuntimeDecisionRecorder } from "../../observability/services/runtime-decision-recorder.js";
@@ -8,6 +5,27 @@ import type { StateDatabase } from "../../storage/db/index.js";
8
5
  import type { ObservabilityDatabase } from "../../observability/db/index.js";
9
6
  import type { ConnectorExecutor } from "../../core/second-nature/orchestrator/effect-dispatcher.js";
10
7
  import type { DynamicConnectorRegistry } from "../../connectors/registry/index.js";
8
+ import { type HeartbeatDigestAssemblerDeps } from "../../observability/services/heartbeat-digest-assembler.js";
9
+ import { type NarrativeTimelineDeps } from "../../observability/services/narrative-timeline-query-service.js";
10
+ import { type SecretAnchorDeps } from "../../observability/services/runtime-secret-anchor-view.js";
11
+ import { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
12
+ import type { RestoreSnapshotStore } from "../../storage/services/restore-snapshot-store.js";
13
+ /** Unified response envelope for all v7 runtime-ops commands. */
14
+ export interface RuntimeOpsEnvelope<T = unknown> {
15
+ ok: boolean;
16
+ command: string;
17
+ runtimeMode: "host_safe_carrier" | "workspace_full_runtime" | "unavailable";
18
+ surfaceMode: "cli" | "openclaw_tool" | "plugin_command" | "cron_probe";
19
+ generatedAt: string;
20
+ data?: T;
21
+ error?: {
22
+ code: string;
23
+ message: string;
24
+ nextStep?: string;
25
+ };
26
+ warnings: string[];
27
+ sourceRefs: string[];
28
+ }
11
29
  export interface OpsRouterDeps {
12
30
  /** When true, packaged runtime artifacts resolved and full graph is loadable */
13
31
  runtimeAvailable: boolean;
@@ -35,9 +53,32 @@ export interface OpsRouterDeps {
35
53
  * T1.2.3: DynamicConnectorRegistry for connector:status and connector:test commands.
36
54
  */
37
55
  registry?: DynamicConnectorRegistry;
56
+ /**
57
+ * In-memory audit store for heartbeat_digest, restore, and audit commands.
58
+ * When absent, commands degrade gracefully.
59
+ */
60
+ auditStore?: AppendOnlyAuditStore;
61
+ /**
62
+ * Deps for heartbeat_digest — includes optional stateMemoryPort and deliveryAdapter.
63
+ * When absent, only auditStore is used (no state-memory enrichment).
64
+ */
65
+ heartbeatDigestDeps?: Omit<HeartbeatDigestAssemblerDeps, "auditStore">;
66
+ /**
67
+ * Deps for narrative timeline (narrative:diff, timeline commands).
68
+ */
69
+ narrativeTimelineDeps?: NarrativeTimelineDeps;
70
+ /**
71
+ * Deps for runtime_secret_bootstrap (key anchor health check).
72
+ */
73
+ secretAnchorDeps?: SecretAnchorDeps;
74
+ /**
75
+ * T-ROS.C.1: RestoreSnapshotStore for bounded state restoration.
76
+ * When absent, restore command degrades to audit-only.
77
+ */
78
+ restoreSnapshotStore?: RestoreSnapshotStore;
38
79
  }
39
80
  export interface OpsRouter {
40
81
  heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
41
- dispatch(command: string, input?: Record<string, unknown>): Promise<HeartbeatSurfaceResult | Record<string, unknown>>;
82
+ dispatch(command: string, input?: Record<string, unknown>): Promise<HeartbeatSurfaceResult | Record<string, unknown> | RuntimeOpsEnvelope>;
42
83
  }
43
84
  export declare function createOpsRouter(deps: OpsRouterDeps): OpsRouter;