@dotsetlabs/dotclaw 1.3.0 → 1.5.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.
- package/README.md +9 -0
- package/config-examples/runtime.json +106 -3
- package/container/agent-runner/package-lock.json +2 -2
- package/container/agent-runner/package.json +1 -1
- package/container/agent-runner/src/agent-config.ts +20 -2
- package/container/agent-runner/src/container-protocol.ts +11 -0
- package/container/agent-runner/src/index.ts +128 -11
- package/container/agent-runner/src/tools.ts +84 -5
- package/dist/agent-context.d.ts +5 -0
- package/dist/agent-context.d.ts.map +1 -1
- package/dist/agent-context.js +19 -8
- package/dist/agent-context.js.map +1 -1
- package/dist/agent-execution.d.ts +6 -0
- package/dist/agent-execution.d.ts.map +1 -1
- package/dist/agent-execution.js +61 -4
- package/dist/agent-execution.js.map +1 -1
- package/dist/background-job-classifier.d.ts +4 -0
- package/dist/background-job-classifier.d.ts.map +1 -1
- package/dist/background-job-classifier.js +36 -15
- package/dist/background-job-classifier.js.map +1 -1
- package/dist/background-jobs.d.ts.map +1 -1
- package/dist/background-jobs.js +81 -4
- package/dist/background-jobs.js.map +1 -1
- package/dist/cli.js +343 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -3
- package/dist/config.js.map +1 -1
- package/dist/container-protocol.d.ts +11 -0
- package/dist/container-protocol.d.ts.map +1 -1
- package/dist/container-runner.d.ts.map +1 -1
- package/dist/container-runner.js +9 -1
- package/dist/container-runner.js.map +1 -1
- package/dist/dashboard.d.ts +5 -0
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +58 -8
- package/dist/dashboard.js.map +1 -1
- package/dist/db.d.ts +11 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +36 -0
- package/dist/db.js.map +1 -1
- package/dist/index.js +300 -37
- package/dist/index.js.map +1 -1
- package/dist/json-helpers.d.ts +6 -0
- package/dist/json-helpers.d.ts.map +1 -0
- package/dist/json-helpers.js +17 -0
- package/dist/json-helpers.js.map +1 -0
- package/dist/logger.d.ts +0 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +35 -1
- package/dist/metrics.js.map +1 -1
- package/dist/planner-probe.d.ts +14 -0
- package/dist/planner-probe.d.ts.map +1 -0
- package/dist/planner-probe.js +97 -0
- package/dist/planner-probe.js.map +1 -0
- package/dist/progress.d.ts +27 -0
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +151 -0
- package/dist/progress.js.map +1 -1
- package/dist/request-router.d.ts +34 -0
- package/dist/request-router.d.ts.map +1 -0
- package/dist/request-router.js +148 -0
- package/dist/request-router.js.map +1 -0
- package/dist/runtime-config.d.ts +67 -0
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +177 -14
- package/dist/runtime-config.js.map +1 -1
- package/dist/task-scheduler.d.ts.map +1 -1
- package/dist/task-scheduler.js +56 -9
- package/dist/task-scheduler.js.map +1 -1
- package/dist/trace-writer.d.ts +1 -0
- package/dist/trace-writer.d.ts.map +1 -1
- package/dist/trace-writer.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ After installation, use the `dotclaw` CLI:
|
|
|
45
45
|
```bash
|
|
46
46
|
dotclaw setup # Full setup (init + configure + build + install service)
|
|
47
47
|
dotclaw configure # Re-configure API keys and model
|
|
48
|
+
dotclaw add-instance # Create and start an isolated instance
|
|
49
|
+
dotclaw instances # List discovered instances
|
|
48
50
|
dotclaw build # Build the Docker container image
|
|
49
51
|
dotclaw start # Start the service
|
|
50
52
|
dotclaw stop # Stop the service
|
|
@@ -55,6 +57,13 @@ dotclaw doctor # Run diagnostics
|
|
|
55
57
|
dotclaw register # Register a new Telegram chat
|
|
56
58
|
```
|
|
57
59
|
|
|
60
|
+
Instance flags:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dotclaw status --id dev # Run against a specific instance (~/.dotclaw-dev)
|
|
64
|
+
dotclaw restart --all # Restart all instances
|
|
65
|
+
```
|
|
66
|
+
|
|
58
67
|
## Configuration
|
|
59
68
|
|
|
60
69
|
All configuration and data is stored in `~/.dotclaw/`:
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"host": {
|
|
3
3
|
"logLevel": "info",
|
|
4
4
|
"container": {
|
|
5
|
-
"mode": "daemon"
|
|
5
|
+
"mode": "daemon",
|
|
6
|
+
"instanceId": ""
|
|
6
7
|
},
|
|
7
8
|
"metrics": {
|
|
8
9
|
"port": 3001,
|
|
@@ -20,9 +21,15 @@
|
|
|
20
21
|
"enabled": true,
|
|
21
22
|
"maxConcurrent": 2,
|
|
22
23
|
"maxRuntimeMs": 2400000,
|
|
24
|
+
"progress": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"startDelayMs": 30000,
|
|
27
|
+
"intervalMs": 120000,
|
|
28
|
+
"maxUpdates": 3
|
|
29
|
+
},
|
|
23
30
|
"autoSpawn": {
|
|
24
31
|
"enabled": true,
|
|
25
|
-
"foregroundTimeoutMs":
|
|
32
|
+
"foregroundTimeoutMs": 90000,
|
|
26
33
|
"onTimeout": true,
|
|
27
34
|
"onToolLimit": true,
|
|
28
35
|
"classifier": {
|
|
@@ -31,7 +38,89 @@
|
|
|
31
38
|
"timeoutMs": 3000,
|
|
32
39
|
"maxOutputTokens": 32,
|
|
33
40
|
"temperature": 0,
|
|
34
|
-
"confidenceThreshold": 0.6
|
|
41
|
+
"confidenceThreshold": 0.6,
|
|
42
|
+
"adaptive": {
|
|
43
|
+
"enabled": true,
|
|
44
|
+
"minThreshold": 0.55,
|
|
45
|
+
"maxThreshold": 0.65,
|
|
46
|
+
"queueDepthLow": 0,
|
|
47
|
+
"queueDepthHigh": 4
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"routing": {
|
|
53
|
+
"enabled": true,
|
|
54
|
+
"maxFastChars": 200,
|
|
55
|
+
"maxStandardChars": 1200,
|
|
56
|
+
"backgroundMinChars": 2000,
|
|
57
|
+
"fastKeywords": ["hi", "hello", "hey", "who are you", "what can you do"],
|
|
58
|
+
"deepKeywords": ["research", "analysis", "report", "dashboard", "refactor", "architecture", "design"],
|
|
59
|
+
"backgroundKeywords": ["background", "long-running", "research", "dashboard", "refactor", "report"],
|
|
60
|
+
"classifierFallback": {
|
|
61
|
+
"enabled": true,
|
|
62
|
+
"minChars": 600
|
|
63
|
+
},
|
|
64
|
+
"plannerProbe": {
|
|
65
|
+
"enabled": true,
|
|
66
|
+
"model": "openai/gpt-5-nano",
|
|
67
|
+
"timeoutMs": 3000,
|
|
68
|
+
"maxOutputTokens": 120,
|
|
69
|
+
"temperature": 0,
|
|
70
|
+
"minChars": 700,
|
|
71
|
+
"minSteps": 4,
|
|
72
|
+
"minTools": 3
|
|
73
|
+
},
|
|
74
|
+
"profiles": {
|
|
75
|
+
"fast": {
|
|
76
|
+
"model": "openai/gpt-5-nano",
|
|
77
|
+
"maxOutputTokens": 256,
|
|
78
|
+
"maxToolSteps": 6,
|
|
79
|
+
"recallMaxResults": 0,
|
|
80
|
+
"recallMaxTokens": 0,
|
|
81
|
+
"enablePlanner": false,
|
|
82
|
+
"enableValidation": false,
|
|
83
|
+
"responseValidationMaxRetries": 0,
|
|
84
|
+
"enableMemoryRecall": false,
|
|
85
|
+
"enableMemoryExtraction": false,
|
|
86
|
+
"progress": { "enabled": false }
|
|
87
|
+
},
|
|
88
|
+
"standard": {
|
|
89
|
+
"model": "openai/gpt-5-mini",
|
|
90
|
+
"maxOutputTokens": 768,
|
|
91
|
+
"maxToolSteps": 16,
|
|
92
|
+
"recallMaxResults": 6,
|
|
93
|
+
"recallMaxTokens": 1500,
|
|
94
|
+
"enablePlanner": true,
|
|
95
|
+
"enableValidation": true,
|
|
96
|
+
"responseValidationMaxRetries": 1,
|
|
97
|
+
"enableMemoryRecall": true,
|
|
98
|
+
"enableMemoryExtraction": true
|
|
99
|
+
},
|
|
100
|
+
"deep": {
|
|
101
|
+
"model": "moonshotai/kimi-k2.5",
|
|
102
|
+
"maxOutputTokens": 1536,
|
|
103
|
+
"maxToolSteps": 32,
|
|
104
|
+
"recallMaxResults": 12,
|
|
105
|
+
"recallMaxTokens": 2500,
|
|
106
|
+
"enablePlanner": true,
|
|
107
|
+
"enableValidation": true,
|
|
108
|
+
"responseValidationMaxRetries": 2,
|
|
109
|
+
"enableMemoryRecall": true,
|
|
110
|
+
"enableMemoryExtraction": true
|
|
111
|
+
},
|
|
112
|
+
"background": {
|
|
113
|
+
"model": "moonshotai/kimi-k2.5",
|
|
114
|
+
"maxOutputTokens": 2048,
|
|
115
|
+
"maxToolSteps": 64,
|
|
116
|
+
"recallMaxResults": 16,
|
|
117
|
+
"recallMaxTokens": 4000,
|
|
118
|
+
"enablePlanner": true,
|
|
119
|
+
"enableValidation": true,
|
|
120
|
+
"responseValidationMaxRetries": 2,
|
|
121
|
+
"enableMemoryRecall": true,
|
|
122
|
+
"enableMemoryExtraction": true,
|
|
123
|
+
"progress": { "enabled": true, "initialMs": 15000, "intervalMs": 60000, "maxUpdates": 3 }
|
|
35
124
|
}
|
|
36
125
|
}
|
|
37
126
|
}
|
|
@@ -44,6 +133,20 @@
|
|
|
44
133
|
"planner": {
|
|
45
134
|
"enabled": true,
|
|
46
135
|
"mode": "auto"
|
|
136
|
+
},
|
|
137
|
+
"responseValidation": {
|
|
138
|
+
"enabled": true,
|
|
139
|
+
"minPromptTokens": 400,
|
|
140
|
+
"minResponseTokens": 160
|
|
141
|
+
},
|
|
142
|
+
"tools": {
|
|
143
|
+
"progress": {
|
|
144
|
+
"enabled": true,
|
|
145
|
+
"minIntervalMs": 30000,
|
|
146
|
+
"notifyTools": ["WebSearch", "WebFetch", "Bash", "GitClone", "NpmInstall"],
|
|
147
|
+
"notifyOnStart": true,
|
|
148
|
+
"notifyOnError": true
|
|
149
|
+
}
|
|
47
150
|
}
|
|
48
151
|
}
|
|
49
152
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotclaw-agent-runner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "dotclaw-agent-runner",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.5.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@openrouter/sdk": "^0.3.0",
|
|
12
12
|
"cron-parser": "^5.0.0",
|
|
@@ -60,6 +60,8 @@ export type AgentRuntimeConfig = {
|
|
|
60
60
|
temperature: number;
|
|
61
61
|
maxRetries: number;
|
|
62
62
|
allowToolCalls: boolean;
|
|
63
|
+
minPromptTokens: number;
|
|
64
|
+
minResponseTokens: number;
|
|
63
65
|
};
|
|
64
66
|
tools: {
|
|
65
67
|
maxToolSteps: number;
|
|
@@ -87,6 +89,13 @@ export type AgentRuntimeConfig = {
|
|
|
87
89
|
maxBytes: number;
|
|
88
90
|
httpTimeoutMs: number;
|
|
89
91
|
};
|
|
92
|
+
progress: {
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
minIntervalMs: number;
|
|
95
|
+
notifyTools: string[];
|
|
96
|
+
notifyOnStart: boolean;
|
|
97
|
+
notifyOnError: boolean;
|
|
98
|
+
};
|
|
90
99
|
toolSummary: {
|
|
91
100
|
enabled: boolean;
|
|
92
101
|
maxBytes: number;
|
|
@@ -160,7 +169,7 @@ const DEFAULT_AGENT_CONFIG: AgentRuntimeConfig['agent'] = {
|
|
|
160
169
|
planner: {
|
|
161
170
|
enabled: true,
|
|
162
171
|
mode: 'auto',
|
|
163
|
-
minTokens:
|
|
172
|
+
minTokens: 800,
|
|
164
173
|
triggerRegex: '(plan|steps|roadmap|research|design|architecture|spec|strategy)',
|
|
165
174
|
maxOutputTokens: 200,
|
|
166
175
|
temperature: 0.2
|
|
@@ -170,7 +179,9 @@ const DEFAULT_AGENT_CONFIG: AgentRuntimeConfig['agent'] = {
|
|
|
170
179
|
maxOutputTokens: 120,
|
|
171
180
|
temperature: 0,
|
|
172
181
|
maxRetries: 1,
|
|
173
|
-
allowToolCalls: false
|
|
182
|
+
allowToolCalls: false,
|
|
183
|
+
minPromptTokens: 400,
|
|
184
|
+
minResponseTokens: 160
|
|
174
185
|
},
|
|
175
186
|
tools: {
|
|
176
187
|
maxToolSteps: 24,
|
|
@@ -198,6 +209,13 @@ const DEFAULT_AGENT_CONFIG: AgentRuntimeConfig['agent'] = {
|
|
|
198
209
|
maxBytes: 800_000,
|
|
199
210
|
httpTimeoutMs: 20_000
|
|
200
211
|
},
|
|
212
|
+
progress: {
|
|
213
|
+
enabled: true,
|
|
214
|
+
minIntervalMs: 30_000,
|
|
215
|
+
notifyTools: ['WebSearch', 'WebFetch', 'Bash', 'GitClone', 'NpmInstall'],
|
|
216
|
+
notifyOnStart: true,
|
|
217
|
+
notifyOnError: true
|
|
218
|
+
},
|
|
201
219
|
toolSummary: {
|
|
202
220
|
enabled: true,
|
|
203
221
|
maxBytes: 60_000,
|
|
@@ -40,6 +40,11 @@ export interface ContainerInput {
|
|
|
40
40
|
modelContextTokens?: number;
|
|
41
41
|
modelMaxOutputTokens?: number;
|
|
42
42
|
modelTemperature?: number;
|
|
43
|
+
timezone?: string;
|
|
44
|
+
disablePlanner?: boolean;
|
|
45
|
+
disableResponseValidation?: boolean;
|
|
46
|
+
responseValidationMaxRetries?: number;
|
|
47
|
+
disableMemoryExtraction?: boolean;
|
|
43
48
|
streaming?: {
|
|
44
49
|
enabled?: boolean;
|
|
45
50
|
draftId?: number;
|
|
@@ -63,6 +68,12 @@ export interface ContainerOutput {
|
|
|
63
68
|
session_recall_count?: number;
|
|
64
69
|
memory_items_upserted?: number;
|
|
65
70
|
memory_items_extracted?: number;
|
|
71
|
+
timings?: {
|
|
72
|
+
planner_ms?: number;
|
|
73
|
+
response_validation_ms?: number;
|
|
74
|
+
memory_extraction_ms?: number;
|
|
75
|
+
tool_ms?: number;
|
|
76
|
+
};
|
|
66
77
|
tool_calls?: Array<{
|
|
67
78
|
name: string;
|
|
68
79
|
args?: unknown;
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
} from './memory.js';
|
|
29
29
|
import { loadPromptPackWithCanary, formatTaskExtractionPack, formatResponseQualityPack, formatToolCallingPack, formatToolOutcomePack, formatMemoryPolicyPack, formatMemoryRecallPack, PromptPack } from './prompt-packs.js';
|
|
30
30
|
|
|
31
|
+
type OpenRouterResult = ReturnType<OpenRouter['callModel']>;
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
const SESSION_ROOT = '/workspace/session';
|
|
33
35
|
const GROUP_DIR = '/workspace/group';
|
|
@@ -69,6 +71,89 @@ function log(message: string): void {
|
|
|
69
71
|
console.error(`[agent-runner] ${message}`);
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
function coerceTextFromContent(content: unknown): string {
|
|
75
|
+
if (!content) return '';
|
|
76
|
+
if (typeof content === 'string') return content;
|
|
77
|
+
if (Array.isArray(content)) {
|
|
78
|
+
return content.map(part => {
|
|
79
|
+
if (!part) return '';
|
|
80
|
+
if (typeof part === 'string') return part;
|
|
81
|
+
if (typeof part === 'object') {
|
|
82
|
+
const record = part as { text?: unknown; content?: unknown; value?: unknown };
|
|
83
|
+
if (typeof record.text === 'string') return record.text;
|
|
84
|
+
if (typeof record.content === 'string') return record.content;
|
|
85
|
+
if (typeof record.value === 'string') return record.value;
|
|
86
|
+
}
|
|
87
|
+
return '';
|
|
88
|
+
}).join('');
|
|
89
|
+
}
|
|
90
|
+
if (typeof content === 'object') {
|
|
91
|
+
const record = content as { text?: unknown; content?: unknown; value?: unknown };
|
|
92
|
+
if (typeof record.text === 'string') return record.text;
|
|
93
|
+
if (typeof record.content === 'string') return record.content;
|
|
94
|
+
if (typeof record.value === 'string') return record.value;
|
|
95
|
+
}
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function extractTextFallbackFromResponse(response: unknown): string {
|
|
100
|
+
if (!response || typeof response !== 'object') return '';
|
|
101
|
+
const record = response as {
|
|
102
|
+
outputText?: unknown;
|
|
103
|
+
output_text?: unknown;
|
|
104
|
+
output?: unknown;
|
|
105
|
+
choices?: unknown;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (typeof record.outputText === 'string' && record.outputText.trim()) {
|
|
109
|
+
return record.outputText;
|
|
110
|
+
}
|
|
111
|
+
if (typeof record.output_text === 'string' && record.output_text.trim()) {
|
|
112
|
+
return record.output_text;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(record.output)) {
|
|
116
|
+
const messageItem = record.output.find(item => !!item && typeof item === 'object' && (item as { type?: unknown }).type === 'message');
|
|
117
|
+
if (messageItem && typeof messageItem === 'object') {
|
|
118
|
+
const content = (messageItem as { content?: unknown }).content;
|
|
119
|
+
const text = coerceTextFromContent(content);
|
|
120
|
+
if (text.trim()) return text;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (Array.isArray(record.choices) && record.choices.length > 0) {
|
|
125
|
+
const choice = record.choices[0] as { message?: { content?: unknown }; text?: unknown } | null | undefined;
|
|
126
|
+
if (choice?.message) {
|
|
127
|
+
const text = coerceTextFromContent(choice.message.content);
|
|
128
|
+
if (text.trim()) return text;
|
|
129
|
+
}
|
|
130
|
+
if (typeof choice?.text === 'string' && choice.text.trim()) {
|
|
131
|
+
return choice.text;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function getTextWithFallback(result: OpenRouterResult, context: string): Promise<string> {
|
|
139
|
+
const text = await result.getText();
|
|
140
|
+
if (text && text.trim()) {
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const response = await result.getResponse();
|
|
145
|
+
const fallbackText = extractTextFallbackFromResponse(response);
|
|
146
|
+
if (fallbackText && fallbackText.trim()) {
|
|
147
|
+
log(`Recovered empty response text from payload (${context})`);
|
|
148
|
+
return fallbackText;
|
|
149
|
+
}
|
|
150
|
+
log(`Model returned empty response and fallback extraction failed (${context})`);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log(`Failed to recover empty response text (${context}): ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
+
}
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
|
|
72
157
|
function writeOutput(output: ContainerOutput): void {
|
|
73
158
|
console.log(OUTPUT_START_MARKER);
|
|
74
159
|
console.log(JSON.stringify(output));
|
|
@@ -318,6 +403,7 @@ function buildSystemInstructions(params: {
|
|
|
318
403
|
taskId?: string;
|
|
319
404
|
isBackgroundJob: boolean;
|
|
320
405
|
jobId?: string;
|
|
406
|
+
timezone?: string;
|
|
321
407
|
planBlock?: string;
|
|
322
408
|
taskExtractionPack?: PromptPack | null;
|
|
323
409
|
responseQualityPack?: PromptPack | null;
|
|
@@ -425,6 +511,10 @@ function buildSystemInstructions(params: {
|
|
|
425
511
|
? `Behavior overrides:\n${JSON.stringify(params.behaviorConfig, null, 2)}`
|
|
426
512
|
: '';
|
|
427
513
|
|
|
514
|
+
const timezoneNote = params.timezone
|
|
515
|
+
? `Timezone: ${params.timezone}. Use this timezone when interpreting or presenting timestamps unless the user specifies another.`
|
|
516
|
+
: '';
|
|
517
|
+
|
|
428
518
|
const scheduledNote = params.isScheduledTask
|
|
429
519
|
? `You are running as a scheduled task${params.taskId ? ` (task id: ${params.taskId})` : ''}. If you need to communicate, use \`mcp__dotclaw__send_message\`.`
|
|
430
520
|
: '';
|
|
@@ -432,7 +522,7 @@ function buildSystemInstructions(params: {
|
|
|
432
522
|
? 'You are running in the background for a user request. Focus on completing the task and return a complete response without asking follow-up questions unless strictly necessary.'
|
|
433
523
|
: '';
|
|
434
524
|
const jobNote = params.isBackgroundJob
|
|
435
|
-
? `You are running as a background job${params.jobId ? ` (job id: ${params.jobId})` : ''}. Return a complete result.
|
|
525
|
+
? `You are running as a background job${params.jobId ? ` (job id: ${params.jobId})` : ''}. Return a complete result. If the task will take more than a few minutes, send periodic \`mcp__dotclaw__job_update\` messages with milestones or intermediate findings (roughly every ~5 minutes or after major steps). Prefer writing large outputs to the job artifacts directory.`
|
|
436
526
|
: '';
|
|
437
527
|
const jobArtifactsNote = params.isBackgroundJob && params.jobId
|
|
438
528
|
? `Job artifacts directory: /workspace/group/jobs/${params.jobId}`
|
|
@@ -496,6 +586,7 @@ function buildSystemInstructions(params: {
|
|
|
496
586
|
browserAutomation,
|
|
497
587
|
groupNotes,
|
|
498
588
|
globalNotes,
|
|
589
|
+
timezoneNote,
|
|
499
590
|
params.planBlock || '',
|
|
500
591
|
toolCallingBlock,
|
|
501
592
|
toolOutcomeBlock,
|
|
@@ -615,7 +706,7 @@ async function updateMemorySummary(params: {
|
|
|
615
706
|
maxOutputTokens: params.maxOutputTokens,
|
|
616
707
|
temperature: 0.1
|
|
617
708
|
});
|
|
618
|
-
const text = await result
|
|
709
|
+
const text = await getTextWithFallback(result, 'summary');
|
|
619
710
|
return parseSummaryResponse(text);
|
|
620
711
|
}
|
|
621
712
|
|
|
@@ -756,7 +847,7 @@ async function validateResponseQuality(params: {
|
|
|
756
847
|
maxOutputTokens: params.maxOutputTokens,
|
|
757
848
|
temperature: params.temperature
|
|
758
849
|
});
|
|
759
|
-
const text = await result
|
|
850
|
+
const text = await getTextWithFallback(result, 'response_validation');
|
|
760
851
|
return parseResponseValidation(text);
|
|
761
852
|
}
|
|
762
853
|
|
|
@@ -835,26 +926,30 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
835
926
|
const maxToolSteps = Number.isFinite(input.maxToolSteps)
|
|
836
927
|
? Math.max(1, Math.floor(input.maxToolSteps as number))
|
|
837
928
|
: agent.tools.maxToolSteps;
|
|
838
|
-
const memoryExtractionEnabled = agent.memory.extraction.enabled;
|
|
929
|
+
const memoryExtractionEnabled = agent.memory.extraction.enabled && !input.disableMemoryExtraction;
|
|
839
930
|
const isDaemon = process.env.DOTCLAW_DAEMON === '1';
|
|
840
931
|
const memoryExtractionAsync = agent.memory.extraction.async;
|
|
841
932
|
const memoryExtractionMaxMessages = agent.memory.extraction.maxMessages;
|
|
842
933
|
const memoryExtractionMaxOutputTokens = agent.memory.extraction.maxOutputTokens;
|
|
843
934
|
const memoryExtractScheduled = agent.memory.extractScheduled;
|
|
844
935
|
const memoryArchiveSync = agent.memory.archiveSync;
|
|
845
|
-
const plannerEnabled = agent.planner.enabled;
|
|
936
|
+
const plannerEnabled = agent.planner.enabled && !input.disablePlanner;
|
|
846
937
|
const plannerMode = String(agent.planner.mode || 'auto').toLowerCase();
|
|
847
938
|
const plannerMinTokens = agent.planner.minTokens;
|
|
848
939
|
const plannerTrigger = buildPlannerTrigger(agent.planner.triggerRegex);
|
|
849
940
|
const plannerModel = agent.models.planner;
|
|
850
941
|
const plannerMaxOutputTokens = agent.planner.maxOutputTokens;
|
|
851
942
|
const plannerTemperature = agent.planner.temperature;
|
|
852
|
-
const responseValidateEnabled = agent.responseValidation.enabled;
|
|
943
|
+
const responseValidateEnabled = agent.responseValidation.enabled && !input.disableResponseValidation;
|
|
853
944
|
const responseValidateModel = agent.models.responseValidation;
|
|
854
945
|
const responseValidateMaxOutputTokens = agent.responseValidation.maxOutputTokens;
|
|
855
946
|
const responseValidateTemperature = agent.responseValidation.temperature;
|
|
856
|
-
const responseValidateMaxRetries =
|
|
947
|
+
const responseValidateMaxRetries = Number.isFinite(input.responseValidationMaxRetries)
|
|
948
|
+
? Math.max(0, Math.floor(input.responseValidationMaxRetries as number))
|
|
949
|
+
: agent.responseValidation.maxRetries;
|
|
857
950
|
const responseValidateAllowToolCalls = agent.responseValidation.allowToolCalls;
|
|
951
|
+
const responseValidateMinPromptTokens = agent.responseValidation.minPromptTokens || 0;
|
|
952
|
+
const responseValidateMinResponseTokens = agent.responseValidation.minResponseTokens || 0;
|
|
858
953
|
const maxContextMessageTokens = agent.context.maxContextMessageTokens;
|
|
859
954
|
const streamingEnabled = Boolean(input.streaming?.enabled && typeof input.streaming?.draftId === 'number');
|
|
860
955
|
const streamingDraftId = streamingEnabled ? input.streaming?.draftId : undefined;
|
|
@@ -884,6 +979,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
884
979
|
const toolCalls: ToolCallRecord[] = [];
|
|
885
980
|
let memoryItemsUpserted = 0;
|
|
886
981
|
let memoryItemsExtracted = 0;
|
|
982
|
+
const timings: { planner_ms?: number; response_validation_ms?: number; memory_extraction_ms?: number; tool_ms?: number } = {};
|
|
887
983
|
const ipc = createIpcHandlers({
|
|
888
984
|
chatJid: input.chatJid,
|
|
889
985
|
groupFolder: input.groupFolder,
|
|
@@ -897,7 +993,11 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
897
993
|
onToolCall: (call) => {
|
|
898
994
|
toolCalls.push(call);
|
|
899
995
|
},
|
|
900
|
-
policy: input.toolPolicy
|
|
996
|
+
policy: input.toolPolicy,
|
|
997
|
+
jobProgress: {
|
|
998
|
+
jobId: input.jobId,
|
|
999
|
+
enabled: Boolean(input.isBackgroundJob)
|
|
1000
|
+
}
|
|
901
1001
|
});
|
|
902
1002
|
|
|
903
1003
|
let streamLastSentAt = 0;
|
|
@@ -1083,6 +1183,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1083
1183
|
taskId: input.taskId,
|
|
1084
1184
|
isBackgroundJob: !!input.isBackgroundJob,
|
|
1085
1185
|
jobId: input.jobId,
|
|
1186
|
+
timezone: typeof input.timezone === 'string' ? input.timezone : undefined,
|
|
1086
1187
|
planBlock: planBlockValue,
|
|
1087
1188
|
taskExtractionPack: taskPackResult?.pack || null,
|
|
1088
1189
|
responseQualityPack: responseQualityResult?.pack || null,
|
|
@@ -1109,6 +1210,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1109
1210
|
trigger: plannerTrigger
|
|
1110
1211
|
})) {
|
|
1111
1212
|
try {
|
|
1213
|
+
const plannerStartedAt = Date.now();
|
|
1112
1214
|
const plannerPrompt = buildPlannerPrompt(plannerContextMessages);
|
|
1113
1215
|
const plannerResult = await openrouter.callModel({
|
|
1114
1216
|
model: plannerModel,
|
|
@@ -1117,11 +1219,12 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1117
1219
|
maxOutputTokens: plannerMaxOutputTokens,
|
|
1118
1220
|
temperature: plannerTemperature
|
|
1119
1221
|
});
|
|
1120
|
-
const plannerText = await plannerResult
|
|
1222
|
+
const plannerText = await getTextWithFallback(plannerResult, 'planner');
|
|
1121
1223
|
const plan = parsePlannerResponse(plannerText);
|
|
1122
1224
|
if (plan) {
|
|
1123
1225
|
planBlock = formatPlanBlock(plan);
|
|
1124
1226
|
}
|
|
1227
|
+
timings.planner_ms = Date.now() - plannerStartedAt;
|
|
1125
1228
|
} catch (err) {
|
|
1126
1229
|
log(`Planner failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1127
1230
|
}
|
|
@@ -1212,7 +1315,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1212
1315
|
}
|
|
1213
1316
|
}
|
|
1214
1317
|
if (!streamed || !localResponseText || !localResponseText.trim()) {
|
|
1215
|
-
localResponseText = await result
|
|
1318
|
+
localResponseText = await getTextWithFallback(result, 'completion');
|
|
1216
1319
|
if (localResponseText && localResponseText.trim()) {
|
|
1217
1320
|
sendStreamUpdate(localResponseText, true);
|
|
1218
1321
|
}
|
|
@@ -1245,6 +1348,8 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1245
1348
|
modelToolCalls = firstAttempt.modelToolCalls;
|
|
1246
1349
|
|
|
1247
1350
|
const shouldValidate = responseValidateEnabled
|
|
1351
|
+
&& promptTokens >= responseValidateMinPromptTokens
|
|
1352
|
+
&& completionTokens >= responseValidateMinResponseTokens
|
|
1248
1353
|
&& (responseValidateAllowToolCalls || modelToolCalls.length === 0);
|
|
1249
1354
|
if (shouldValidate) {
|
|
1250
1355
|
let retriesLeft = responseValidateMaxRetries;
|
|
@@ -1257,6 +1362,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1257
1362
|
validationResult = { verdict: 'fail', issues: ['Response was empty.'], missing: [] };
|
|
1258
1363
|
} else {
|
|
1259
1364
|
try {
|
|
1365
|
+
const validationStartedAt = Date.now();
|
|
1260
1366
|
validationResult = await validateResponseQuality({
|
|
1261
1367
|
openrouter,
|
|
1262
1368
|
model: responseValidateModel,
|
|
@@ -1265,6 +1371,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1265
1371
|
maxOutputTokens: responseValidateMaxOutputTokens,
|
|
1266
1372
|
temperature: responseValidateTemperature
|
|
1267
1373
|
});
|
|
1374
|
+
timings.response_validation_ms = (timings.response_validation_ms ?? 0) + (Date.now() - validationStartedAt);
|
|
1268
1375
|
} catch (err) {
|
|
1269
1376
|
log(`Response validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1270
1377
|
}
|
|
@@ -1306,6 +1413,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1306
1413
|
session_recall_count: sessionRecallCount,
|
|
1307
1414
|
memory_items_upserted: memoryItemsUpserted,
|
|
1308
1415
|
memory_items_extracted: memoryItemsExtracted,
|
|
1416
|
+
timings: Object.keys(timings).length > 0 ? timings : undefined,
|
|
1309
1417
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
1310
1418
|
latency_ms: latencyMs
|
|
1311
1419
|
};
|
|
@@ -1335,6 +1443,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1335
1443
|
const runMemoryExtraction = async () => {
|
|
1336
1444
|
const extractionMessages = history.slice(-memoryExtractionMaxMessages);
|
|
1337
1445
|
if (extractionMessages.length === 0) return;
|
|
1446
|
+
const extractionStartedAt = Date.now();
|
|
1338
1447
|
const extractionPrompt = buildMemoryExtractionPrompt({
|
|
1339
1448
|
assistantName,
|
|
1340
1449
|
userId: input.userId,
|
|
@@ -1349,7 +1458,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1349
1458
|
maxOutputTokens: memoryExtractionMaxOutputTokens,
|
|
1350
1459
|
temperature: 0.1
|
|
1351
1460
|
});
|
|
1352
|
-
const extractionText = await extractionResult
|
|
1461
|
+
const extractionText = await getTextWithFallback(extractionResult, 'memory_extraction');
|
|
1353
1462
|
const extractedItems = parseMemoryExtraction(extractionText);
|
|
1354
1463
|
if (extractedItems.length === 0) return;
|
|
1355
1464
|
|
|
@@ -1380,6 +1489,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1380
1489
|
memoryItemsExtracted += normalizedItems.length;
|
|
1381
1490
|
memoryItemsUpserted += normalizedItems.length;
|
|
1382
1491
|
}
|
|
1492
|
+
timings.memory_extraction_ms = (timings.memory_extraction_ms ?? 0) + (Date.now() - extractionStartedAt);
|
|
1383
1493
|
};
|
|
1384
1494
|
|
|
1385
1495
|
if (memoryExtractionEnabled && (!input.isScheduledTask || memoryExtractScheduled)) {
|
|
@@ -1398,6 +1508,12 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1398
1508
|
|
|
1399
1509
|
// Normalize empty/whitespace-only responses to null
|
|
1400
1510
|
const finalResult = responseText && responseText.trim() ? responseText : null;
|
|
1511
|
+
if (toolCalls.length > 0) {
|
|
1512
|
+
const totalToolMs = toolCalls.reduce((sum, call) => sum + (call.duration_ms || 0), 0);
|
|
1513
|
+
if (totalToolMs > 0) {
|
|
1514
|
+
timings.tool_ms = totalToolMs;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1401
1517
|
|
|
1402
1518
|
return {
|
|
1403
1519
|
status: 'success',
|
|
@@ -1413,6 +1529,7 @@ export async function runAgentOnce(input: ContainerInput): Promise<ContainerOutp
|
|
|
1413
1529
|
session_recall_count: sessionRecallCount,
|
|
1414
1530
|
memory_items_upserted: memoryItemsUpserted,
|
|
1415
1531
|
memory_items_extracted: memoryItemsExtracted,
|
|
1532
|
+
timings: Object.keys(timings).length > 0 ? timings : undefined,
|
|
1416
1533
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
1417
1534
|
latency_ms: latencyMs
|
|
1418
1535
|
};
|