@swarmclawai/swarmclaw 1.9.37 → 1.9.39

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 (32) hide show
  1. package/README.md +43 -1
  2. package/package.json +2 -2
  3. package/src/app/api/chats/[id]/context-status/route.ts +2 -0
  4. package/src/app/api/chats/context-status-route.test.ts +59 -0
  5. package/src/app/api/setup/check-provider/route.test.ts +12 -0
  6. package/src/app/api/setup/check-provider/route.ts +6 -0
  7. package/src/lib/providers/index.ts +23 -0
  8. package/src/lib/server/autonomy/supervisor-reflection.test.ts +10 -1
  9. package/src/lib/server/connectors/outbox.ts +22 -2
  10. package/src/lib/server/context-manager.ts +4 -0
  11. package/src/lib/server/openrouter-model-context.test.ts +205 -0
  12. package/src/lib/server/openrouter-model-context.ts +169 -0
  13. package/src/lib/server/provider-health.ts +1 -0
  14. package/src/lib/server/runtime/queue/core.ts +160 -18
  15. package/src/lib/server/runtime/queue/orphan-recovery.test.ts +49 -0
  16. package/src/lib/server/runtime/queue/orphan-recovery.ts +32 -0
  17. package/src/lib/server/runtime/scheduled-run-preflight.test.ts +73 -0
  18. package/src/lib/server/runtime/scheduled-run-preflight.ts +83 -0
  19. package/src/lib/server/schedules/schedule-lifecycle.test.ts +44 -0
  20. package/src/lib/server/schedules/schedule-lifecycle.ts +27 -0
  21. package/src/lib/server/storage-normalization.ts +13 -0
  22. package/src/lib/server/tasks/task-followups.test.ts +124 -41
  23. package/src/lib/server/tasks/task-followups.ts +28 -3
  24. package/src/lib/server/tasks/task-lifecycle.test.ts +25 -0
  25. package/src/lib/server/tasks/task-lifecycle.ts +6 -0
  26. package/src/lib/server/tasks/task-result.test.ts +25 -1
  27. package/src/lib/server/tasks/task-result.ts +22 -0
  28. package/src/lib/server/workspace-paths.test.ts +72 -0
  29. package/src/lib/server/workspace-paths.ts +60 -0
  30. package/src/lib/setup-defaults.test.ts +10 -1
  31. package/src/lib/setup-defaults.ts +20 -0
  32. package/src/types/provider.ts +1 -1
@@ -0,0 +1,60 @@
1
+ import path from 'path'
2
+ import os from 'os'
3
+ import { DATA_DIR, WORKSPACE_DIR } from './data-dir'
4
+
5
+ export interface NormalizeLegacyWorkspacePathOptions {
6
+ workspaceRoot?: string
7
+ taskId?: string | null
8
+ }
9
+
10
+ function splitSegments(p: string): string[] {
11
+ return path.normalize(p).split(path.sep).filter(Boolean)
12
+ }
13
+
14
+ /**
15
+ * Remaps a path persisted under a previous workspace root (e.g.
16
+ * /root/.swarmclaw/workspace/tasks/<id>) onto the current WORKSPACE_DIR.
17
+ *
18
+ * Only remaps when a safety signal identifies the path as a SwarmClaw-managed
19
+ * workspace location; intentional custom cwds pass through unchanged:
20
+ * - the prefix before a `workspace` segment contains a `.swarmclaw` segment
21
+ * - the tail after a `workspace` segment is `tasks/<taskId>` (or under it)
22
+ * - the prefix matches a known default workspace root that differs from the
23
+ * current one (`~/.swarmclaw/workspace`, `DATA_DIR/workspace`)
24
+ */
25
+ export function normalizeLegacyWorkspacePath(
26
+ raw: string | null | undefined,
27
+ options: NormalizeLegacyWorkspacePathOptions = {},
28
+ ): string {
29
+ const input = typeof raw === 'string' ? raw.trim() : ''
30
+ if (!input || !path.isAbsolute(input)) return input
31
+
32
+ const workspaceRoot = path.resolve(options.workspaceRoot ?? WORKSPACE_DIR)
33
+ const resolved = path.resolve(input)
34
+ const rel = path.relative(workspaceRoot, resolved)
35
+ if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) return input
36
+
37
+ const segments = splitSegments(resolved)
38
+ const taskId = typeof options.taskId === 'string' ? options.taskId.trim() : ''
39
+ const knownDefaultRoots = new Set(
40
+ [path.join(os.homedir(), '.swarmclaw', 'workspace'), path.join(DATA_DIR, 'workspace')]
41
+ .map((p) => path.resolve(p))
42
+ .filter((p) => p !== workspaceRoot),
43
+ )
44
+
45
+ for (let i = segments.length - 1; i >= 0; i--) {
46
+ if (segments[i] !== 'workspace') continue
47
+ const prefixSegments = segments.slice(0, i)
48
+ const tailSegments = segments.slice(i + 1)
49
+ const legacyRoot = tailSegments.length > 0
50
+ ? path.resolve(resolved, ...tailSegments.map(() => '..'))
51
+ : resolved
52
+ const hasSwarmclawMarker = prefixSegments.includes('.swarmclaw')
53
+ const matchesTaskTail = Boolean(taskId) && tailSegments[0] === 'tasks' && tailSegments[1] === taskId
54
+ const isKnownDefaultRoot = knownDefaultRoots.has(legacyRoot)
55
+ if (!hasSwarmclawMarker && !matchesTaskTail && !isKnownDefaultRoot) continue
56
+ return tailSegments.length > 0 ? path.join(workspaceRoot, ...tailSegments) : workspaceRoot
57
+ }
58
+
59
+ return input
60
+ }
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
3
  import { CLI_PROVIDER_METADATA } from './providers/cli-provider-metadata'
