@haaaiawd/second-nature 0.1.24 → 0.1.25
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/index.js +77 -0
- package/package.json +5 -5
- package/runtime/cli/commands/goal.d.ts +26 -0
- package/runtime/cli/commands/goal.js +159 -0
- package/runtime/cli/commands/index.js +37 -2
- package/runtime/cli/ops/ops-router.d.ts +1 -1
- package/runtime/cli/ops/ops-router.js +55 -1
- package/runtime/cli/read-models/index.d.ts +14 -2
- package/runtime/cli/read-models/index.js +328 -97
- package/runtime/cli/read-models/types.d.ts +80 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +33 -6
- package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +2 -1
- package/runtime/core/second-nature/orchestrator/goal-priority.js +8 -7
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +3 -1
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +39 -1
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +21 -2
- package/runtime/guidance/draft-outreach-message.js +14 -1
- package/runtime/guidance/outreach-draft-schema.d.ts +104 -0
- package/runtime/guidance/outreach-draft-schema.js +14 -0
- package/runtime/observability/audit/audit-envelope.d.ts +1 -1
- package/runtime/observability/services/lived-experience-audit.d.ts +22 -0
- package/runtime/observability/services/lived-experience-audit.js +30 -0
- package/runtime/storage/db/schema/narrative-state.d.ts +1 -1
- package/runtime/storage/db/schema/narrative-state.js +2 -2
package/index.js
CHANGED
|
@@ -103,6 +103,13 @@ const WORKSPACE_BRIDGE_COMMANDS = new Set([
|
|
|
103
103
|
"audit",
|
|
104
104
|
// T3.3.2: near-real connector smoke sentinel
|
|
105
105
|
"near_real_smoke",
|
|
106
|
+
// v6 ops surface (CR8-01): narrative, goal, dream:recent, connector_status/test, cycle:recent
|
|
107
|
+
"narrative",
|
|
108
|
+
"goal",
|
|
109
|
+
"dream:recent",
|
|
110
|
+
"connector_status",
|
|
111
|
+
"connector_test",
|
|
112
|
+
"cycle:recent",
|
|
106
113
|
]);
|
|
107
114
|
function isWorkspaceBridgeCommand(command, input) {
|
|
108
115
|
if (command === "credential") {
|
|
@@ -632,6 +639,43 @@ function createHostSafeRouter(spine) {
|
|
|
632
639
|
description: "Run near-real connector smoke (workspace runtime + connectors required)",
|
|
633
640
|
execute: async () => createUnavailableActionError("HOST_SAFE_NEAR_REAL_SMOKE_UNAVAILABLE", "Near-real connector smoke requires workspace state and observability databases; host-safe plugin cannot run connector harness.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
634
641
|
},
|
|
642
|
+
// v6 ops surface (CR8-01): host-safe router returns unavailable for workspace-only commands
|
|
643
|
+
{
|
|
644
|
+
name: "narrative",
|
|
645
|
+
description: "Show current NarrativeState (workspace runtime required)",
|
|
646
|
+
execute: async () => createUnavailableActionError("HOST_SAFE_NARRATIVE_UNAVAILABLE", "NarrativeState read requires workspace state database; host-safe plugin does not load persisted narrative rows.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: "goal",
|
|
650
|
+
description: "Owner-governed goal operations (workspace runtime required)",
|
|
651
|
+
execute: async (input) => {
|
|
652
|
+
const action = typeof input?.action === "string" ? input.action : "list";
|
|
653
|
+
if (action === "set" || action === "accept" || action === "reject") {
|
|
654
|
+
return createUnavailableActionError("HOST_SAFE_GOAL_MUTATE_UNAVAILABLE", "Goal mutation requires workspace state database; host-safe plugin cannot write persisted goal rows.", [], "run_workspace_second_nature_cli_or_full_runtime_package");
|
|
655
|
+
}
|
|
656
|
+
return createUnavailableActionError("HOST_SAFE_GOAL_READ_UNAVAILABLE", "Goal list/read requires workspace state database; host-safe plugin does not load persisted goal rows.", [], "run_workspace_second_nature_cli_or_full_runtime_package");
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: "dream:recent",
|
|
661
|
+
description: "Show recent Dream runs (workspace runtime required)",
|
|
662
|
+
execute: async () => createUnavailableActionError("HOST_SAFE_DREAM_RECENT_UNAVAILABLE", "Dream recent read requires workspace observability database; host-safe plugin does not load persisted audit events.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
name: "connector_status",
|
|
666
|
+
description: "Show connector inventory (workspace runtime required)",
|
|
667
|
+
execute: async () => createUnavailableActionError("HOST_SAFE_CONNECTOR_STATUS_UNAVAILABLE", "Connector status requires workspace state and registry scan; host-safe plugin cannot access connector manifests.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "connector_test",
|
|
671
|
+
description: "Dry-run test a connector (workspace runtime required)",
|
|
672
|
+
execute: async () => createUnavailableActionError("HOST_SAFE_CONNECTOR_TEST_UNAVAILABLE", "Connector test requires workspace state and registry; host-safe plugin cannot run connector harness.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
name: "cycle:recent",
|
|
676
|
+
description: "Show recent cycle summary (workspace runtime required)",
|
|
677
|
+
execute: async () => createUnavailableActionError("HOST_SAFE_CYCLE_RECENT_UNAVAILABLE", "Cycle recent read requires workspace observability database; host-safe plugin does not load persisted audit events.", [], "run_workspace_second_nature_cli_or_full_runtime_package"),
|
|
678
|
+
},
|
|
635
679
|
];
|
|
636
680
|
return {
|
|
637
681
|
commands,
|
|
@@ -786,6 +830,39 @@ function parseCommandInput(rawArgs) {
|
|
|
786
830
|
input: wantRepair ? { runRepairFixture: true } : undefined,
|
|
787
831
|
};
|
|
788
832
|
}
|
|
833
|
+
// v6 ops surface (CR8-01): simple command parsing for new commands
|
|
834
|
+
case "narrative":
|
|
835
|
+
return {
|
|
836
|
+
ok: true,
|
|
837
|
+
command,
|
|
838
|
+
input: rest[0] ? { narrativeId: rest[0] } : undefined,
|
|
839
|
+
};
|
|
840
|
+
case "goal":
|
|
841
|
+
return {
|
|
842
|
+
ok: true,
|
|
843
|
+
command,
|
|
844
|
+
input: rest.length > 0 ? { action: rest[0], goalId: rest[1] } : undefined,
|
|
845
|
+
};
|
|
846
|
+
case "dream:recent":
|
|
847
|
+
return {
|
|
848
|
+
ok: true,
|
|
849
|
+
command,
|
|
850
|
+
input: rest[0] ? { limit: Number(rest[0]) } : undefined,
|
|
851
|
+
};
|
|
852
|
+
case "connector_status":
|
|
853
|
+
return { ok: true, command, input: undefined };
|
|
854
|
+
case "connector_test":
|
|
855
|
+
return {
|
|
856
|
+
ok: true,
|
|
857
|
+
command,
|
|
858
|
+
input: rest[0] ? { platformId: rest[0] } : undefined,
|
|
859
|
+
};
|
|
860
|
+
case "cycle:recent":
|
|
861
|
+
return {
|
|
862
|
+
ok: true,
|
|
863
|
+
command,
|
|
864
|
+
input: rest[0] ? { limit: Number(rest[0]) } : undefined,
|
|
865
|
+
};
|
|
789
866
|
default:
|
|
790
867
|
return {
|
|
791
868
|
ok: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/second-nature",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "OpenClaw native plugin with synchronous registration, a packaged runtime artifact, and operator-facing status/explain flows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"./index.js"
|
|
34
34
|
],
|
|
35
35
|
"compat": {
|
|
36
|
-
"pluginApi": ">=2026.5.
|
|
36
|
+
"pluginApi": ">=2026.5.12"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"openclaw": ">=2026.5.
|
|
40
|
+
"openclaw": ">=2026.5.12"
|
|
41
41
|
},
|
|
42
42
|
"peerDependenciesMeta": {
|
|
43
43
|
"openclaw": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"drizzle-orm": "^0.
|
|
48
|
+
"drizzle-orm": "^0.45.2",
|
|
49
49
|
"sql.js": "^1.14.1",
|
|
50
|
-
"zod": "^4.
|
|
50
|
+
"zod": "^4.4.3"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StateDatabase } from "../../storage/db/index.js";
|
|
2
|
+
export interface GoalCommandInput {
|
|
3
|
+
action: "set" | "list" | "accept" | "reject";
|
|
4
|
+
goalId?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
completionCriteria?: string;
|
|
7
|
+
risk?: "low" | "medium" | "high";
|
|
8
|
+
kind?: "short_term" | "long_term";
|
|
9
|
+
statusFilter?: string;
|
|
10
|
+
originFilter?: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface GoalCommandResult {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
command: "goal";
|
|
16
|
+
action: string;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
error?: {
|
|
19
|
+
code: string;
|
|
20
|
+
message: string;
|
|
21
|
+
requiredUserInput?: string[];
|
|
22
|
+
nextStep?: string;
|
|
23
|
+
};
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
export declare function goalCommand(stateDb: StateDatabase | undefined, input: GoalCommandInput): Promise<GoalCommandResult>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createAgentGoalStore } from "../../storage/goal/agent-goal-store.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
function createGoalCommandError(code, message, requiredUserInput, nextStep) {
|
|
4
|
+
return {
|
|
5
|
+
ok: false,
|
|
6
|
+
command: "goal",
|
|
7
|
+
action: "unknown",
|
|
8
|
+
error: {
|
|
9
|
+
code,
|
|
10
|
+
message,
|
|
11
|
+
requiredUserInput,
|
|
12
|
+
nextStep,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function serializeGoal(goal) {
|
|
17
|
+
return {
|
|
18
|
+
goalId: goal.goalId,
|
|
19
|
+
kind: goal.kind,
|
|
20
|
+
status: goal.status,
|
|
21
|
+
origin: goal.origin,
|
|
22
|
+
description: goal.description,
|
|
23
|
+
completionCriteria: goal.completionCriteria,
|
|
24
|
+
risk: goal.risk,
|
|
25
|
+
priorityHint: goal.priorityHint,
|
|
26
|
+
sourceRefs: goal.sourceRefs,
|
|
27
|
+
acceptedBy: goal.acceptedBy,
|
|
28
|
+
createdAt: goal.createdAt,
|
|
29
|
+
updatedAt: goal.updatedAt,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function goalCommand(stateDb, input) {
|
|
33
|
+
if (!stateDb) {
|
|
34
|
+
return createGoalCommandError("STATE_UNAVAILABLE", "goal command requires StateDatabase to be wired into OpsRouterDeps", [], "wire_state_into_ops_router");
|
|
35
|
+
}
|
|
36
|
+
const store = createAgentGoalStore(stateDb);
|
|
37
|
+
const action = input.action;
|
|
38
|
+
switch (action) {
|
|
39
|
+
case "set": {
|
|
40
|
+
const description = input.description?.trim();
|
|
41
|
+
if (!description || description.length === 0) {
|
|
42
|
+
return createGoalCommandError("MISSING_DESCRIPTION", "goal set requires description", ["description"], "reinvoke_goal_set_with_description");
|
|
43
|
+
}
|
|
44
|
+
const goalId = input.goalId?.trim() || randomUUID();
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
await store.upsertAgentGoal({
|
|
47
|
+
goalId,
|
|
48
|
+
kind: input.kind ?? "short_term",
|
|
49
|
+
status: "accepted",
|
|
50
|
+
origin: "owner_set",
|
|
51
|
+
description,
|
|
52
|
+
completionCriteria: input.completionCriteria?.trim() || "",
|
|
53
|
+
risk: input.risk ?? "low",
|
|
54
|
+
priorityHint: 0,
|
|
55
|
+
sourceRefs: [],
|
|
56
|
+
acceptedBy: "owner",
|
|
57
|
+
createdAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
});
|
|
60
|
+
const created = await store.loadAgentGoal(goalId);
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
command: "goal",
|
|
64
|
+
action: "set",
|
|
65
|
+
data: {
|
|
66
|
+
goal: created ? serializeGoal(created) : null,
|
|
67
|
+
before: null,
|
|
68
|
+
after: { status: "accepted", origin: "owner_set", acceptedBy: "owner" },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
case "list": {
|
|
73
|
+
const statuses = input.statusFilter
|
|
74
|
+
? input.statusFilter.split(",").map((s) => s.trim())
|
|
75
|
+
: undefined;
|
|
76
|
+
const origins = input.originFilter
|
|
77
|
+
? input.originFilter.split(",").map((s) => s.trim())
|
|
78
|
+
: undefined;
|
|
79
|
+
const goals = await store.listAgentGoals({
|
|
80
|
+
statuses,
|
|
81
|
+
origins,
|
|
82
|
+
limit: input.limit ?? 50,
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
command: "goal",
|
|
87
|
+
action: "list",
|
|
88
|
+
data: {
|
|
89
|
+
total: goals.length,
|
|
90
|
+
goals: goals.map(serializeGoal),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case "accept": {
|
|
95
|
+
const goalId = input.goalId?.trim();
|
|
96
|
+
if (!goalId) {
|
|
97
|
+
return createGoalCommandError("MISSING_GOAL_ID", "goal accept requires goalId", ["goalId"], "reinvoke_goal_accept_with_goalId");
|
|
98
|
+
}
|
|
99
|
+
const before = await store.loadAgentGoal(goalId);
|
|
100
|
+
if (!before) {
|
|
101
|
+
return createGoalCommandError("GOAL_NOT_FOUND", `No goal found for goalId: ${goalId}`, ["goalId"], "verify_goal_id_or_run_goal_list");
|
|
102
|
+
}
|
|
103
|
+
if (before.status !== "proposal") {
|
|
104
|
+
return createGoalCommandError("INVALID_STATUS_TRANSITION", `Cannot accept goal with status '${before.status}'. Only 'proposal' goals can be accepted.`, ["goalId"], "verify_goal_status_or_run_goal_list");
|
|
105
|
+
}
|
|
106
|
+
await store.transitionGoalStatus({
|
|
107
|
+
goalId,
|
|
108
|
+
newStatus: "accepted",
|
|
109
|
+
acceptedBy: "owner",
|
|
110
|
+
updatedAt: new Date().toISOString(),
|
|
111
|
+
});
|
|
112
|
+
const after = await store.loadAgentGoal(goalId);
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
command: "goal",
|
|
116
|
+
action: "accept",
|
|
117
|
+
data: {
|
|
118
|
+
goalId,
|
|
119
|
+
before: { status: before.status, origin: before.origin },
|
|
120
|
+
after: after
|
|
121
|
+
? { status: after.status, origin: after.origin, acceptedBy: after.acceptedBy }
|
|
122
|
+
: null,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
case "reject": {
|
|
127
|
+
const goalId = input.goalId?.trim();
|
|
128
|
+
if (!goalId) {
|
|
129
|
+
return createGoalCommandError("MISSING_GOAL_ID", "goal reject requires goalId", ["goalId"], "reinvoke_goal_reject_with_goalId");
|
|
130
|
+
}
|
|
131
|
+
const before = await store.loadAgentGoal(goalId);
|
|
132
|
+
if (!before) {
|
|
133
|
+
return createGoalCommandError("GOAL_NOT_FOUND", `No goal found for goalId: ${goalId}`, ["goalId"], "verify_goal_id_or_run_goal_list");
|
|
134
|
+
}
|
|
135
|
+
if (before.status !== "proposal") {
|
|
136
|
+
return createGoalCommandError("INVALID_STATUS_TRANSITION", `Cannot reject goal with status '${before.status}'. Only 'proposal' goals can be rejected.`, ["goalId"], "verify_goal_status_or_run_goal_list");
|
|
137
|
+
}
|
|
138
|
+
await store.transitionGoalStatus({
|
|
139
|
+
goalId,
|
|
140
|
+
newStatus: "rejected",
|
|
141
|
+
updatedAt: new Date().toISOString(),
|
|
142
|
+
});
|
|
143
|
+
const after = await store.loadAgentGoal(goalId);
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
command: "goal",
|
|
147
|
+
action: "reject",
|
|
148
|
+
data: {
|
|
149
|
+
goalId,
|
|
150
|
+
before: { status: before.status, origin: before.origin },
|
|
151
|
+
after: after ? { status: after.status, origin: after.origin } : null,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
return createGoalCommandError("UNKNOWN_GOAL_ACTION", `Unknown goal action: ${action}. Supported: set, list, accept, reject.`, ["action"], "reinvoke_with_supported_action");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -26,10 +26,10 @@ export function createCliCommands(deps) {
|
|
|
26
26
|
return [
|
|
27
27
|
{
|
|
28
28
|
name: "status",
|
|
29
|
-
description: "Show aggregated Second Nature status",
|
|
29
|
+
description: "T1.2.6 — Show v6 aggregated Second Nature status (narrative + dream + cycles + runtime)",
|
|
30
30
|
execute: async (input) => {
|
|
31
31
|
const scope = typeof input?.scope === "string" ? input.scope : undefined;
|
|
32
|
-
const data = await readModels.
|
|
32
|
+
const data = await readModels.loadV6Status(scope);
|
|
33
33
|
return { ok: true, data };
|
|
34
34
|
},
|
|
35
35
|
},
|
|
@@ -260,5 +260,40 @@ export function createCliCommands(deps) {
|
|
|
260
260
|
return surface;
|
|
261
261
|
},
|
|
262
262
|
},
|
|
263
|
+
{
|
|
264
|
+
name: "goal",
|
|
265
|
+
description: "T1.2.4 — owner-governed goal operations: set, list, accept, reject",
|
|
266
|
+
execute: async (input) => {
|
|
267
|
+
const surface = await Promise.resolve(opsRouter.dispatch("goal", input));
|
|
268
|
+
return surface;
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "narrative",
|
|
273
|
+
description: "T1.2.1 — show current NarrativeState: focus, progress, next intent, source refs, grounding status",
|
|
274
|
+
execute: async (input) => {
|
|
275
|
+
const narrativeId = typeof input?.narrativeId === "string" ? input.narrativeId : undefined;
|
|
276
|
+
const data = await readModels.loadNarrative(narrativeId);
|
|
277
|
+
return { ok: true, data };
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "dream:recent",
|
|
282
|
+
description: "T1.2.2 — show recent Dream run results, candidate/accepted status, fallback/partial summary",
|
|
283
|
+
execute: async (input) => {
|
|
284
|
+
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
285
|
+
const data = await readModels.loadDreamRecent(limit);
|
|
286
|
+
return { ok: true, data };
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "cycle:recent",
|
|
291
|
+
description: "T1.2.5 — aggregate recent heartbeat, narrative, Dream, delivery, connector cycle summary",
|
|
292
|
+
execute: async (input) => {
|
|
293
|
+
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
294
|
+
const data = await readModels.loadCycleRecent(limit);
|
|
295
|
+
return { ok: true, data };
|
|
296
|
+
},
|
|
297
|
+
},
|
|
263
298
|
];
|
|
264
299
|
}
|
|
@@ -38,6 +38,6 @@ export interface OpsRouterDeps {
|
|
|
38
38
|
}
|
|
39
39
|
export interface OpsRouter {
|
|
40
40
|
heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
|
|
41
|
-
dispatch(command: string, input?: Record<string, unknown>):
|
|
41
|
+
dispatch(command: string, input?: Record<string, unknown>): Promise<HeartbeatSurfaceResult | Record<string, unknown>>;
|
|
42
42
|
}
|
|
43
43
|
export declare function createOpsRouter(deps: OpsRouterDeps): OpsRouter;
|
|
@@ -8,6 +8,7 @@ import { recordHostCapability } from "../host-capability/record-host-capability.
|
|
|
8
8
|
import { runNearRealConnectorSmoke } from "../../connectors/near-real/near-real-connector-smoke.js";
|
|
9
9
|
import { connectorInit } from "../commands/connector-init.js";
|
|
10
10
|
import { connectorStatus, connectorTest } from "../commands/connector-status.js";
|
|
11
|
+
import { goalCommand } from "../commands/goal.js";
|
|
11
12
|
function coerceProbeOnlyFlag(input) {
|
|
12
13
|
const v = input?.probeOnly;
|
|
13
14
|
return v === true || v === "true" || v === 1 || v === "1";
|
|
@@ -45,7 +46,7 @@ export function createOpsRouter(deps) {
|
|
|
45
46
|
workspaceRoot: input.workspaceRoot ?? deps.workspaceRoot,
|
|
46
47
|
connectorExecutor: input.connectorExecutor ?? deps.connectorExecutor,
|
|
47
48
|
}),
|
|
48
|
-
dispatch(command, input) {
|
|
49
|
+
async dispatch(command, input) {
|
|
49
50
|
if (command === "heartbeat_check") {
|
|
50
51
|
const runtimeAvailable = typeof input?.runtimeAvailable === "boolean"
|
|
51
52
|
? input.runtimeAvailable
|
|
@@ -213,6 +214,59 @@ export function createOpsRouter(deps) {
|
|
|
213
214
|
dryRun: input?.dryRun === false ? false : true, // default dry-run
|
|
214
215
|
});
|
|
215
216
|
}
|
|
217
|
+
if (command === "goal") {
|
|
218
|
+
const rawAction = typeof input?.action === "string" ? input.action : "list";
|
|
219
|
+
const action = ["set", "list", "accept", "reject"].includes(rawAction)
|
|
220
|
+
? rawAction
|
|
221
|
+
: "list";
|
|
222
|
+
return goalCommand(deps.state, {
|
|
223
|
+
action,
|
|
224
|
+
goalId: typeof input?.goalId === "string" ? input.goalId : undefined,
|
|
225
|
+
description: typeof input?.description === "string" ? input.description : undefined,
|
|
226
|
+
completionCriteria: typeof input?.completionCriteria === "string"
|
|
227
|
+
? input.completionCriteria
|
|
228
|
+
: undefined,
|
|
229
|
+
risk: typeof input?.risk === "string"
|
|
230
|
+
? input.risk
|
|
231
|
+
: undefined,
|
|
232
|
+
kind: typeof input?.kind === "string"
|
|
233
|
+
? input.kind
|
|
234
|
+
: undefined,
|
|
235
|
+
statusFilter: typeof input?.statusFilter === "string" ? input.statusFilter : undefined,
|
|
236
|
+
originFilter: typeof input?.originFilter === "string" ? input.originFilter : undefined,
|
|
237
|
+
limit: typeof input?.limit === "number" ? input.limit : undefined,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (command === "dream:recent") {
|
|
241
|
+
if (!deps.readModels) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
error: {
|
|
245
|
+
code: "READ_MODELS_UNAVAILABLE",
|
|
246
|
+
message: "dream:recent requires workspace read models",
|
|
247
|
+
nextStep: "wire_read_models_into_ops_router",
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
252
|
+
const data = await deps.readModels.loadDreamRecent(limit);
|
|
253
|
+
return { ok: true, data };
|
|
254
|
+
}
|
|
255
|
+
if (command === "cycle:recent") {
|
|
256
|
+
if (!deps.readModels) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
error: {
|
|
260
|
+
code: "READ_MODELS_UNAVAILABLE",
|
|
261
|
+
message: "cycle:recent requires workspace read models",
|
|
262
|
+
nextStep: "wire_read_models_into_ops_router",
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
267
|
+
const data = await deps.readModels.loadCycleRecent(limit);
|
|
268
|
+
return { ok: true, data };
|
|
269
|
+
}
|
|
216
270
|
return {
|
|
217
271
|
ok: false,
|
|
218
272
|
error: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { StateDatabase } from "../../storage/db/index.js";
|
|
2
2
|
import type { ObservabilityDatabase } from "../../observability/db/index.js";
|
|
3
3
|
import { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
|
|
4
|
-
import type { StatusReadModel, DailyReportReadModel, QuietReadModel, SessionDetailReadModel, CredentialReadModel, ExplainReadModel, ExplainSubjectKind, AuditSummaryReadModel } from "./types.js";
|
|
5
|
-
export type { AuditSummaryReadModel } from "./types.js";
|
|
4
|
+
import type { StatusReadModel, DailyReportReadModel, QuietReadModel, SessionDetailReadModel, CredentialReadModel, ExplainReadModel, ExplainSubjectKind, AuditSummaryReadModel, DreamRecentReadModel, CycleRecentReadModel, NarrativeReadModel, StatusV6ReadModel } from "./types.js";
|
|
5
|
+
export type { AuditSummaryReadModel, StatusV6ReadModel } from "./types.js";
|
|
6
6
|
export type { ExplainSubjectKind } from "./types.js";
|
|
7
7
|
import type { OperatorFallbackView } from "../../storage/fallback/operator-fallback-view.js";
|
|
8
8
|
import { type RhythmPolicySnapshot } from "../../storage/rhythm/rhythm-policy-snapshot.js";
|
|
@@ -24,6 +24,18 @@ export interface CliReadModels {
|
|
|
24
24
|
* Empty store returns `{ totalEvents: 0, events: [] }` (honest empty, not an error).
|
|
25
25
|
*/
|
|
26
26
|
loadAuditSummary(): Promise<AuditSummaryReadModel>;
|
|
27
|
+
/** T1.2.2 — recent Dream runs from audit store. */
|
|
28
|
+
loadDreamRecent(limit?: number): Promise<DreamRecentReadModel>;
|
|
29
|
+
/** T1.2.5 — recent cycle summary from audit store. */
|
|
30
|
+
loadCycleRecent(limit?: number): Promise<CycleRecentReadModel>;
|
|
31
|
+
/** T1.2.1 — current NarrativeState; returns nothing_yet when no data exists. */
|
|
32
|
+
loadNarrative(narrativeId?: string): Promise<NarrativeReadModel>;
|
|
33
|
+
/**
|
|
34
|
+
* T1.2.6 — v6 status aggregate: StatusReadModel extended with narrative, dream recent,
|
|
35
|
+
* and cycle recent sections. Each section has a sentinel status (nothing_yet / has_runs /
|
|
36
|
+
* has_cycles) so operators always get a meaningful, non-empty response.
|
|
37
|
+
*/
|
|
38
|
+
loadV6Status(scope?: string): Promise<StatusV6ReadModel>;
|
|
27
39
|
}
|
|
28
40
|
/** T1.2.1 / T1.2.2 — operator-facing read surface (subset of full CLI read models). */
|
|
29
41
|
export type OpsReadModelPort = Pick<CliReadModels, "loadStatus" | "loadDailyReport" | "loadQuiet" | "loadSession" | "loadCredential" | "explain" | "loadFallbackView">;
|