principles-disciple 1.8.0 → 1.8.1
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/dist/core/config.d.ts +2 -0
- package/dist/core/session-tracker.d.ts +3 -1
- package/dist/core/session-tracker.js +12 -3
- package/dist/hooks/llm.d.ts +1 -0
- package/dist/hooks/llm.js +17 -70
- package/dist/hooks/progressive-trust-gate.d.ts +1 -0
- package/dist/hooks/progressive-trust-gate.js +45 -0
- package/dist/hooks/prompt.d.ts +2 -0
- package/dist/hooks/prompt.js +27 -6
- package/dist/hooks/subagent.js +2 -2
- package/dist/http/principles-console-route.js +114 -0
- package/dist/service/empathy-observer-manager.d.ts +46 -10
- package/dist/service/empathy-observer-manager.js +249 -64
- package/dist/service/evolution-worker.js +1 -0
- package/dist/service/health-query-service.d.ts +170 -0
- package/dist/service/health-query-service.js +662 -0
- package/dist/service/nocturnal-runtime.d.ts +2 -2
- package/dist/service/nocturnal-runtime.js +75 -4
- package/dist/service/nocturnal-service.js +2 -2
- package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +48 -0
- package/dist/service/subagent-workflow/empathy-observer-workflow-manager.js +480 -0
- package/dist/service/subagent-workflow/index.d.ts +4 -0
- package/dist/service/subagent-workflow/index.js +3 -0
- package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +77 -0
- package/dist/service/subagent-workflow/runtime-direct-driver.js +75 -0
- package/dist/service/subagent-workflow/types.d.ts +259 -0
- package/dist/service/subagent-workflow/types.js +11 -0
- package/dist/service/subagent-workflow/workflow-store.d.ts +26 -0
- package/dist/service/subagent-workflow/workflow-store.js +165 -0
- package/dist/tools/deep-reflect.js +2 -2
- package/openclaw.plugin.json +6 -1
- package/package.json +3 -3
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { SubagentRunResult, SubagentWaitResult, SubagentGetSessionMessagesResult, PluginLogger } from '../../openclaw-sdk.js';
|
|
2
|
+
export interface TransportDriver {
|
|
3
|
+
run(params: RunParams): Promise<RunResult>;
|
|
4
|
+
wait(params: WaitParams): Promise<WaitResult>;
|
|
5
|
+
getResult(params: GetResultParams): Promise<GetResultResult>;
|
|
6
|
+
cleanup(params: CleanupParams): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface RunParams {
|
|
9
|
+
sessionKey: string;
|
|
10
|
+
message: string;
|
|
11
|
+
lane?: string;
|
|
12
|
+
deliver?: boolean;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
expectsCompletionMessage?: boolean;
|
|
15
|
+
extraSystemPrompt?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface RunResult {
|
|
18
|
+
runId: string;
|
|
19
|
+
}
|
|
20
|
+
export interface WaitParams {
|
|
21
|
+
runId: string;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface WaitResult {
|
|
25
|
+
status: 'ok' | 'error' | 'timeout';
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface GetResultParams {
|
|
29
|
+
sessionKey: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface GetResultResult {
|
|
33
|
+
messages: unknown[];
|
|
34
|
+
assistantTexts?: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface CleanupParams {
|
|
37
|
+
sessionKey: string;
|
|
38
|
+
deleteTranscript?: boolean;
|
|
39
|
+
}
|
|
40
|
+
type PluginRuntimeSubagent = {
|
|
41
|
+
run: (params: {
|
|
42
|
+
sessionKey: string;
|
|
43
|
+
message: string;
|
|
44
|
+
lane?: string;
|
|
45
|
+
deliver?: boolean;
|
|
46
|
+
idempotencyKey?: string;
|
|
47
|
+
expectsCompletionMessage?: boolean;
|
|
48
|
+
extraSystemPrompt?: string;
|
|
49
|
+
}) => Promise<SubagentRunResult>;
|
|
50
|
+
waitForRun: (params: {
|
|
51
|
+
runId: string;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}) => Promise<SubagentWaitResult>;
|
|
54
|
+
getSessionMessages: (params: {
|
|
55
|
+
sessionKey: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
}) => Promise<SubagentGetSessionMessagesResult>;
|
|
58
|
+
deleteSession: (params: {
|
|
59
|
+
sessionKey: string;
|
|
60
|
+
deleteTranscript?: boolean;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
export declare class RuntimeDirectDriver implements TransportDriver {
|
|
64
|
+
private readonly subagent;
|
|
65
|
+
private readonly logger;
|
|
66
|
+
constructor(options: {
|
|
67
|
+
subagent: PluginRuntimeSubagent;
|
|
68
|
+
logger: PluginLogger;
|
|
69
|
+
});
|
|
70
|
+
/** Expose subagent for availability checking (used by workflow manager for surface degrade) */
|
|
71
|
+
getSubagent(): PluginRuntimeSubagent;
|
|
72
|
+
run(params: RunParams): Promise<RunResult>;
|
|
73
|
+
wait(params: WaitParams): Promise<WaitResult>;
|
|
74
|
+
getResult(params: GetResultParams): Promise<GetResultResult>;
|
|
75
|
+
cleanup(params: CleanupParams): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export class RuntimeDirectDriver {
|
|
2
|
+
subagent;
|
|
3
|
+
logger;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.subagent = options.subagent;
|
|
6
|
+
this.logger = options.logger;
|
|
7
|
+
}
|
|
8
|
+
/** Expose subagent for availability checking (used by workflow manager for surface degrade) */
|
|
9
|
+
getSubagent() {
|
|
10
|
+
return this.subagent;
|
|
11
|
+
}
|
|
12
|
+
async run(params) {
|
|
13
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Spawning subagent: sessionKey=${params.sessionKey}`);
|
|
14
|
+
try {
|
|
15
|
+
const result = await this.subagent.run({
|
|
16
|
+
sessionKey: params.sessionKey,
|
|
17
|
+
message: params.message,
|
|
18
|
+
lane: params.lane ?? 'subagent',
|
|
19
|
+
deliver: params.deliver ?? false,
|
|
20
|
+
idempotencyKey: params.idempotencyKey,
|
|
21
|
+
expectsCompletionMessage: params.expectsCompletionMessage ?? true,
|
|
22
|
+
extraSystemPrompt: params.extraSystemPrompt,
|
|
23
|
+
});
|
|
24
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Spawn succeeded: runId=${result.runId}`);
|
|
25
|
+
return { runId: result.runId };
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
this.logger.error(`[PD:RuntimeDirectDriver] Spawn failed: ${String(error)}`);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async wait(params) {
|
|
33
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Waiting for run: runId=${params.runId}, timeout=${params.timeoutMs}ms`);
|
|
34
|
+
try {
|
|
35
|
+
const result = await this.subagent.waitForRun({
|
|
36
|
+
runId: params.runId,
|
|
37
|
+
timeoutMs: params.timeoutMs,
|
|
38
|
+
});
|
|
39
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Wait completed: status=${result.status}`);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
this.logger.error(`[PD:RuntimeDirectDriver] Wait failed: ${String(error)}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async getResult(params) {
|
|
48
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Getting messages: sessionKey=${params.sessionKey}`);
|
|
49
|
+
try {
|
|
50
|
+
const result = await this.subagent.getSessionMessages({
|
|
51
|
+
sessionKey: params.sessionKey,
|
|
52
|
+
limit: params.limit ?? 20,
|
|
53
|
+
});
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.logger.error(`[PD:RuntimeDirectDriver] GetResult failed: ${String(error)}`);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async cleanup(params) {
|
|
62
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Cleaning up session: sessionKey=${params.sessionKey}`);
|
|
63
|
+
try {
|
|
64
|
+
await this.subagent.deleteSession({
|
|
65
|
+
sessionKey: params.sessionKey,
|
|
66
|
+
deleteTranscript: params.deleteTranscript,
|
|
67
|
+
});
|
|
68
|
+
this.logger.info(`[PD:RuntimeDirectDriver] Cleanup succeeded`);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.logger.error(`[PD:RuntimeDirectDriver] Cleanup failed: ${String(error)}`);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Workflow Helper - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* This file defines the TypeScript interfaces for the workflow helper system
|
|
5
|
+
* that manages subagent lifecycle (empathy observer, deep-reflect, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Design reference: docs/design/2026-03-31-subagent-workflow-helper-design.md
|
|
8
|
+
*
|
|
9
|
+
* @module subagent-workflow/types
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* First-phase helper transport.
|
|
13
|
+
* This helper currently models only plugin-owned runtime_direct workflows.
|
|
14
|
+
*/
|
|
15
|
+
export type WorkflowTransport = 'runtime_direct';
|
|
16
|
+
/**
|
|
17
|
+
* States in the workflow state machine.
|
|
18
|
+
*
|
|
19
|
+
* State transitions:
|
|
20
|
+
*
|
|
21
|
+
* PENDING ──→ ACTIVE ──→ WAIT_RESULT ──→ FINALIZING ──→ COMPLETED
|
|
22
|
+
* │ │ │
|
|
23
|
+
* │ ├──────────────────────────────┤
|
|
24
|
+
* │ │ (on timeout/error) │
|
|
25
|
+
* │ ▼ ▼
|
|
26
|
+
* │ TERMINAL_ERROR ◄─────────────────┘
|
|
27
|
+
* │ │
|
|
28
|
+
* │ │ (cleanup_pending if deleteSession fails)
|
|
29
|
+
* │ ▼
|
|
30
|
+
* └──────── CLEANUP_PENDING
|
|
31
|
+
*
|
|
32
|
+
* States marked with (*) are terminal states.
|
|
33
|
+
*/
|
|
34
|
+
export type WorkflowState = 'pending' | 'active' | 'wait_result' | 'finalizing' | 'completed' | 'terminal_error' | 'cleanup_pending' | 'expired';
|
|
35
|
+
/**
|
|
36
|
+
* Metadata stored with each workflow for auditing and debugging.
|
|
37
|
+
*/
|
|
38
|
+
export interface WorkflowMetadata {
|
|
39
|
+
parentSessionId: string;
|
|
40
|
+
workspaceDir?: string;
|
|
41
|
+
taskInput: unknown;
|
|
42
|
+
startedAt: number;
|
|
43
|
+
/** Human-readable workflow type for debugging */
|
|
44
|
+
workflowType: string;
|
|
45
|
+
/** Extra custom metadata specific to workflow type */
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Context passed to parseResult() when reading subagent output.
|
|
50
|
+
*/
|
|
51
|
+
export interface WorkflowResultContext {
|
|
52
|
+
/** Raw messages from getSessionMessages() */
|
|
53
|
+
messages: unknown[];
|
|
54
|
+
/** Convenience: pre-extracted assistant texts */
|
|
55
|
+
assistantTexts?: string[];
|
|
56
|
+
/** The workflow run metadata */
|
|
57
|
+
metadata: WorkflowMetadata;
|
|
58
|
+
/** The subagent run result status (only for runtime_direct) */
|
|
59
|
+
waitStatus?: 'ok' | 'error' | 'timeout';
|
|
60
|
+
/** Error message if waitStatus is 'error' */
|
|
61
|
+
waitError?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Context passed to persistResult() after successful parsing.
|
|
65
|
+
*/
|
|
66
|
+
export interface WorkflowPersistContext<TResult> {
|
|
67
|
+
/** Parsed result from parseResult() */
|
|
68
|
+
result: TResult;
|
|
69
|
+
/** The workflow run metadata */
|
|
70
|
+
metadata: WorkflowMetadata;
|
|
71
|
+
/** Workspace context directory */
|
|
72
|
+
workspaceDir: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Result of calling startWorkflow().
|
|
76
|
+
* Returned to caller so they can track the workflow.
|
|
77
|
+
*/
|
|
78
|
+
export interface WorkflowHandle {
|
|
79
|
+
/** Unique workflow ID */
|
|
80
|
+
workflowId: string;
|
|
81
|
+
/** Child session key for the subagent */
|
|
82
|
+
childSessionKey: string;
|
|
83
|
+
/** Run ID (only for runtime_direct transport) */
|
|
84
|
+
runId?: string;
|
|
85
|
+
/** The workflow state at creation */
|
|
86
|
+
state: WorkflowState;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Specification for a subagent workflow.
|
|
90
|
+
* Each workflow type (empathy observer, deep-reflect) implements this spec.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const empathySpec: SubagentWorkflowSpec<EmpathyResult> = {
|
|
95
|
+
* workflowType: 'empathy-observer',
|
|
96
|
+
* transport: 'runtime_direct',
|
|
97
|
+
* timeoutMs: 30_000,
|
|
98
|
+
* ttlMs: 5 * 60 * 1000,
|
|
99
|
+
* shouldDeleteSessionAfterFinalize: true,
|
|
100
|
+
* parseResult: async (ctx) => parseEmpathyResult(ctx),
|
|
101
|
+
* persistResult: async (ctx) => { /* persist to trajectory *\/ },
|
|
102
|
+
* shouldFinalizeOnWaitStatus: (status) => status === 'ok',
|
|
103
|
+
* };
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export interface SubagentWorkflowSpec<TResult> {
|
|
107
|
+
/** Unique identifier for this workflow type */
|
|
108
|
+
workflowType: string;
|
|
109
|
+
/** Which transport mechanism to use */
|
|
110
|
+
transport: WorkflowTransport;
|
|
111
|
+
/** Build the prompt / input payload for the child subagent */
|
|
112
|
+
buildPrompt: (taskInput: unknown, metadata: WorkflowMetadata) => string;
|
|
113
|
+
/** Max time to wait for subagent completion */
|
|
114
|
+
timeoutMs: number;
|
|
115
|
+
/** Time-to-live for orphan cleanup (e.g., 5 minutes) */
|
|
116
|
+
ttlMs: number;
|
|
117
|
+
/** Whether to delete session after successful finalize */
|
|
118
|
+
shouldDeleteSessionAfterFinalize: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Parse subagent output into structured result.
|
|
121
|
+
* Return null if parsing fails or result is invalid.
|
|
122
|
+
*/
|
|
123
|
+
parseResult: (ctx: WorkflowResultContext) => Promise<TResult | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Persist the parsed result to storage (trajectory, pain signal, etc.)
|
|
126
|
+
*/
|
|
127
|
+
persistResult: (ctx: WorkflowPersistContext<TResult>) => Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Determine if workflow should finalize given wait status.
|
|
130
|
+
* For runtime_direct: typically finalize only on 'ok', skip on 'timeout'/'error'.
|
|
131
|
+
*/
|
|
132
|
+
shouldFinalizeOnWaitStatus: (status: 'ok' | 'error' | 'timeout') => boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Payload returned by empathy observer subagent.
|
|
136
|
+
*/
|
|
137
|
+
export type EmpathyObserverPayload = {
|
|
138
|
+
damageDetected?: boolean;
|
|
139
|
+
severity?: 'mild' | 'moderate' | 'severe' | string;
|
|
140
|
+
confidence?: number;
|
|
141
|
+
reason?: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Empathy observer workflow result.
|
|
145
|
+
*/
|
|
146
|
+
export interface EmpathyResult {
|
|
147
|
+
damageDetected: boolean;
|
|
148
|
+
severity: 'mild' | 'moderate' | 'severe';
|
|
149
|
+
confidence: number;
|
|
150
|
+
reason: string;
|
|
151
|
+
/** Derived pain score based on severity */
|
|
152
|
+
painScore: number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Empathy observer workflow specification.
|
|
156
|
+
* This is the concrete spec for PR2 migration.
|
|
157
|
+
*/
|
|
158
|
+
export interface EmpathyObserverWorkflowSpec extends SubagentWorkflowSpec<EmpathyResult> {
|
|
159
|
+
workflowType: 'empathy-observer';
|
|
160
|
+
transport: 'runtime_direct';
|
|
161
|
+
timeoutMs: 30_000;
|
|
162
|
+
ttlMs: 300_000;
|
|
163
|
+
shouldDeleteSessionAfterFinalize: true;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The main workflow manager interface.
|
|
167
|
+
* This is what the helper exposes to business modules.
|
|
168
|
+
*/
|
|
169
|
+
export interface WorkflowManager {
|
|
170
|
+
/**
|
|
171
|
+
* Start a new workflow.
|
|
172
|
+
* Creates workflow state, spawns subagent, and returns handle.
|
|
173
|
+
*/
|
|
174
|
+
startWorkflow: <TResult>(spec: SubagentWorkflowSpec<TResult>, options: {
|
|
175
|
+
parentSessionId: string;
|
|
176
|
+
workspaceDir?: string;
|
|
177
|
+
taskInput: unknown;
|
|
178
|
+
metadata?: Record<string, unknown>;
|
|
179
|
+
}) => Promise<WorkflowHandle>;
|
|
180
|
+
/**
|
|
181
|
+
* Notify workflow of wait result (runtime_direct path).
|
|
182
|
+
* Called by the wait polling logic after waitForRun completes.
|
|
183
|
+
*/
|
|
184
|
+
notifyWaitResult: (workflowId: string, status: 'ok' | 'error' | 'timeout', error?: string) => Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* Optional fallback observation path.
|
|
187
|
+
* This is not the primary completion contract for runtime_direct workflows.
|
|
188
|
+
* Use it only for recovery / compatibility if a lifecycle event is observed later.
|
|
189
|
+
*/
|
|
190
|
+
notifyLifecycleEvent: (workflowId: string, event: 'subagent_spawned' | 'subagent_ended', data?: {
|
|
191
|
+
outcome?: 'ok' | 'error' | 'timeout' | 'killed' | 'reset' | 'deleted';
|
|
192
|
+
error?: string;
|
|
193
|
+
}) => Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Finalize a workflow exactly once (idempotent).
|
|
196
|
+
* Reads result, parses, persists, and cleans up.
|
|
197
|
+
*/
|
|
198
|
+
finalizeOnce: (workflowId: string) => Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Sweep expired workflows (orphan cleanup).
|
|
201
|
+
* Should be called periodically by the evolution worker.
|
|
202
|
+
*/
|
|
203
|
+
sweepExpiredWorkflows: (maxAgeMs?: number) => Promise<number>;
|
|
204
|
+
/**
|
|
205
|
+
* Return a compact workflow-centric debug view for operators.
|
|
206
|
+
*/
|
|
207
|
+
getWorkflowDebugSummary: (workflowId: string, eventLimit?: number) => Promise<WorkflowDebugSummary | null>;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Database row for subagent_workflows table.
|
|
211
|
+
*/
|
|
212
|
+
export interface WorkflowRow {
|
|
213
|
+
workflow_id: string;
|
|
214
|
+
workflow_type: string;
|
|
215
|
+
transport: WorkflowTransport;
|
|
216
|
+
parent_session_id: string;
|
|
217
|
+
child_session_key: string;
|
|
218
|
+
run_id: string | null;
|
|
219
|
+
state: WorkflowState;
|
|
220
|
+
cleanup_state: 'none' | 'pending' | 'failed' | 'completed';
|
|
221
|
+
created_at: number;
|
|
222
|
+
updated_at: number;
|
|
223
|
+
last_observed_at?: number | null;
|
|
224
|
+
metadata_json: string;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Database row for subagent_workflow_events table.
|
|
228
|
+
*/
|
|
229
|
+
export interface WorkflowEventRow {
|
|
230
|
+
workflow_id: string;
|
|
231
|
+
event_type: string;
|
|
232
|
+
from_state: WorkflowState | null;
|
|
233
|
+
to_state: WorkflowState;
|
|
234
|
+
reason: string;
|
|
235
|
+
payload_json: string;
|
|
236
|
+
created_at: number;
|
|
237
|
+
}
|
|
238
|
+
export interface WorkflowDebugSummary {
|
|
239
|
+
workflowId: string;
|
|
240
|
+
workflowType: string;
|
|
241
|
+
transport: WorkflowTransport;
|
|
242
|
+
parentSessionId: string;
|
|
243
|
+
childSessionKey: string;
|
|
244
|
+
runId: string | null;
|
|
245
|
+
state: WorkflowState;
|
|
246
|
+
cleanupState: 'none' | 'pending' | 'failed' | 'completed';
|
|
247
|
+
lastObservedAt: number | null;
|
|
248
|
+
metadata: WorkflowMetadata;
|
|
249
|
+
recentEvents: Array<{
|
|
250
|
+
eventType: string;
|
|
251
|
+
fromState: WorkflowState | null;
|
|
252
|
+
toState: WorkflowState;
|
|
253
|
+
reason: string;
|
|
254
|
+
createdAt: number;
|
|
255
|
+
payload: Record<string, unknown>;
|
|
256
|
+
}>;
|
|
257
|
+
}
|
|
258
|
+
export type { SubagentRunResult, SubagentWaitResult, SubagentGetSessionMessagesResult, } from '../../openclaw-sdk.js';
|
|
259
|
+
export type { PluginLogger } from '../../openclaw-sdk.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Workflow Helper - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* This file defines the TypeScript interfaces for the workflow helper system
|
|
5
|
+
* that manages subagent lifecycle (empathy observer, deep-reflect, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Design reference: docs/design/2026-03-31-subagent-workflow-helper-design.md
|
|
8
|
+
*
|
|
9
|
+
* @module subagent-workflow/types
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { WorkflowRow, WorkflowEventRow, WorkflowState } from './types.js';
|
|
2
|
+
export interface WorkflowStoreOptions {
|
|
3
|
+
workspaceDir: string;
|
|
4
|
+
busyTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class WorkflowStore {
|
|
7
|
+
private readonly workspaceDir;
|
|
8
|
+
private readonly dbPath;
|
|
9
|
+
private readonly db;
|
|
10
|
+
constructor(opts: WorkflowStoreOptions);
|
|
11
|
+
dispose(): void;
|
|
12
|
+
private initSchema;
|
|
13
|
+
createWorkflow(row: Omit<WorkflowRow, 'cleanup_state'>): void;
|
|
14
|
+
updateWorkflowState(workflowId: string, state: WorkflowState, reason?: string): void;
|
|
15
|
+
updateWorkflowRunId(workflowId: string, runId: string): void;
|
|
16
|
+
updateCleanupState(workflowId: string, cleanupState: 'none' | 'pending' | 'failed' | 'completed'): void;
|
|
17
|
+
touchWorkflow(workflowId: string): void;
|
|
18
|
+
getWorkflow(workflowId: string): WorkflowRow | null;
|
|
19
|
+
getWorkflowByChildSession(childSessionKey: string): WorkflowRow | null;
|
|
20
|
+
getWorkflowByParentSession(parentSessionId: string, workflowType?: string): WorkflowRow | null;
|
|
21
|
+
getActiveWorkflows(workflowType?: string): WorkflowRow[];
|
|
22
|
+
getExpiredWorkflows(maxAgeMs: number): WorkflowRow[];
|
|
23
|
+
recordEvent(workflowId: string, eventType: string, fromState: WorkflowState | null, toState: WorkflowState, reason: string, payload: Record<string, unknown>): void;
|
|
24
|
+
getEvents(workflowId: string): WorkflowEventRow[];
|
|
25
|
+
deleteWorkflow(workflowId: string): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
const SCHEMA_VERSION = 1;
|
|
5
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
6
|
+
export class WorkflowStore {
|
|
7
|
+
workspaceDir;
|
|
8
|
+
dbPath;
|
|
9
|
+
db;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.workspaceDir = path.resolve(opts.workspaceDir);
|
|
12
|
+
const stateDir = path.join(this.workspaceDir, '.state');
|
|
13
|
+
this.dbPath = path.join(stateDir, 'subagent_workflows.db');
|
|
14
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
15
|
+
this.db = new Database(this.dbPath);
|
|
16
|
+
this.db.pragma('journal_mode = WAL');
|
|
17
|
+
this.db.pragma('foreign_keys = ON');
|
|
18
|
+
this.db.pragma('synchronous = NORMAL');
|
|
19
|
+
this.db.pragma(`busy_timeout = ${Math.max(0, opts.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS)}`);
|
|
20
|
+
this.initSchema();
|
|
21
|
+
}
|
|
22
|
+
dispose() {
|
|
23
|
+
this.db.close();
|
|
24
|
+
}
|
|
25
|
+
initSchema() {
|
|
26
|
+
this.db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
28
|
+
version INTEGER NOT NULL
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS subagent_workflows (
|
|
32
|
+
workflow_id TEXT PRIMARY KEY,
|
|
33
|
+
workflow_type TEXT NOT NULL,
|
|
34
|
+
transport TEXT NOT NULL,
|
|
35
|
+
parent_session_id TEXT NOT NULL,
|
|
36
|
+
child_session_key TEXT NOT NULL,
|
|
37
|
+
run_id TEXT,
|
|
38
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
39
|
+
cleanup_state TEXT NOT NULL DEFAULT 'none',
|
|
40
|
+
created_at INTEGER NOT NULL,
|
|
41
|
+
updated_at INTEGER NOT NULL,
|
|
42
|
+
last_observed_at INTEGER,
|
|
43
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS subagent_workflow_events (
|
|
47
|
+
workflow_id TEXT NOT NULL,
|
|
48
|
+
event_type TEXT NOT NULL,
|
|
49
|
+
from_state TEXT,
|
|
50
|
+
to_state TEXT NOT NULL,
|
|
51
|
+
reason TEXT NOT NULL,
|
|
52
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
53
|
+
created_at INTEGER NOT NULL,
|
|
54
|
+
FOREIGN KEY (workflow_id) REFERENCES subagent_workflows(workflow_id) ON DELETE CASCADE
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_parent_session ON subagent_workflows(parent_session_id);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_child_session ON subagent_workflows(child_session_key);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_state ON subagent_workflows(state);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_type ON subagent_workflows(workflow_type);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_events_workflow ON subagent_workflow_events(workflow_id);
|
|
62
|
+
`);
|
|
63
|
+
const row = this.db.prepare('SELECT version FROM schema_version LIMIT 1').get();
|
|
64
|
+
if (!row) {
|
|
65
|
+
this.db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);
|
|
66
|
+
}
|
|
67
|
+
else if (row.version !== SCHEMA_VERSION) {
|
|
68
|
+
this.db.prepare('UPDATE schema_version SET version = ?').run(SCHEMA_VERSION);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
createWorkflow(row) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
this.db.prepare(`
|
|
74
|
+
INSERT INTO subagent_workflows (
|
|
75
|
+
workflow_id, workflow_type, transport, parent_session_id, child_session_key,
|
|
76
|
+
run_id, state, cleanup_state, created_at, updated_at, last_observed_at, metadata_json
|
|
77
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'none', ?, ?, NULL, ?)
|
|
78
|
+
`).run(row.workflow_id, row.workflow_type, row.transport, row.parent_session_id, row.child_session_key, row.run_id, row.state, row.created_at, row.updated_at, row.metadata_json);
|
|
79
|
+
}
|
|
80
|
+
updateWorkflowState(workflowId, state, reason) {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const current = this.getWorkflow(workflowId);
|
|
83
|
+
if (!current)
|
|
84
|
+
return;
|
|
85
|
+
this.db.prepare(`
|
|
86
|
+
UPDATE subagent_workflows SET state = ?, updated_at = ?, last_observed_at = ? WHERE workflow_id = ?
|
|
87
|
+
`).run(state, now, now, workflowId);
|
|
88
|
+
if (reason) {
|
|
89
|
+
this.recordEvent(workflowId, 'state_change', current.state, state, reason, {});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
updateWorkflowRunId(workflowId, runId) {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
this.db.prepare(`
|
|
95
|
+
UPDATE subagent_workflows SET run_id = ?, updated_at = ? WHERE workflow_id = ?
|
|
96
|
+
`).run(runId, now, workflowId);
|
|
97
|
+
}
|
|
98
|
+
updateCleanupState(workflowId, cleanupState) {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
this.db.prepare(`
|
|
101
|
+
UPDATE subagent_workflows SET cleanup_state = ?, updated_at = ? WHERE workflow_id = ?
|
|
102
|
+
`).run(cleanupState, now, workflowId);
|
|
103
|
+
}
|
|
104
|
+
touchWorkflow(workflowId) {
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
this.db.prepare(`
|
|
107
|
+
UPDATE subagent_workflows SET last_observed_at = ?, updated_at = ? WHERE workflow_id = ?
|
|
108
|
+
`).run(now, now, workflowId);
|
|
109
|
+
}
|
|
110
|
+
getWorkflow(workflowId) {
|
|
111
|
+
const row = this.db.prepare('SELECT * FROM subagent_workflows WHERE workflow_id = ?').get(workflowId);
|
|
112
|
+
return row ?? null;
|
|
113
|
+
}
|
|
114
|
+
getWorkflowByChildSession(childSessionKey) {
|
|
115
|
+
const row = this.db.prepare('SELECT * FROM subagent_workflows WHERE child_session_key = ?').get(childSessionKey);
|
|
116
|
+
return row ?? null;
|
|
117
|
+
}
|
|
118
|
+
getWorkflowByParentSession(parentSessionId, workflowType) {
|
|
119
|
+
let sql = 'SELECT * FROM subagent_workflows WHERE parent_session_id = ?';
|
|
120
|
+
const params = [parentSessionId];
|
|
121
|
+
if (workflowType) {
|
|
122
|
+
sql += ' AND workflow_type = ?';
|
|
123
|
+
params.push(workflowType);
|
|
124
|
+
}
|
|
125
|
+
sql += ' ORDER BY created_at DESC LIMIT 1';
|
|
126
|
+
const row = this.db.prepare(sql).get(...params);
|
|
127
|
+
return row ?? null;
|
|
128
|
+
}
|
|
129
|
+
getActiveWorkflows(workflowType) {
|
|
130
|
+
let sql = "SELECT * FROM subagent_workflows WHERE state NOT IN ('completed', 'terminal_error', 'expired')";
|
|
131
|
+
const params = [];
|
|
132
|
+
if (workflowType) {
|
|
133
|
+
sql += ' AND workflow_type = ?';
|
|
134
|
+
params.push(workflowType);
|
|
135
|
+
}
|
|
136
|
+
sql += ' ORDER BY created_at ASC';
|
|
137
|
+
return this.db.prepare(sql).all(...params);
|
|
138
|
+
}
|
|
139
|
+
getExpiredWorkflows(maxAgeMs) {
|
|
140
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
141
|
+
return this.db.prepare(`
|
|
142
|
+
SELECT * FROM subagent_workflows
|
|
143
|
+
WHERE last_observed_at IS NOT NULL
|
|
144
|
+
AND last_observed_at < ?
|
|
145
|
+
AND state NOT IN ('completed', 'terminal_error', 'expired')
|
|
146
|
+
ORDER BY last_observed_at ASC
|
|
147
|
+
`).all(cutoff);
|
|
148
|
+
}
|
|
149
|
+
recordEvent(workflowId, eventType, fromState, toState, reason, payload) {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
this.db.prepare(`
|
|
152
|
+
INSERT INTO subagent_workflow_events (
|
|
153
|
+
workflow_id, event_type, from_state, to_state, reason, payload_json, created_at
|
|
154
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
155
|
+
`).run(workflowId, eventType, fromState, toState, reason, JSON.stringify(payload), now);
|
|
156
|
+
}
|
|
157
|
+
getEvents(workflowId) {
|
|
158
|
+
return this.db.prepare(`
|
|
159
|
+
SELECT * FROM subagent_workflow_events WHERE workflow_id = ? ORDER BY created_at ASC
|
|
160
|
+
`).all(workflowId);
|
|
161
|
+
}
|
|
162
|
+
deleteWorkflow(workflowId) {
|
|
163
|
+
this.db.prepare('DELETE FROM subagent_workflows WHERE workflow_id = ?').run(workflowId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -225,13 +225,13 @@ export function createDeepReflectTool(api) {
|
|
|
225
225
|
}]
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
|
-
await subagentRuntime.run({
|
|
228
|
+
const runResult = await subagentRuntime.run({
|
|
229
229
|
sessionKey,
|
|
230
230
|
message: `请对我当前的任务进行深层次反思。\n\n上下文:${context}`,
|
|
231
231
|
extraSystemPrompt,
|
|
232
232
|
deliver: false
|
|
233
233
|
});
|
|
234
|
-
const finalStatus = await subagentRuntime.waitForRun({ runId:
|
|
234
|
+
const finalStatus = await subagentRuntime.waitForRun({ runId: runResult.runId });
|
|
235
235
|
const duration = Date.now() - startTime;
|
|
236
236
|
if (finalStatus.status === 'timeout') {
|
|
237
237
|
return {
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "principles-disciple",
|
|
3
3
|
"name": "Principles Disciple",
|
|
4
4
|
"description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
|
|
5
|
-
"version": "1.8.
|
|
5
|
+
"version": "1.8.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -74,5 +74,10 @@
|
|
|
74
74
|
"deep_reflection": {
|
|
75
75
|
"label": "💡 AI 深度反思功能"
|
|
76
76
|
}
|
|
77
|
+
},
|
|
78
|
+
"buildFingerprint": {
|
|
79
|
+
"gitSha": "4e632d4c5c2c",
|
|
80
|
+
"bundleMd5": "ed0922a01d46d55a49cd96f8feb158a9",
|
|
81
|
+
"builtAt": "2026-03-31T03:48:08.676Z"
|
|
77
82
|
}
|
|
78
83
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "principles-disciple",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Native OpenClaw plugin for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@vitest/coverage-v8": "^4.1.0",
|
|
52
52
|
"esbuild": "^0.27.4",
|
|
53
53
|
"jsdom": "^29.0.1",
|
|
54
|
-
"typescript": "^
|
|
54
|
+
"typescript": "^6.0.2",
|
|
55
55
|
"vitest": "^4.1.0",
|
|
56
56
|
"ws": "^8.18.0"
|
|
57
57
|
},
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@sinclair/typebox": "^0.34.48",
|
|
68
68
|
"better-sqlite3": "^12.8.0",
|
|
69
|
-
"lucide-react": "^
|
|
69
|
+
"lucide-react": "^1.7.0",
|
|
70
70
|
"micromatch": "^4.0.8",
|
|
71
71
|
"react": "^19.2.0",
|
|
72
72
|
"react-dom": "^19.2.0",
|