principles-disciple 1.16.0 → 1.18.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/README.md +13 -5
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/commands/archive-impl.ts +3 -3
- package/src/commands/capabilities.ts +1 -1
- package/src/commands/context.ts +3 -3
- package/src/commands/disable-impl.ts +1 -1
- package/src/commands/evolution-status.ts +2 -2
- package/src/commands/focus.ts +2 -2
- package/src/commands/nocturnal-train.ts +6 -6
- package/src/commands/pain.ts +4 -4
- package/src/commands/pd-reflect.ts +87 -0
- package/src/commands/rollback-impl.ts +4 -4
- package/src/commands/rollback.ts +2 -2
- package/src/commands/samples.ts +2 -2
- package/src/commands/workflow-debug.ts +1 -1
- package/src/config/errors.ts +1 -1
- package/src/core/adaptive-thresholds.ts +1 -1
- package/src/core/code-implementation-storage.ts +2 -2
- package/src/core/config.ts +1 -1
- package/src/core/diagnostician-task-store.ts +2 -2
- package/src/core/empathy-keyword-matcher.ts +3 -3
- package/src/core/event-log.ts +5 -5
- package/src/core/evolution-engine.ts +4 -4
- package/src/core/evolution-logger.ts +1 -1
- package/src/core/evolution-reducer.ts +3 -3
- package/src/core/evolution-types.ts +5 -5
- package/src/core/external-training-contract.ts +1 -1
- package/src/core/focus-history.ts +14 -14
- package/src/core/hygiene/tracker.ts +1 -1
- package/src/core/init.ts +2 -2
- package/src/core/model-deployment-registry.ts +2 -2
- package/src/core/model-training-registry.ts +2 -2
- package/src/core/nocturnal-arbiter.ts +1 -1
- package/src/core/nocturnal-artificer.ts +2 -2
- package/src/core/nocturnal-candidate-scoring.ts +2 -2
- package/src/core/nocturnal-compliance.ts +4 -3
- package/src/core/nocturnal-dataset.ts +3 -3
- package/src/core/nocturnal-export.ts +4 -4
- package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
- package/src/core/nocturnal-snapshot-contract.ts +112 -0
- package/src/core/nocturnal-trajectory-extractor.ts +7 -5
- package/src/core/nocturnal-trinity.ts +480 -158
- package/src/core/pain-context-extractor.ts +3 -3
- package/src/core/pain.ts +124 -11
- package/src/core/path-resolver.ts +4 -4
- package/src/core/pd-task-reconciler.ts +10 -10
- package/src/core/pd-task-service.ts +1 -1
- package/src/core/pd-task-store.ts +1 -1
- package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
- package/src/core/principle-training-state.ts +2 -2
- package/src/core/principle-tree-ledger.ts +7 -7
- package/src/core/promotion-gate.ts +9 -9
- package/src/core/replay-engine.ts +12 -12
- package/src/core/risk-calculator.ts +1 -1
- package/src/core/rule-host-types.ts +2 -2
- package/src/core/rule-host.ts +5 -5
- package/src/core/schema/db-types.ts +1 -1
- package/src/core/schema/schema-definitions.ts +1 -1
- package/src/core/session-tracker.ts +96 -4
- package/src/core/shadow-observation-registry.ts +3 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-os-parser.ts +1 -1
- package/src/core/training-program.ts +2 -2
- package/src/core/trajectory.ts +8 -8
- package/src/core/workspace-context.ts +2 -2
- package/src/core/workspace-dir-service.ts +85 -0
- package/src/core/workspace-dir-validation.ts +30 -107
- package/src/hooks/bash-risk.ts +3 -3
- package/src/hooks/edit-verification.ts +4 -4
- package/src/hooks/gate-block-helper.ts +4 -4
- package/src/hooks/gate.ts +10 -10
- package/src/hooks/gfi-gate.ts +7 -7
- package/src/hooks/lifecycle.ts +2 -2
- package/src/hooks/llm.ts +1 -1
- package/src/hooks/pain.ts +25 -5
- package/src/hooks/progressive-trust-gate.ts +7 -7
- package/src/hooks/prompt.ts +24 -5
- package/src/hooks/subagent.ts +2 -2
- package/src/hooks/thinking-checkpoint.ts +2 -2
- package/src/hooks/trajectory-collector.ts +1 -1
- package/src/http/principles-console-route.ts +14 -6
- package/src/i18n/commands.ts +4 -0
- package/src/index.ts +181 -185
- package/src/service/central-health-service.ts +1 -1
- package/src/service/central-overview-service.ts +3 -3
- package/src/service/evolution-query-service.ts +1 -1
- package/src/service/evolution-worker.ts +221 -109
- package/src/service/health-query-service.ts +27 -17
- package/src/service/monitoring-query-service.ts +3 -3
- package/src/service/nocturnal-runtime.ts +4 -4
- package/src/service/nocturnal-service.ts +40 -23
- package/src/service/nocturnal-target-selector.ts +11 -4
- package/src/service/runtime-summary-service.ts +1 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -1
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +3 -3
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +16 -13
- package/src/service/subagent-workflow/runtime-direct-driver.ts +10 -6
- package/src/service/subagent-workflow/types.ts +4 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +5 -5
- package/src/service/subagent-workflow/workflow-store.ts +2 -2
- package/src/tools/critique-prompt.ts +2 -3
- package/src/tools/deep-reflect.ts +17 -16
- package/src/tools/model-index.ts +1 -1
- package/src/utils/file-lock.ts +1 -1
- package/src/utils/io.ts +7 -2
- package/src/utils/nlp.ts +1 -1
- package/src/utils/plugin-logger.ts +2 -2
- package/src/utils/retry.ts +3 -2
- package/src/utils/subagent-probe.ts +20 -33
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +111 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +1 -1
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/pain_settings.json +1 -1
- package/tests/build-artifacts.test.ts +4 -58
- package/tests/commands/pd-reflect.test.ts +49 -0
- package/tests/core/nocturnal-snapshot-contract.test.ts +70 -0
- package/tests/core/pain-auto-repair.test.ts +96 -0
- package/tests/core/pain-integration.test.ts +483 -0
- package/tests/core/pain.test.ts +5 -4
- package/tests/core/workspace-dir-service.test.ts +68 -0
- package/tests/core/workspace-dir-validation.test.ts +56 -192
- package/tests/hooks/pain.test.ts +20 -0
- package/tests/http/principles-console-route.test.ts +42 -20
- package/tests/integration/empathy-workflow-integration.test.ts +1 -2
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +9 -17
- package/tests/service/empathy-observer-workflow-manager.test.ts +1 -2
- package/tests/service/evolution-worker.nocturnal.test.ts +118 -109
- package/tests/service/nocturnal-runtime-hardening.test.ts +33 -0
- package/tests/utils/subagent-probe.test.ts +32 -0
|
@@ -33,8 +33,9 @@ vi.mock('../../src/service/subagent-workflow/nocturnal-workflow-manager.js', ()
|
|
|
33
33
|
},
|
|
34
34
|
}));
|
|
35
35
|
|
|
36
|
-
const { mockGetNocturnalSessionSnapshot } = vi.hoisted(() => ({
|
|
36
|
+
const { mockGetNocturnalSessionSnapshot, mockListRecentNocturnalCandidateSessions } = vi.hoisted(() => ({
|
|
37
37
|
mockGetNocturnalSessionSnapshot: vi.fn(),
|
|
38
|
+
mockListRecentNocturnalCandidateSessions: vi.fn(() => []),
|
|
38
39
|
}));
|
|
39
40
|
vi.mock('../../src/core/nocturnal-trajectory-extractor.js', async () => {
|
|
40
41
|
const actual = await vi.importActual<typeof import('../../src/core/nocturnal-trajectory-extractor.js')>(
|
|
@@ -44,11 +45,14 @@ vi.mock('../../src/core/nocturnal-trajectory-extractor.js', async () => {
|
|
|
44
45
|
...actual,
|
|
45
46
|
createNocturnalTrajectoryExtractor: vi.fn(() => ({
|
|
46
47
|
getNocturnalSessionSnapshot: mockGetNocturnalSessionSnapshot,
|
|
48
|
+
listRecentNocturnalCandidateSessions: mockListRecentNocturnalCandidateSessions,
|
|
47
49
|
})),
|
|
48
50
|
};
|
|
49
51
|
});
|
|
50
52
|
|
|
51
|
-
import { EvolutionWorkerService } from '../../src/service/evolution-worker.js';
|
|
53
|
+
import { EvolutionWorkerService, readRecentPainContext } from '../../src/service/evolution-worker.js';
|
|
54
|
+
import { WorkspaceContext } from '../../src/core/workspace-context.js';
|
|
55
|
+
import { handlePdReflect } from '../../src/commands/pd-reflect.js';
|
|
52
56
|
import { safeRmDir } from '../test-utils.js';
|
|
53
57
|
|
|
54
58
|
function readQueue(stateDir: string) {
|
|
@@ -67,141 +71,146 @@ describe('EvolutionWorkerService nocturnal hardening', () => {
|
|
|
67
71
|
EvolutionWorkerService.api = null;
|
|
68
72
|
});
|
|
69
73
|
|
|
70
|
-
it('
|
|
71
|
-
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-
|
|
74
|
+
it('extracts session_id from .pain_flag file correctly', async () => {
|
|
75
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pain-session-'));
|
|
72
76
|
const stateDir = path.join(workspaceDir, '.state');
|
|
73
|
-
fs.mkdirSync(
|
|
74
|
-
fs.mkdirSync(path.join(stateDir, 'logs'), { recursive: true });
|
|
75
|
-
|
|
76
|
-
mockGetNocturnalSessionSnapshot.mockReturnValue(null);
|
|
77
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
77
78
|
|
|
79
|
+
// Write a pain flag WITH session_id
|
|
78
80
|
fs.writeFileSync(
|
|
79
|
-
path.join(stateDir, '
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
source: 'nocturnal',
|
|
87
|
-
reason: 'Sleep reflection',
|
|
88
|
-
timestamp: '2026-04-10T00:00:00.000Z',
|
|
89
|
-
enqueued_at: '2026-04-10T00:00:00.000Z',
|
|
90
|
-
status: 'pending',
|
|
91
|
-
retryCount: 0,
|
|
92
|
-
maxRetries: 1,
|
|
93
|
-
recentPainContext: {
|
|
94
|
-
mostRecent: null,
|
|
95
|
-
recentPainCount: 0,
|
|
96
|
-
recentMaxPainScore: 0,
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
], null, 2),
|
|
81
|
+
path.join(stateDir, '.pain_flag'),
|
|
82
|
+
`source: test_pain
|
|
83
|
+
score: 80
|
|
84
|
+
reason: test reason
|
|
85
|
+
time: 2026-04-10T00:00:00.000Z
|
|
86
|
+
session_id: explicit-session-from-pain
|
|
87
|
+
`,
|
|
100
88
|
'utf8'
|
|
101
89
|
);
|
|
102
90
|
|
|
91
|
+
// Create a WorkspaceContext to test the function
|
|
92
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, stateDir, logger: console } as any);
|
|
93
|
+
|
|
103
94
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
const context = readRecentPainContext(wctx);
|
|
96
|
+
|
|
97
|
+
// Verify the session_id was extracted from the pain flag file
|
|
98
|
+
expect(context.mostRecent).toBeDefined();
|
|
99
|
+
expect(context.mostRecent.sessionId).toBe('explicit-session-from-pain');
|
|
100
|
+
expect(context.mostRecent.score).toBe(80);
|
|
101
|
+
expect(context.recentPainCount).toBe(1);
|
|
102
|
+
} finally {
|
|
103
|
+
safeRmDir(workspaceDir);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
110
106
|
|
|
111
|
-
|
|
107
|
+
it('treats malformed pain flag data as unusable context', async () => {
|
|
108
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pain-invalid-'));
|
|
109
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
110
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
112
111
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
fs.writeFileSync(
|
|
113
|
+
path.join(stateDir, '.pain_flag'),
|
|
114
|
+
`source: test_pain
|
|
115
|
+
score: 80`,
|
|
116
|
+
'utf8'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, stateDir, logger: console } as any);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const context = readRecentPainContext(wctx);
|
|
123
|
+
expect(context.mostRecent).toBeNull();
|
|
124
|
+
expect(context.recentPainCount).toBe(0);
|
|
118
125
|
} finally {
|
|
119
|
-
EvolutionWorkerService.stop({ workspaceDir, stateDir, logger: console } as any);
|
|
120
126
|
safeRmDir(workspaceDir);
|
|
121
127
|
}
|
|
122
128
|
});
|
|
123
129
|
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
// === End-to-End Contract Tests ===
|
|
131
|
+
|
|
132
|
+
it('e2e: pain flag → worker enqueue → session_id is correctly attached to queued task', async () => {
|
|
133
|
+
// This test verifies the contract: when a pain flag with session_id exists,
|
|
134
|
+
// any sleep_reflection task created by the worker MUST carry that session_id
|
|
135
|
+
// in its recentPainContext.mostRecent.sessionId field.
|
|
136
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-e2e-pain-enqueue-'));
|
|
126
137
|
const stateDir = path.join(workspaceDir, '.state');
|
|
127
|
-
fs.mkdirSync(
|
|
128
|
-
fs.mkdirSync(path.join(stateDir, 'logs'), { recursive: true });
|
|
129
|
-
|
|
130
|
-
mockGetNocturnalSessionSnapshot.mockReturnValue({
|
|
131
|
-
sessionId: 'sleep-gateway',
|
|
132
|
-
startedAt: '2026-04-10T00:00:00.000Z',
|
|
133
|
-
updatedAt: '2026-04-10T00:01:00.000Z',
|
|
134
|
-
assistantTurns: [],
|
|
135
|
-
userTurns: [],
|
|
136
|
-
toolCalls: [],
|
|
137
|
-
painEvents: [],
|
|
138
|
-
gateBlocks: [],
|
|
139
|
-
stats: {
|
|
140
|
-
totalAssistantTurns: 1,
|
|
141
|
-
totalToolCalls: 1,
|
|
142
|
-
totalPainEvents: 0,
|
|
143
|
-
totalGateBlocks: 0,
|
|
144
|
-
failureCount: 0,
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
mockStartWorkflow.mockResolvedValue({ workflowId: 'wf-1', childSessionKey: 'child-1', state: 'active' });
|
|
148
|
-
mockGetWorkflowDebugSummary.mockResolvedValue({
|
|
149
|
-
state: 'terminal_error',
|
|
150
|
-
metadata: {},
|
|
151
|
-
recentEvents: [
|
|
152
|
-
{
|
|
153
|
-
reason: 'Error: Plugin runtime subagent methods are only available during a gateway request.',
|
|
154
|
-
payload: {},
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
EvolutionWorkerService.api = {
|
|
160
|
-
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
161
|
-
runtime: {},
|
|
162
|
-
} as any;
|
|
138
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
163
139
|
|
|
140
|
+
// Write a pain flag WITH session_id
|
|
164
141
|
fs.writeFileSync(
|
|
165
|
-
path.join(stateDir, '
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
source: 'nocturnal',
|
|
173
|
-
reason: 'Sleep reflection',
|
|
174
|
-
timestamp: '2026-04-10T00:00:00.000Z',
|
|
175
|
-
enqueued_at: '2026-04-10T00:00:00.000Z',
|
|
176
|
-
status: 'pending',
|
|
177
|
-
retryCount: 0,
|
|
178
|
-
maxRetries: 1,
|
|
179
|
-
recentPainContext: {
|
|
180
|
-
mostRecent: { score: 0.5, source: 'pain', reason: 'x', timestamp: '2026-04-10T00:00:00.000Z' },
|
|
181
|
-
recentPainCount: 1,
|
|
182
|
-
recentMaxPainScore: 0.5,
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
], null, 2),
|
|
142
|
+
path.join(stateDir, '.pain_flag'),
|
|
143
|
+
`source: tool_failure
|
|
144
|
+
score: 70
|
|
145
|
+
reason: Test pain with session
|
|
146
|
+
time: 2026-04-10T00:00:00.000Z
|
|
147
|
+
session_id: pain-session-abc
|
|
148
|
+
`,
|
|
186
149
|
'utf8'
|
|
187
150
|
);
|
|
188
151
|
|
|
152
|
+
// Verify the worker's readRecentPainContext extracts the session_id correctly
|
|
153
|
+
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, stateDir, logger: console } as any);
|
|
154
|
+
const painContext = readRecentPainContext(wctx);
|
|
155
|
+
|
|
156
|
+
// Contract: session_id must be extracted from the pain flag
|
|
157
|
+
expect(painContext.mostRecent).toBeDefined();
|
|
158
|
+
expect(painContext.mostRecent.sessionId).toBe('pain-session-abc');
|
|
159
|
+
expect(painContext.mostRecent.score).toBe(70);
|
|
160
|
+
expect(painContext.mostRecent.source).toBe('tool_failure');
|
|
161
|
+
|
|
162
|
+
// Now simulate what the worker does: attach this context to a queued task
|
|
163
|
+
const simulatedTask = {
|
|
164
|
+
id: 'simulated-task',
|
|
165
|
+
taskKind: 'sleep_reflection',
|
|
166
|
+
recentPainContext: painContext,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Verify the contract holds end-to-end
|
|
170
|
+
expect(simulatedTask.recentPainContext.mostRecent.sessionId).toBe('pain-session-abc');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('e2e: /pd-reflect command writes to workspace/.state, never to HOME/.state', async () => {
|
|
174
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-e2e-command-writes-'));
|
|
175
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
176
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
177
|
+
fs.mkdirSync(path.join(stateDir, 'sessions'), { recursive: true });
|
|
178
|
+
|
|
179
|
+
// Ensure HOME/.state does NOT have the queue file
|
|
180
|
+
const homeState = path.join(os.homedir(), '.state');
|
|
181
|
+
const homeQueue = path.join(homeState, 'evolution_queue.json');
|
|
182
|
+
const homeExistedBefore = fs.existsSync(homeQueue);
|
|
183
|
+
|
|
189
184
|
try {
|
|
190
|
-
|
|
185
|
+
// Execute the command with explicit workspaceDir
|
|
186
|
+
const result = await handlePdReflect.handler({
|
|
191
187
|
workspaceDir,
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
channel: 'test',
|
|
189
|
+
isAuthorizedSender: true,
|
|
190
|
+
commandBody: '',
|
|
194
191
|
config: {},
|
|
192
|
+
api: { logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() } } as any,
|
|
195
193
|
} as any);
|
|
196
194
|
|
|
197
|
-
|
|
195
|
+
// Command should succeed
|
|
196
|
+
expect(result.isError).toBeFalsy();
|
|
197
|
+
expect(result.text).toContain('enqueued');
|
|
198
|
+
|
|
199
|
+
// Queue file should exist in workspace
|
|
200
|
+
const workspaceQueue = path.join(stateDir, 'evolution_queue.json');
|
|
201
|
+
expect(fs.existsSync(workspaceQueue)).toBe(true);
|
|
198
202
|
|
|
203
|
+
// Verify the task is in the workspace queue
|
|
199
204
|
const queue = readQueue(stateDir);
|
|
200
|
-
|
|
201
|
-
expect(
|
|
202
|
-
expect(
|
|
205
|
+
const manualTasks = queue.filter((t: any) => t.id.startsWith('manual_'));
|
|
206
|
+
expect(manualTasks.length).toBe(1);
|
|
207
|
+
expect(manualTasks[0].taskKind).toBe('sleep_reflection');
|
|
208
|
+
|
|
209
|
+
// HOME/.state/evolution_queue.json should NOT have been created/modified by this command
|
|
210
|
+
if (!homeExistedBefore) {
|
|
211
|
+
expect(fs.existsSync(homeQueue)).toBe(false);
|
|
212
|
+
}
|
|
203
213
|
} finally {
|
|
204
|
-
EvolutionWorkerService.stop({ workspaceDir, stateDir, logger: console } as any);
|
|
205
214
|
safeRmDir(workspaceDir);
|
|
206
215
|
}
|
|
207
216
|
});
|
|
@@ -82,4 +82,37 @@ describe('NocturnalWorkflowManager runtime hardening', () => {
|
|
|
82
82
|
|
|
83
83
|
manager.dispose();
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it('rejects malformed snapshot ingress before starting the async pipeline', async () => {
|
|
87
|
+
const manager = new NocturnalWorkflowManager({
|
|
88
|
+
workspaceDir,
|
|
89
|
+
stateDir,
|
|
90
|
+
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
|
|
91
|
+
runtimeAdapter: {} as any,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const handle = await manager.startWorkflow(nocturnalWorkflowSpec, {
|
|
95
|
+
parentSessionId: 'sleep_reflection:test',
|
|
96
|
+
taskInput: {},
|
|
97
|
+
metadata: {
|
|
98
|
+
snapshot: {
|
|
99
|
+
sessionId: 'session-1',
|
|
100
|
+
stats: {
|
|
101
|
+
totalAssistantTurns: 1,
|
|
102
|
+
totalToolCalls: 1,
|
|
103
|
+
totalPainEvents: 0,
|
|
104
|
+
totalGateBlocks: 0,
|
|
105
|
+
failureCount: 0,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const summary = await manager.getWorkflowDebugSummary(handle.workflowId);
|
|
112
|
+
expect(summary?.state).toBe('terminal_error');
|
|
113
|
+
expect(summary?.recentEvents.some((event) => event.eventType === 'nocturnal_failed')).toBe(true);
|
|
114
|
+
expect(mockExecuteNocturnalReflectionAsync).not.toHaveBeenCalled();
|
|
115
|
+
|
|
116
|
+
manager.dispose();
|
|
117
|
+
});
|
|
85
118
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
getSubagentRuntimeAvailability,
|
|
4
|
+
isSubagentRuntimeAvailable,
|
|
5
|
+
} from '../../src/utils/subagent-probe.js';
|
|
6
|
+
|
|
7
|
+
describe('subagent-probe', () => {
|
|
8
|
+
it('treats any callable run entrypoint as available', () => {
|
|
9
|
+
const runtime = {
|
|
10
|
+
run() {
|
|
11
|
+
return Promise.resolve({ runId: 'run-1' });
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
expect(getSubagentRuntimeAvailability(runtime)).toEqual({
|
|
16
|
+
available: true,
|
|
17
|
+
reason: 'callable',
|
|
18
|
+
});
|
|
19
|
+
expect(isSubagentRuntimeAvailable(runtime)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('reports missing runtime and missing run distinctly', () => {
|
|
23
|
+
expect(getSubagentRuntimeAvailability(undefined)).toEqual({
|
|
24
|
+
available: false,
|
|
25
|
+
reason: 'missing_runtime',
|
|
26
|
+
});
|
|
27
|
+
expect(getSubagentRuntimeAvailability({})).toEqual({
|
|
28
|
+
available: false,
|
|
29
|
+
reason: 'missing_run',
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|