principles-disciple 1.102.0 → 1.103.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.
@@ -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.102.0",
5
+ "version": "1.103.0",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.102.0",
3
+ "version": "1.103.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -50,9 +50,9 @@ export class PromptActivationReader {
50
50
  }
51
51
 
52
52
  if (artifactRow === null) {
53
- const warning = `artifact_not_found: artifactId=${activation.artifactId}; nextAction=check_pi_artifacts_table`;
53
+ const warning = `artifact_not_found: artifactId=${activation.artifactId}; nextAction=check_pi_artifacts_table_or_remove_stale_activation`;
54
54
  warnings.push(warning);
55
- this.deps.logger?.warn?.(`[PD:RuntimeV2] ${warning}`);
55
+ this.deps.logger?.info?.(`[PD:RuntimeV2] ${warning}`);
56
56
  continue;
57
57
  }
58
58
 
@@ -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',
@@ -387,13 +387,13 @@ describe('Runtime V2 prompt activation injection', () => {
387
387
 
388
388
  await insertPromptActivation({ artifactId, principleId });
389
389
 
390
- const warnSpy = vi.fn();
390
+ const infoSpy = vi.fn();
391
391
  const ctx = {
392
392
  workspaceDir: tempWorkspaceDir,
393
393
  trigger: 'user',
394
394
  sessionId: 'test-session-v2',
395
395
  api: {
396
- logger: { info: vi.fn(), warn: warnSpy, error: vi.fn() },
396
+ logger: { info: infoSpy, warn: vi.fn(), error: vi.fn() },
397
397
  runtime: {},
398
398
  config: {},
399
399
  },
@@ -404,8 +404,8 @@ describe('Runtime V2 prompt activation injection', () => {
404
404
 
405
405
  expect(result).toBeDefined();
406
406
  expect(result?.appendSystemContext).not.toContain(TEST_PRINCIPLE_TEXT);
407
- const warnCalls = warnSpy.mock.calls.map((c: unknown[]) => String(c[0]));
408
- const hasActivationWarning = warnCalls.some((c: string) => c.includes('artifact_not_found') || c.includes('artifact_query_unexpected') || c.includes('activation'));
407
+ const infoCalls = infoSpy.mock.calls.map((c: unknown[]) => String(c[0]));
408
+ const hasActivationWarning = infoCalls.some((c: string) => c.includes('artifact_not_found') || c.includes('artifact_query_unexpected') || c.includes('activation'));
409
409
  expect(hasActivationWarning).toBe(true);
410
410
  });
411
411
 
@@ -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
  });