principles-disciple 1.34.1 → 1.35.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.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Workspace Directory Resolution Utilities
3
+ *
4
+ * Shared helpers for resolving workspace directories across commands and hooks.
5
+ */
6
+
7
+ import type { OpenClawPluginApi } from '../openclaw-sdk.js';
8
+ import { validateWorkspaceDir, type WorkspaceResolutionContext } from '../core/workspace-dir-validation.js';
9
+ import { resolveWorkspaceDir } from '../core/workspace-dir-service.js';
10
+ import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
11
+
12
+ /**
13
+ * Resolve workspace directory for command execution.
14
+ *
15
+ * Chain: ctx.workspaceDir → resolveWorkspaceDirFromApi (official OpenClaw API + env vars)
16
+ *
17
+ * CRITICAL: Throws if workspaceDir cannot be resolved. Silent failures are dangerous
18
+ * because commands might operate on the wrong directory.
19
+ */
20
+ export function resolveCommandWorkspaceDir(
21
+ api: OpenClawPluginApi,
22
+ ctx: { workspaceDir?: string },
23
+ ): string {
24
+ // 1. Direct from command context (most reliable — set by OpenClaw for current session)
25
+ if (ctx.workspaceDir) {
26
+ const issue = validateWorkspaceDir(ctx.workspaceDir);
27
+ if (!issue) return ctx.workspaceDir;
28
+ api.logger.error(`[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
29
+ }
30
+
31
+ // 2. Official OpenClaw API → env vars → config file
32
+ const resolved = resolveWorkspaceDirFromApi(api);
33
+ if (resolved) return resolved;
34
+
35
+ // CRITICAL FAILURE: Cannot determine workspace directory
36
+ const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
37
+ `ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
38
+ `Commands will NOT execute to prevent data corruption.`;
39
+ api.logger.error(errorMsg);
40
+
41
+ throw new Error(errorMsg);
42
+ }
43
+
44
+ /**
45
+ * Resolve workspace directory for tool hook execution (safe version).
46
+ * Returns undefined instead of throwing if resolution fails.
47
+ */
48
+ export function resolveToolHookWorkspaceDirSafe(
49
+ ctx: WorkspaceResolutionContext,
50
+ api: OpenClawPluginApi,
51
+ source: string,
52
+ ): string | undefined {
53
+ return resolveWorkspaceDir(api, ctx, { source });
54
+ }
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import * as os from 'os';
3
- import { validateWorkspaceDir, resolveValidWorkspaceDir, logWorkspaceDirHealth } from '../../src/core/workspace-dir-validation.js';
3
+ import { validateWorkspaceDir, resolveValidWorkspaceDir, logWorkspaceDirHealth } from '../../src/core/workspace-dir-service.js';
4
4
 
5
5
  const homeDir = os.homedir();
6
6
 
@@ -65,7 +65,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
65
65
 
66
66
  describe('Scenario 2: ctx.workspaceDir is undefined (current OpenClaw behavior)', () => {
67
67
  it('should fallback to agentId resolution', async () => {
68
- const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-validation.js');
68
+ const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
69
69
 
70
70
  const mockApi = createMockApi(testWorkspaceDir);
71
71
  const ctx = {
@@ -80,7 +80,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
80
80
  });
81
81
 
82
82
  it('should refuse to guess a workspace when agentId is also undefined', async () => {
83
- const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-validation.js');
83
+ const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
84
84
 
85
85
  const mockApi = createMockApi(testWorkspaceDir);
86
86
  const ctx = {
@@ -131,7 +131,7 @@ describe('E2E: Tool Hooks workspaceDir Resolution', () => {
131
131
 
132
132
  describe('Scenario 4: Invalid workspace candidates are rejected', () => {
133
133
  it('should return undefined when all workspace resolution candidates are invalid', async () => {
134
- const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-validation.js');
134
+ const { resolveValidWorkspaceDir } = await import('../../src/core/workspace-dir-service.js');
135
135
 
136
136
  const mockApi = createMockApi(os.homedir());
137
137
  mockApi.runtime.agent.resolveAgentWorkspaceDir.mockReturnValue(os.homedir());
package/vitest.config.ts CHANGED
@@ -1,12 +1,41 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
 
3
+ /**
4
+ * Vitest configuration with test layering
5
+ *
6
+ * LAYERS:
7
+ * - unit: Mock-based tests, no real DB (fast, parallel)
8
+ * - integration: Tests using real SQLite DB (requires threads pool)
9
+ *
10
+ * USAGE:
11
+ * - npm test → run all tests
12
+ * - npm run test:unit → run unit tests only (fast)
13
+ * - npm run test:integration → run integration tests only
14
+ *
15
+ * WHY threads pool?
16
+ * better-sqlite3 native handles don't clean up properly in fork subprocesses,
17
+ * causing teardown hangs. Threads pool handles this correctly.
18
+ */
19
+
20
+ // Integration tests: use real SQLite database
21
+ const integrationTests = [
22
+ 'tests/core/control-ui-db.test.ts',
23
+ 'tests/core/evolution-logger.test.ts',
24
+ 'tests/core/nocturnal-e2e.test.ts',
25
+ 'tests/core/nocturnal-trajectory-extractor.test.ts',
26
+ 'tests/core/replay-engine.test.ts',
27
+ 'tests/core/trajectory.test.ts',
28
+ 'tests/integration/**/*.test.ts',
29
+ 'tests/integration/**/*.test.tsx',
30
+ 'tests/service/nocturnal-service-code-candidate.test.ts',
31
+ 'tests/service/nocturnal-target-selector.test.ts',
32
+ ];
33
+
3
34
  export default defineConfig({
4
35
  test: {
5
36
  environment: 'node',
6
37
  include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
7
- // vitest 4: pool: 'forks' 默认启用隔离,每个测试文件在独立进程中运行
8
- pool: 'forks',
9
- // 确保测试完成后进程能正常退出
38
+ pool: 'threads',
10
39
  teardownTimeout: 15000,
11
40
  coverage: {
12
41
  provider: 'v8',
@@ -18,6 +47,24 @@ export default defineConfig({
18
47
  branches: 60,
19
48
  statements: 70,
20
49
  },
21
- }
22
- }
23
- });
50
+ },
51
+ // Workspace projects for layered testing
52
+ projects: [
53
+ {
54
+ test: {
55
+ name: 'unit',
56
+ include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
57
+ exclude: integrationTests,
58
+ pool: 'threads',
59
+ },
60
+ },
61
+ {
62
+ test: {
63
+ name: 'integration',
64
+ include: integrationTests,
65
+ pool: 'threads',
66
+ },
67
+ },
68
+ ],
69
+ },
70
+ });