4
- import { DEFAULT_AGENTS, getDefaultModelForProvider } from './setup-defaults'
4
+ import { DEFAULT_AGENTS, SETUP_PROVIDERS, getDefaultModelForProvider } from './setup-defaults'
5
5
 
6
6
  // ---------------------------------------------------------------------------
7
7
  // OpenClaw default model is empty (not 'default')
@@ -33,6 +33,15 @@ test('getDefaultModelForProvider returns non-empty for openrouter', () => {
33
33
  assert.ok(model, 'openrouter model should be truthy')
34
34
  })
35
35
 
36
+ test('TokenMix has setup metadata and a default agent model', () => {
37
+ const provider = SETUP_PROVIDERS.find((candidate) => candidate.id === 'tokenmix')
38
+ assert.ok(provider, 'tokenmix should appear in setup providers')
39
+ assert.equal(provider.defaultEndpoint, 'https://api.tokenmix.ai/v1')
40
+ assert.equal(provider.supportsEndpoint, false)
41
+ assert.equal(provider.requiresKey, true)
42
+ assert.equal(getDefaultModelForProvider('tokenmix'), 'claude-sonnet-4-6')
43
+ })
44
+
36
45
  test('getDefaultModelForProvider returns non-empty for anthropic', () => {
37
46
  const model = getDefaultModelForProvider('anthropic')
38
47
  assert.ok(model, 'anthropic model should be truthy')
@@ -86,6 +86,19 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
86
86
  icon: 'R',
87
87
  modelLibraryUrl: 'https://openrouter.ai/models',
88
88
  },
89
+ {
90
+ id: 'tokenmix',
91
+ name: 'TokenMix',
92
+ description: 'One OpenAI-compatible API relay for Claude, OpenAI, Gemini, DeepSeek, Qwen, and other hosted models.',
93
+ requiresKey: true,
94
+ supportsEndpoint: false,
95
+ defaultEndpoint: 'https://api.tokenmix.ai/v1',
96
+ keyUrl: 'https://tokenmix.ai',
97
+ keyLabel: 'tokenmix.ai',
98
+ badge: 'Catalog',
99
+ icon: 'T',
100
+ modelLibraryUrl: 'https://tokenmix.ai/models',
101
+ },
89
102
  {
90
103
  id: 'openclaw',
91
104
  name: 'OpenClaw',
@@ -781,6 +794,13 @@ export const DEFAULT_AGENTS = {
781
794
  model: 'anthropic/claude-sonnet-4.6',
782
795
  tools: STARTER_AGENT_TOOLS,
783
796
  },
797
+ tokenmix: {
798
+ name: 'TokenMix Agent',
799
+ description: 'A helpful assistant powered through TokenMix.',
800
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
801
+ model: 'claude-sonnet-4-6',
802
+ tools: STARTER_AGENT_TOOLS,
803
+ },
784
804
  google: {
785
805
  name: 'Gemini',
786
806
  description: 'A helpful Gemini-powered assistant.',
@@ -1,4 +1,4 @@
1
- export type ProviderType = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'opencode-web' | 'gemini-cli' | 'copilot-cli' | 'droid-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose' | 'aider-cli' | 'amp-cli' | 'augment-cli' | 'adal-cli' | 'bob-cli' | 'cline-cli' | 'codebuddy-cli' | 'command-code-cli' | 'continue-cli' | 'cortex-cli' | 'crush-cli' | 'deepagents-cli' | 'firebender-cli' | 'iflow-cli' | 'junie-cli' | 'kilo-code-cli' | 'kimi-cli' | 'kode-cli' | 'mcpjam-cli' | 'mistral-vibe-cli' | 'mux-cli' | 'neovate-cli' | 'openhands-cli' | 'pochi-cli' | 'qoder-cli' | 'replit-cli' | 'roo-code-cli' | 'trae-cn-cli' | 'warp-cli' | 'windsurf-cli' | 'zencoder-cli' | 'openai' | 'openrouter' | 'ollama' | 'anthropic' | 'openclaw' | 'hermes' | 'lmstudio' | 'google' | 'deepseek' | 'groq' | 'together' | 'mistral' | 'xai' | 'fireworks' | 'nebius' | 'deepinfra'
1
+ export type ProviderType = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'opencode-web' | 'gemini-cli' | 'copilot-cli' | 'droid-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose' | 'aider-cli' | 'amp-cli' | 'augment-cli' | 'adal-cli' | 'bob-cli' | 'cline-cli' | 'codebuddy-cli' | 'command-code-cli' | 'continue-cli' | 'cortex-cli' | 'crush-cli' | 'deepagents-cli' | 'firebender-cli' | 'iflow-cli' | 'junie-cli' | 'kilo-code-cli' | 'kimi-cli' | 'kode-cli' | 'mcpjam-cli' | 'mistral-vibe-cli' | 'mux-cli' | 'neovate-cli' | 'openhands-cli' | 'pochi-cli' | 'qoder-cli' | 'replit-cli' | 'roo-code-cli' | 'trae-cn-cli' | 'warp-cli' | 'windsurf-cli' | 'zencoder-cli' | 'openai' | 'openrouter' | 'tokenmix' | 'ollama' | 'anthropic' | 'openclaw' | 'hermes' | 'lmstudio' | 'google' | 'deepseek' | 'groq' | 'together' | 'mistral' | 'xai' | 'fireworks' | 'nebius' | 'deepinfra'
2
2
  export type ProviderId = ProviderType | (string & {})
3
3
 
4
4
  export interface ProviderInfo {