tackle-harness 0.0.2
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/LICENSE +21 -0
- package/README.en.md +259 -0
- package/README.md +261 -0
- package/bin/tackle.js +150 -0
- package/package.json +29 -0
- package/plugins/contracts/plugin-interface.js +244 -0
- package/plugins/core/hook-skill-gate/index.js +437 -0
- package/plugins/core/hook-skill-gate/plugin.json +12 -0
- package/plugins/core/provider-memory-store/index.js +403 -0
- package/plugins/core/provider-memory-store/plugin.json +9 -0
- package/plugins/core/provider-role-registry/index.js +477 -0
- package/plugins/core/provider-role-registry/plugin.json +9 -0
- package/plugins/core/provider-state-store/index.js +244 -0
- package/plugins/core/provider-state-store/plugin.json +9 -0
- package/plugins/core/skill-agent-dispatcher/plugin.json +13 -0
- package/plugins/core/skill-agent-dispatcher/skill.md +912 -0
- package/plugins/core/skill-batch-task-creator/plugin.json +13 -0
- package/plugins/core/skill-batch-task-creator/skill.md +616 -0
- package/plugins/core/skill-checklist/plugin.json +10 -0
- package/plugins/core/skill-checklist/skill.md +115 -0
- package/plugins/core/skill-completion-report/plugin.json +10 -0
- package/plugins/core/skill-completion-report/skill.md +331 -0
- package/plugins/core/skill-experience-logger/plugin.json +10 -0
- package/plugins/core/skill-experience-logger/skill.md +235 -0
- package/plugins/core/skill-human-checkpoint/plugin.json +10 -0
- package/plugins/core/skill-human-checkpoint/skill.md +194 -0
- package/plugins/core/skill-progress-tracker/plugin.json +10 -0
- package/plugins/core/skill-progress-tracker/skill.md +204 -0
- package/plugins/core/skill-role-manager/plugin.json +10 -0
- package/plugins/core/skill-role-manager/skill.md +252 -0
- package/plugins/core/skill-split-work-package/plugin.json +13 -0
- package/plugins/core/skill-split-work-package/skill.md +446 -0
- package/plugins/core/skill-task-creator/plugin.json +13 -0
- package/plugins/core/skill-task-creator/skill.md +744 -0
- package/plugins/core/skill-team-cleanup/plugin.json +10 -0
- package/plugins/core/skill-team-cleanup/skill.md +266 -0
- package/plugins/core/skill-workflow-orchestrator/plugin.json +13 -0
- package/plugins/core/skill-workflow-orchestrator/skill.md +274 -0
- package/plugins/core/validator-doc-sync/index.js +248 -0
- package/plugins/core/validator-doc-sync/plugin.json +9 -0
- package/plugins/core/validator-work-package/index.js +300 -0
- package/plugins/core/validator-work-package/plugin.json +9 -0
- package/plugins/plugin-registry.json +118 -0
- package/plugins/runtime/config-manager.js +306 -0
- package/plugins/runtime/event-bus.js +187 -0
- package/plugins/runtime/harness-build.js +1019 -0
- package/plugins/runtime/logger.js +174 -0
- package/plugins/runtime/plugin-loader.js +339 -0
- package/plugins/runtime/state-store.js +277 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigManager - Three-layer configuration management for AI Agent Harness
|
|
3
|
+
*
|
|
4
|
+
* Layer precedence (highest wins):
|
|
5
|
+
* 1. Environment variables (HARNESS_<KEY>)
|
|
6
|
+
* 2. harness-config.yaml (user/project-level)
|
|
7
|
+
* 3. plugin defaults (built into each plugin)
|
|
8
|
+
*
|
|
9
|
+
* Supports:
|
|
10
|
+
* - get(key) with dot-notation
|
|
11
|
+
* - getForPlugin(pluginName, key) with per-plugin override
|
|
12
|
+
* - setOverride(key, value) runtime override
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
var fs = require('fs');
|
|
18
|
+
var path = require('path');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Minimal YAML-like parser for simple key-value YAML files.
|
|
22
|
+
* Handles nested structure via indentation (spaces only).
|
|
23
|
+
* This is NOT a full YAML parser - it covers the subset used by harness-config.yaml.
|
|
24
|
+
*/
|
|
25
|
+
function parseSimpleYaml(content) {
|
|
26
|
+
var result = {};
|
|
27
|
+
var lines = content.split('\n');
|
|
28
|
+
var stack = [{ obj: result, indent: -1 }];
|
|
29
|
+
|
|
30
|
+
for (var i = 0; i < lines.length; i++) {
|
|
31
|
+
var line = lines[i];
|
|
32
|
+
|
|
33
|
+
// Skip empty lines, comments, and document separators
|
|
34
|
+
if (!line.trim() || line.trim().indexOf('#') === 0 || line.trim() === '---') {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Calculate indentation
|
|
39
|
+
var indent = line.search(/\S/);
|
|
40
|
+
if (indent < 0) continue;
|
|
41
|
+
|
|
42
|
+
// Pop stack to find parent
|
|
43
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
44
|
+
stack.pop();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var parent = stack[stack.length - 1].obj;
|
|
48
|
+
var trimmed = line.trim();
|
|
49
|
+
|
|
50
|
+
// Check if it's a list item
|
|
51
|
+
if (trimmed.indexOf('- ') === 0) {
|
|
52
|
+
if (!Array.isArray(parent)) continue;
|
|
53
|
+
var itemValue = parseValue(trimmed.substring(2));
|
|
54
|
+
parent.push(itemValue);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Key-value pair
|
|
59
|
+
var colonIdx = trimmed.indexOf(':');
|
|
60
|
+
if (colonIdx === -1) continue;
|
|
61
|
+
|
|
62
|
+
var key = trimmed.substring(0, colonIdx).trim();
|
|
63
|
+
var valuePart = trimmed.substring(colonIdx + 1).trim();
|
|
64
|
+
|
|
65
|
+
if (valuePart === '' || valuePart === null) {
|
|
66
|
+
// Nested object
|
|
67
|
+
var child = {};
|
|
68
|
+
parent[key] = child;
|
|
69
|
+
stack.push({ obj: child, indent: indent });
|
|
70
|
+
} else {
|
|
71
|
+
parent[key] = parseValue(valuePart);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse a YAML scalar value.
|
|
80
|
+
*/
|
|
81
|
+
function parseValue(val) {
|
|
82
|
+
if (val === 'true') return true;
|
|
83
|
+
if (val === 'false') return false;
|
|
84
|
+
if (val === 'null' || val === '~') return null;
|
|
85
|
+
|
|
86
|
+
// Remove surrounding quotes
|
|
87
|
+
if ((val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') ||
|
|
88
|
+
(val.charAt(0) === "'" && val.charAt(val.length - 1) === "'")) {
|
|
89
|
+
return val.substring(1, val.length - 1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Try number
|
|
93
|
+
var num = Number(val);
|
|
94
|
+
if (!isNaN(num) && val !== '') return num;
|
|
95
|
+
|
|
96
|
+
return val;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class ConfigManager {
|
|
100
|
+
/**
|
|
101
|
+
* @param {object} [options]
|
|
102
|
+
* @param {string} [options.configPath] - path to harness-config.yaml
|
|
103
|
+
* @param {object} [options.defaults] - plugin default configs { pluginName: { key: value } }
|
|
104
|
+
*/
|
|
105
|
+
constructor(options) {
|
|
106
|
+
options = options || {};
|
|
107
|
+
/** @type {string} */
|
|
108
|
+
this._configPath = options.configPath || path.join(this._findProjectRoot(), '.claude', 'config', 'harness-config.yaml');
|
|
109
|
+
/** @type {object} plugin defaults */
|
|
110
|
+
this._defaults = options.defaults || {};
|
|
111
|
+
/** @type {object|null} parsed YAML config */
|
|
112
|
+
this._yamlConfig = null;
|
|
113
|
+
/** @type {object} runtime overrides */
|
|
114
|
+
this._overrides = {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- public API ---
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get a config value with three-layer resolution:
|
|
121
|
+
* runtime override > harness-config.yaml > plugin defaults
|
|
122
|
+
*
|
|
123
|
+
* @param {string} key - dot-notation key
|
|
124
|
+
* @param {*} [defaultValue] - fallback if key not found in any layer
|
|
125
|
+
* @returns {*}
|
|
126
|
+
*/
|
|
127
|
+
get(key, defaultValue) {
|
|
128
|
+
// Layer 1: runtime overrides
|
|
129
|
+
var overrideVal = this._getNested(this._overrides, key);
|
|
130
|
+
if (overrideVal !== undefined) {
|
|
131
|
+
return overrideVal;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Layer 2: environment variables (HARNESS_ prefix, double underscore for nesting)
|
|
135
|
+
var envKey = 'HARNESS_' + key.toUpperCase().replace(/\./g, '__');
|
|
136
|
+
if (process.env[envKey] !== undefined) {
|
|
137
|
+
return parseValue(process.env[envKey]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Layer 3: harness-config.yaml
|
|
141
|
+
var yamlVal = this._getNested(this._getYamlConfig(), key);
|
|
142
|
+
if (yamlVal !== undefined) {
|
|
143
|
+
return yamlVal;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return defaultValue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get a config value for a specific plugin.
|
|
151
|
+
* Resolution order:
|
|
152
|
+
* runtime override > env var > harness-config overrides section > harness-config root > plugin defaults
|
|
153
|
+
*
|
|
154
|
+
* @param {string} pluginName
|
|
155
|
+
* @param {string} key
|
|
156
|
+
* @param {*} [defaultValue]
|
|
157
|
+
* @returns {*}
|
|
158
|
+
*/
|
|
159
|
+
getForPlugin(pluginName, key, defaultValue) {
|
|
160
|
+
// Try plugin-specific override first
|
|
161
|
+
var pluginOverride = this.get('overrides.' + pluginName + '.' + key);
|
|
162
|
+
if (pluginOverride !== undefined) {
|
|
163
|
+
return pluginOverride;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Try general get
|
|
167
|
+
var generalVal = this.get(key);
|
|
168
|
+
if (generalVal !== undefined) {
|
|
169
|
+
return generalVal;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Try plugin defaults
|
|
173
|
+
var defaultVal = this._getNested(this._defaults, pluginName + '.' + key);
|
|
174
|
+
if (defaultVal !== undefined) {
|
|
175
|
+
return defaultVal;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return defaultValue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set a runtime override. Takes highest priority.
|
|
183
|
+
* @param {string} key
|
|
184
|
+
* @param {*} value
|
|
185
|
+
*/
|
|
186
|
+
setOverride(key, value) {
|
|
187
|
+
this._setNested(this._overrides, key, value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Clear a runtime override.
|
|
192
|
+
* @param {string} key
|
|
193
|
+
*/
|
|
194
|
+
clearOverride(key) {
|
|
195
|
+
this._deleteNested(this._overrides, key);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get the full parsed YAML config (for advanced usage).
|
|
200
|
+
* @returns {object}
|
|
201
|
+
*/
|
|
202
|
+
getAll() {
|
|
203
|
+
return this._getYamlConfig();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a scoped config getter for a specific plugin.
|
|
208
|
+
* @param {string} pluginName
|
|
209
|
+
* @returns {{ get: Function }}
|
|
210
|
+
*/
|
|
211
|
+
forPlugin(pluginName) {
|
|
212
|
+
var self = this;
|
|
213
|
+
return {
|
|
214
|
+
get: function (key, defaultValue) {
|
|
215
|
+
return self.getForPlugin(pluginName, key, defaultValue);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- internal ---
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Load and cache the YAML config.
|
|
224
|
+
* @returns {object}
|
|
225
|
+
*/
|
|
226
|
+
_getYamlConfig() {
|
|
227
|
+
if (this._yamlConfig !== null) {
|
|
228
|
+
return this._yamlConfig;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
var content = fs.readFileSync(this._configPath, 'utf-8');
|
|
233
|
+
this._yamlConfig = parseSimpleYaml(content);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
// Config file not found or unreadable - use empty config
|
|
236
|
+
this._yamlConfig = {};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return this._yamlConfig;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get nested value by dot-notation key.
|
|
244
|
+
*/
|
|
245
|
+
_getNested(obj, key) {
|
|
246
|
+
var parts = key.split('.');
|
|
247
|
+
var current = obj;
|
|
248
|
+
for (var i = 0; i < parts.length; i++) {
|
|
249
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
current = current[parts[i]];
|
|
253
|
+
}
|
|
254
|
+
return current;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Set nested value by dot-notation key.
|
|
259
|
+
*/
|
|
260
|
+
_setNested(obj, key, value) {
|
|
261
|
+
var parts = key.split('.');
|
|
262
|
+
var current = obj;
|
|
263
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
264
|
+
if (typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
265
|
+
current[parts[i]] = {};
|
|
266
|
+
}
|
|
267
|
+
current = current[parts[i]];
|
|
268
|
+
}
|
|
269
|
+
current[parts[parts.length - 1]] = value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Delete nested value by dot-notation key.
|
|
274
|
+
*/
|
|
275
|
+
_deleteNested(obj, key) {
|
|
276
|
+
var parts = key.split('.');
|
|
277
|
+
var current = obj;
|
|
278
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
279
|
+
if (typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
current = current[parts[i]];
|
|
283
|
+
}
|
|
284
|
+
delete current[parts[parts.length - 1]];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Walk up from cwd to find the project root (directory containing task.md or CLAUDE.md).
|
|
289
|
+
* Falls back to cwd if not found.
|
|
290
|
+
* @returns {string}
|
|
291
|
+
*/
|
|
292
|
+
_findProjectRoot() {
|
|
293
|
+
var dir = process.cwd();
|
|
294
|
+
for (var i = 0; i < 10; i++) {
|
|
295
|
+
if (fs.existsSync(path.join(dir, 'task.md')) || fs.existsSync(path.join(dir, 'CLAUDE.md'))) {
|
|
296
|
+
return dir;
|
|
297
|
+
}
|
|
298
|
+
var parent = path.dirname(dir);
|
|
299
|
+
if (parent === dir) break; // filesystem root
|
|
300
|
+
dir = parent;
|
|
301
|
+
}
|
|
302
|
+
return process.cwd();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus - Event dispatch system for AI Agent Harness
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - on(event, handler) register a listener
|
|
6
|
+
* - once(event, handler) one-time listener
|
|
7
|
+
* - off(event, handler) unregister a listener
|
|
8
|
+
* - emit(event, data) dispatch an event
|
|
9
|
+
* - History recording and query for debugging
|
|
10
|
+
* - Subscription objects with unsubscribe() support
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
class EventBus {
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} [options]
|
|
18
|
+
* @param {number} [options.maxHistory=100] - max event history entries
|
|
19
|
+
*/
|
|
20
|
+
constructor(options) {
|
|
21
|
+
options = options || {};
|
|
22
|
+
/** @type {Map<string, Set<Function>>} event -> handlers */
|
|
23
|
+
this._handlers = new Map();
|
|
24
|
+
/** @type {object[]} */
|
|
25
|
+
this._history = [];
|
|
26
|
+
/** @type {number} */
|
|
27
|
+
this._maxHistory = options.maxHistory || 100;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- public API ---
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Register an event handler.
|
|
34
|
+
* @param {string} event - event name
|
|
35
|
+
* @param {Function} handler - callback(eventData)
|
|
36
|
+
* @returns {{ unsubscribe: Function }} subscription handle
|
|
37
|
+
*/
|
|
38
|
+
on(event, handler) {
|
|
39
|
+
if (typeof handler !== 'function') {
|
|
40
|
+
throw new TypeError('EventBus.on(): handler must be a function');
|
|
41
|
+
}
|
|
42
|
+
if (!this._handlers.has(event)) {
|
|
43
|
+
this._handlers.set(event, new Set());
|
|
44
|
+
}
|
|
45
|
+
this._handlers.get(event).add(handler);
|
|
46
|
+
|
|
47
|
+
var self = this;
|
|
48
|
+
var handlers = this._handlers.get(event);
|
|
49
|
+
return {
|
|
50
|
+
unsubscribe: function () {
|
|
51
|
+
handlers.delete(handler);
|
|
52
|
+
if (handlers.size === 0) {
|
|
53
|
+
self._handlers.delete(event);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a one-time event handler. Automatically removed after first invocation.
|
|
61
|
+
* @param {string} event
|
|
62
|
+
* @param {Function} handler - callback(eventData)
|
|
63
|
+
* @returns {{ unsubscribe: Function }}
|
|
64
|
+
*/
|
|
65
|
+
once(event, handler) {
|
|
66
|
+
if (typeof handler !== 'function') {
|
|
67
|
+
throw new TypeError('EventBus.once(): handler must be a function');
|
|
68
|
+
}
|
|
69
|
+
var self = this;
|
|
70
|
+
var wrapped = function (data) {
|
|
71
|
+
handler(data);
|
|
72
|
+
innerSub.unsubscribe();
|
|
73
|
+
};
|
|
74
|
+
var innerSub = this.on(event, wrapped);
|
|
75
|
+
return innerSub;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Unregister a specific handler from an event.
|
|
80
|
+
* @param {string} event
|
|
81
|
+
* @param {Function} handler - the exact function reference originally passed to on()
|
|
82
|
+
*/
|
|
83
|
+
off(event, handler) {
|
|
84
|
+
var handlers = this._handlers.get(event);
|
|
85
|
+
if (!handlers) return;
|
|
86
|
+
handlers.delete(handler);
|
|
87
|
+
if (handlers.size === 0) {
|
|
88
|
+
this._handlers.delete(event);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Emit an event, calling all registered handlers synchronously.
|
|
94
|
+
* Errors in individual handlers are caught and logged, not propagated.
|
|
95
|
+
* @param {string} event
|
|
96
|
+
* @param {*} data
|
|
97
|
+
*/
|
|
98
|
+
emit(event, data) {
|
|
99
|
+
// Record in history
|
|
100
|
+
this._history.push({
|
|
101
|
+
event: event,
|
|
102
|
+
data: data,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
});
|
|
105
|
+
if (this._history.length > this._maxHistory) {
|
|
106
|
+
this._history.shift();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Dispatch to handlers
|
|
110
|
+
var handlers = this._handlers.get(event);
|
|
111
|
+
if (!handlers) return;
|
|
112
|
+
|
|
113
|
+
handlers.forEach(function (handler) {
|
|
114
|
+
try {
|
|
115
|
+
handler(data);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(
|
|
118
|
+
'[EventBus] Error in handler for event "' + event + '":',
|
|
119
|
+
err.message
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Query event history for debugging.
|
|
127
|
+
* @param {object} [filter]
|
|
128
|
+
* @param {string} [filter.event] - substring match on event name
|
|
129
|
+
* @param {number} [filter.since] - timestamp lower bound (ms)
|
|
130
|
+
* @param {number} [filter.until] - timestamp upper bound (ms)
|
|
131
|
+
* @param {number} [filter.limit] - max entries to return
|
|
132
|
+
* @returns {object[]}
|
|
133
|
+
*/
|
|
134
|
+
getHistory(filter) {
|
|
135
|
+
filter = filter || {};
|
|
136
|
+
var results = this._history;
|
|
137
|
+
|
|
138
|
+
if (filter.event) {
|
|
139
|
+
var substr = filter.event;
|
|
140
|
+
results = results.filter(function (e) {
|
|
141
|
+
return e.event.indexOf(substr) !== -1;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (filter.since) {
|
|
145
|
+
results = results.filter(function (e) { return e.timestamp >= filter.since; });
|
|
146
|
+
}
|
|
147
|
+
if (filter.until) {
|
|
148
|
+
results = results.filter(function (e) { return e.timestamp <= filter.until; });
|
|
149
|
+
}
|
|
150
|
+
if (filter.limit) {
|
|
151
|
+
results = results.slice(-filter.limit);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear all event history.
|
|
159
|
+
*/
|
|
160
|
+
clearHistory() {
|
|
161
|
+
this._history = [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Remove all handlers for a specific event, or all events.
|
|
166
|
+
* @param {string} [event] - if omitted, clears all handlers
|
|
167
|
+
*/
|
|
168
|
+
removeAllListeners(event) {
|
|
169
|
+
if (event) {
|
|
170
|
+
this._handlers.delete(event);
|
|
171
|
+
} else {
|
|
172
|
+
this._handlers.clear();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the number of handlers registered for a given event.
|
|
178
|
+
* @param {string} event
|
|
179
|
+
* @returns {number}
|
|
180
|
+
*/
|
|
181
|
+
listenerCount(event) {
|
|
182
|
+
var handlers = this._handlers.get(event);
|
|
183
|
+
return handlers ? handlers.size : 0;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = EventBus;
|