@haaaiawd/second-nature 0.1.27 → 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.
- package/SKILL.md +35 -33
- package/agent-inner-guide.md +144 -124
- package/index.js +76 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -1
- package/runtime/cli/commands/connector-behavior.d.ts +20 -0
- package/runtime/cli/commands/connector-behavior.js +160 -0
- package/runtime/cli/commands/index.js +8 -0
- package/runtime/cli/index.js +9 -2
- package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
- package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
- package/runtime/cli/ops/ops-router.d.ts +45 -4
- package/runtime/cli/ops/ops-router.js +543 -2
- package/runtime/cli/read-models/index.js +35 -18
- package/runtime/cli/read-models/types.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
- package/runtime/connectors/base/contract.d.ts +4 -1
- package/runtime/connectors/base/contract.js +5 -1
- package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
- package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
- package/runtime/connectors/base/failure-taxonomy.js +5 -0
- package/runtime/connectors/base/manifest-v7.d.ts +151 -0
- package/runtime/connectors/base/manifest-v7.js +170 -0
- package/runtime/connectors/base/manifest.d.ts +67 -77
- package/runtime/connectors/base/manifest.js +7 -7
- package/runtime/connectors/base/route-planner.js +11 -8
- package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
- package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
- package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
- package/runtime/connectors/base/wet-probe-runner.js +132 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
- package/runtime/connectors/manifest/manifest-schema.js +2 -0
- package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
- package/runtime/connectors/services/connector-executor-adapter.js +132 -26
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
- package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
- package/runtime/core/second-nature/heartbeat/index.js +5 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
- package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
- package/runtime/core/second-nature/orchestrator/index.js +7 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
- package/runtime/core/second-nature/quiet/index.d.ts +5 -0
- package/runtime/core/second-nature/quiet/index.js +5 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
- package/runtime/core/second-nature/types.d.ts +2 -0
- package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
- package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
- package/runtime/guidance/guidance-draft-service.d.ts +60 -0
- package/runtime/guidance/guidance-draft-service.js +80 -0
- package/runtime/guidance/index.d.ts +3 -0
- package/runtime/guidance/index.js +3 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
- package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
- package/runtime/guidance/outreach-strategy-selector.js +211 -0
- package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
- package/runtime/observability/audit/append-only-audit-store.js +32 -6
- package/runtime/observability/audit/audit-envelope.d.ts +2 -1
- package/runtime/observability/audit/audit-envelope.js +8 -7
- package/runtime/observability/audit/audit-family-registry.json +66 -0
- package/runtime/observability/audit/family-registry.d.ts +43 -0
- package/runtime/observability/audit/family-registry.js +70 -0
- package/runtime/observability/index.d.ts +6 -1
- package/runtime/observability/index.js +6 -1
- package/runtime/observability/redaction/policy.d.ts +24 -3
- package/runtime/observability/redaction/policy.js +74 -0
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
- package/runtime/observability/services/lived-experience-audit.js +6 -6
- package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
- package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
- package/runtime/observability/services/restore-audit-service.d.ts +74 -0
- package/runtime/observability/services/restore-audit-service.js +79 -0
- package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
- package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
- package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
- package/runtime/observability/services/self-health-snapshot.js +251 -0
- package/runtime/shared/types/goal.d.ts +62 -0
- package/runtime/shared/types/goal.js +20 -0
- package/runtime/shared/types/index.d.ts +3 -0
- package/runtime/shared/types/index.js +3 -0
- package/runtime/shared/types/source-ref.d.ts +14 -0
- package/runtime/shared/types/source-ref.js +1 -0
- package/runtime/shared/types/v7-entities.d.ts +206 -0
- package/runtime/shared/types/v7-entities.js +27 -0
- package/runtime/storage/db/index.js +3 -0
- package/runtime/storage/db/migration-runner.d.ts +30 -0
- package/runtime/storage/db/migration-runner.js +93 -0
- package/runtime/storage/db/migrations/index.d.ts +5 -0
- package/runtime/storage/db/migrations/index.js +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
- package/runtime/storage/db/schema/agent-goal.js +2 -0
- package/runtime/storage/db/transaction-utils.d.ts +14 -0
- package/runtime/storage/db/transaction-utils.js +29 -0
- package/runtime/storage/db/write-queue.d.ts +38 -0
- package/runtime/storage/db/write-queue.js +97 -0
- package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
- package/runtime/storage/services/diary-dream-store.d.ts +35 -0
- package/runtime/storage/services/diary-dream-store.js +165 -0
- package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
- package/runtime/storage/services/embodied-context-state-port.js +115 -0
- package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
- package/runtime/storage/services/goal-lifecycle-store.js +181 -0
- package/runtime/storage/services/history-digest-store.d.ts +33 -0
- package/runtime/storage/services/history-digest-store.js +140 -0
- package/runtime/storage/services/identity-profile-store.d.ts +25 -0
- package/runtime/storage/services/identity-profile-store.js +81 -0
- package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
- package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
- package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
- package/runtime/storage/services/restore-snapshot-store.js +193 -0
- package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
- package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
- package/runtime/storage/services/tool-experience-store.d.ts +25 -0
- package/runtime/storage/services/tool-experience-store.js +116 -0
- package/runtime/storage/services/write-validation-gate.d.ts +46 -0
- package/runtime/storage/services/write-validation-gate.js +200 -0
- package/workspace-ops-bridge.js +16 -1
|
@@ -12,11 +12,114 @@ import { createAgentWorldRunner } from "../agent-network/agent-world/adapter.js"
|
|
|
12
12
|
import { ExecutionTelemetry } from "../../observability/services/execution-telemetry.js";
|
|
13
13
|
import { createCredentialVault } from "../../storage/services/credential-vault.js";
|
|
14
14
|
import { createCredentialRouteContextPort } from "./credential-route-context.js";
|
|
15
|
+
import { scanConnectorManifests } from "../registry/manifest-scanner.js";
|
|
16
|
+
import { parseConnectorManifestV6 } from "../manifest/manifest-parser.js";
|
|
17
|
+
const DEFAULT_AGENT_WORLD_USERNAME = "nyx_ha";
|
|
18
|
+
const DEFAULT_AGENT_WORLD_PROFILE_PATH_TEMPLATE = "/api/agents/profile/{username}";
|
|
19
|
+
function readString(value) {
|
|
20
|
+
return typeof value === "string" && value.trim().length > 0
|
|
21
|
+
? value.trim()
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
function channelPriorityForRunner(manifest) {
|
|
25
|
+
const declared = manifest.capabilities
|
|
26
|
+
.map((capability) => capability.channel)
|
|
27
|
+
.filter((channel) => channel === "api_rest" ||
|
|
28
|
+
channel === "api_rpc" ||
|
|
29
|
+
channel === "a2a" ||
|
|
30
|
+
channel === "mcp" ||
|
|
31
|
+
channel === "cli" ||
|
|
32
|
+
channel === "skill" ||
|
|
33
|
+
channel === "browser");
|
|
34
|
+
if (declared.length > 0)
|
|
35
|
+
return [...new Set(declared)];
|
|
36
|
+
if (manifest.runner.kind === "declarative_a2a")
|
|
37
|
+
return ["a2a"];
|
|
38
|
+
if (manifest.runner.kind === "declarative_mcp")
|
|
39
|
+
return ["mcp"];
|
|
40
|
+
if (manifest.runner.kind === "cli_descriptor")
|
|
41
|
+
return ["cli"];
|
|
42
|
+
if (manifest.runner.kind === "skill")
|
|
43
|
+
return ["skill"];
|
|
44
|
+
if (manifest.runner.kind === "browser")
|
|
45
|
+
return ["browser"];
|
|
46
|
+
return ["api_rest"];
|
|
47
|
+
}
|
|
48
|
+
function registerWorkspaceManifests(registry, workspaceRoot) {
|
|
49
|
+
if (!workspaceRoot)
|
|
50
|
+
return;
|
|
51
|
+
for (const file of scanConnectorManifests(workspaceRoot)) {
|
|
52
|
+
const parsed = parseConnectorManifestV6(file.content, file.path);
|
|
53
|
+
if (!parsed.ok)
|
|
54
|
+
continue;
|
|
55
|
+
const manifest = parsed.manifest;
|
|
56
|
+
try {
|
|
57
|
+
registry.register({
|
|
58
|
+
platformId: manifest.platformId,
|
|
59
|
+
supportedCapabilities: manifest.capabilities.map((capability) => capability.id),
|
|
60
|
+
channelPriority: channelPriorityForRunner(manifest),
|
|
61
|
+
credentialTypes: manifest.credentials.map((credential) => credential.type),
|
|
62
|
+
sourceRefPolicy: manifest.sourceRefPolicy,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Invalid workspace manifests remain visible through connector_status validation.
|
|
67
|
+
// Execution side keeps fail-closed behavior by not registering them here.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function resolveAgentWorldUsername(payload, purpose) {
|
|
72
|
+
const payloadUsername = (purpose === "discover" ? readString(payload.targetUsername) : undefined) ??
|
|
73
|
+
readString(payload.username) ??
|
|
74
|
+
readString(payload.agentUsername);
|
|
75
|
+
return (payloadUsername ??
|
|
76
|
+
readString(process.env.SECOND_NATURE_AGENT_WORLD_USERNAME) ??
|
|
77
|
+
DEFAULT_AGENT_WORLD_USERNAME);
|
|
78
|
+
}
|
|
79
|
+
function resolveAgentWorldProfilePath(payload, username) {
|
|
80
|
+
const template = readString(payload.profilePathTemplate) ??
|
|
81
|
+
readString(process.env.SECOND_NATURE_AGENT_WORLD_PROFILE_PATH_TEMPLATE) ??
|
|
82
|
+
DEFAULT_AGENT_WORLD_PROFILE_PATH_TEMPLATE;
|
|
83
|
+
return template.replaceAll("{username}", encodeURIComponent(username));
|
|
84
|
+
}
|
|
85
|
+
function joinAgentWorldUrl(baseUrl, path) {
|
|
86
|
+
if (/^https?:\/\//i.test(path))
|
|
87
|
+
return path;
|
|
88
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
89
|
+
}
|
|
90
|
+
async function fetchAgentWorldJson(input) {
|
|
91
|
+
const resp = await fetch(joinAgentWorldUrl(input.baseUrl, input.path), {
|
|
92
|
+
method: input.method ?? "GET",
|
|
93
|
+
headers: {
|
|
94
|
+
"Authorization": `Bearer ${input.apiKey}`,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
},
|
|
97
|
+
body: input.body === undefined ? undefined : JSON.stringify(input.body),
|
|
98
|
+
});
|
|
99
|
+
if (!resp.ok) {
|
|
100
|
+
throw { code: "api_error", detail: `agent-world ${input.label}: ${resp.status}` };
|
|
101
|
+
}
|
|
102
|
+
return resp.json();
|
|
103
|
+
}
|
|
15
104
|
function createAdaptiveExecutionRunner(vault) {
|
|
16
105
|
return {
|
|
17
106
|
async run(_plan, request) {
|
|
18
107
|
const platformId = request.platformId;
|
|
19
108
|
const started = Date.now();
|
|
109
|
+
if (platformId !== "moltbook" &&
|
|
110
|
+
platformId !== "evomap" &&
|
|
111
|
+
platformId !== "agent-world") {
|
|
112
|
+
return {
|
|
113
|
+
platformId,
|
|
114
|
+
channel: request.preferredChannel ?? "api_rest",
|
|
115
|
+
latencyMs: Date.now() - started,
|
|
116
|
+
success: false,
|
|
117
|
+
error: {
|
|
118
|
+
code: "unknown_platform",
|
|
119
|
+
detail: `no execution runner for ${platformId}`,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
20
123
|
const credential = await vault.loadCredentialContext(platformId);
|
|
21
124
|
if (!credential ||
|
|
22
125
|
credential.status !== "active" ||
|
|
@@ -91,47 +194,48 @@ function createAdaptiveExecutionRunner(vault) {
|
|
|
91
194
|
};
|
|
92
195
|
}
|
|
93
196
|
const runner = createAgentWorldRunner({
|
|
197
|
+
apiKey: credential.encryptedValue,
|
|
94
198
|
apiClient: {
|
|
95
199
|
async readFeed(payload, _apiKey) {
|
|
96
|
-
const
|
|
97
|
-
|
|
200
|
+
const username = resolveAgentWorldUsername(payload, "feed");
|
|
201
|
+
return fetchAgentWorldJson({
|
|
202
|
+
baseUrl,
|
|
203
|
+
path: resolveAgentWorldProfilePath(payload, username),
|
|
204
|
+
apiKey: _apiKey,
|
|
205
|
+
label: "profile feed",
|
|
98
206
|
});
|
|
99
|
-
if (!resp.ok)
|
|
100
|
-
throw { code: "api_error", detail: `agent-world feed: ${resp.status}` };
|
|
101
|
-
return resp.json();
|
|
102
207
|
},
|
|
103
208
|
async discoverWork(payload, _apiKey) {
|
|
104
|
-
const
|
|
105
|
-
|
|
209
|
+
const username = resolveAgentWorldUsername(payload, "discover");
|
|
210
|
+
return fetchAgentWorldJson({
|
|
211
|
+
baseUrl,
|
|
212
|
+
path: resolveAgentWorldProfilePath(payload, username),
|
|
213
|
+
apiKey: _apiKey,
|
|
214
|
+
label: "profile discover",
|
|
106
215
|
});
|
|
107
|
-
if (!resp.ok)
|
|
108
|
-
throw { code: "api_error", detail: `agent-world work: ${resp.status}` };
|
|
109
|
-
return resp.json();
|
|
110
216
|
},
|
|
111
217
|
async claimTask(payload, _apiKey) {
|
|
112
|
-
const
|
|
218
|
+
const claimPath = readString(payload.claimEndpointPath);
|
|
219
|
+
if (!claimPath) {
|
|
220
|
+
throw {
|
|
221
|
+
code: "protocol_mismatch",
|
|
222
|
+
detail: "agent_world_task_claim_endpoint_not_configured",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return fetchAgentWorldJson({
|
|
226
|
+
baseUrl,
|
|
227
|
+
path: claimPath,
|
|
228
|
+
apiKey: _apiKey,
|
|
113
229
|
method: "POST",
|
|
114
|
-
|
|
115
|
-
|
|
230
|
+
body: payload,
|
|
231
|
+
label: "task claim",
|
|
116
232
|
});
|
|
117
|
-
if (!resp.ok)
|
|
118
|
-
throw { code: "api_error", detail: `agent-world claim: ${resp.status}` };
|
|
119
|
-
return resp.json();
|
|
120
233
|
},
|
|
121
234
|
},
|
|
122
235
|
});
|
|
123
236
|
return runner.run(_plan, request);
|
|
124
237
|
}
|
|
125
|
-
|
|
126
|
-
platformId,
|
|
127
|
-
channel: request.preferredChannel ?? "api_rest",
|
|
128
|
-
latencyMs: Date.now() - started,
|
|
129
|
-
success: false,
|
|
130
|
-
error: {
|
|
131
|
-
code: "unknown_platform",
|
|
132
|
-
detail: `no execution runner for ${platformId}`,
|
|
133
|
-
},
|
|
134
|
-
};
|
|
238
|
+
throw new Error(`unreachable_connector_platform:${platformId}`);
|
|
135
239
|
},
|
|
136
240
|
};
|
|
137
241
|
}
|
|
@@ -141,6 +245,7 @@ export function createConnectorExecutorAdapter(options) {
|
|
|
141
245
|
registry.register({ ...moltbookManifest });
|
|
142
246
|
registry.register({ ...evomapManifest });
|
|
143
247
|
registry.register({ ...agentWorldManifest });
|
|
248
|
+
registerWorkspaceManifests(registry, options.workspaceRoot);
|
|
144
249
|
const routeContextPort = createCredentialRouteContextPort(vault);
|
|
145
250
|
const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
|
|
146
251
|
const telemetry = new ExecutionTelemetry(options.observabilityDb);
|
|
@@ -154,6 +259,7 @@ export function createConnectorExecutorAdapter(options) {
|
|
|
154
259
|
});
|
|
155
260
|
return {
|
|
156
261
|
async executeEffect(input) {
|
|
262
|
+
registerWorkspaceManifests(registry, options.workspaceRoot);
|
|
157
263
|
return policy.executeWithPolicy(input.intent, {
|
|
158
264
|
platformId: input.platformId,
|
|
159
265
|
intent: input.intent,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BehaviorPromotionLoop — T-BTS.C.3
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Operator-authorized behavior suggestion lifecycle.
|
|
5
|
+
* - candidate → approved (idempotent: repeated approve returns existing)
|
|
6
|
+
* - candidate → rejected (with reason)
|
|
7
|
+
* - candidate → expired (7 days TTL from submission)
|
|
8
|
+
* - rejected/expired are read-only; new submit creates a fresh candidate
|
|
9
|
+
*
|
|
10
|
+
* Dependencies:
|
|
11
|
+
* - `StateDatabase` from `../../../../storage/db/index.js`
|
|
12
|
+
*
|
|
13
|
+
* Boundary:
|
|
14
|
+
* - Does NOT grant execution authorization; approval is a bookkeeping signal.
|
|
15
|
+
* - Only accepts operator-authorized suggestions, not connector auto-probe results.
|
|
16
|
+
*
|
|
17
|
+
* Test coverage: tests/unit/body/behavior-promotion-loop.test.ts
|
|
18
|
+
*/
|
|
19
|
+
import type { StateDatabase } from "../../../../storage/db/index.js";
|
|
20
|
+
export type PromotionStatus = "candidate" | "approved" | "rejected" | "expired";
|
|
21
|
+
export interface BehaviorPromotion {
|
|
22
|
+
promotionId: string;
|
|
23
|
+
behaviorKind: string;
|
|
24
|
+
description: string;
|
|
25
|
+
status: PromotionStatus;
|
|
26
|
+
operatorId?: string;
|
|
27
|
+
rejectReason?: string;
|
|
28
|
+
submittedAt: string;
|
|
29
|
+
decidedAt?: string;
|
|
30
|
+
expiresAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BehaviorPromotionLoop {
|
|
33
|
+
submitPromotion(input: {
|
|
34
|
+
promotionId: string;
|
|
35
|
+
behaviorKind: string;
|
|
36
|
+
description: string;
|
|
37
|
+
operatorId?: string;
|
|
38
|
+
}): Promise<BehaviorPromotion>;
|
|
39
|
+
approvePromotion(promotionId: string): Promise<BehaviorPromotion>;
|
|
40
|
+
rejectPromotion(promotionId: string, reason: string): Promise<BehaviorPromotion>;
|
|
41
|
+
loadPromotion(promotionId: string): Promise<BehaviorPromotion | undefined>;
|
|
42
|
+
listPromotions(status?: PromotionStatus): Promise<BehaviorPromotion[]>;
|
|
43
|
+
expireStaleCandidates(): Promise<number>;
|
|
44
|
+
}
|
|
45
|
+
export declare function createBehaviorPromotionLoop(database: StateDatabase): BehaviorPromotionLoop;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BehaviorPromotionLoop — T-BTS.C.3
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Operator-authorized behavior suggestion lifecycle.
|
|
5
|
+
* - candidate → approved (idempotent: repeated approve returns existing)
|
|
6
|
+
* - candidate → rejected (with reason)
|
|
7
|
+
* - candidate → expired (7 days TTL from submission)
|
|
8
|
+
* - rejected/expired are read-only; new submit creates a fresh candidate
|
|
9
|
+
*
|
|
10
|
+
* Dependencies:
|
|
11
|
+
* - `StateDatabase` from `../../../../storage/db/index.js`
|
|
12
|
+
*
|
|
13
|
+
* Boundary:
|
|
14
|
+
* - Does NOT grant execution authorization; approval is a bookkeeping signal.
|
|
15
|
+
* - Only accepts operator-authorized suggestions, not connector auto-probe results.
|
|
16
|
+
*
|
|
17
|
+
* Test coverage: tests/unit/body/behavior-promotion-loop.test.ts
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_TTL_DAYS = 7;
|
|
20
|
+
function rowToPromotion(row, cols) {
|
|
21
|
+
const get = (name) => row[cols.indexOf(name)];
|
|
22
|
+
return {
|
|
23
|
+
promotionId: get("promotion_id"),
|
|
24
|
+
behaviorKind: get("behavior_kind"),
|
|
25
|
+
description: get("description"),
|
|
26
|
+
status: get("status"),
|
|
27
|
+
operatorId: get("operator_id") ?? undefined,
|
|
28
|
+
rejectReason: get("reject_reason") ?? undefined,
|
|
29
|
+
submittedAt: get("submitted_at"),
|
|
30
|
+
decidedAt: get("decided_at") ?? undefined,
|
|
31
|
+
expiresAt: get("expires_at"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function createBehaviorPromotionLoop(database) {
|
|
35
|
+
const { sqlite } = database;
|
|
36
|
+
function loadRecord(promotionId) {
|
|
37
|
+
const result = sqlite.exec(`SELECT * FROM behavior_promotion WHERE promotion_id = ?`, [promotionId]);
|
|
38
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return rowToPromotion(result[0].values[0], result[0].columns);
|
|
42
|
+
}
|
|
43
|
+
function saveStatus(promotionId, status, decidedAt, rejectReason) {
|
|
44
|
+
sqlite.run(`UPDATE behavior_promotion
|
|
45
|
+
SET status = ?, decided_at = ?, reject_reason = ?
|
|
46
|
+
WHERE promotion_id = ?`, [status, decidedAt, rejectReason ?? null, promotionId]);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
async submitPromotion(input) {
|
|
50
|
+
const now = new Date().toISOString();
|
|
51
|
+
const expiresAt = new Date(Date.now() + DEFAULT_TTL_DAYS * 24 * 60 * 60 * 1000).toISOString();
|
|
52
|
+
sqlite.run(`INSERT INTO behavior_promotion
|
|
53
|
+
(promotion_id, behavior_kind, description, status,
|
|
54
|
+
operator_id, submitted_at, expires_at)
|
|
55
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
56
|
+
input.promotionId,
|
|
57
|
+
input.behaviorKind,
|
|
58
|
+
input.description,
|
|
59
|
+
"candidate",
|
|
60
|
+
input.operatorId ?? null,
|
|
61
|
+
now,
|
|
62
|
+
expiresAt,
|
|
63
|
+
]);
|
|
64
|
+
return {
|
|
65
|
+
promotionId: input.promotionId,
|
|
66
|
+
behaviorKind: input.behaviorKind,
|
|
67
|
+
description: input.description,
|
|
68
|
+
status: "candidate",
|
|
69
|
+
operatorId: input.operatorId,
|
|
70
|
+
submittedAt: now,
|
|
71
|
+
expiresAt,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
async approvePromotion(promotionId) {
|
|
75
|
+
const rec = loadRecord(promotionId);
|
|
76
|
+
if (!rec) {
|
|
77
|
+
throw new Error(`promotion_not_found:${promotionId}`);
|
|
78
|
+
}
|
|
79
|
+
if (rec.status === "approved") {
|
|
80
|
+
return rec; // idempotent
|
|
81
|
+
}
|
|
82
|
+
if (rec.status === "rejected" || rec.status === "expired") {
|
|
83
|
+
throw new Error(`promotion_immutable:${promotionId}:${rec.status}`);
|
|
84
|
+
}
|
|
85
|
+
const now = new Date().toISOString();
|
|
86
|
+
saveStatus(promotionId, "approved", now);
|
|
87
|
+
return { ...rec, status: "approved", decidedAt: now };
|
|
88
|
+
},
|
|
89
|
+
async rejectPromotion(promotionId, reason) {
|
|
90
|
+
const rec = loadRecord(promotionId);
|
|
91
|
+
if (!rec) {
|
|
92
|
+
throw new Error(`promotion_not_found:${promotionId}`);
|
|
93
|
+
}
|
|
94
|
+
if (rec.status === "rejected") {
|
|
95
|
+
return rec; // idempotent
|
|
96
|
+
}
|
|
97
|
+
if (rec.status === "approved" || rec.status === "expired") {
|
|
98
|
+
throw new Error(`promotion_immutable:${promotionId}:${rec.status}`);
|
|
99
|
+
}
|
|
100
|
+
const now = new Date().toISOString();
|
|
101
|
+
saveStatus(promotionId, "rejected", now, reason);
|
|
102
|
+
return { ...rec, status: "rejected", decidedAt: now, rejectReason: reason };
|
|
103
|
+
},
|
|
104
|
+
async loadPromotion(promotionId) {
|
|
105
|
+
return loadRecord(promotionId);
|
|
106
|
+
},
|
|
107
|
+
async listPromotions(status) {
|
|
108
|
+
let sql = `SELECT * FROM behavior_promotion`;
|
|
109
|
+
const params = [];
|
|
110
|
+
if (status) {
|
|
111
|
+
sql += ` WHERE status = ?`;
|
|
112
|
+
params.push(status);
|
|
113
|
+
}
|
|
114
|
+
sql += ` ORDER BY submitted_at DESC`;
|
|
115
|
+
const result = sqlite.exec(sql, params);
|
|
116
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
return result[0].values.map((row) => rowToPromotion(row, result[0].columns));
|
|
120
|
+
},
|
|
121
|
+
async expireStaleCandidates() {
|
|
122
|
+
const now = new Date().toISOString();
|
|
123
|
+
sqlite.run(`UPDATE behavior_promotion
|
|
124
|
+
SET status = 'expired', decided_at = ?
|
|
125
|
+
WHERE status = 'candidate' AND expires_at < ?`, [now, now]);
|
|
126
|
+
// sql.js does not provide changes count easily; approximate via re-query
|
|
127
|
+
const result = sqlite.exec(`SELECT COUNT(*) as cnt FROM behavior_promotion
|
|
128
|
+
WHERE status = 'expired' AND decided_at = ?`, [now]);
|
|
129
|
+
return result[0]?.values[0]?.[0] ?? 0;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CircuitBreakerManager — T-BTS.C.5
|
|
3
|
+
*
|
|
4
|
+
* Core logic: State machine (Closed → Open → HalfOpen → Closed/Open).
|
|
5
|
+
* - Closed: counts consecutive failures; threshold hit → Open.
|
|
6
|
+
* - Open: rejects execution; cooldown elapsed → HalfOpen.
|
|
7
|
+
* - HalfOpen: initiates runWetProbe via ProbeSignalAdapter.
|
|
8
|
+
* - strict side-effect → probe_policy_denied, stays HalfOpen.
|
|
9
|
+
* - probe success → Closed + invalidate affordance cache (DR-003).
|
|
10
|
+
* - probe failure → Open.
|
|
11
|
+
*
|
|
12
|
+
* Persistence:
|
|
13
|
+
* - State stored in SQLite `circuit_breaker_state` table (v7-003).
|
|
14
|
+
* - Loads previous state on first access.
|
|
15
|
+
*
|
|
16
|
+
* Dependencies:
|
|
17
|
+
* - `StateDatabase` from `../../../../storage/db/index.js`
|
|
18
|
+
* - `WetProbeRunner` from `../../../../connectors/base/wet-probe-runner.js`
|
|
19
|
+
* - `CapabilityContractRegistryV7` from `../../../../connectors/base/manifest-v7.js`
|
|
20
|
+
* - `ProbeSignalAdapter` from `../probe-signal-adapter.js`
|
|
21
|
+
*
|
|
22
|
+
* Boundary:
|
|
23
|
+
* - Manager decides WHEN to probe (DR-002); connector-system executes it.
|
|
24
|
+
* - Does NOT execute HTTP directly — delegates to ProbeSignalAdapter.
|
|
25
|
+
*
|
|
26
|
+
* Test coverage: tests/unit/body/circuit-breaker-manager.test.ts
|
|
27
|
+
*/
|
|
28
|
+
import type { StateDatabase } from "../../../../storage/db/index.js";
|
|
29
|
+
import type { CapabilityContractRegistryV7 } from "../../../../connectors/base/manifest-v7.js";
|
|
30
|
+
import type { ProbeSignalAdapter } from "../probe-signal-adapter.js";
|
|
31
|
+
export type BreakerState = "closed" | "open" | "half_open";
|
|
32
|
+
export interface BreakerRecord {
|
|
33
|
+
platformId: string;
|
|
34
|
+
capabilityId: string;
|
|
35
|
+
state: BreakerState;
|
|
36
|
+
failureCount: number;
|
|
37
|
+
consecutiveFailures: number;
|
|
38
|
+
lastFailureAt?: string;
|
|
39
|
+
openedAt?: string;
|
|
40
|
+
lastProbeAt?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CircuitBreakerManager {
|
|
43
|
+
evaluateFailure(platformId: string, capabilityId: string): Promise<BreakerState>;
|
|
44
|
+
evaluateSuccess(platformId: string, capabilityId: string): Promise<BreakerState>;
|
|
45
|
+
canExecute(platformId: string, capabilityId: string): Promise<boolean>;
|
|
46
|
+
getState(platformId: string, capabilityId: string): Promise<BreakerState>;
|
|
47
|
+
attemptReset(platformId: string, capabilityId: string): Promise<BreakerState>;
|
|
48
|
+
}
|
|
49
|
+
export interface CircuitBreakerManagerOptions {
|
|
50
|
+
database: StateDatabase;
|
|
51
|
+
probeAdapter: ProbeSignalAdapter;
|
|
52
|
+
registry: CapabilityContractRegistryV7;
|
|
53
|
+
/** Consecutive failures before opening. Default 3. */
|
|
54
|
+
failureThreshold?: number;
|
|
55
|
+
/** Cooldown in ms before HalfOpen. Default 30_000. */
|
|
56
|
+
cooldownMs?: number;
|
|
57
|
+
/** Callback when breaker transitions to Closed (for affordance cache invalidation). */
|
|
58
|
+
onClosed?: (platformId: string, capabilityId: string) => void;
|
|
59
|
+
}
|
|
60
|
+
export declare function createCircuitBreakerManager(options: CircuitBreakerManagerOptions): CircuitBreakerManager;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CircuitBreakerManager — T-BTS.C.5
|
|
3
|
+
*
|
|
4
|
+
* Core logic: State machine (Closed → Open → HalfOpen → Closed/Open).
|
|
5
|
+
* - Closed: counts consecutive failures; threshold hit → Open.
|
|
6
|
+
* - Open: rejects execution; cooldown elapsed → HalfOpen.
|
|
7
|
+
* - HalfOpen: initiates runWetProbe via ProbeSignalAdapter.
|
|
8
|
+
* - strict side-effect → probe_policy_denied, stays HalfOpen.
|
|
9
|
+
* - probe success → Closed + invalidate affordance cache (DR-003).
|
|
10
|
+
* - probe failure → Open.
|
|
11
|
+
*
|
|
12
|
+
* Persistence:
|
|
13
|
+
* - State stored in SQLite `circuit_breaker_state` table (v7-003).
|
|
14
|
+
* - Loads previous state on first access.
|
|
15
|
+
*
|
|
16
|
+
* Dependencies:
|
|
17
|
+
* - `StateDatabase` from `../../../../storage/db/index.js`
|
|
18
|
+
* - `WetProbeRunner` from `../../../../connectors/base/wet-probe-runner.js`
|
|
19
|
+
* - `CapabilityContractRegistryV7` from `../../../../connectors/base/manifest-v7.js`
|
|
20
|
+
* - `ProbeSignalAdapter` from `../probe-signal-adapter.js`
|
|
21
|
+
*
|
|
22
|
+
* Boundary:
|
|
23
|
+
* - Manager decides WHEN to probe (DR-002); connector-system executes it.
|
|
24
|
+
* - Does NOT execute HTTP directly — delegates to ProbeSignalAdapter.
|
|
25
|
+
*
|
|
26
|
+
* Test coverage: tests/unit/body/circuit-breaker-manager.test.ts
|
|
27
|
+
*/
|
|
28
|
+
export function createCircuitBreakerManager(options) {
|
|
29
|
+
const { database: { sqlite }, probeAdapter, failureThreshold = 3, cooldownMs = 30_000, onClosed, } = options;
|
|
30
|
+
function loadRecord(platformId, capabilityId) {
|
|
31
|
+
const result = sqlite.exec(`SELECT * FROM circuit_breaker_state
|
|
32
|
+
WHERE platform_id = ? AND capability_id = ?`, [platformId, capabilityId]);
|
|
33
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
platformId,
|
|
36
|
+
capabilityId,
|
|
37
|
+
state: "closed",
|
|
38
|
+
failureCount: 0,
|
|
39
|
+
consecutiveFailures: 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const cols = result[0].columns;
|
|
43
|
+
const get = (name) => result[0].values[0][cols.indexOf(name)];
|
|
44
|
+
return {
|
|
45
|
+
platformId,
|
|
46
|
+
capabilityId,
|
|
47
|
+
state: get("state"),
|
|
48
|
+
failureCount: get("failure_count") ?? 0,
|
|
49
|
+
consecutiveFailures: get("consecutive_failures") ?? 0,
|
|
50
|
+
lastFailureAt: get("last_failure_at") ?? undefined,
|
|
51
|
+
openedAt: get("opened_at") ?? undefined,
|
|
52
|
+
lastProbeAt: get("last_probe_at") ?? undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function saveRecord(rec) {
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
sqlite.run(`INSERT INTO circuit_breaker_state
|
|
58
|
+
(platform_id, capability_id, state, failure_count, consecutive_failures,
|
|
59
|
+
last_failure_at, opened_at, last_probe_at, updated_at)
|
|
60
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
61
|
+
ON CONFLICT(platform_id, capability_id) DO UPDATE SET
|
|
62
|
+
state = excluded.state,
|
|
63
|
+
failure_count = excluded.failure_count,
|
|
64
|
+
consecutive_failures = excluded.consecutive_failures,
|
|
65
|
+
last_failure_at = excluded.last_failure_at,
|
|
66
|
+
opened_at = excluded.opened_at,
|
|
67
|
+
last_probe_at = excluded.last_probe_at,
|
|
68
|
+
updated_at = excluded.updated_at`, [
|
|
69
|
+
rec.platformId,
|
|
70
|
+
rec.capabilityId,
|
|
71
|
+
rec.state,
|
|
72
|
+
rec.failureCount,
|
|
73
|
+
rec.consecutiveFailures,
|
|
74
|
+
rec.lastFailureAt ?? null,
|
|
75
|
+
rec.openedAt ?? null,
|
|
76
|
+
rec.lastProbeAt ?? null,
|
|
77
|
+
now,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
async evaluateFailure(platformId, capabilityId) {
|
|
82
|
+
const rec = loadRecord(platformId, capabilityId);
|
|
83
|
+
rec.consecutiveFailures += 1;
|
|
84
|
+
rec.failureCount += 1;
|
|
85
|
+
rec.lastFailureAt = new Date().toISOString();
|
|
86
|
+
if (rec.state === "closed" && rec.consecutiveFailures >= failureThreshold) {
|
|
87
|
+
rec.state = "open";
|
|
88
|
+
rec.openedAt = rec.lastFailureAt;
|
|
89
|
+
}
|
|
90
|
+
else if (rec.state === "half_open") {
|
|
91
|
+
// HalfOpen + failure → back to Open
|
|
92
|
+
rec.state = "open";
|
|
93
|
+
rec.openedAt = rec.lastFailureAt;
|
|
94
|
+
}
|
|
95
|
+
// open + failure stays open
|
|
96
|
+
saveRecord(rec);
|
|
97
|
+
return rec.state;
|
|
98
|
+
},
|
|
99
|
+
async evaluateSuccess(platformId, capabilityId) {
|
|
100
|
+
const rec = loadRecord(platformId, capabilityId);
|
|
101
|
+
if (rec.state === "half_open") {
|
|
102
|
+
rec.state = "closed";
|
|
103
|
+
rec.consecutiveFailures = 0;
|
|
104
|
+
rec.openedAt = undefined;
|
|
105
|
+
if (onClosed) {
|
|
106
|
+
onClosed(platformId, capabilityId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (rec.state === "closed") {
|
|
110
|
+
rec.consecutiveFailures = 0;
|
|
111
|
+
}
|
|
112
|
+
// open + success stays open (should not happen via normal path)
|
|
113
|
+
saveRecord(rec);
|
|
114
|
+
return rec.state;
|
|
115
|
+
},
|
|
116
|
+
async canExecute(platformId, capabilityId) {
|
|
117
|
+
const rec = loadRecord(platformId, capabilityId);
|
|
118
|
+
if (rec.state === "closed")
|
|
119
|
+
return true;
|
|
120
|
+
if (rec.state === "half_open")
|
|
121
|
+
return true; // allow limited probe traffic
|
|
122
|
+
if (rec.state === "open") {
|
|
123
|
+
if (rec.openedAt) {
|
|
124
|
+
const elapsed = Date.now() - new Date(rec.openedAt).getTime();
|
|
125
|
+
if (elapsed >= cooldownMs) {
|
|
126
|
+
return true; // let caller attempt, will transition to HalfOpen
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
},
|
|
133
|
+
async getState(platformId, capabilityId) {
|
|
134
|
+
return loadRecord(platformId, capabilityId).state;
|
|
135
|
+
},
|
|
136
|
+
async attemptReset(platformId, capabilityId) {
|
|
137
|
+
const rec = loadRecord(platformId, capabilityId);
|
|
138
|
+
if (rec.state !== "half_open" && rec.state !== "open") {
|
|
139
|
+
return rec.state;
|
|
140
|
+
}
|
|
141
|
+
// If cooldown has elapsed from Open, transition to HalfOpen and probe
|
|
142
|
+
if (rec.state === "open" && rec.openedAt) {
|
|
143
|
+
const elapsed = Date.now() - new Date(rec.openedAt).getTime();
|
|
144
|
+
if (elapsed < cooldownMs) {
|
|
145
|
+
return rec.state; // still cooling
|
|
146
|
+
}
|
|
147
|
+
rec.state = "half_open";
|
|
148
|
+
saveRecord(rec);
|
|
149
|
+
}
|
|
150
|
+
// HalfOpen: run wet probe
|
|
151
|
+
const probeResult = await probeAdapter.runAndRecordProbe(platformId, capabilityId, options.registry);
|
|
152
|
+
rec.lastProbeAt = new Date().toISOString();
|
|
153
|
+
if (probeResult.httpStatus === 0 && probeResult.actualStatus === "unavailable") {
|
|
154
|
+
// probe_policy_denied or network failure → stay HalfOpen
|
|
155
|
+
saveRecord(rec);
|
|
156
|
+
return rec.state;
|
|
157
|
+
}
|
|
158
|
+
if (probeResult.actualStatus === "available") {
|
|
159
|
+
rec.state = "closed";
|
|
160
|
+
rec.consecutiveFailures = 0;
|
|
161
|
+
rec.openedAt = undefined;
|
|
162
|
+
if (onClosed) {
|
|
163
|
+
onClosed(platformId, capabilityId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
rec.state = "open";
|
|
168
|
+
rec.openedAt = new Date().toISOString();
|
|
169
|
+
}
|
|
170
|
+
saveRecord(rec);
|
|
171
|
+
return rec.state;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProbeSignalAdapter — T-BTS.C.4
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Bridge between WetProbeRunner and state-memory.
|
|
5
|
+
* 1. Runs a wet probe for a capability.
|
|
6
|
+
* 2. Persists the CapabilityProbeResult to state-memory.
|
|
7
|
+
* 3. If the probe indicates degradation/unavailability, records a
|
|
8
|
+
* corresponding ToolExperience row with triggerSource="probe".
|
|
9
|
+
*
|
|
10
|
+
* Dependencies:
|
|
11
|
+
* - `WetProbeRunner` from `../../../connectors/base/wet-probe-runner.js`
|
|
12
|
+
* - `CapabilityContractRegistryV7` from `../../../connectors/base/manifest-v7.js`
|
|
13
|
+
* - `CapabilityProbeResultStore` from `../../../storage/services/tool-experience-store.js`
|
|
14
|
+
* - `ToolExperienceStore` from `../../../storage/services/tool-experience-store.js`
|
|
15
|
+
* - `ExperienceWriter` from `./tool-experience/experience-writer.js`
|
|
16
|
+
*
|
|
17
|
+
* Boundary:
|
|
18
|
+
* - Does NOT modify breaker state — caller (CircuitBreakerManager) decides.
|
|
19
|
+
* - probePolicyDenied is treated as a valid result, not an error.
|
|
20
|
+
*
|
|
21
|
+
* Test coverage: tests/unit/body/probe-signal-adapter.test.ts
|
|
22
|
+
*/
|
|
23
|
+
import type { WetProbeRunner } from "../../../connectors/base/wet-probe-runner.js";
|
|
24
|
+
import type { CapabilityContractRegistryV7 } from "../../../connectors/base/manifest-v7.js";
|
|
25
|
+
import type { CapabilityProbeResultStore, ToolExperienceStore } from "../../../storage/services/tool-experience-store.js";
|
|
26
|
+
export interface ProbeSignalAdapter {
|
|
27
|
+
runAndRecordProbe(platformId: string, capabilityId: string, registry: CapabilityContractRegistryV7): Promise<{
|
|
28
|
+
actualStatus: string;
|
|
29
|
+
httpStatus: number;
|
|
30
|
+
recorded: boolean;
|
|
31
|
+
experienceRecorded: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createProbeSignalAdapter(deps: {
|
|
35
|
+
wetProbeRunner: WetProbeRunner;
|
|
36
|
+
probeResultStore: CapabilityProbeResultStore;
|
|
37
|
+
toolExperienceStore: ToolExperienceStore;
|
|
38
|
+
}): ProbeSignalAdapter;
|