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.
Files changed (132) hide show
  1. package/README.md +13 -5
  2. package/openclaw.plugin.json +4 -4
  3. package/package.json +1 -1
  4. package/src/commands/archive-impl.ts +3 -3
  5. package/src/commands/capabilities.ts +1 -1
  6. package/src/commands/context.ts +3 -3
  7. package/src/commands/disable-impl.ts +1 -1
  8. package/src/commands/evolution-status.ts +2 -2
  9. package/src/commands/focus.ts +2 -2
  10. package/src/commands/nocturnal-train.ts +6 -6
  11. package/src/commands/pain.ts +4 -4
  12. package/src/commands/pd-reflect.ts +87 -0
  13. package/src/commands/rollback-impl.ts +4 -4
  14. package/src/commands/rollback.ts +2 -2
  15. package/src/commands/samples.ts +2 -2
  16. package/src/commands/workflow-debug.ts +1 -1
  17. package/src/config/errors.ts +1 -1
  18. package/src/core/adaptive-thresholds.ts +1 -1
  19. package/src/core/code-implementation-storage.ts +2 -2
  20. package/src/core/config.ts +1 -1
  21. package/src/core/diagnostician-task-store.ts +2 -2
  22. package/src/core/empathy-keyword-matcher.ts +3 -3
  23. package/src/core/event-log.ts +5 -5
  24. package/src/core/evolution-engine.ts +4 -4
  25. package/src/core/evolution-logger.ts +1 -1
  26. package/src/core/evolution-reducer.ts +3 -3
  27. package/src/core/evolution-types.ts +5 -5
  28. package/src/core/external-training-contract.ts +1 -1
  29. package/src/core/focus-history.ts +14 -14
  30. package/src/core/hygiene/tracker.ts +1 -1
  31. package/src/core/init.ts +2 -2
  32. package/src/core/model-deployment-registry.ts +2 -2
  33. package/src/core/model-training-registry.ts +2 -2
  34. package/src/core/nocturnal-arbiter.ts +1 -1
  35. package/src/core/nocturnal-artificer.ts +2 -2
  36. package/src/core/nocturnal-candidate-scoring.ts +2 -2
  37. package/src/core/nocturnal-compliance.ts +4 -3
  38. package/src/core/nocturnal-dataset.ts +3 -3
  39. package/src/core/nocturnal-export.ts +4 -4
  40. package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
  41. package/src/core/nocturnal-snapshot-contract.ts +112 -0
  42. package/src/core/nocturnal-trajectory-extractor.ts +7 -5
  43. package/src/core/nocturnal-trinity.ts +480 -158
  44. package/src/core/pain-context-extractor.ts +3 -3
  45. package/src/core/pain.ts +124 -11
  46. package/src/core/path-resolver.ts +4 -4
  47. package/src/core/pd-task-reconciler.ts +10 -10
  48. package/src/core/pd-task-service.ts +1 -1
  49. package/src/core/pd-task-store.ts +1 -1
  50. package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
  51. package/src/core/principle-training-state.ts +2 -2
  52. package/src/core/principle-tree-ledger.ts +7 -7
  53. package/src/core/promotion-gate.ts +9 -9
  54. package/src/core/replay-engine.ts +12 -12
  55. package/src/core/risk-calculator.ts +1 -1
  56. package/src/core/rule-host-types.ts +2 -2
  57. package/src/core/rule-host.ts +5 -5
  58. package/src/core/schema/db-types.ts +1 -1
  59. package/src/core/schema/schema-definitions.ts +1 -1
  60. package/src/core/session-tracker.ts +96 -4
  61. package/src/core/shadow-observation-registry.ts +3 -3
  62. package/src/core/system-logger.ts +2 -2
  63. package/src/core/thinking-os-parser.ts +1 -1
  64. package/src/core/training-program.ts +2 -2
  65. package/src/core/trajectory.ts +8 -8
  66. package/src/core/workspace-context.ts +2 -2
  67. package/src/core/workspace-dir-service.ts +85 -0
  68. package/src/core/workspace-dir-validation.ts +30 -107
  69. package/src/hooks/bash-risk.ts +3 -3
  70. package/src/hooks/edit-verification.ts +4 -4
  71. package/src/hooks/gate-block-helper.ts +4 -4
  72. package/src/hooks/gate.ts +10 -10
  73. package/src/hooks/gfi-gate.ts +7 -7
  74. package/src/hooks/lifecycle.ts +2 -2
  75. package/src/hooks/llm.ts +1 -1
  76. package/src/hooks/pain.ts +25 -5
  77. package/src/hooks/progressive-trust-gate.ts +7 -7
  78. package/src/hooks/prompt.ts +24 -5
  79. package/src/hooks/subagent.ts +2 -2
  80. package/src/hooks/thinking-checkpoint.ts +2 -2
  81. package/src/hooks/trajectory-collector.ts +1 -1
  82. package/src/http/principles-console-route.ts +14 -6
  83. package/src/i18n/commands.ts +4 -0
  84. package/src/index.ts +181 -185
  85. package/src/service/central-health-service.ts +1 -1
  86. package/src/service/central-overview-service.ts +3 -3
  87. package/src/service/evolution-query-service.ts +1 -1
  88. package/src/service/evolution-worker.ts +221 -109
  89. package/src/service/health-query-service.ts +27 -17
  90. package/src/service/monitoring-query-service.ts +3 -3
  91. package/src/service/nocturnal-runtime.ts +4 -4
  92. package/src/service/nocturnal-service.ts +40 -23
  93. package/src/service/nocturnal-target-selector.ts +11 -4
  94. package/src/service/runtime-summary-service.ts +1 -1
  95. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -1
  96. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +3 -3
  97. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +16 -13
  98. package/src/service/subagent-workflow/runtime-direct-driver.ts +10 -6
  99. package/src/service/subagent-workflow/types.ts +4 -4
  100. package/src/service/subagent-workflow/workflow-manager-base.ts +5 -5
  101. package/src/service/subagent-workflow/workflow-store.ts +2 -2
  102. package/src/tools/critique-prompt.ts +2 -3
  103. package/src/tools/deep-reflect.ts +17 -16
  104. package/src/tools/model-index.ts +1 -1
  105. package/src/utils/file-lock.ts +1 -1
  106. package/src/utils/io.ts +7 -2
  107. package/src/utils/nlp.ts +1 -1
  108. package/src/utils/plugin-logger.ts +2 -2
  109. package/src/utils/retry.ts +3 -2
  110. package/src/utils/subagent-probe.ts +20 -33
  111. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +8 -7
  112. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +111 -0
  113. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +1 -1
  114. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +1 -1
  115. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +8 -7
  116. package/templates/pain_settings.json +1 -1
  117. package/tests/build-artifacts.test.ts +4 -58
  118. package/tests/commands/pd-reflect.test.ts +49 -0
  119. package/tests/core/nocturnal-snapshot-contract.test.ts +70 -0
  120. package/tests/core/pain-auto-repair.test.ts +96 -0
  121. package/tests/core/pain-integration.test.ts +483 -0
  122. package/tests/core/pain.test.ts +5 -4
  123. package/tests/core/workspace-dir-service.test.ts +68 -0
  124. package/tests/core/workspace-dir-validation.test.ts +56 -192
  125. package/tests/hooks/pain.test.ts +20 -0
  126. package/tests/http/principles-console-route.test.ts +42 -20
  127. package/tests/integration/empathy-workflow-integration.test.ts +1 -2
  128. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +9 -17
  129. package/tests/service/empathy-observer-workflow-manager.test.ts +1 -2
  130. package/tests/service/evolution-worker.nocturnal.test.ts +118 -109
  131. package/tests/service/nocturnal-runtime-hardening.test.ts +33 -0
  132. package/tests/utils/subagent-probe.test.ts +32 -0
