principles-disciple 1.102.1 → 1.104.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/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.
|
|
5
|
+
"version": "1.104.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|
package/package.json
CHANGED
|
@@ -64,6 +64,28 @@ export function buildTrajectoryEvidence(wctx: WorkspaceContext, sessionId: strin
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// PRI-358: Extract failed tool_calls as evidence
|
|
68
|
+
try {
|
|
69
|
+
const allToolCalls = wctx.trajectory.listToolCallsForSession(sessionId) ?? [];
|
|
70
|
+
const failedToolCalls = allToolCalls.filter(tc => tc.outcome === 'failure').slice(-3);
|
|
71
|
+
for (const tc of failedToolCalls) {
|
|
72
|
+
if (evidence.length >= MAX_EVIDENCE_ENTRIES) break;
|
|
73
|
+
const note = `Tool ${tc.toolName} failed: ${tc.errorType ?? 'unknown'} (exitCode: ${tc.exitCode ?? 'N/A'})`;
|
|
74
|
+
evidence.push({
|
|
75
|
+
sourceRef: `tool_call_failure:${tc.createdAt}`,
|
|
76
|
+
note: sanitizeAssistantText(note.slice(0, MAX_EVIDENCE_NOTE_CHARS)),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// Only add unavailable entry when no other evidence exists (avoid noise)
|
|
81
|
+
if (evidence.length === 0) {
|
|
82
|
+
evidence.push({
|
|
83
|
+
sourceRef: 'tool_call_failure:unavailable',
|
|
84
|
+
note: `trajectory_tool_calls_unavailable: ${String(e).slice(0, 100)}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
if (evidence.length === 0) {
|
|
68
90
|
evidence.push({
|
|
69
91
|
sourceRef: 'trajectory:empty',
|
|
@@ -278,17 +278,17 @@ describe('surface-guard', () => {
|
|
|
278
278
|
const mockLogger = { info: vi.fn(), debug: vi.fn() };
|
|
279
279
|
const service: OpenClawPluginService = { id: 'test-service' };
|
|
280
280
|
|
|
281
|
-
//
|
|
282
|
-
const first = guardService('service:
|
|
281
|
+
// service:evolution-worker is quiet/disabled — use it for the rate-limit test.
|
|
282
|
+
const first = guardService('service:evolution-worker', service, mockLogger);
|
|
283
283
|
expect(first).toBeNull();
|
|
284
284
|
expect(mockLogger.info).toHaveBeenCalledTimes(1);
|
|
285
285
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
286
|
-
expect.stringContaining('SKIP service service:
|
|
286
|
+
expect.stringContaining('SKIP service service:evolution-worker'),
|
|
287
287
|
);
|
|
288
288
|
|
|
289
289
|
// Subsequent guardService calls for the same surfaceId stay silent.
|
|
290
|
-
guardService('service:
|
|
291
|
-
guardService('service:
|
|
290
|
+
guardService('service:evolution-worker', service, mockLogger);
|
|
291
|
+
guardService('service:evolution-worker', service, mockLogger);
|
|
292
292
|
expect(mockLogger.info).toHaveBeenCalledTimes(1);
|
|
293
293
|
});
|
|
294
294
|
});
|
|
@@ -23,6 +23,7 @@ describe('buildTrajectoryEvidence', () => {
|
|
|
23
23
|
mockTrajectory = {
|
|
24
24
|
listUserTurnsForSession: vi.fn(),
|
|
25
25
|
listAssistantTurns: vi.fn(),
|
|
26
|
+
listToolCallsForSession: vi.fn(),
|
|
26
27
|
};
|
|
27
28
|
mockWctx = {
|
|
28
29
|
trajectory: mockTrajectory as TrajectoryDatabase,
|
|
@@ -191,4 +192,89 @@ describe('buildTrajectoryEvidence', () => {
|
|
|
191
192
|
// Note should be truncated to MAX_EVIDENCE_NOTE_CHARS (1000 from core)
|
|
192
193
|
expect(result[0].note.length).toBeLessThanOrEqual(1000);
|
|
193
194
|
});
|
|
195
|
+
|
|
196
|
+
// ── PRI-358: Failed tool_calls evidence ────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
describe('PRI-358: failed tool_calls evidence', () => {
|
|
199
|
+
it('extracts failed tool_calls as evidence entries', () => {
|
|
200
|
+
vi.mocked(mockTrajectory.listUserTurnsForSession!).mockReturnValue([]);
|
|
201
|
+
vi.mocked(mockTrajectory.listAssistantTurns!).mockReturnValue([]);
|
|
202
|
+
vi.mocked(mockTrajectory.listToolCallsForSession!).mockReturnValue([
|
|
203
|
+
{ id: 1, toolName: 'bash', outcome: 'failure', errorType: 'non_zero_exit', exitCode: 1, errorMessage: 'Command failed', filePath: null, durationMs: 500, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:00:00Z' },
|
|
204
|
+
{ id: 2, toolName: 'write_file', outcome: 'success', errorType: null, exitCode: 0, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:01:00Z' },
|
|
205
|
+
{ id: 3, toolName: 'bash', outcome: 'failure', errorType: 'timeout', exitCode: 124, errorMessage: 'Timed out', filePath: null, durationMs: 30000, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:02:00Z' },
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
const result = buildTrajectoryEvidence(mockWctx as WorkspaceContext, 'session-123');
|
|
209
|
+
|
|
210
|
+
const failureEntries = result.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
211
|
+
expect(failureEntries.length).toBe(2);
|
|
212
|
+
expect(failureEntries[0].note).toContain('bash');
|
|
213
|
+
expect(failureEntries[0].note).toContain('non_zero_exit');
|
|
214
|
+
expect(failureEntries[1].note).toContain('timeout');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('does not add tool_call_failure entries when no failures exist', () => {
|
|
218
|
+
vi.mocked(mockTrajectory.listUserTurnsForSession!).mockReturnValue([]);
|
|
219
|
+
vi.mocked(mockTrajectory.listAssistantTurns!).mockReturnValue([]);
|
|
220
|
+
vi.mocked(mockTrajectory.listToolCallsForSession!).mockReturnValue([
|
|
221
|
+
{ id: 1, toolName: 'bash', outcome: 'success', errorType: null, exitCode: 0, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:00:00Z' },
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
const result = buildTrajectoryEvidence(mockWctx as WorkspaceContext, 'session-123');
|
|
225
|
+
|
|
226
|
+
const failureEntries = result.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
227
|
+
expect(failureEntries.length).toBe(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('limits failed tool_calls to 3 entries', () => {
|
|
231
|
+
vi.mocked(mockTrajectory.listUserTurnsForSession!).mockReturnValue([]);
|
|
232
|
+
vi.mocked(mockTrajectory.listAssistantTurns!).mockReturnValue([]);
|
|
233
|
+
vi.mocked(mockTrajectory.listToolCallsForSession!).mockReturnValue([
|
|
234
|
+
{ id: 1, toolName: 'bash', outcome: 'failure', errorType: 'err1', exitCode: 1, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:00:00Z' },
|
|
235
|
+
{ id: 2, toolName: 'bash', outcome: 'failure', errorType: 'err2', exitCode: 2, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:01:00Z' },
|
|
236
|
+
{ id: 3, toolName: 'bash', outcome: 'failure', errorType: 'err3', exitCode: 3, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:02:00Z' },
|
|
237
|
+
{ id: 4, toolName: 'bash', outcome: 'failure', errorType: 'err4', exitCode: 4, errorMessage: null, filePath: null, durationMs: 100, gfiBefore: null, gfiAfter: null, createdAt: '2024-01-15T10:03:00Z' },
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
const result = buildTrajectoryEvidence(mockWctx as WorkspaceContext, 'session-123');
|
|
241
|
+
|
|
242
|
+
const failureEntries = result.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
243
|
+
expect(failureEntries.length).toBe(3);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles listToolCallsForSession throwing gracefully', () => {
|
|
247
|
+
vi.mocked(mockTrajectory.listUserTurnsForSession!).mockReturnValue([]);
|
|
248
|
+
vi.mocked(mockTrajectory.listAssistantTurns!).mockReturnValue([]);
|
|
249
|
+
vi.mocked(mockTrajectory.listToolCallsForSession!).mockImplementation(() => {
|
|
250
|
+
throw new Error('DB read error');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const result = buildTrajectoryEvidence(mockWctx as WorkspaceContext, 'session-123');
|
|
254
|
+
|
|
255
|
+
const unavailableEntry = result.find(e => e.sourceRef === 'tool_call_failure:unavailable');
|
|
256
|
+
expect(unavailableEntry).toBeDefined();
|
|
257
|
+
expect(unavailableEntry!.note).toContain('trajectory_tool_calls_unavailable');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('does not add unavailable entry when other evidence exists and tool_calls throws', () => {
|
|
261
|
+
const mockUserTurn = {
|
|
262
|
+
createdAt: '2024-01-15T10:00:00Z',
|
|
263
|
+
correctionDetected: true,
|
|
264
|
+
rawExcerpt: 'Owner correction',
|
|
265
|
+
};
|
|
266
|
+
vi.mocked(mockTrajectory.listUserTurnsForSession!).mockReturnValue([mockUserTurn] as any);
|
|
267
|
+
vi.mocked(mockTrajectory.listAssistantTurns!).mockReturnValue([]);
|
|
268
|
+
vi.mocked(mockTrajectory.listToolCallsForSession!).mockImplementation(() => {
|
|
269
|
+
throw new Error('DB read error');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const result = buildTrajectoryEvidence(mockWctx as WorkspaceContext, 'session-123');
|
|
273
|
+
|
|
274
|
+
// Should have owner message but NOT a tool_call_failure:unavailable entry
|
|
275
|
+
// because we already have evidence
|
|
276
|
+
expect(result.some(e => e.sourceRef.startsWith('owner_message:'))).toBe(true);
|
|
277
|
+
expect(result.some(e => e.sourceRef === 'tool_call_failure:unavailable')).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
194
280
|
});
|
|
@@ -435,11 +435,11 @@ describe('MVP Surface Registry Guard (PRI-289)', () => {
|
|
|
435
435
|
expect(guarded).toBeNull();
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
-
it('guardService returns
|
|
438
|
+
it('guardService returns the service for core surfaces (trajectory is now core)', async () => {
|
|
439
439
|
const { guardService } = await import('../../src/core/surface-guard.js');
|
|
440
440
|
const service = { api: null, start: () => {} };
|
|
441
441
|
const guarded = guardService('service:trajectory', service);
|
|
442
|
-
expect(guarded).
|
|
442
|
+
expect(guarded).toBe(service);
|
|
443
443
|
});
|
|
444
444
|
|
|
445
445
|
it('guardService returns null for unregistered surfaces', async () => {
|