@haaaiawd/second-nature 0.1.38 → 0.1.40
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/agent-inner-guide.md +18 -0
- package/index.js +10 -2
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/runtime/cli/commands/connector-init.js +11 -4
- package/runtime/cli/index.js +6 -1
- package/runtime/cli/ops/heartbeat-surface.d.ts +15 -0
- package/runtime/cli/ops/heartbeat-surface.js +16 -2
- package/runtime/cli/ops/ops-router.js +229 -83
- package/runtime/cli/ops/workspace-heartbeat-runner.js +49 -4
- package/runtime/connectors/services/connector-executor-adapter.js +192 -41
- package/runtime/core/second-nature/guidance/apply-guidance.d.ts +2 -0
- package/runtime/core/second-nature/guidance/apply-guidance.js +6 -1
- package/runtime/core/second-nature/guidance/user-reply-continuity.d.ts +1 -1
- package/runtime/core/second-nature/guidance/user-reply-continuity.js +14 -5
- package/runtime/core/second-nature/orchestrator/intent-planner.js +15 -0
- package/runtime/core/second-nature/runtime/service-entry.d.ts +3 -0
- package/runtime/core/second-nature/runtime/service-entry.js +1 -2
- package/runtime/dream/dream-engine.d.ts +14 -0
- package/runtime/dream/dream-engine.js +306 -0
- package/runtime/dream/dream-input-loader.d.ts +37 -0
- package/runtime/dream/dream-input-loader.js +155 -0
- package/runtime/dream/dream-scheduler.d.ts +75 -0
- package/runtime/dream/dream-scheduler.js +131 -0
- package/runtime/dream/index.d.ts +16 -0
- package/runtime/dream/index.js +14 -0
- package/runtime/dream/insight-extractor.d.ts +32 -0
- package/runtime/dream/insight-extractor.js +135 -0
- package/runtime/dream/memory-consolidator.d.ts +45 -0
- package/runtime/dream/memory-consolidator.js +140 -0
- package/runtime/dream/narrative-update-proposal.d.ts +34 -0
- package/runtime/dream/narrative-update-proposal.js +83 -0
- package/runtime/dream/output-validator.d.ts +20 -0
- package/runtime/dream/output-validator.js +110 -0
- package/runtime/dream/redaction-gate.d.ts +31 -0
- package/runtime/dream/redaction-gate.js +109 -0
- package/runtime/dream/relationship-update-proposal.d.ts +27 -0
- package/runtime/dream/relationship-update-proposal.js +119 -0
- package/runtime/dream/sampler.d.ts +30 -0
- package/runtime/dream/sampler.js +65 -0
- package/runtime/dream/types.d.ts +187 -0
- package/runtime/dream/types.js +11 -0
- package/runtime/guidance/fallback.js +6 -3
- package/runtime/guidance/guidance-assembler.js +5 -3
- package/runtime/guidance/output-guard.d.ts +4 -1
- package/runtime/guidance/output-guard.js +24 -0
- package/runtime/guidance/template-registry.d.ts +5 -1
- package/runtime/guidance/template-registry.js +71 -30
- package/runtime/guidance/types.d.ts +14 -0
- package/runtime/observability/projections/guidance-audit.js +4 -1
package/agent-inner-guide.md
CHANGED
|
@@ -167,3 +167,21 @@ Second Nature 在一些特定动作发生之前,会给你一个简短的内心
|
|
|
167
167
|
定制 impulse 不需要很长。一到三段,把这个平台在这个动作上的具体质感讲清楚就够了。
|
|
168
168
|
|
|
169
169
|
格式和内置 impulse 一致——前置 frontmatter,正文纯文字,第一人称"我",不带标题,不带列表。
|
|
170
|
+
|
|
171
|
+
## expression boundary 与 output guard
|
|
172
|
+
|
|
173
|
+
Second Nature 会给你一组表达边界(expression boundary),用来提醒你避免某些腔调或表达陷阱。
|
|
174
|
+
|
|
175
|
+
它不是格式规范,也不是硬 guard 的 verdict。它只负责一件事:**让你的表达更像你自己,而不是像某个模板、客服系统或日报机器。**
|
|
176
|
+
|
|
177
|
+
比如它会提醒你:
|
|
178
|
+
|
|
179
|
+
- 避免客服腔或通知系统语气
|
|
180
|
+
- 避免日报、周报或例行播报腔
|
|
181
|
+
- 避免教程、步骤说明或培训手册腔
|
|
182
|
+
- 不要虚构经历、关系、情绪事件
|
|
183
|
+
- 不要滑进高重复、硬模板化的措辞
|
|
184
|
+
|
|
185
|
+
这些提醒不会替你决定「能不能做这件事」——能不能做由 hard guard 单独判断。expression boundary 只在你已经决定要做之后,轻轻 shaping 一下你的表达方式。
|
|
186
|
+
|
|
187
|
+
你也不需要把它当成必须逐条遵守的评分表。它只是空气里的一道微弱边界:当你快要滑进某种腔调时,你会感觉到它在。
|
package/index.js
CHANGED
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
import fs from "node:fs";
|
|
56
56
|
import path from "node:path";
|
|
57
57
|
import { fileURLToPath } from "node:url";
|
|
58
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
const PLUGIN_VERSION = JSON.parse(fs.readFileSync(path.resolve(__dirname, "package.json"), "utf-8")).version;
|
|
58
60
|
import { startRuntimeService, } from "./runtime/core/second-nature/runtime/service-entry.js";
|
|
59
61
|
import { getLifecycleState, recordRegistration, } from "./runtime/core/second-nature/runtime/lifecycle-service.js";
|
|
60
62
|
import { openWorkspaceOpsBridge } from "./workspace-ops-bridge.js";
|
|
@@ -106,8 +108,9 @@ const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
|
106
108
|
"connector_test",
|
|
107
109
|
"connector_behavior_add",
|
|
108
110
|
"cycle:recent",
|
|
109
|
-
// v7 ops surface (T-ROS.C.1 / T-ROS.C.2 / T-ROS.C.3): self_health, tool_affordance,
|
|
110
|
-
// narrative:diff, timeline, restore, runtime_secret_bootstrap,
|
|
111
|
+
// v7 ops surface (T-ROS.C.1 / T-ROS.C.2 / T-ROS.C.3 / T-V7C.C.5): self_health, tool_affordance,
|
|
112
|
+
// heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap,
|
|
113
|
+
// connector:run, guidance_payload
|
|
111
114
|
"self_health",
|
|
112
115
|
"tool_affordance",
|
|
113
116
|
"heartbeat_digest",
|
|
@@ -117,6 +120,8 @@ const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
|
117
120
|
"restore",
|
|
118
121
|
"runtime_secret_bootstrap",
|
|
119
122
|
"connector:run",
|
|
123
|
+
// T-V7C.C.5: host ops surface parity — guidance_payload must be whitelisted for Claw reachability
|
|
124
|
+
"guidance_payload",
|
|
120
125
|
]);
|
|
121
126
|
function isWorkspaceBridgeCommand(command, input) {
|
|
122
127
|
if (command === "credential") {
|
|
@@ -193,6 +198,7 @@ function syncWorkspaceRootFromTool(spine, toolWorkspaceRoot) {
|
|
|
193
198
|
if (changed) {
|
|
194
199
|
spine.runtimeHandle = startRuntimeService({
|
|
195
200
|
workspaceRoot: next.runtimeRoot,
|
|
201
|
+
version: PLUGIN_VERSION,
|
|
196
202
|
});
|
|
197
203
|
}
|
|
198
204
|
}
|
|
@@ -885,6 +891,7 @@ function createActivationSpine() {
|
|
|
885
891
|
router: undefined,
|
|
886
892
|
runtimeHandle: startRuntimeService({
|
|
887
893
|
workspaceRoot: workspaceRootContext.runtimeRoot,
|
|
894
|
+
version: PLUGIN_VERSION,
|
|
888
895
|
}),
|
|
889
896
|
lifecycleState: getLifecycleState(),
|
|
890
897
|
serviceStartRecorded: false,
|
|
@@ -933,6 +940,7 @@ function refreshRegistrationState() {
|
|
|
933
940
|
spine.workspaceRootContext = workspaceRootContext;
|
|
934
941
|
spine.runtimeHandle = startRuntimeService({
|
|
935
942
|
workspaceRoot: workspaceRootContext.runtimeRoot,
|
|
943
|
+
version: PLUGIN_VERSION,
|
|
936
944
|
});
|
|
937
945
|
spine.lifecycleState = recordRegistration();
|
|
938
946
|
spine.serviceStartRecorded = false;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "second-nature",
|
|
3
3
|
"name": "Second Nature",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap.",
|
|
4
|
+
"version": "0.1.40",
|
|
5
|
+
"description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED
|
@@ -11,23 +11,30 @@ function generateManifestYaml(input) {
|
|
|
11
11
|
const displayName = input.displayName ?? platformId;
|
|
12
12
|
const family = input.family ?? "custom";
|
|
13
13
|
const runnerKind = input.runnerKind ?? "declarative_http";
|
|
14
|
-
const
|
|
14
|
+
const trustStatus = runnerKind === "declarative_http" ||
|
|
15
|
+
runnerKind === "declarative_a2a" ||
|
|
16
|
+
runnerKind === "declarative_mcp"
|
|
17
|
+
? "declarative_trusted"
|
|
18
|
+
: "custom_adapter_pending_trust";
|
|
19
|
+
const configBlock = input.baseUrl
|
|
20
|
+
? `\n config:\n baseUrl: ${input.baseUrl}`
|
|
21
|
+
: "";
|
|
15
22
|
return `schemaVersion: sn.connector.v1
|
|
16
23
|
platformId: ${platformId}
|
|
17
24
|
displayName: ${displayName}
|
|
18
|
-
family: ${family}
|
|
25
|
+
family: ${family}
|
|
19
26
|
capabilities:
|
|
20
27
|
- id: ${platformId}.placeholder
|
|
21
28
|
description: Placeholder capability — replace with real capability declarations
|
|
22
29
|
runner:
|
|
23
30
|
kind: ${runnerKind}
|
|
24
|
-
entrypoint: ""
|
|
31
|
+
entrypoint: ""${configBlock}
|
|
25
32
|
credentials: []
|
|
26
33
|
sourceRefPolicy:
|
|
27
34
|
minSourceRefs: 1
|
|
28
35
|
rejectInlineSensitivePayload: true
|
|
29
36
|
trust:
|
|
30
|
-
status:
|
|
37
|
+
status: ${trustStatus}
|
|
31
38
|
reason: generated_by_connector_init
|
|
32
39
|
`;
|
|
33
40
|
}
|
package/runtime/cli/index.js
CHANGED
|
@@ -113,7 +113,12 @@ function createWorkspaceAffordanceAssembler(registry, workspaceRoot) {
|
|
|
113
113
|
return undefined;
|
|
114
114
|
},
|
|
115
115
|
},
|
|
116
|
-
|
|
116
|
+
// W80: built-in connectors without probe history should posture as
|
|
117
|
+
// "needs_auth" (guard allows) rather than "unavailable" (guard defers).
|
|
118
|
+
credentialRequired: (platformId) => {
|
|
119
|
+
const builtInPlatforms = new Set(["moltbook", "evomap", "agent-world", "instreet"]);
|
|
120
|
+
return builtInPlatforms.has(platformId);
|
|
121
|
+
},
|
|
117
122
|
});
|
|
118
123
|
}
|
|
119
124
|
function stringifyDeltaField(delta, key) {
|
|
@@ -13,6 +13,8 @@ import type { ConnectorExecutor } from "../../core/second-nature/orchestrator/ef
|
|
|
13
13
|
import type { CapabilityContractRegistry } from "../../connectors/base/manifest.js";
|
|
14
14
|
import type { AffordanceMap } from "../../shared/types/v7-entities.js";
|
|
15
15
|
import type { ExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
|
|
16
|
+
import type { QuietDreamSchedulePort } from "../../core/second-nature/quiet/run-source-backed-quiet.js";
|
|
17
|
+
import type { HeartbeatDigestAssemblerDeps } from "../../observability/services/heartbeat-digest-assembler.js";
|
|
16
18
|
export type HeartbeatSurfaceStatus = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
|
|
17
19
|
export interface HeartbeatSurfaceResult {
|
|
18
20
|
ok: boolean;
|
|
@@ -56,5 +58,18 @@ export interface HeartbeatCheckInput {
|
|
|
56
58
|
affordanceMap?: AffordanceMap;
|
|
57
59
|
/** v7 T-V7C.C.2: experience writer for heartbeat connector attempts. */
|
|
58
60
|
experienceWriter?: ExperienceWriter;
|
|
61
|
+
/**
|
|
62
|
+
* v7 T-V7C.C.6: when present, a successful Quiet write auto-triggers Dream scheduling.
|
|
63
|
+
* Fixes the production-data gap where dream_output_index does not grow after Quiet.
|
|
64
|
+
*/
|
|
65
|
+
dreamSchedulePort?: QuietDreamSchedulePort;
|
|
66
|
+
/**
|
|
67
|
+
* v7 T-V7C.C.6: when present, generates a HeartbeatDigest after each cycle.
|
|
68
|
+
* Fixes the production-data gap where heartbeat_digest does not grow.
|
|
69
|
+
*/
|
|
70
|
+
digestOpts?: {
|
|
71
|
+
assemblerDeps: HeartbeatDigestAssemblerDeps;
|
|
72
|
+
digestWindowHour?: number;
|
|
73
|
+
};
|
|
59
74
|
}
|
|
60
75
|
export declare function heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
|
|
@@ -77,7 +77,21 @@ export async function heartbeatCheck(input) {
|
|
|
77
77
|
connectorRegistry: input.connectorRegistry,
|
|
78
78
|
affordanceMap: input.affordanceMap,
|
|
79
79
|
experienceWriter: input.experienceWriter,
|
|
80
|
+
dreamSchedulePort: input.dreamSchedulePort,
|
|
81
|
+
digestOpts: input.digestOpts,
|
|
80
82
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
try {
|
|
84
|
+
const cycle = await run(signal);
|
|
85
|
+
return mapCycleToSurface(cycle, "workspace_full_runtime");
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
status: "denied",
|
|
92
|
+
surfaceMode: "workspace_full_runtime",
|
|
93
|
+
reasons: [`heartbeat_cycle_exception:${msg.slice(0, 120)}`],
|
|
94
|
+
livedExperienceLoopClaimed: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
83
97
|
}
|
|
@@ -30,10 +30,54 @@ import { createExperienceWriter } from "../../core/second-nature/body/tool-exper
|
|
|
30
30
|
import { createCapabilityProbeResultStore, createToolExperienceStore, } from "../../storage/services/tool-experience-store.js";
|
|
31
31
|
import { createWetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
|
|
32
32
|
import { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
|
|
33
|
+
// v7 T-V7C.C.6: Dream scheduling deps for heartbeat_check quiet→dream auto-trigger
|
|
34
|
+
import { scheduleDream } from "../../dream/dream-scheduler.js";
|
|
35
|
+
import { createDreamInputLoader } from "../../dream/dream-input-loader.js";
|
|
36
|
+
import { createDiaryDreamStore } from "../../storage/services/diary-dream-store.js";
|
|
33
37
|
function coerceProbeOnlyFlag(input) {
|
|
34
38
|
const v = input?.probeOnly;
|
|
35
39
|
return v === true || v === "true" || v === 1 || v === "1";
|
|
36
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* v7 T-V7C.C.6: Build a minimal QuietDreamSchedulePort backed by the state DB.
|
|
43
|
+
* When a source-backed Quiet write completes, this port triggers Dream scheduling
|
|
44
|
+
* via the standard scheduleDream path (rules-only mode when no model port).
|
|
45
|
+
*/
|
|
46
|
+
function createQuietDreamSchedulePort(state) {
|
|
47
|
+
return {
|
|
48
|
+
async scheduleDream({ triggerKind, runId, traceId }) {
|
|
49
|
+
const dreamStore = createDiaryDreamStore(state);
|
|
50
|
+
const inputLoader = createDreamInputLoader({ database: state });
|
|
51
|
+
const statePort = {
|
|
52
|
+
async loadDreamInputs(query) {
|
|
53
|
+
return inputLoader.loadDreamInputs(query);
|
|
54
|
+
},
|
|
55
|
+
async writeDreamOutput(output) {
|
|
56
|
+
// Bridge: dream-engine emits dream/types DreamOutput; diary-dream-store expects shared/types.
|
|
57
|
+
// Structures are identical at runtime; TS strictness requires the cast.
|
|
58
|
+
await dreamStore.appendDreamOutput(output);
|
|
59
|
+
return { outputId: output.outputId, status: "acknowledged" };
|
|
60
|
+
},
|
|
61
|
+
async markDreamOutputLifecycle(input) {
|
|
62
|
+
// transitionDreamOutputLifecycle only accepts accepted|archived.
|
|
63
|
+
if (input.newStatus !== "accepted" && input.newStatus !== "archived") {
|
|
64
|
+
return { outputId: input.outputId, status: "degraded" };
|
|
65
|
+
}
|
|
66
|
+
await dreamStore.transitionDreamOutputLifecycle(input.outputId, input.newStatus);
|
|
67
|
+
return { outputId: input.outputId, status: "acknowledged" };
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const result = await scheduleDream({
|
|
71
|
+
triggerKind,
|
|
72
|
+
runId,
|
|
73
|
+
traceId,
|
|
74
|
+
statePort,
|
|
75
|
+
windowKey: "quiet_completion",
|
|
76
|
+
});
|
|
77
|
+
return { status: result.status, reason: result.reason };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
37
81
|
const SNAPSHOT_TABLE_BY_KIND = {
|
|
38
82
|
identity_profile: "identity_profile",
|
|
39
83
|
agent_goal: "agent_goal",
|
|
@@ -278,6 +322,8 @@ export function createOpsRouter(deps) {
|
|
|
278
322
|
connectorExecutor: input.connectorExecutor ?? deps.connectorExecutor,
|
|
279
323
|
connectorRegistry: input
|
|
280
324
|
?.connectorRegistry ?? deps.connectorRegistry,
|
|
325
|
+
digestOpts: input.digestOpts,
|
|
326
|
+
dreamSchedulePort: input.dreamSchedulePort,
|
|
281
327
|
}),
|
|
282
328
|
async dispatch(command, input) {
|
|
283
329
|
if (command === "heartbeat_check") {
|
|
@@ -298,61 +344,98 @@ export function createOpsRouter(deps) {
|
|
|
298
344
|
if (deps.state) {
|
|
299
345
|
experienceWriter = createExperienceWriter(createToolExperienceStore(deps.state));
|
|
300
346
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
?.
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
// v7 T-V7C.C.6: assemble digest opts when auditStore is wired.
|
|
348
|
+
let digestOpts;
|
|
349
|
+
if (deps.auditStore) {
|
|
350
|
+
digestOpts = {
|
|
351
|
+
assemblerDeps: {
|
|
352
|
+
auditStore: deps.auditStore,
|
|
353
|
+
...deps.heartbeatDigestDeps,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// v7 T-V7C.C.6: assemble dream schedule port when state DB is wired.
|
|
358
|
+
let dreamSchedulePort;
|
|
359
|
+
if (deps.state) {
|
|
360
|
+
dreamSchedulePort = createQuietDreamSchedulePort(deps.state);
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const result = await heartbeatCheck({
|
|
364
|
+
probeOnly: coerceProbeOnlyFlag(input),
|
|
365
|
+
runtimeAvailable,
|
|
366
|
+
fakeControlPlanePassthrough: input?.fakeControlPlanePassthrough &&
|
|
367
|
+
typeof input.fakeControlPlanePassthrough === "object"
|
|
368
|
+
? input.fakeControlPlanePassthrough
|
|
369
|
+
: undefined,
|
|
370
|
+
readModels: input?.readModels ??
|
|
371
|
+
deps.readModels,
|
|
372
|
+
runtimeRecorder: input
|
|
373
|
+
?.runtimeRecorder ?? deps.runtimeRecorder,
|
|
374
|
+
state: input?.state ??
|
|
375
|
+
deps.state,
|
|
376
|
+
workspaceRoot: input
|
|
377
|
+
?.workspaceRoot ?? deps.workspaceRoot,
|
|
378
|
+
timestamp: typeof input?.timestamp === "string" ? input.timestamp : undefined,
|
|
379
|
+
sessionContext: typeof input?.sessionContext === "string"
|
|
380
|
+
? input.sessionContext
|
|
381
|
+
: undefined,
|
|
382
|
+
scopeHint: input?.scopeHint,
|
|
383
|
+
connectorExecutor: input
|
|
384
|
+
?.connectorExecutor ?? deps.connectorExecutor,
|
|
385
|
+
connectorRegistry: input
|
|
386
|
+
?.connectorRegistry ?? deps.connectorRegistry,
|
|
387
|
+
affordanceMap,
|
|
388
|
+
experienceWriter,
|
|
389
|
+
digestOpts,
|
|
390
|
+
dreamSchedulePort,
|
|
391
|
+
});
|
|
392
|
+
if (result.ok &&
|
|
393
|
+
result.surfaceMode === "workspace_full_runtime" &&
|
|
394
|
+
!coerceProbeOnlyFlag(input) &&
|
|
395
|
+
deps.state &&
|
|
396
|
+
deps.restoreSnapshotStore) {
|
|
397
|
+
try {
|
|
398
|
+
const capture = await captureRuntimeSnapshot(deps, {
|
|
399
|
+
snapshotId: `heartbeat:${result.decisionId ?? "cycle"}:${Date.now()}`,
|
|
400
|
+
subjectId: result.decisionId ?? "heartbeat_check",
|
|
401
|
+
reasonCode: "heartbeat_check",
|
|
402
|
+
summaryText: `Heartbeat ${result.status} captured bounded restore snapshot`,
|
|
403
|
+
focus: result.status,
|
|
404
|
+
progress: result.reasons.join(",") || "heartbeat_completed",
|
|
405
|
+
nextIntent: "continue_runtime_loop",
|
|
406
|
+
sourceRefs: result.decisionId
|
|
407
|
+
? [`heartbeat:${result.decisionId}`]
|
|
408
|
+
: ["heartbeat:runtime"],
|
|
409
|
+
});
|
|
410
|
+
if (capture.ok) {
|
|
411
|
+
result.reasons = [...result.reasons, "restore_snapshot_captured"];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
416
|
+
result.reasons = [...result.reasons, `restore_snapshot_capture_failed:${msg}`];
|
|
348
417
|
}
|
|
349
418
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
423
|
+
const envelope = {
|
|
424
|
+
ok: false,
|
|
425
|
+
command: "heartbeat_check",
|
|
426
|
+
runtimeMode: runtimeAvailable ? "workspace_full_runtime" : "unavailable",
|
|
427
|
+
surfaceMode: "cli",
|
|
428
|
+
generatedAt: new Date().toISOString(),
|
|
429
|
+
error: {
|
|
430
|
+
code: "HEARTBEAT_CYCLE_EXCEPTION",
|
|
431
|
+
message: `heartbeat_check cycle threw unexpectedly: ${msg.slice(0, 200)}`,
|
|
432
|
+
nextStep: "check_logs_and_report",
|
|
433
|
+
},
|
|
434
|
+
warnings: [],
|
|
435
|
+
sourceRefs: [],
|
|
436
|
+
};
|
|
437
|
+
return envelope;
|
|
354
438
|
}
|
|
355
|
-
return result;
|
|
356
439
|
}
|
|
357
440
|
if (command === "fallback") {
|
|
358
441
|
const ref = typeof input?.ref === "string" ? input.ref.trim() : "";
|
|
@@ -569,7 +652,9 @@ export function createOpsRouter(deps) {
|
|
|
569
652
|
warnings.push("state_db_unavailable:capability_probe_result_not_persisted");
|
|
570
653
|
}
|
|
571
654
|
return {
|
|
572
|
-
|
|
655
|
+
// T-V7C.C.5: only "available" (HTTP 200-299) counts as success;
|
|
656
|
+
// "degraded" (429/503) and "unavailable" both result in ok=false.
|
|
657
|
+
ok: wetResult.probeResult.actualStatus === "available",
|
|
573
658
|
command: "connector_test",
|
|
574
659
|
data: {
|
|
575
660
|
...data,
|
|
@@ -1047,29 +1132,84 @@ export function createOpsRouter(deps) {
|
|
|
1047
1132
|
};
|
|
1048
1133
|
return envelope;
|
|
1049
1134
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1135
|
+
let restoreTarget;
|
|
1136
|
+
let fromVersion;
|
|
1137
|
+
let toVersion;
|
|
1138
|
+
// T-V7C.C.5: snapshotId operator-friendly parameter takes precedence over legacy fields.
|
|
1139
|
+
// When snapshotId is provided, resolve restoreTarget/fromVersion/toVersion from the
|
|
1140
|
+
// matching snapshot row; otherwise fall back to explicit legacy parameters.
|
|
1141
|
+
const snapshotId = textInput(input, "snapshotId");
|
|
1142
|
+
if (snapshotId) {
|
|
1143
|
+
if (!deps.restoreSnapshotStore) {
|
|
1144
|
+
const envelope = {
|
|
1145
|
+
ok: false,
|
|
1146
|
+
command: "restore",
|
|
1147
|
+
runtimeMode: "unavailable",
|
|
1148
|
+
surfaceMode: "cli",
|
|
1149
|
+
generatedAt,
|
|
1150
|
+
error: {
|
|
1151
|
+
code: "RESTORE_SNAPSHOT_STORE_UNAVAILABLE",
|
|
1152
|
+
message: "snapshotId restore requires restoreSnapshotStore in OpsRouterDeps",
|
|
1153
|
+
nextStep: "wire_restore_snapshot_store_into_ops_router",
|
|
1154
|
+
},
|
|
1155
|
+
warnings: [],
|
|
1156
|
+
sourceRefs: [],
|
|
1157
|
+
};
|
|
1158
|
+
return envelope;
|
|
1159
|
+
}
|
|
1160
|
+
const snapshots = await deps.restoreSnapshotStore.listSnapshots();
|
|
1161
|
+
const match = snapshots.find((s) => s.snapshotId === snapshotId);
|
|
1162
|
+
if (match) {
|
|
1163
|
+
restoreTarget = snapshotId;
|
|
1164
|
+
fromVersion = match.capturedAt;
|
|
1165
|
+
toVersion = snapshotId;
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
const envelope = {
|
|
1169
|
+
ok: false,
|
|
1170
|
+
command: "restore",
|
|
1171
|
+
runtimeMode: "workspace_full_runtime",
|
|
1172
|
+
surfaceMode: "cli",
|
|
1173
|
+
generatedAt,
|
|
1174
|
+
error: {
|
|
1175
|
+
code: "SNAPSHOT_NOT_FOUND",
|
|
1176
|
+
message: `snapshotId ${snapshotId} not found in restore_snapshot table`,
|
|
1177
|
+
nextStep: "list_available_snapshots_or_verify_snapshotId",
|
|
1178
|
+
},
|
|
1179
|
+
warnings: [],
|
|
1180
|
+
sourceRefs: [],
|
|
1181
|
+
};
|
|
1182
|
+
return envelope;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
const missingFields = [];
|
|
1187
|
+
if (typeof input?.restoreTarget !== "string")
|
|
1188
|
+
missingFields.push("restoreTarget");
|
|
1189
|
+
if (typeof input?.fromVersion !== "string")
|
|
1190
|
+
missingFields.push("fromVersion");
|
|
1191
|
+
if (typeof input?.toVersion !== "string")
|
|
1192
|
+
missingFields.push("toVersion");
|
|
1193
|
+
if (missingFields.length > 0) {
|
|
1194
|
+
const envelope = {
|
|
1195
|
+
ok: false,
|
|
1196
|
+
command: "restore",
|
|
1197
|
+
runtimeMode: "workspace_full_runtime",
|
|
1198
|
+
surfaceMode: "cli",
|
|
1199
|
+
generatedAt,
|
|
1200
|
+
error: {
|
|
1201
|
+
code: "MISSING_RESTORE_FIELDS",
|
|
1202
|
+
message: `restore requires: ${missingFields.join(", ")}`,
|
|
1203
|
+
nextStep: "reinvoke_with_required_fields",
|
|
1204
|
+
},
|
|
1205
|
+
warnings: [],
|
|
1206
|
+
sourceRefs: [],
|
|
1207
|
+
};
|
|
1208
|
+
return envelope;
|
|
1209
|
+
}
|
|
1210
|
+
restoreTarget = input.restoreTarget;
|
|
1211
|
+
fromVersion = input.fromVersion;
|
|
1212
|
+
toVersion = input.toVersion;
|
|
1073
1213
|
}
|
|
1074
1214
|
// [NEW] Invoke bounded restore via RestoreSnapshotStore when wired
|
|
1075
1215
|
let restoreResult = {
|
|
@@ -1080,16 +1220,16 @@ export function createOpsRouter(deps) {
|
|
|
1080
1220
|
};
|
|
1081
1221
|
if (deps.restoreSnapshotStore) {
|
|
1082
1222
|
restoreResult = await deps.restoreSnapshotStore.applyBoundedRestore({
|
|
1083
|
-
restoreTarget:
|
|
1084
|
-
fromVersion:
|
|
1085
|
-
toVersion:
|
|
1223
|
+
restoreTarget: restoreTarget,
|
|
1224
|
+
fromVersion: fromVersion,
|
|
1225
|
+
toVersion: toVersion,
|
|
1086
1226
|
});
|
|
1087
1227
|
}
|
|
1088
1228
|
const event = {
|
|
1089
1229
|
id: `restore-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1090
|
-
restoreTarget:
|
|
1091
|
-
fromVersion:
|
|
1092
|
-
toVersion:
|
|
1230
|
+
restoreTarget: restoreTarget,
|
|
1231
|
+
fromVersion: fromVersion,
|
|
1232
|
+
toVersion: toVersion,
|
|
1093
1233
|
triggeredBy: input?.triggeredBy ?? "operator",
|
|
1094
1234
|
reason: typeof input?.reason === "string" ? input.reason : "manual_restore",
|
|
1095
1235
|
completedEntities: restoreResult.completedEntities,
|
|
@@ -1241,7 +1381,10 @@ export function createOpsRouter(deps) {
|
|
|
1241
1381
|
capabilityIntent,
|
|
1242
1382
|
platformId,
|
|
1243
1383
|
});
|
|
1244
|
-
const
|
|
1384
|
+
const { buildExpressionBoundary } = await import("../../guidance/output-guard.js");
|
|
1385
|
+
const { getShortAtmosphereTemplate } = await import("../../guidance/template-registry.js");
|
|
1386
|
+
const atmosphere = getShortAtmosphereTemplate("active", "low");
|
|
1387
|
+
const expressionBoundary = buildExpressionBoundary(sceneType);
|
|
1245
1388
|
const envelope = {
|
|
1246
1389
|
ok: true,
|
|
1247
1390
|
command: "guidance_payload",
|
|
@@ -1258,6 +1401,8 @@ export function createOpsRouter(deps) {
|
|
|
1258
1401
|
impulseReviewStatus: impulseResult.impulse?.reviewStatus ?? null,
|
|
1259
1402
|
atmosphereText: atmosphere.text,
|
|
1260
1403
|
atmosphereReviewStatus: atmosphere.reviewStatus,
|
|
1404
|
+
expressionBoundaryConstraints: expressionBoundary.constraints,
|
|
1405
|
+
expressionBoundaryStyle: expressionBoundary.style,
|
|
1261
1406
|
},
|
|
1262
1407
|
warnings: impulseResult.source === "none"
|
|
1263
1408
|
? ["no_impulse_available_for_this_scene_and_capability"]
|
|
@@ -1266,6 +1411,7 @@ export function createOpsRouter(deps) {
|
|
|
1266
1411
|
"guidance/capability-class.ts",
|
|
1267
1412
|
"guidance/impulse-assembler.ts",
|
|
1268
1413
|
"guidance/template-registry.ts",
|
|
1414
|
+
"guidance/output-guard.ts",
|
|
1269
1415
|
],
|
|
1270
1416
|
};
|
|
1271
1417
|
return envelope;
|