package/src/hooks/gate.ts CHANGED
@@ -134,7 +134,7 @@ export function handleBeforeToolCall(
134
134
  const mutationMatch = /(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/.exec(command);
135
135
 
136
136
  if (mutationMatch) {
137
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring -- Reason: mutationMatch[1] assigned to reassignable outer let - destructuring would shadow outer variable
137
+
138
138
  filePath = mutationMatch[1];
139
139
  } else {
140
140
  const hasRiskPath = profile.risk_paths.some(rp => command.includes(rp));
@@ -167,30 +167,30 @@ export function handleBeforeToolCall(
167
167
  action: {
168
168
  toolName: event.toolName,
169
169
  normalizedPath: relPath,
170
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
170
+
171
171
  paramsSummary: _extractParamsSummary(event.params),
172
172
  },
173
173
  workspace: {
174
174
  isRiskPath: risky,
175
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
175
+
176
176
  planStatus: _getPlanStatus(ctx.workspaceDir),
177
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
177
+
178
178
  hasPlanFile: _hasPlanFile(ctx.workspaceDir),
179
179
  },
180
180
  session: {
181
181
  sessionId: ctx.sessionId,
182
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
182
+
183
183
  currentGfi: _getCurrentGfi(ctx.sessionId),
184
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
184
+
185
185
  recentThinking: _hasRecentThinking(ctx.sessionId),
186
186
  },
187
187
  evolution: {
188
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
188
+
189
189
  epTier: _getEpTier(wctx.workspaceDir),
190
190
  },
191
191
  derived: {
192
192
  estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params }),
193
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
193
+
194
194
  bashRisk: _getBashRisk(event, profile),
195
195
  },
196
196
  };
@@ -337,12 +337,12 @@ function _getEpTier(workspaceDir: string): number {
337
337
  }
338
338
  }
