tackle-harness 0.0.5 → 0.0.6
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/bin/tackle.js +1 -1
- package/package.json +29 -29
- package/plugins/core/provider-watchdog/assets/daemon-config.template.json +32 -0
- package/plugins/core/provider-watchdog/assets/lib/config.js +170 -0
- package/plugins/core/provider-watchdog/assets/lib/daemon.js +721 -0
- package/plugins/core/provider-watchdog/assets/lib/logger.js +206 -0
- package/plugins/core/provider-watchdog/assets/lib/process-manager.js +195 -0
- package/plugins/core/provider-watchdog/assets/lib/state-files.js +303 -0
- package/plugins/core/provider-watchdog/assets/watchdog.js +704 -0
- package/plugins/core/provider-watchdog/index.js +75 -0
- package/plugins/core/provider-watchdog/plugin.json +13 -0
- package/plugins/core/skill-agent-dispatcher/skill.md +271 -0
- package/plugins/core/skill-batch-task-creator/skill.md +1 -1
- package/plugins/core/skill-progress-tracker/skill.md +2 -13
- package/plugins/core/skill-split-work-package/skill.md +10 -20
- package/plugins/core/skill-task-creator/skill.md +1 -1
- package/plugins/core/skill-watchdog-manager/plugin.json +10 -0
- package/plugins/core/skill-watchdog-manager/skill.md +143 -0
- package/plugins/plugin-registry.json +12 -0
- package/plugins/runtime/harness-build.js +53 -3
package/bin/tackle.js
CHANGED
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "tackle-harness",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Plugin-based AI Agent Harness framework for Claude Code",
|
|
5
|
-
"main": "plugins/runtime/harness-build.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"tackle-harness": "bin/tackle.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin/",
|
|
11
|
-
"plugins/"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "node bin/tackle.js build",
|
|
15
|
-
"validate": "node bin/tackle.js validate"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"claude",
|
|
19
|
-
"claude-code",
|
|
20
|
-
"agent",
|
|
21
|
-
"harness",
|
|
22
|
-
"plugin",
|
|
23
|
-
"workflow"
|
|
24
|
-
],
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"engines": {
|
|
27
|
-
"node": ">=14.0.0"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "tackle-harness",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "Plugin-based AI Agent Harness framework for Claude Code",
|
|
5
|
+
"main": "plugins/runtime/harness-build.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tackle-harness": "bin/tackle.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"plugins/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "node bin/tackle.js build",
|
|
15
|
+
"validate": "node bin/tackle.js validate"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"agent",
|
|
21
|
+
"harness",
|
|
22
|
+
"plugin",
|
|
23
|
+
"workflow"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"daemon": {
|
|
3
|
+
"mode": "background"
|
|
4
|
+
},
|
|
5
|
+
"heartbeat_dir": ".claude-daemon",
|
|
6
|
+
"check_interval_sec": 30,
|
|
7
|
+
"timeouts": {
|
|
8
|
+
"task_stalled_min": 15,
|
|
9
|
+
"session_stalled_min": 10,
|
|
10
|
+
"restart_cooldown_min": 2,
|
|
11
|
+
"marker_window_min": 5
|
|
12
|
+
},
|
|
13
|
+
"retry": {
|
|
14
|
+
"max_retries": 3,
|
|
15
|
+
"timeout_scale": [1.0, 1.33, 2.0],
|
|
16
|
+
"cooldown_min": [0, 2, 5]
|
|
17
|
+
},
|
|
18
|
+
"circuit_breaker": {
|
|
19
|
+
"consecutive_failures_threshold": 3,
|
|
20
|
+
"total_retries_multiplier": 2,
|
|
21
|
+
"cooldown_after_break_min": 15
|
|
22
|
+
},
|
|
23
|
+
"process": {
|
|
24
|
+
"kill_timeout_sec": 10,
|
|
25
|
+
"restart_command": "claude --skill agent-dispatcher"
|
|
26
|
+
},
|
|
27
|
+
"logging": {
|
|
28
|
+
"level": "info",
|
|
29
|
+
"max_file_size_mb": 10,
|
|
30
|
+
"max_files": 3
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 默认配置模板
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
daemon: {
|
|
11
|
+
mode: 'background'
|
|
12
|
+
},
|
|
13
|
+
heartbeat_dir: '.claude-daemon',
|
|
14
|
+
check_interval_sec: 30,
|
|
15
|
+
timeouts: {
|
|
16
|
+
task_stalled_min: 15,
|
|
17
|
+
session_stalled_min: 10,
|
|
18
|
+
restart_cooldown_min: 2,
|
|
19
|
+
marker_window_min: 5
|
|
20
|
+
},
|
|
21
|
+
retry: {
|
|
22
|
+
max_retries: 3,
|
|
23
|
+
timeout_scale: [1.0, 1.33, 2.0],
|
|
24
|
+
cooldown_min: [0, 2, 5]
|
|
25
|
+
},
|
|
26
|
+
circuit_breaker: {
|
|
27
|
+
consecutive_failures_threshold: 3,
|
|
28
|
+
total_retries_multiplier: 2,
|
|
29
|
+
cooldown_after_break_min: 15
|
|
30
|
+
},
|
|
31
|
+
process: {
|
|
32
|
+
kill_timeout_sec: 10,
|
|
33
|
+
restart_command: 'claude --skill agent-dispatcher'
|
|
34
|
+
},
|
|
35
|
+
logging: {
|
|
36
|
+
level: 'info',
|
|
37
|
+
max_file_size_mb: 10,
|
|
38
|
+
max_files: 3
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 配置校验规则
|
|
44
|
+
*/
|
|
45
|
+
const VALIDATORS = {
|
|
46
|
+
daemon: {
|
|
47
|
+
mode: (val) => ['foreground', 'background'].includes(val)
|
|
48
|
+
},
|
|
49
|
+
check_interval_sec: (val) => val > 0,
|
|
50
|
+
timeouts: {
|
|
51
|
+
task_stalled_min: (val) => val > 0,
|
|
52
|
+
session_stalled_min: (val) => val > 0,
|
|
53
|
+
restart_cooldown_min: (val) => val >= 0,
|
|
54
|
+
marker_window_min: (val) => val > 0
|
|
55
|
+
},
|
|
56
|
+
retry: {
|
|
57
|
+
max_retries: (val) => val > 0,
|
|
58
|
+
timeout_scale: (val) => Array.isArray(val) && val.length > 0 && val.every(v => v > 0),
|
|
59
|
+
cooldown_min: (val) => Array.isArray(val) && val.length > 0 && val.every(v => v >= 0)
|
|
60
|
+
},
|
|
61
|
+
circuit_breaker: {
|
|
62
|
+
consecutive_failures_threshold: (val) => val > 0,
|
|
63
|
+
total_retries_multiplier: (val) => val > 0,
|
|
64
|
+
cooldown_after_break_min: (val) => val >= 0
|
|
65
|
+
},
|
|
66
|
+
process: {
|
|
67
|
+
kill_timeout_sec: (val) => val > 0,
|
|
68
|
+
restart_command: (val) => typeof val === 'string' && val.length > 0
|
|
69
|
+
},
|
|
70
|
+
logging: {
|
|
71
|
+
level: (val) => ['debug', 'info', 'warn', 'error'].includes(val),
|
|
72
|
+
max_file_size_mb: (val) => val > 0,
|
|
73
|
+
max_files: (val) => val > 0
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 深度合并对象
|
|
79
|
+
*/
|
|
80
|
+
function deepMerge(target, source) {
|
|
81
|
+
const result = { ...target };
|
|
82
|
+
|
|
83
|
+
for (const key in source) {
|
|
84
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
85
|
+
result[key] = deepMerge(target[key] || {}, source[key]);
|
|
86
|
+
} else {
|
|
87
|
+
result[key] = source[key];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 校验配置
|
|
96
|
+
* @param {Object} config - 待校验的配置
|
|
97
|
+
* @throws {Error} 校验失败时抛出错误
|
|
98
|
+
*/
|
|
99
|
+
function validateConfig(config) {
|
|
100
|
+
const errors = [];
|
|
101
|
+
|
|
102
|
+
function validate(obj, schema, prefix = '') {
|
|
103
|
+
for (const key in schema) {
|
|
104
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
105
|
+
|
|
106
|
+
if (!(key in obj)) {
|
|
107
|
+
errors.push(`缺少必需字段: ${fullPath}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const validator = schema[key];
|
|
112
|
+
const value = obj[key];
|
|
113
|
+
|
|
114
|
+
if (typeof validator === 'function') {
|
|
115
|
+
if (!validator(value)) {
|
|
116
|
+
errors.push(`字段 ${fullPath} 值无效: ${JSON.stringify(value)}`);
|
|
117
|
+
}
|
|
118
|
+
} else if (typeof validator === 'object' && typeof value === 'object' && !Array.isArray(value)) {
|
|
119
|
+
validate(value, validator, fullPath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
validate(config, VALIDATORS);
|
|
125
|
+
|
|
126
|
+
if (errors.length > 0) {
|
|
127
|
+
throw new Error('配置校验失败:\n - ' + errors.join('\n - '));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 加载配置文件
|
|
133
|
+
* @param {string} configPath - 配置文件路径
|
|
134
|
+
* @returns {Object} 合并后的配置对象
|
|
135
|
+
*/
|
|
136
|
+
function loadConfig(configPath) {
|
|
137
|
+
let userConfig = {};
|
|
138
|
+
|
|
139
|
+
// 尝试读取用户配置文件
|
|
140
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
141
|
+
try {
|
|
142
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
143
|
+
userConfig = JSON.parse(content);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw new Error(`配置文件解析失败: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 深度合并默认配置和用户配置
|
|
150
|
+
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
151
|
+
|
|
152
|
+
// 校验最终配置
|
|
153
|
+
validateConfig(config);
|
|
154
|
+
|
|
155
|
+
return config;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 获取配置文件模板路径
|
|
160
|
+
* @returns {string} 模板文件的绝对路径
|
|
161
|
+
*/
|
|
162
|
+
function getTemplatePath() {
|
|
163
|
+
return path.join(__dirname, '..', 'daemon-config.template.json');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
loadConfig,
|
|
168
|
+
getTemplatePath,
|
|
169
|
+
DEFAULT_CONFIG
|
|
170
|
+
};
|