principles-disciple 1.11.0 → 1.13.0
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/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- package/templates/workspace/.state/WORKBOARD.json +0 -4
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { PluginLogger } from '../../openclaw-sdk.js';
|
|
2
2
|
import type {
|
|
3
|
-
WorkflowManager,
|
|
4
|
-
WorkflowHandle,
|
|
5
3
|
SubagentWorkflowSpec,
|
|
6
4
|
WorkflowMetadata,
|
|
7
|
-
WorkflowDebugSummary,
|
|
8
5
|
EmpathyObserverPayload,
|
|
9
6
|
EmpathyResult,
|
|
10
7
|
WorkflowResultContext,
|
|
11
8
|
WorkflowPersistContext,
|
|
9
|
+
WorkflowHandle,
|
|
12
10
|
} from './types.js';
|
|
13
|
-
import { RuntimeDirectDriver
|
|
14
|
-
import { WorkflowStore } from './workflow-store.js';
|
|
15
|
-
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
11
|
+
import type { RuntimeDirectDriver } from './runtime-direct-driver.js';
|
|
16
12
|
import { WorkspaceContext } from '../../core/workspace-context.js';
|
|
17
13
|
import { trackFriction } from '../../core/session-tracker.js';
|
|
14
|
+
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
15
|
+
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
|
+
import { normalizeSeverity } from '../../core/empathy-types.js';
|
|
18
17
|
|
|
19
18
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
20
19
|
|
|
@@ -25,25 +24,24 @@ export interface EmpathyObserverWorkflowOptions {
|
|
|
25
24
|
workspaceDir: string;
|
|
26
25
|
logger: PluginLogger;
|
|
27
26
|
subagent: RuntimeDirectDriver['subagent'];
|
|
27
|
+
/** Pass api.runtime.agent.session to enable heartbeat-safe cleanup (#188) */
|
|
28
|
+
agentSession?: RuntimeDirectDriver['agentSession'];
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
export class EmpathyObserverWorkflowManager
|
|
31
|
-
private readonly store: WorkflowStore;
|
|
32
|
-
private readonly driver: RuntimeDirectDriver;
|
|
33
|
-
private readonly logger: PluginLogger;
|
|
34
|
-
private readonly workspaceDir: string;
|
|
35
|
-
|
|
36
|
-
private activeWorkflows = new Map<string, NodeJS.Timeout>();
|
|
37
|
-
private completedWorkflows = new Map<string, number>();
|
|
38
|
-
private workflowSpecs = new Map<string, SubagentWorkflowSpec<unknown>>();
|
|
39
|
-
|
|
31
|
+
export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
40
32
|
constructor(opts: EmpathyObserverWorkflowOptions) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
super({
|
|
34
|
+
workspaceDir: opts.workspaceDir,
|
|
35
|
+
logger: opts.logger,
|
|
36
|
+
subagent: opts.subagent,
|
|
37
|
+
agentSession: opts.agentSession,
|
|
38
|
+
workflowType: 'empathy-observer',
|
|
39
|
+
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
40
|
+
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
41
|
+
defaultTtlMs: DEFAULT_TTL_MS,
|
|
42
|
+
});
|
|
45
43
|
}
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
async startWorkflow<TResult>(
|
|
48
46
|
spec: SubagentWorkflowSpec<TResult>,
|
|
49
47
|
options: {
|
|
@@ -53,66 +51,27 @@ export class EmpathyObserverWorkflowManager implements WorkflowManager {
|
|
|
53
51
|
metadata?: Record<string, unknown>;
|
|
54
52
|
}
|
|
55
53
|
): Promise<WorkflowHandle> {
|
|
56
|
-
const workflowId = this.generateWorkflowId();
|
|
57
|
-
const childSessionKey = this.buildChildSessionKey(options.parentSessionId);
|
|
58
|
-
const now = Date.now();
|
|
59
|
-
|
|
60
|
-
const metadata: WorkflowMetadata = {
|
|
61
|
-
parentSessionId: options.parentSessionId,
|
|
62
|
-
workspaceDir: options.workspaceDir,
|
|
63
|
-
taskInput: options.taskInput,
|
|
64
|
-
startedAt: now,
|
|
65
|
-
workflowType: spec.workflowType,
|
|
66
|
-
...options.metadata,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] Starting workflow: workflowId=${workflowId}, type=${spec.workflowType}`);
|
|
70
|
-
|
|
71
54
|
// Surface degrade: skip boot sessions (they run outside gateway request context)
|
|
72
55
|
if (options.parentSessionId.startsWith('boot-')) {
|
|
73
56
|
this.logger.info(`[PD:EmpathyObserverWorkflow] Skipping workflow: boot session (gateway request context unavailable)`);
|
|
74
57
|
throw new Error(`EmpathyObserverWorkflowManager: cannot start workflow for boot session`);
|
|
75
58
|
}
|
|
76
|
-
|
|
59
|
+
|
|
77
60
|
// Surface degrade: check subagent runtime availability before calling run()
|
|
78
61
|
if (!isSubagentRuntimeAvailable(this.driver.getSubagent())) {
|
|
79
62
|
this.logger.info(`[PD:EmpathyObserverWorkflow] Skipping workflow: subagent runtime unavailable`);
|
|
80
63
|
throw new Error(`EmpathyObserverWorkflowManager: subagent runtime unavailable`);
|
|
81
64
|
}
|
|
82
|
-
|
|
65
|
+
|
|
83
66
|
if (spec.transport !== 'runtime_direct') {
|
|
84
67
|
throw new Error(`EmpathyObserverWorkflowManager only supports runtime_direct transport`);
|
|
85
68
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const runResult = await this.driver.run(runParams);
|
|
89
|
-
|
|
90
|
-
this.store.createWorkflow({
|
|
91
|
-
workflow_id: workflowId,
|
|
92
|
-
workflow_type: spec.workflowType,
|
|
93
|
-
transport: spec.transport,
|
|
94
|
-
parent_session_id: options.parentSessionId,
|
|
95
|
-
child_session_key: childSessionKey,
|
|
96
|
-
run_id: runResult.runId,
|
|
97
|
-
state: 'active',
|
|
98
|
-
created_at: now,
|
|
99
|
-
updated_at: now,
|
|
100
|
-
metadata_json: JSON.stringify(metadata),
|
|
101
|
-
});
|
|
102
|
-
this.store.recordEvent(workflowId, 'spawned', null, 'active', 'subagent spawned', { runId: runResult.runId });
|
|
103
|
-
this.workflowSpecs.set(workflowId, spec as SubagentWorkflowSpec<unknown>);
|
|
104
|
-
|
|
105
|
-
this.scheduleWaitPoll(workflowId, spec.timeoutMs ?? DEFAULT_TIMEOUT_MS, runResult.runId);
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
workflowId,
|
|
109
|
-
childSessionKey,
|
|
110
|
-
runId: runResult.runId,
|
|
111
|
-
state: 'active',
|
|
112
|
-
};
|
|
69
|
+
|
|
70
|
+
return super.startWorkflow(spec, options);
|
|
113
71
|
}
|
|
114
|
-
|
|
115
|
-
|
|
72
|
+
|
|
73
|
+
/* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: Subclass stores metadata via spec, not class fields */
|
|
74
|
+
protected override createWorkflowMetadata<TResult>(
|
|
116
75
|
spec: SubagentWorkflowSpec<TResult>,
|
|
117
76
|
options: {
|
|
118
77
|
parentSessionId: string;
|
|
@@ -120,29 +79,21 @@ export class EmpathyObserverWorkflowManager implements WorkflowManager {
|
|
|
120
79
|
taskInput: unknown;
|
|
121
80
|
metadata?: Record<string, unknown>;
|
|
122
81
|
},
|
|
123
|
-
|
|
124
|
-
):
|
|
125
|
-
|
|
82
|
+
now: number
|
|
83
|
+
): WorkflowMetadata {
|
|
84
|
+
// EmpathyObserver stores standard metadata
|
|
85
|
+
return {
|
|
126
86
|
parentSessionId: options.parentSessionId,
|
|
127
87
|
workspaceDir: options.workspaceDir,
|
|
128
88
|
taskInput: options.taskInput,
|
|
129
|
-
startedAt:
|
|
89
|
+
startedAt: now,
|
|
130
90
|
workflowType: spec.workflowType,
|
|
131
|
-
...
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
sessionKey: childSessionKey,
|
|
136
|
-
message,
|
|
137
|
-
lane: 'subagent',
|
|
138
|
-
deliver: false,
|
|
139
|
-
idempotencyKey: options.parentSessionId
|
|
140
|
-
? `${options.parentSessionId}:${Date.now()}`
|
|
141
|
-
: `pd:${childSessionKey}:${Date.now()}`,
|
|
142
|
-
expectsCompletionMessage: true,
|
|
91
|
+
...options.metadata,
|
|
143
92
|
};
|
|
144
93
|
}
|
|
145
|
-
|
|
94
|
+
|
|
95
|
+
// ── EmpathyObserver-Specific Methods ─────────────────────────────────────
|
|
96
|
+
|
|
146
97
|
static buildEmpathyPrompt(userMessage: string): string {
|
|
147
98
|
return [
|
|
148
99
|
'You are an empathy observer.',
|
|
@@ -151,302 +102,23 @@ export class EmpathyObserverWorkflowManager implements WorkflowManager {
|
|
|
151
102
|
`User message: ${JSON.stringify(userMessage.trim())}`,
|
|
152
103
|
].join('\n');
|
|
153
104
|
}
|
|
154
|
-
|
|
155
|
-
private scheduleWaitPoll(
|
|
156
|
-
workflowId: string,
|
|
157
|
-
timeoutMs: number,
|
|
158
|
-
runId: string
|
|
159
|
-
): void {
|
|
160
|
-
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
161
|
-
|
|
162
|
-
const timeout = setTimeout(async () => {
|
|
163
|
-
try {
|
|
164
|
-
const result = await this.driver.wait({ runId, timeoutMs: effectiveTimeoutMs });
|
|
165
|
-
await this.notifyWaitResult(workflowId, result.status, result.error);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
this.logger.error(`[PD:EmpathyObserverWorkflow] Wait poll failed: ${String(error)}`);
|
|
168
|
-
await this.notifyWaitResult(workflowId, 'error', String(error));
|
|
169
|
-
}
|
|
170
|
-
}, 100);
|
|
171
|
-
|
|
172
|
-
this.activeWorkflows.set(workflowId, timeout);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async notifyWaitResult(
|
|
176
|
-
workflowId: string,
|
|
177
|
-
status: 'ok' | 'error' | 'timeout',
|
|
178
|
-
error?: string
|
|
179
|
-
): Promise<void> {
|
|
180
|
-
const workflow = this.store.getWorkflow(workflowId);
|
|
181
|
-
if (!workflow) {
|
|
182
|
-
this.logger.warn(`[PD:EmpathyObserverWorkflow] notifyWaitResult: workflow not found: ${workflowId}`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (workflow.state === 'completed' || workflow.state === 'terminal_error' || workflow.state === 'expired') {
|
|
187
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] notifyWaitResult: ignoring terminal workflow: ${workflowId}, state=${workflow.state}`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] notifyWaitResult: workflowId=${workflowId}, status=${status}`);
|
|
192
|
-
|
|
193
|
-
const previousState = workflow.state;
|
|
194
|
-
this.store.updateWorkflowState(workflowId, 'wait_result');
|
|
195
|
-
this.store.recordEvent(workflowId, 'wait_result', previousState, 'wait_result', `wait completed: ${status}`, { error });
|
|
196
|
-
|
|
197
|
-
const spec = this.workflowSpecs.get(workflowId);
|
|
198
|
-
const shouldFinalize = spec ? spec.shouldFinalizeOnWaitStatus(status) : status === 'ok';
|
|
199
|
-
|
|
200
|
-
if (shouldFinalize) {
|
|
201
|
-
await this.finalizeOnce(workflowId);
|
|
202
|
-
} else {
|
|
203
|
-
this.store.updateWorkflowState(workflowId, 'terminal_error');
|
|
204
|
-
this.store.recordEvent(workflowId, 'finalize_skipped', 'wait_result', 'terminal_error', `wait status: ${status}`, { error });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async notifyLifecycleEvent(
|
|
209
|
-
workflowId: string,
|
|
210
|
-
event: 'subagent_spawned' | 'subagent_ended',
|
|
211
|
-
data?: { outcome?: 'ok' | 'error' | 'timeout' | 'killed' | 'reset' | 'deleted'; error?: string }
|
|
212
|
-
): Promise<void> {
|
|
213
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] notifyLifecycleEvent: workflowId=${workflowId}, event=${event}`);
|
|
214
|
-
|
|
215
|
-
if (event === 'subagent_ended' && data?.outcome) {
|
|
216
|
-
await this.notifyWaitResult(workflowId, data.outcome === 'ok' ? 'ok' : data.outcome === 'error' ? 'error' : 'timeout', data.error);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async finalizeOnce(workflowId: string): Promise<void> {
|
|
221
|
-
const workflow = this.store.getWorkflow(workflowId);
|
|
222
|
-
if (!workflow) {
|
|
223
|
-
this.logger.warn(`[PD:EmpathyObserverWorkflow] finalizeOnce: workflow not found: ${workflowId}`);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const spec = this.workflowSpecs.get(workflowId);
|
|
228
|
-
if (!spec) {
|
|
229
|
-
throw new Error(`Workflow spec not registered for ${workflowId}`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (this.isCompleted(workflowId)) {
|
|
233
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] finalizeOnce: already completed: ${workflowId}`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] Finalizing workflow: ${workflowId}`);
|
|
238
|
-
|
|
239
|
-
this.store.updateWorkflowState(workflowId, 'finalizing');
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const result = await this.driver.getResult({ sessionKey: workflow.child_session_key, limit: 20 });
|
|
243
|
-
|
|
244
|
-
const metadata = JSON.parse(workflow.metadata_json) as WorkflowMetadata;
|
|
245
|
-
const parsed = await spec.parseResult({
|
|
246
|
-
messages: result.messages,
|
|
247
|
-
assistantTexts: result.assistantTexts,
|
|
248
|
-
metadata,
|
|
249
|
-
waitStatus: 'ok',
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (!parsed) {
|
|
253
|
-
this.store.updateWorkflowState(workflowId, 'terminal_error');
|
|
254
|
-
this.store.recordEvent(workflowId, 'parse_failed', 'finalizing', 'terminal_error', 'spec.parseResult returned null', {});
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
await spec.persistResult({
|
|
259
|
-
result: parsed,
|
|
260
|
-
metadata,
|
|
261
|
-
workspaceDir: this.workspaceDir,
|
|
262
|
-
});
|
|
263
|
-
this.store.recordEvent(workflowId, 'persisted', 'finalizing', 'finalizing', 'result persisted', {});
|
|
264
|
-
|
|
265
|
-
if (spec.shouldDeleteSessionAfterFinalize && workflow.run_id) {
|
|
266
|
-
try {
|
|
267
|
-
await this.driver.cleanup({ sessionKey: workflow.child_session_key });
|
|
268
|
-
this.store.updateCleanupState(workflowId, 'completed');
|
|
269
|
-
} catch (cleanupError) {
|
|
270
|
-
this.logger.error(`[PD:EmpathyObserverWorkflow] cleanup failed after persistence: ${String(cleanupError)}`);
|
|
271
|
-
this.store.updateCleanupState(workflowId, 'failed');
|
|
272
|
-
this.store.updateWorkflowState(workflowId, 'cleanup_pending');
|
|
273
|
-
this.store.recordEvent(workflowId, 'cleanup_failed', 'finalizing', 'cleanup_pending', String(cleanupError), {});
|
|
274
|
-
this.markCompleted(workflowId);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
this.store.updateWorkflowState(workflowId, 'completed');
|
|
280
|
-
this.store.recordEvent(workflowId, 'finalized', 'finalizing', 'completed', 'success', {});
|
|
281
|
-
this.markCompleted(workflowId);
|
|
282
|
-
|
|
283
|
-
} catch (error) {
|
|
284
|
-
this.logger.error(`[PD:EmpathyObserverWorkflow] finalizeOnce failed: ${String(error)}`);
|
|
285
|
-
this.store.updateWorkflowState(workflowId, 'terminal_error');
|
|
286
|
-
this.store.recordEvent(workflowId, 'finalize_error', 'finalizing', 'terminal_error', String(error), {});
|
|
287
|
-
throw error;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async sweepExpiredWorkflows(maxAgeMs = DEFAULT_TTL_MS): Promise<number> {
|
|
292
|
-
const expired = this.store.getExpiredWorkflows(maxAgeMs);
|
|
293
|
-
|
|
294
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] sweepExpiredWorkflows: found ${expired.length} expired`);
|
|
295
|
-
|
|
296
|
-
for (const workflow of expired) {
|
|
297
|
-
try {
|
|
298
|
-
this.logger.info(`[PD:EmpathyObserverWorkflow] Sweeping expired workflow: ${workflow.workflow_id}`);
|
|
299
|
-
|
|
300
|
-
await this.driver.cleanup({ sessionKey: workflow.child_session_key });
|
|
301
|
-
this.store.updateCleanupState(workflow.workflow_id, 'completed');
|
|
302
|
-
this.store.updateWorkflowState(workflow.workflow_id, 'expired');
|
|
303
|
-
this.store.recordEvent(workflow.workflow_id, 'swept', workflow.state, 'expired', 'TTL expired', {});
|
|
304
|
-
|
|
305
|
-
} catch (error) {
|
|
306
|
-
this.logger.error(`[PD:EmpathyObserverWorkflow] Sweep cleanup failed for ${workflow.workflow_id}: ${String(error)}`);
|
|
307
|
-
this.store.updateCleanupState(workflow.workflow_id, 'failed');
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
105
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
for (const [id, timestamp] of this.completedWorkflows) {
|
|
314
|
-
if (timestamp < cutoff) {
|
|
315
|
-
this.completedWorkflows.delete(id);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
for (const [id, timeout] of this.activeWorkflows) {
|
|
319
|
-
const wf = this.store.getWorkflow(id);
|
|
320
|
-
if (!wf || wf.state === 'expired' || wf.state === 'completed' || wf.state === 'terminal_error') {
|
|
321
|
-
clearTimeout(timeout);
|
|
322
|
-
this.activeWorkflows.delete(id);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return expired.length;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async getWorkflowDebugSummary(workflowId: string, eventLimit = 10): Promise<WorkflowDebugSummary | null> {
|
|
330
|
-
const workflow = this.store.getWorkflow(workflowId);
|
|
331
|
-
if (!workflow) return null;
|
|
332
|
-
|
|
333
|
-
const metadata = JSON.parse(workflow.metadata_json) as WorkflowMetadata;
|
|
334
|
-
const recentEvents = this.store
|
|
335
|
-
.getEvents(workflowId)
|
|
336
|
-
.slice(-eventLimit)
|
|
337
|
-
.map((event) => ({
|
|
338
|
-
eventType: event.event_type,
|
|
339
|
-
fromState: event.from_state,
|
|
340
|
-
toState: event.to_state,
|
|
341
|
-
reason: event.reason,
|
|
342
|
-
createdAt: event.created_at,
|
|
343
|
-
payload: JSON.parse(event.payload_json || '{}') as Record<string, unknown>,
|
|
344
|
-
}));
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
workflowId: workflow.workflow_id,
|
|
348
|
-
workflowType: workflow.workflow_type,
|
|
349
|
-
transport: workflow.transport,
|
|
350
|
-
parentSessionId: workflow.parent_session_id,
|
|
351
|
-
childSessionKey: workflow.child_session_key,
|
|
352
|
-
runId: workflow.run_id,
|
|
353
|
-
state: workflow.state,
|
|
354
|
-
cleanupState: workflow.cleanup_state,
|
|
355
|
-
lastObservedAt: workflow.last_observed_at ?? null,
|
|
356
|
-
metadata,
|
|
357
|
-
recentEvents,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
private generateWorkflowId(): string {
|
|
106
|
+
/* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: Subclass overrides id generation pattern */
|
|
107
|
+
protected override generateWorkflowId(): string {
|
|
362
108
|
return `wf_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
363
109
|
}
|
|
364
|
-
|
|
365
|
-
private buildChildSessionKey(parentSessionId: string): string {
|
|
366
|
-
const safeParentSessionId = parentSessionId
|
|
367
|
-
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
368
|
-
.substring(0, 64);
|
|
369
|
-
const timestamp = Date.now();
|
|
370
|
-
return `${WORKFLOW_SESSION_PREFIX}${safeParentSessionId}-${timestamp}`;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private extractAssistantText(messages: unknown[], assistantTexts?: string[]): string {
|
|
374
|
-
if (assistantTexts && assistantTexts.length > 0) {
|
|
375
|
-
return assistantTexts[assistantTexts.length - 1] || '';
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
379
|
-
const msg = messages[i] as { role?: string; content?: unknown };
|
|
380
|
-
if (msg?.role !== 'assistant') continue;
|
|
381
|
-
if (typeof msg.content === 'string') return msg.content;
|
|
382
|
-
if (Array.isArray(msg.content)) {
|
|
383
|
-
const txt = msg.content
|
|
384
|
-
.filter((part: any) => part?.type === 'text' && typeof part.text === 'string')
|
|
385
|
-
.map((part: any) => part.text)
|
|
386
|
-
.join('\n');
|
|
387
|
-
if (txt) return txt;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return '';
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
parseEmpathyPayload(rawText: string): EmpathyObserverPayload | null {
|
|
395
|
-
if (!rawText?.trim()) return null;
|
|
396
|
-
|
|
397
|
-
try {
|
|
398
|
-
return JSON.parse(rawText.trim()) as EmpathyObserverPayload;
|
|
399
|
-
} catch {
|
|
400
|
-
const match = rawText.match(/\{[\s\S]*\}/);
|
|
401
|
-
if (!match) {
|
|
402
|
-
this.logger.warn('[PD:EmpathyObserverWorkflow] Observer payload is not valid JSON');
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
try {
|
|
406
|
-
return JSON.parse(match[0]) as EmpathyObserverPayload;
|
|
407
|
-
} catch {
|
|
408
|
-
this.logger.warn('[PD:EmpathyObserverWorkflow] Failed to parse observer JSON payload');
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
private isCompleted(workflowId: string): boolean {
|
|
415
|
-
const timestamp = this.completedWorkflows.get(workflowId);
|
|
416
|
-
if (!timestamp) return false;
|
|
417
|
-
if (Date.now() - timestamp > 5 * 60 * 1000) {
|
|
418
|
-
this.completedWorkflows.delete(workflowId);
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
return true;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
private markCompleted(workflowId: string): void {
|
|
425
|
-
this.completedWorkflows.set(workflowId, Date.now());
|
|
426
|
-
this.workflowSpecs.delete(workflowId);
|
|
427
|
-
|
|
428
|
-
const timeout = this.activeWorkflows.get(workflowId);
|
|
429
|
-
if (timeout) {
|
|
430
|
-
clearTimeout(timeout);
|
|
431
|
-
this.activeWorkflows.delete(workflowId);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
dispose(): void {
|
|
436
|
-
for (const timeout of this.activeWorkflows.values()) {
|
|
437
|
-
clearTimeout(timeout);
|
|
438
|
-
}
|
|
439
|
-
this.activeWorkflows.clear();
|
|
440
|
-
this.store.dispose();
|
|
441
|
-
}
|
|
442
110
|
}
|
|
443
111
|
|
|
112
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
444
114
|
export function createEmpathyObserverWorkflowManager(
|
|
445
115
|
opts: EmpathyObserverWorkflowOptions
|
|
446
116
|
): EmpathyObserverWorkflowManager {
|
|
447
117
|
return new EmpathyObserverWorkflowManager(opts);
|
|
448
118
|
}
|
|
449
119
|
|
|
120
|
+
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
|
121
|
+
|
|
450
122
|
/**
|
|
451
123
|
* Extract raw assistant text from messages or assistantTexts array.
|
|
452
124
|
*/
|
|
@@ -460,8 +132,8 @@ function extractAssistantTextForSpec(messages: unknown[], assistantTexts?: strin
|
|
|
460
132
|
if (typeof msg.content === 'string') return msg.content;
|
|
461
133
|
if (Array.isArray(msg.content)) {
|
|
462
134
|
const txt = msg.content
|
|
463
|
-
.filter((part:
|
|
464
|
-
.map((part:
|
|
135
|
+
.filter((part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'text' && typeof (part as { text?: unknown }).text === 'string')
|
|
136
|
+
.map((part: unknown) => (part as { text: string }).text)
|
|
465
137
|
.join('\n');
|
|
466
138
|
if (txt) return txt;
|
|
467
139
|
}
|
|
@@ -477,7 +149,7 @@ function parseEmpathyPayloadForSpec(rawText: string): EmpathyObserverPayload | n
|
|
|
477
149
|
try {
|
|
478
150
|
return JSON.parse(rawText.trim()) as EmpathyObserverPayload;
|
|
479
151
|
} catch {
|
|
480
|
-
const match =
|
|
152
|
+
const match = /\{[\s\S]*\}/.exec(rawText);
|
|
481
153
|
if (!match) return null;
|
|
482
154
|
try {
|
|
483
155
|
return JSON.parse(match[0]) as EmpathyObserverPayload;
|
|
@@ -487,15 +159,6 @@ function parseEmpathyPayloadForSpec(rawText: string): EmpathyObserverPayload | n
|
|
|
487
159
|
}
|
|
488
160
|
}
|
|
489
161
|
|
|
490
|
-
/**
|
|
491
|
-
* Normalize severity to valid enum.
|
|
492
|
-
*/
|
|
493
|
-
function normalizeSeverityForSpec(severity: string | undefined): 'mild' | 'moderate' | 'severe' {
|
|
494
|
-
if (severity === 'severe') return 'severe';
|
|
495
|
-
if (severity === 'moderate') return 'moderate';
|
|
496
|
-
return 'mild';
|
|
497
|
-
}
|
|
498
|
-
|
|
499
162
|
/**
|
|
500
163
|
* Normalize confidence to [0, 1] range.
|
|
501
164
|
*/
|
|
@@ -513,10 +176,8 @@ function scoreFromSeverityForSpec(severity: string | undefined, wctx: WorkspaceC
|
|
|
513
176
|
return Number(wctx.config.get('empathy_engine.penalties.mild') ?? 10);
|
|
514
177
|
}
|
|
515
178
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
* This spec drives EmpathyObserverWorkflowManager for the empathy observer workflow.
|
|
519
|
-
*/
|
|
179
|
+
// ─── Workflow Spec ─────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
520
181
|
export const empathyObserverWorkflowSpec: SubagentWorkflowSpec<EmpathyResult> = {
|
|
521
182
|
workflowType: 'empathy-observer',
|
|
522
183
|
transport: 'runtime_direct',
|
|
@@ -524,6 +185,7 @@ export const empathyObserverWorkflowSpec: SubagentWorkflowSpec<EmpathyResult> =
|
|
|
524
185
|
ttlMs: 300_000,
|
|
525
186
|
shouldDeleteSessionAfterFinalize: true,
|
|
526
187
|
|
|
188
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: interface method signature, unused parameter for spec compliance */
|
|
527
189
|
buildPrompt(taskInput: unknown, _metadata: WorkflowMetadata): string {
|
|
528
190
|
const userMessage = String(taskInput).trim();
|
|
529
191
|
return [
|
|
@@ -541,7 +203,7 @@ export const empathyObserverWorkflowSpec: SubagentWorkflowSpec<EmpathyResult> =
|
|
|
541
203
|
|
|
542
204
|
return {
|
|
543
205
|
damageDetected: payload.damageDetected ?? false,
|
|
544
|
-
severity:
|
|
206
|
+
severity: normalizeSeverity(payload.severity),
|
|
545
207
|
confidence: normalizeConfidenceForSpec(payload.confidence),
|
|
546
208
|
reason: payload.reason ?? '',
|
|
547
209
|
painScore: 0,
|