339
339
 
340
- /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: type-only parameter not used at runtime */
340
+
341
341
  function _getBashRisk(
342
342
  event: PluginHookBeforeToolCallEvent,
343
343
  _profile: { risk_paths: string[] }
344
344
  ): 'safe' | 'normal' | 'dangerous' | 'unknown' {
345
- /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
345
+
346
346
  if (!BASH_TOOLS_SET.has(event.toolName)) return 'unknown';
347
347
  try {
348
348
  const command = String(event.params.command || event.params.args || '');
@@ -47,16 +47,16 @@ export interface GfiGateConfig {
47
47
  /**
48
48
  * Internal helper to call the shared block helper with gfi-gate source tag.
49
49
  */
50
- /* eslint-disable @typescript-eslint/max-params -- Reason: Helper function requires all params for block recording */
50
+
51
51
  function block(
52
52
  wctx: WorkspaceContext,
53
53
  filePath: string,
54
54
  reason: string,
55
55
  toolName: string,
56
56
  sessionId: string | undefined,
57
- /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
57
+
58
58
  logger?: { info?: (message: string) => void; warn?: (message: string) => void; error?: (message: string) => void }
59
- /* eslint-enable no-unused-vars */
59
+
60
60
  ): PluginHookBeforeToolCallResult {
61
61
  return recordGateBlockAndReturn(wctx, {
62
62
  filePath,
@@ -64,19 +64,19 @@ function block(
64
64
  toolName,
65
65
  sessionId,
66
66
  blockSource: 'gfi-gate',
67
- }, logger || // eslint-disable-next-line @typescript-eslint/no-empty-function -- empty warn/error no-op
67
+ }, logger ||
68
68
  { warn: () => {}, error: () => {} } as const);
69
69
  }
70
70
 
71
- /* eslint-disable @typescript-eslint/max-params -- Reason: Gate function requires all params for comprehensive checks */
71
+
72
72
  export function checkGfiGate(
73
73
  event: PluginHookBeforeToolCallEvent,
74
74
  wctx: WorkspaceContext,
75
75
  sessionId: string | undefined,
76
76
  config: GfiGateConfig,
77
- /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
77
+
78
78
  logger?: { info?: (message: string) => void; warn?: (message: string) => void }
79
- /* eslint-enable no-unused-vars */
79
+
80
80
  ): PluginHookBeforeToolCallResult | undefined {
81
81
  if (!config || config.enabled === false || !sessionId) {
82
82
  return undefined;
@@ -122,7 +122,7 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
122
122
  try {
123
123
  rl.close();
124
124
  fileStream.destroy();
125
- } catch (_e) { // eslint-disable-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: intentionally unused - cleanup errors ignored
125
+ } catch (_e) { // eslint-disable-line @typescript-eslint/no-unused-vars -- Reason: intentionally unused - cleanup errors ignored
126
126
  // Ignore cleanup errors
127
127
  }
128
128
  }
@@ -196,7 +196,7 @@ export async function handleBeforeCompaction(
196
196
  await extractPainFromSessionFile(event.sessionFile, ctx);
197
197
 
198
198
  // 新增:提取并保存工作记忆
199
- /* eslint-disable @typescript-eslint/no-use-before-define -- Reason: extractAndSaveWorkingMemory is defined later in this file */
199
+
200
200
  await extractAndSaveWorkingMemory(event.sessionFile, ctx, wctx);
201
201
  }
202
202
  }
package/src/hooks/llm.ts CHANGED
@@ -255,7 +255,7 @@ export function handleLlmOutput(
255
255
  }
256
256
 
257
257
  // ═══ Thinking OS: Mental Model Usage Tracking ═══
258
- /* eslint-disable @typescript-eslint/no-use-before-define -- Reason: trackThinkingModelUsage is defined later in this file */
258
+
259
259
  trackThinkingModelUsage({
260
260
  text,
261
261
  wctx,
package/src/hooks/pain.ts CHANGED
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import { isRisky, normalizePath } from '../utils/io.js';
3
3
  import { normalizeProfile } from '../core/profile.js';
4
4
  import { computePainScore, buildPainFlag, writePainFlag, trackPrincipleValue } from '../core/pain.js';
5
- import { getSession, trackFriction, resetFriction, getInjectedProbationIds, clearInjectedProbationIds } from '../core/session-tracker.js';
5
+ import { getSession, trackFriction, resetFriction, getInjectedProbationIds, clearInjectedProbationIds, type SessionState } from '../core/session-tracker.js';
6
6
  import { denoiseError, computeHash } from '../utils/hashing.js';
7
7
  import { SystemLogger } from '../core/system-logger.js';
8
8
  import { WorkspaceContext } from '../core/workspace-context.js';
@@ -10,6 +10,8 @@ import { getEvolutionLogger, createTraceId } from '../core/evolution-logger.js';
10
10
  import { recordEvolutionSuccess, recordEvolutionFailure } from '../core/evolution-engine.js';
11
11
  import type { EvolutionLoopEvent } from '../core/evolution-types.js';
12
12
  import type { PluginHookAfterToolCallEvent, PluginHookToolContext, OpenClawPluginApi } from '../openclaw-sdk.js';
13
+ import { validateWorkspaceDir } from '../core/workspace-dir-validation.js';
14
+ import { resolveWorkspaceDir } from '../core/workspace-dir-service.js';
13
15
 
14
16
  /**
15
17
  * Interface for tool parameters to avoid 'any'
@@ -49,7 +51,9 @@ export function handleAfterToolCall(
49
51
  ctx: PluginHookToolContext & { workspaceDir?: string; pluginConfig?: Record<string, unknown> },
50
52
  api?: OpenClawPluginApi
51
53
  ): void {
52
- const effectiveWorkspaceDir = ctx.workspaceDir || (api as unknown as { workspaceDir?: string })?.workspaceDir || api?.resolvePath?.('.');
54
+ const effectiveWorkspaceDir = api
55
+ ? resolveWorkspaceDir(api, ctx, { source: 'after_tool_call' })
56
+ : validateWorkspaceDir(ctx.workspaceDir) ? undefined : ctx.workspaceDir;
53
57
  if (!effectiveWorkspaceDir) {
54
58
  return;
55
59
  }
@@ -123,10 +127,10 @@ export function handleAfterToolCall(
123
127
  const hash = computeHash(denoised);
124
128
 
125
129
  const deltaF = config.get('scores.tool_failure_friction') || 30;
126
- const updatedState = trackFriction(sessionId, deltaF, hash, effectiveWorkspaceDir);
130
+ const updatedState = trackFriction(sessionId, deltaF, hash, effectiveWorkspaceDir, { source: 'tool_failure' });
127
131
 
128
132
  // ── Trust Engine: Record failure ──
129
- /* eslint-disable @typescript-eslint/no-use-before-define -- Reason: extractErrorType is defined later in this file */
133
+
130
134
  const errorType = extractErrorType(event.error || errorText);
131
135
  const filePath = params.file_path || params.path || params.file;
132
136
  const relPath = typeof filePath === 'string' ? normalizePath(filePath, effectiveWorkspaceDir) : 'unknown';
@@ -184,7 +188,23 @@ export function handleAfterToolCall(
184
188
  clearInjectedProbationIds(sessionId, effectiveWorkspaceDir);
185
189
  } else {
186
190
  // ── SUCCESS BRANCH ──
187
- const resetState = resetFriction(sessionId, effectiveWorkspaceDir);
191
+ // Only reduce tool_failure source GFI by 50%, preserve user_empathy and other sources
192
+ // This prevents "read file success" from wiping user frustration signals
193
+ const session = getSession(sessionId);
194
+ const toolFailureGfi = session?.gfiBySource?.['tool_failure'] || 0;
195
+
196
+ let resetState: SessionState;
197
+ if (toolFailureGfi > 0) {
198
+ // Reduce tool_failure source by 50% (relief from successful tool execution)
199
+ const reliefAmount = toolFailureGfi * 0.5;
200
+ resetState = resetFriction(sessionId, effectiveWorkspaceDir, {
201
+ source: 'tool_failure',
202
+ amount: reliefAmount,
203
+ });
204
+ } else {
205
+ // No tool_failure GFI to reduce, just get current state
206
+ resetState = session || resetFriction(sessionId, effectiveWorkspaceDir);
207
+ }
188
208
 
189
209
  recordEvolutionSuccess(effectiveWorkspaceDir, event.toolName, {
190
210
  sessionId,
@@ -86,15 +86,15 @@ export function buildEvolutionGateReason(
86
86
  /**
87
87
  * Internal helper to call the shared block helper with progressive-trust-gate source tag.
88
88
  */
89
- /* eslint-disable @typescript-eslint/max-params -- Reason: Helper function requires all parameters for block recording */
89
+
90
90
  function block(
91
91
  filePath: string,
92
92
  reason: string,
93
93
  wctx: WorkspaceContext,
94
94
  toolName: string,
95
- /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
95
+
96
96
  logger: { warn?: (message: string) => void; error?: (message: string) => void },
97
- /* eslint-enable no-unused-vars */
97
+
98
98
  sessionId?: string
99
99
  ): PluginHookBeforeToolCallResult {
100
100
  return recordGateBlockAndReturn(wctx, {
@@ -119,16 +119,16 @@ function block(
119
119
  * @param profile - Gate profile containing risk_paths config
120
120
  * @returns PluginHookBeforeToolCallResult to block, or undefined to allow
121
121
  */
122
- /* eslint-disable @typescript-eslint/max-params -- Reason: Gate function requires all parameters for comprehensive checks */
122
+
123
123
  export function checkProgressiveTrustGate(
124
124
  event: PluginHookBeforeToolCallEvent,
125
125
  wctx: WorkspaceContext,
126
126
  relPath: string,
127
127
  risky: boolean,
128
128
  lineChanges: number,
129
- /* eslint-disable no-unused-vars -- Reason: type-only callback params in logger type */
129
+
130
130
  logger: { warn?: (message: string) => void; error?: (message: string) => void; info?: (message: string) => void },
131
- /* eslint-enable no-unused-vars */
131
+
132
132
  ctx: { workspaceDir?: string; sessionId?: string },
133
133
  profile?: { risk_paths: string[]; core_governance_files?: string[] }
134
134
  ): PluginHookBeforeToolCallResult | void {
@@ -151,7 +151,7 @@ export function checkProgressiveTrustGate(
151
151
  });
152
152
 
153
153
  const currentTier = epDecision.currentTier ?? 1;
154
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
154
+
155
155
  const tierName = getTierName(currentTier);
156
156
 
157
157
  logger.info?.(`[PD_GATE] EP Gate: Tier ${currentTier} (${tierName}), Tool: ${event.toolName}, Risk: ${risky}, Allowed: ${epDecision.allowed}`);
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import type { PluginHookBeforePromptBuildEvent, PluginHookAgentContext, PluginHookBeforePromptBuildResult, PluginLogger, OpenClawPluginApi } from '../openclaw-sdk.js';
4
- import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds, trackFriction } from '../core/session-tracker.js';
4
+ import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds, trackFriction, decayGfi, getGfiDecayElapsed } from '../core/session-tracker.js';
5
5
  import { WorkspaceContext } from '../core/workspace-context.js';
6
6
  import type { ContextInjectionConfig} from '../types.js';
7
7
  import { defaultContextConfig } from '../types.js';
@@ -308,6 +308,13 @@ export async function handleBeforePromptBuild(
308
308
  return;
309
309
  }
310
310
 
311
+ // ──── DEBUG: Verify subagent availability in this context ────
312
+ const subagent = ctx.api?.runtime?.subagent;
313
+ logger?.info?.(`[PD:DEBUG:SubagentCheck] trigger=${ctx.trigger}, subagent_exists=${!!subagent}, subagent.run_exists=${!!subagent?.run}`);
314
+ if (subagent?.run) {
315
+ logger?.info?.('[PD:DEBUG:SubagentCheck] run entrypoint is callable');
316
+ }
317
+
311
318
  const wctx = WorkspaceContext.fromHookContext(ctx);
312
319
  const { trigger, sessionId, api } = ctx;
313
320
  if (sessionId) {
@@ -358,7 +365,7 @@ export async function handleBeforePromptBuild(
358
365
  // appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
359
366
  // prependContext: Only short dynamic directives: evolutionDirective + heartbeat
360
367
 
361
- // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment
368
+
362
369
  let prependSystemContext = '';
363
370
  let prependContext = '';
364
371
  let appendSystemContext = '';
@@ -612,6 +619,18 @@ The empathy observer subagent handles pain detection independently.
612
619
 
613
620
  // ──── 4. Heartbeat-specific checklist ────
614
621
  if (trigger === 'heartbeat') {
622
+ // ──── 4a. GFI Time-based Decay ────
623
+ // Apply segmented exponential decay to GFI on each heartbeat
624
+ if (sessionId) {
625
+ const elapsedMinutes = getGfiDecayElapsed(sessionId);
626
+ if (elapsedMinutes >= 1) {
627
+ const decayedState = decayGfi(sessionId, elapsedMinutes);
628
+ if (decayedState) {
629
+ logger?.info?.(`[PD:GFI] Heartbeat decay applied: ${elapsedMinutes}min elapsed, GFI now ${decayedState.currentGfi.toFixed(1)}`);
630
+ }
631
+ }
632
+ }
633
+
615
634
  const heartbeatPath = wctx.resolve('HEARTBEAT');
616
635
  if (fs.existsSync(heartbeatPath)) {
617
636
  try {
@@ -628,7 +647,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
628
647
  }
629
648
 
630
649
  // ──── 6. Dynamic Attitude Matrix (based on GFI) ────
631
- // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment
650
+
632
651
  let attitudeDirective = '';
633
652
  const currentGfi = session?.currentGfi || 0;
634
653
 
@@ -853,10 +872,10 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
853
872
  const filePattern = /\b([a-zA-Z]:\\?[^\s,]+\.[a-z]{2,10}|[./][^\s,]+\.[a-z]{2,10})\b/gi;
854
873
  const toolMatches = toolPatterns.flatMap(({ pattern, tool }) => {
855
874
  const matches: string[] = [];
856
- // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in while loop condition
875
+
857
876
  let _m;
858
877
  const r = new RegExp(pattern.source, pattern.flags);
859
- /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: regex exec side effect used, match variable intentionally unused */
878
+ /* eslint-disable @typescript-eslint/no-unused-vars -- Reason: regex exec side effect used, match variable intentionally unused */
860
879
  while ((_m = r.exec(latestUserText)) !== null) matches.push(tool);
861
880
  return matches;
862
881
  });
@@ -12,7 +12,7 @@ import type { WorkflowManager } from '../service/subagent-workflow/types.js';
12
12
  * Factory to create the appropriate WorkflowManager by workflow_type string.
13
13
  * Used by the subagent_ended hook to dispatch lifecycle recovery to the right manager.
14
14
  */
15
- /* eslint-disable @typescript-eslint/max-params -- Reason: Factory function requires workflowType, workspaceDir, logger, and subagent */
15
+
16
16
  function createWorkflowManagerForType(
17
17
  workflowType: string,
18
18
  workspaceDir: string,
@@ -23,7 +23,7 @@ function createWorkflowManagerForType(
23
23
  info: (m: string) => logger.info(String(m)),
24
24
  warn: (m: string) => logger.warn(String(m)),
25
25
  error: (m: string) => logger.error(String(m)),
26
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- debug logger no-op
26
+
27
27
  debug: () => {},
28
28
  } as unknown as PluginLogger;
29
29
 
@@ -40,12 +40,12 @@ export interface ThinkingCheckpointConfig {
40
40
  * @param logger - Optional logger for info messages
41
41
  * @returns Block result if thinking required, undefined otherwise
42
42
  */
43
- /* eslint-disable @typescript-eslint/max-params -- Reason: Checkpoint function requires event, config, sessionId, and logger */
43
+
44
44
  export function checkThinkingCheckpoint(
45
45
  event: PluginHookBeforeToolCallEvent,
46
46
  config: ThinkingCheckpointConfig,
47
47
  sessionId: string | undefined,
48
- /* eslint-disable no-unused-vars -- Reason: callback parameter in type annotation */
48
+
49
49
  logger?: { info?: (message: string) => void }
50
50
  ): PluginHookBeforeToolCallResult | undefined {
51
51
  const enabled = config.enabled ?? false;
@@ -228,7 +228,7 @@ export function handleBeforeMessageWrite(
228
228
  // 提取文本内容
229
229
  let content = '';
230
230
  if (typeof msg.content === 'string') {
231
- /* eslint-disable @typescript-eslint/prefer-destructuring */
231
+
232
232
  // Reason: msg.content is string | ContentPart[]; destructuring would require renaming in the else branch
233
233
  content = msg.content;
234
234
  } else if (Array.isArray(msg.content)) {
@@ -9,6 +9,7 @@ import { TrajectoryRegistry } from '../core/trajectory.js';
9
9
  import { getCentralDatabase } from '../service/central-database.js';
10
10
  import { CentralOverviewService } from '../service/central-overview-service.js';
11
11
  import { CentralHealthService } from '../service/central-health-service.js';
12
+ import { resolveRequiredWorkspaceDir } from '../core/workspace-dir-service.js';
12
13
 
13
14
  const ROUTE_PREFIX = '/plugins/principles';
14
15
  const API_PREFIX = `${ROUTE_PREFIX}/api`;
@@ -90,11 +91,11 @@ function serveFile(res: ServerResponse, filePath: string): boolean {
90
91
  }
91
92
 
92
93
  function createService(api: OpenClawPluginApi): ControlUiQueryService {
93
- const workspaceDir = api.resolvePath('.');
94
+ const workspaceDir = resolveRequiredWorkspaceDir(api, { agentId: 'main' }, { source: 'principles_console.control_ui', fallbackAgentId: 'main' });
94
95
  return new ControlUiQueryService(workspaceDir);
95
96
  }
96
97
 
97
- /* eslint-disable @typescript-eslint/max-params -- Reason: Route handler requires api, pathname, req, and res */
98
+
98
99
  function handleApiRoute(
99
100
  api: OpenClawPluginApi,
100
101
  pathname: string,
@@ -102,13 +103,20 @@ function handleApiRoute(
102
103
  res: ServerResponse,
103
104
  ): Promise<boolean> | boolean {
104
105
  // Check authentication for API routes
105
- /* eslint-disable @typescript-eslint/no-use-before-define -- Reason: validateGatewayAuth is defined later in the file */
106
+
106
107
  if (!validateGatewayAuth(req)) {
107
108
  json(res, 401, { error: 'unauthorized', message: 'Valid Gateway token required.' });
108
109
  return true;
109
110
  }
110
111
 
111
- const service = createService(api);
112
+ let service: ControlUiQueryService;
113
+ try {
114
+ service = createService(api);
115
+ } catch (error) {
116
+ api.logger.warn(`[PD:ControlUI] Failed to resolve workspace for ${pathname}: ${String(error)}`);
117
+ json(res, 500, { error: 'internal_error', message: String(error) });
118
+ return true;
119
+ }
112
120
  const url = new URL(req.url || pathname, 'http://127.0.0.1');
113
121
  const method = (req.method || 'GET').toUpperCase();
114
122
 
@@ -338,7 +346,7 @@ function handleApiRoute(
338
346
 
339
347
  // === Evolution API ===
340
348
  const evolutionService = () => {
341
- const workspaceDir = api.resolvePath('.');
349
+ const workspaceDir = resolveRequiredWorkspaceDir(api, { agentId: 'main' }, { source: 'principles_console.evolution', fallbackAgentId: 'main' });
342
350
  const trajectory = TrajectoryRegistry.get(workspaceDir);
343
351
  return getEvolutionQueryService(trajectory);
344
352
  };
@@ -398,7 +406,7 @@ function handleApiRoute(
398
406
 
399
407
  // === Health Query API (v1.1 new endpoints) ===
400
408
  const healthService = () => {
401
- const workspaceDir = api.resolvePath('.');
409
+ const workspaceDir = resolveRequiredWorkspaceDir(api, { agentId: 'main' }, { source: 'principles_console.health', fallbackAgentId: 'main' });
402
410
  return new HealthQueryService(workspaceDir);
403
411
  };
404
412
 
@@ -37,6 +37,10 @@ export const commandDescriptions: Record<string, Record<SupportedLanguage, strin
37
37
  zh: '管理思维模型 [status|propose|audit]',
38
38
  en: 'Manage Thinking OS [status|propose|audit]'
39
39
  },
40
+ 'pd-reflect': {
41
+ zh: '手动触发 Nocturnal 睡眠反射(跳过 idle 检测)',
42
+ en: 'Manually trigger Nocturnal sleep reflection (bypass idle check)'
43
+ },
40
44
  'pd-daily': {
41
45
  zh: '配置并发送进化日报',
42
46
  en: 'Configure and send daily evolution report'