principles-disciple 1.133.0 → 1.134.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.
@@ -49,6 +49,8 @@ export const SCHEMAS = {
49
49
  empathy_signal_json TEXT NOT NULL,
50
50
  blob_ref TEXT,
51
51
  raw_excerpt TEXT,
52
+ stop_reason TEXT,
53
+ thinking_blocks_count INTEGER,
52
54
  created_at TEXT NOT NULL
53
55
  )`,
54
56
  indexes: [
@@ -87,6 +89,7 @@ export const SCHEMAS = {
87
89
  gfi_before REAL,
88
90
  gfi_after REAL,
89
91
  params_json TEXT NOT NULL,
92
+ result_preview TEXT,
90
93
  created_at TEXT NOT NULL
91
94
  )`,
92
95
  indexes: [
@@ -28,6 +28,8 @@ export interface TrajectoryAssistantTurnInput {
28
28
  sanitizedText: string;
29
29
  usageJson: unknown;
30
30
  empathySignalJson: unknown;
31
+ stopReason?: string | null;
32
+ thinkingBlocksCount?: number | null;
31
33
  createdAt?: string;
32
34
  }
33
35
  export interface TrajectoryUserTurnInput {
@@ -50,6 +52,7 @@ export interface TrajectoryToolCallInput {
50
52
  gfiBefore?: number | null;
51
53
  gfiAfter?: number | null;
52
54
  paramsJson?: unknown;
55
+ resultPreview?: string | null;
53
56
  createdAt?: string;
54
57
  }
55
58
  export interface TrajectoryPainEventInput {
@@ -190,6 +193,8 @@ export interface AssistantTurnRecord {
190
193
  rawText: string;
191
194
  sanitizedText: string;
192
195
  blobRef: string | null;
196
+ stopReason: string | null;
197
+ thinkingBlocksCount: number | null;
193
198
  createdAt: string;
194
199
  }
195
200
  export interface CorrectionSampleRecord {
@@ -118,6 +118,7 @@ export declare class TrajectoryDatabase {
118
118
  errorMessage: string | null;
119
119
  gfiBefore: number | null;
120
120
  gfiAfter: number | null;
121
+ resultPreview: string | null;
121
122
  createdAt: string;
122
123
  }[];
123
124
  /**
@@ -93,9 +93,9 @@ export class TrajectoryDatabase {
93
93
  const result = this.db.prepare(`
94
94
  INSERT INTO assistant_turns (
95
95
  session_id, run_id, provider, model, raw_text, sanitized_text, usage_json,
96
- empathy_signal_json, blob_ref, raw_excerpt, created_at
97
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
- `).run(input.sessionId, input.runId, input.provider, input.model, rawStorage.inlineText, input.sanitizedText, safeJson(input.usageJson), safeJson(input.empathySignalJson), rawStorage.blobRef, rawStorage.excerpt, createdAt);
96
+ empathy_signal_json, blob_ref, raw_excerpt, stop_reason, thinking_blocks_count, created_at
97
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
+ `).run(input.sessionId, input.runId, input.provider, input.model, rawStorage.inlineText, input.sanitizedText, safeJson(input.usageJson), safeJson(input.empathySignalJson), rawStorage.blobRef, rawStorage.excerpt, input.stopReason ?? null, input.thinkingBlocksCount ?? null, createdAt);
99
99
  return Number(result.lastInsertRowid);
100
100
  });
101
101
  }
@@ -120,9 +120,9 @@ export class TrajectoryDatabase {
120
120
  const result = this.db.prepare(`
121
121
  INSERT INTO tool_calls (
122
122
  session_id, tool_name, outcome, duration_ms, exit_code, error_type, error_message,
123
- gfi_before, gfi_after, params_json, created_at
124
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
125
- `).run(input.sessionId, input.toolName, input.outcome, input.durationMs ?? null, input.exitCode ?? null, input.errorType ?? null, input.errorMessage ?? null, input.gfiBefore ?? null, input.gfiAfter ?? null, safeJson(input.paramsJson), createdAt);
123
+ gfi_before, gfi_after, params_json, result_preview, created_at
124
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
125
+ `).run(input.sessionId, input.toolName, input.outcome, input.durationMs ?? null, input.exitCode ?? null, input.errorType ?? null, input.errorMessage ?? null, input.gfiBefore ?? null, input.gfiAfter ?? null, safeJson(input.paramsJson), input.resultPreview ?? null, createdAt);
126
126
  return Number(result.lastInsertRowid);
127
127
  });
128
128
  if (input.outcome === 'success') {
@@ -548,7 +548,8 @@ export class TrajectoryDatabase {
548
548
  */
549
549
  listAssistantTurns(sessionId) {
550
550
  const rows = this.db.prepare(`
551
- SELECT id, session_id, run_id, provider, model, raw_text, sanitized_text, blob_ref, created_at
551
+ SELECT id, session_id, run_id, provider, model, raw_text, sanitized_text, blob_ref,
552
+ stop_reason, thinking_blocks_count, created_at
552
553
  FROM assistant_turns
553
554
  WHERE session_id = ?
554
555
  ORDER BY id ASC
@@ -562,6 +563,8 @@ export class TrajectoryDatabase {
562
563
  rawText: this.restoreRawText(row.raw_text, row.blob_ref),
563
564
  sanitizedText: String(row.sanitized_text ?? ''),
564
565
  blobRef: row.blob_ref ? String(row.blob_ref) : null,
566
+ stopReason: row.stop_reason != null ? String(row.stop_reason) : null,
567
+ thinkingBlocksCount: row.thinking_blocks_count != null ? Number(row.thinking_blocks_count) : null,
565
568
  createdAt: String(row.created_at),
566
569
  }));
567
570
  }
@@ -574,7 +577,7 @@ export class TrajectoryDatabase {
574
577
  listToolCallsForSession(sessionId) {
575
578
  const rows = this.db.prepare(`
576
579
  SELECT id, tool_name, outcome, params_json, duration_ms, exit_code, error_type, error_message,
577
- gfi_before, gfi_after, created_at
580
+ gfi_before, gfi_after, result_preview, created_at
578
581
  FROM tool_calls
579
582
  WHERE session_id = ?
580
583
  ORDER BY id ASC
@@ -604,6 +607,7 @@ export class TrajectoryDatabase {
604
607
  errorMessage: row.error_message ? String(row.error_message) : null,
605
608
  gfiBefore: row.gfi_before != null ? Number(row.gfi_before) : null,
606
609
  gfiAfter: row.gfi_after != null ? Number(row.gfi_after) : null,
610
+ resultPreview: row.result_preview != null ? String(row.result_preview) : null,
607
611
  createdAt: String(row.created_at),
608
612
  };
609
613
  });
@@ -1085,6 +1089,23 @@ export class TrajectoryDatabase {
1085
1089
  }
1086
1090
  }
1087
1091
  }
1092
+ // Trajectory enhancement: add stop_reason, thinking_blocks_count, result_preview
1093
+ const trajectoryEnhancementColumns = [
1094
+ { table: 'assistant_turns', name: 'stop_reason', type: 'TEXT' },
1095
+ { table: 'assistant_turns', name: 'thinking_blocks_count', type: 'INTEGER' },
1096
+ { table: 'tool_calls', name: 'result_preview', type: 'TEXT' },
1097
+ ];
1098
+ for (const col of trajectoryEnhancementColumns) {
1099
+ try {
1100
+ this.db.exec(`ALTER TABLE ${col.table} ADD COLUMN ${col.name} ${col.type}`);
1101
+ }
1102
+ catch (err) {
1103
+ const message = err instanceof Error ? err.message : String(err);
1104
+ if (!message.includes('duplicate column name') && !message.includes('no column named')) {
1105
+ throw err;
1106
+ }
1107
+ }
1108
+ }
1088
1109
  // PRI-406: Partial unique index on canonical_pain_id (non-null only) for dedup
1089
1110
  this.db.exec(`
1090
1111
  CREATE UNIQUE INDEX IF NOT EXISTS idx_pain_events_canonical_pain_id
@@ -17,6 +17,11 @@ import { type SessionState } from '../core/session-tracker.js';
17
17
  import { WorkspaceContext } from '../core/workspace-context.js';
18
18
  import type { PluginHookAfterToolCallEvent } from '../openclaw-sdk.js';
19
19
  import type { ToolCallOutcome, ToolCallObservation, PainAdmissionDecision } from './after-tool-call-types.js';
20
+ /**
21
+ * Extract a preview string from tool call result for diagnostic evidence.
22
+ * Pure function — no I/O, no side effects. ERR-001 / ERR-014 compliant.
23
+ */
24
+ export declare function extractToolResultPreview(result: unknown): string | null;
20
25
  /**
21
26
  * Classify the outcome of a tool call event.
22
27
  *
@@ -25,6 +25,68 @@ import { resolveSourceKind, buildToolFailureObservation } from './raw-observatio
25
25
  import { evaluateEvidenceTriage } from './triage-adapter.js';
26
26
  import { evaluateTriggerController } from '@principles/core/runtime-v2';
27
27
  import { buildTrajectoryEvidence } from './trajectory-evidence.js';
28
+ const RESULT_PREVIEW_MAX_LENGTH = 500;
29
+ /**
30
+ * Extract a preview string from tool call result for diagnostic evidence.
31
+ * Pure function — no I/O, no side effects. ERR-001 / ERR-014 compliant.
32
+ */
33
+ export function extractToolResultPreview(result) {
34
+ if (result === null || result === undefined)
35
+ return null;
36
+ try {
37
+ // String result: truncate directly
38
+ if (typeof result === 'string') {
39
+ return result.length > RESULT_PREVIEW_MAX_LENGTH
40
+ ? `${result.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
41
+ : result;
42
+ }
43
+ // Object with content array (e.g., Anthropic content blocks)
44
+ if (typeof result === 'object' && !Array.isArray(result)) {
45
+ const obj = result;
46
+ if (Array.isArray(obj.content)) {
47
+ const textParts = [];
48
+ for (const block of obj.content) {
49
+ if (block && typeof block === 'object' && !Array.isArray(block)) {
50
+ const blockObj = block;
51
+ if (blockObj.type === 'text' && typeof blockObj.text === 'string') {
52
+ textParts.push(blockObj.text);
53
+ }
54
+ }
55
+ }
56
+ if (textParts.length > 0) {
57
+ const joined = textParts.join('\n');
58
+ return joined.length > RESULT_PREVIEW_MAX_LENGTH
59
+ ? `${joined.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
60
+ : joined;
61
+ }
62
+ }
63
+ // Object without content array: JSON.stringify with depth limiter (ERR-014)
64
+ const serialized = JSON.stringify(obj, (_key, value) => {
65
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
66
+ const keys = Object.keys(value);
67
+ if (keys.length > 10) {
68
+ const truncated = {};
69
+ for (const k of keys.slice(0, 10)) {
70
+ truncated[k] = value[k];
71
+ }
72
+ truncated['__truncated__'] = true;
73
+ return truncated;
74
+ }
75
+ }
76
+ return value;
77
+ }, 2);
78
+ if (serialized && serialized !== '{}') {
79
+ return serialized.length > RESULT_PREVIEW_MAX_LENGTH
80
+ ? `${serialized.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
81
+ : serialized;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ catch {
87
+ return '[result_preview_extraction_failed]';
88
+ }
89
+ }
28
90
  // ── Stage 1: Classify ───────────────────────────────────────────────────────
29
91
  /**
30
92
  * Classify the outcome of a tool call event.
@@ -150,6 +212,7 @@ export function handleFrictionTrackingForFailure(sessionId, event, outcome, obse
150
212
  gfiBefore,
151
213
  gfiAfter: updatedState.currentGfi,
152
214
  paramsJson: sanitizeToolParamsForEvidence(event.params, workspaceDir),
215
+ resultPreview: extractToolResultPreview(event.result),
153
216
  });
154
217
  return updatedState;
155
218
  }
@@ -188,6 +251,7 @@ export function handleFrictionTrackingForSuccess(sessionId, event, outcome, obse
188
251
  gfiBefore,
189
252
  gfiAfter: resetState.currentGfi,
190
253
  paramsJson: sanitizeToolParamsForEvidence(event.params, workspaceDir),
254
+ resultPreview: extractToolResultPreview(event.result),
191
255
  });
192
256
  wctx.eventLog.recordToolCall(sessionId, {
193
257
  toolName: event.toolName,
@@ -8,6 +8,14 @@ export interface EmpathySignal {
8
8
  }
9
9
  export declare function extractEmpathySignal(text: string): EmpathySignal;
10
10
  export declare function isEmpathyAuditPayload(text: string): boolean;
11
+ /**
12
+ * Extract enhanced fields from lastAssistant (complete AssistantMessage) in hook payload.
13
+ * Pure function — no I/O, no side effects. ERR-001 compliant.
14
+ */
15
+ export declare function extractAssistantEnhancedFields(lastAssistant: unknown): {
16
+ stopReason: string | null;
17
+ thinkingBlocksCount: number | null;
18
+ };
11
19
  export declare function handleLlmOutput(event: PluginHookLlmOutputEvent, ctx: PluginHookAgentContext & {
12
20
  workspaceDir?: string;
13
21
  }): void;
package/dist/hooks/llm.js CHANGED
@@ -124,6 +124,33 @@ export function isEmpathyAuditPayload(text) {
124
124
  return true;
125
125
  return false;
126
126
  }
127
+ /**
128
+ * Extract enhanced fields from lastAssistant (complete AssistantMessage) in hook payload.
129
+ * Pure function — no I/O, no side effects. ERR-001 compliant.
130
+ */
131
+ export function extractAssistantEnhancedFields(lastAssistant) {
132
+ if (!lastAssistant || typeof lastAssistant !== 'object' || Array.isArray(lastAssistant)) {
133
+ return { stopReason: null, thinkingBlocksCount: null };
134
+ }
135
+ const obj = lastAssistant;
136
+ // stopReason: typeof guard
137
+ const stopReason = typeof obj.stopReason === 'string' ? obj.stopReason : null;
138
+ // thinkingBlocksCount: iterate content array, count thinking/redacted_thinking blocks
139
+ let thinkingBlocksCount = null;
140
+ if (Array.isArray(obj.content)) {
141
+ let count = 0;
142
+ for (const block of obj.content) {
143
+ if (block && typeof block === 'object' && !Array.isArray(block)) {
144
+ const blockType = block.type;
145
+ if (blockType === 'thinking' || blockType === 'redacted_thinking') {
146
+ count++;
147
+ }
148
+ }
149
+ }
150
+ thinkingBlocksCount = count;
151
+ }
152
+ return { stopReason, thinkingBlocksCount };
153
+ }
127
154
  export function handleLlmOutput(event, ctx) {
128
155
  if (!ctx.workspaceDir || !ctx.sessionId)
129
156
  return;
@@ -142,6 +169,7 @@ export function handleLlmOutput(event, ctx) {
142
169
  return;
143
170
  const text = event.assistantTexts.join('\n');
144
171
  const signal = extractEmpathySignal(text);
172
+ const enhancedFields = extractAssistantEnhancedFields(event.lastAssistant);
145
173
  const createdAt = new Date().toISOString();
146
174
  let assistantTurnId = null;
147
175
  try {
@@ -154,6 +182,8 @@ export function handleLlmOutput(event, ctx) {
154
182
  sanitizedText: sanitizeAssistantText(text),
155
183
  usageJson: event.usage || {},
156
184
  empathySignalJson: signal,
185
+ stopReason: enhancedFields.stopReason,
186
+ thinkingBlocksCount: enhancedFields.thinkingBlocksCount,
157
187
  createdAt,
158
188
  });
159
189
  }
@@ -41,9 +41,11 @@ export function buildTrajectoryEvidence(wctx, sessionId) {
41
41
  if (evidence.length >= MAX_EVIDENCE_ENTRIES)
42
42
  break;
43
43
  const sanitizedNote = sanitizeAssistantText((turn.sanitizedText ?? '').slice(0, MAX_EVIDENCE_NOTE_CHARS));
44
+ // Enhanced: append truncation warning when stop_reason=length
45
+ const truncationWarning = turn.stopReason === 'length' ? ' [TRUNCATED: output cut off by length limit]' : '';
44
46
  evidence.push({
45
47
  sourceRef: `agent_turn:${turn.createdAt}`,
46
- note: sanitizedNote,
48
+ note: sanitizedNote + truncationWarning,
47
49
  });
48
50
  }
49
51
  }
@@ -62,7 +64,9 @@ export function buildTrajectoryEvidence(wctx, sessionId) {
62
64
  for (const tc of failedToolCalls) {
63
65
  if (evidence.length >= MAX_EVIDENCE_ENTRIES)
64
66
  break;
65
- const note = `Tool ${tc.toolName} failed: ${tc.errorType ?? 'unknown'} (exitCode: ${tc.exitCode ?? 'N/A'})`;
67
+ // Enhanced: append resultPreview when available
68
+ const previewSuffix = tc.resultPreview ? ` | ${tc.resultPreview.slice(0, 200)}` : '';
69
+ const note = `Tool ${tc.toolName} failed: ${tc.errorType ?? 'unknown'} (exitCode: ${tc.exitCode ?? 'N/A'})${previewSuffix}`;
66
70
  evidence.push({
67
71
  sourceRef: `tool_call_failure:${tc.createdAt}`,
68
72
  note: sanitizeAssistantText(note.slice(0, MAX_EVIDENCE_NOTE_CHARS)),
@@ -231,6 +231,7 @@ export interface PluginHookLlmOutputEvent {
231
231
  usage?: TokenUsage;
232
232
  assistantTexts?: string[];
233
233
  trigger?: string;
234
+ lastAssistant?: unknown;
234
235
  [key: string]: unknown;
235
236
  }
236
237
  export interface PluginHookSubagentSpawningEvent {
@@ -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.133.0",
5
+ "version": "1.134.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.133.0",
3
+ "version": "1.134.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",