@cmgmyr/hive 1.2.1 → 1.3.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 +1 -1
- package/claude-plugin/README.md +3 -0
- package/claude-plugin/kickoff.mjs +2 -1
- package/claude-plugin/skills/profile/SKILL.md +78 -0
- package/claude-plugin/skills/profile/references/interview.md +139 -0
- package/claude-plugin/skills/profile/references/recipes/index.md +12 -0
- package/claude-plugin/skills/profile/references/recipes/review-decision.md +41 -0
- package/claude-plugin/skills/profile/references/recipes/session-continuity.md +37 -0
- package/claude-plugin/skills/profile/references/recipes/shared-test-resource.md +45 -0
- package/claude-plugin/skills/profile/references/recipes/verification.md +50 -0
- package/claude-plugin/skills/profile/references/recipes/worker-context-checkpoint.md +51 -0
- package/dist/build-info.json +1 -1
- package/dist/cli.js +78 -59
- package/dist/codexHome.js +23 -8
- package/dist/config.js +8 -61
- package/dist/db.js +20 -3
- package/dist/globalConfig.js +247 -0
- package/dist/harnesses.js +8 -0
- package/dist/help.js +2 -2
- package/dist/hooks.js +2 -1
- package/dist/kickoff.js +3 -4
- package/dist/projectYml.js +224 -179
- package/dist/result.js +19 -2
- package/dist/scheduler.js +12 -6
- package/dist/spawn.js +17 -4
- package/dist/strictInput.js +24 -3
- package/dist/tools/agents.js +149 -15
- package/dist/tools/kv.js +24 -0
- package/dist/tools/leases.js +12 -0
- package/dist/tools/meta.js +42 -0
- package/dist/tools/pads.js +42 -0
- package/dist/tools/todos.js +54 -0
- package/dist/tools/wakes.js +36 -0
- package/package.json +2 -2
package/dist/projectYml.js
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { resolve, sep } from "node:path";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { harnessNames } from "./harnesses.js";
|
|
6
6
|
import { isValidProfileName } from "./profiles.js";
|
|
7
7
|
import { errorMessage } from "./result.js";
|
|
8
8
|
import { isWindowLayout, WINDOW_LAYOUTS } from "./tmux.js";
|
|
9
|
+
import { globalConfigPath, isEmptyYamlDocument, readGlobalConfig, resolveAttachSetting, } from "./globalConfig.js";
|
|
10
|
+
export const BUILT_IN_PROJECT_YML = Object.freeze({
|
|
11
|
+
lead: null,
|
|
12
|
+
placement: null,
|
|
13
|
+
layout: null,
|
|
14
|
+
profile: null,
|
|
15
|
+
agents: null,
|
|
16
|
+
lead_branches: null,
|
|
17
|
+
context_checkpoint_percent: null,
|
|
18
|
+
lead_turn_budget: null,
|
|
19
|
+
dashboard: false,
|
|
20
|
+
vars: Object.freeze({}),
|
|
21
|
+
processes: Object.freeze({}),
|
|
22
|
+
});
|
|
23
|
+
export const PROJECT_YML_KEYS = Object.freeze(Object.keys(BUILT_IN_PROJECT_YML));
|
|
24
|
+
export function createBuiltInProjectYml() {
|
|
25
|
+
return structuredClone(BUILT_IN_PROJECT_YML);
|
|
26
|
+
}
|
|
9
27
|
export const DEFAULT_LEAD_BRANCHES = ["main", "master"];
|
|
10
28
|
export const NO_PROFILE = "none";
|
|
11
29
|
export function activeProfile(config) {
|
|
@@ -37,224 +55,251 @@ export function mergedProjectVars(config) {
|
|
|
37
55
|
delete safe[key];
|
|
38
56
|
return { ...safe, ...agentVars(config) };
|
|
39
57
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
function isMapping(value) {
|
|
59
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
60
|
+
return false;
|
|
61
|
+
const prototype = Object.getPrototypeOf(value);
|
|
62
|
+
return prototype === Object.prototype || prototype === null;
|
|
63
|
+
}
|
|
64
|
+
function parseFile(path, warnings) {
|
|
65
|
+
let text;
|
|
46
66
|
try {
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
warnings.push(`hive.yml is not valid YAML: ${errorMessage(e)}`);
|
|
51
|
-
return { config: null, warnings };
|
|
67
|
+
text = readFileSync(path, "utf8");
|
|
52
68
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const root = raw;
|
|
58
|
-
let context_checkpoint_percent = null;
|
|
59
|
-
if (root.context_checkpoint_percent != null) {
|
|
60
|
-
const value = root.context_checkpoint_percent;
|
|
61
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100) {
|
|
62
|
-
context_checkpoint_percent = value;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
warnings.push("context_checkpoint_percent must be an integer from 1 through 100; ignoring it.");
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT") {
|
|
71
|
+
warnings.push(`${path}: could not read hive.yml: ${errorMessage(error)}`);
|
|
66
72
|
}
|
|
73
|
+
return null;
|
|
67
74
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
:
|
|
74
|
-
|
|
75
|
-
const stop = budget?.stop;
|
|
76
|
-
if (typeof warn === "number" && Number.isInteger(warn) && warn > 0 &&
|
|
77
|
-
typeof stop === "number" && Number.isInteger(stop) && stop > warn) {
|
|
78
|
-
lead_turn_budget = { warn, stop };
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
warnings.push("lead_turn_budget must contain positive integer warn and stop values, with stop greater than warn; ignoring it.");
|
|
75
|
+
try {
|
|
76
|
+
const raw = parse(text);
|
|
77
|
+
if (raw == null && isEmptyYamlDocument(text))
|
|
78
|
+
return null;
|
|
79
|
+
if (!isMapping(raw)) {
|
|
80
|
+
warnings.push(`${path}: hive.yml must be a YAML mapping.`);
|
|
81
|
+
return null;
|
|
82
82
|
}
|
|
83
|
+
return raw;
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (root.placement === "split" || root.placement === "window") {
|
|
88
|
-
placement = root.placement;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
warnings.push(`placement must be "split" or "window"; ignoring "${String(root.placement)}".`);
|
|
92
|
-
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
warnings.push(`${path}: hive.yml is not valid YAML: ${errorMessage(error)}`);
|
|
87
|
+
return null;
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
}
|
|
90
|
+
function parseProcesses(value, path, warnings) {
|
|
91
|
+
if (!isMapping(value)) {
|
|
92
|
+
warnings.push(`${path}: hive.yml \`processes\` must be a mapping of name to command.`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const processes = {};
|
|
96
|
+
for (const [name, item] of Object.entries(value)) {
|
|
97
|
+
if (typeof item === "string" && item.trim() !== "") {
|
|
98
|
+
processes[name] = { command: item.trim(), dir: null, auto_start: true, visible: true, env: {} };
|
|
99
|
+
continue;
|
|
98
100
|
}
|
|
99
|
-
|
|
100
|
-
warnings.push(
|
|
101
|
+
if (!isMapping(item)) {
|
|
102
|
+
warnings.push(`${path}: Process "${name}" needs a command string; skipped.`);
|
|
103
|
+
continue;
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const value = String(root.profile).trim();
|
|
106
|
-
if (value === "") {
|
|
107
|
-
warnings.push("profile is empty; ignoring it.");
|
|
105
|
+
if (typeof item.command !== "string" || item.command.trim() === "") {
|
|
106
|
+
warnings.push(`${path}: Process "${name}" has no command; skipped.`);
|
|
107
|
+
continue;
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const dirValue = item.dir ?? item.working_dir;
|
|
110
|
+
let visible = true;
|
|
111
|
+
if (item.visible != null) {
|
|
112
|
+
if (typeof item.visible === "boolean")
|
|
113
|
+
visible = item.visible;
|
|
114
|
+
else
|
|
115
|
+
warnings.push(`${path}: Process "${name}": visible must be true or false; ignoring "${String(item.visible)}".`);
|
|
111
116
|
}
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
const env = {};
|
|
118
|
+
if (isMapping(item.env)) {
|
|
119
|
+
for (const [key, envValue] of Object.entries(item.env))
|
|
120
|
+
env[key] = String(envValue);
|
|
114
121
|
}
|
|
122
|
+
processes[name] = {
|
|
123
|
+
command: item.command.trim(),
|
|
124
|
+
dir: typeof dirValue === "string" && dirValue.trim() !== "" ? dirValue.trim() : null,
|
|
125
|
+
auto_start: item.auto_start !== false,
|
|
126
|
+
visible,
|
|
127
|
+
env,
|
|
128
|
+
};
|
|
115
129
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
return processes;
|
|
131
|
+
}
|
|
132
|
+
function applyLayer(root, path, source, config, sources, warnings) {
|
|
133
|
+
const has = (key) => Object.hasOwn(root, key);
|
|
134
|
+
const warn = (message) => warnings.push(`${path}: ${message}`);
|
|
135
|
+
const assignNullable = (key, valid, message) => {
|
|
136
|
+
if (!has(key))
|
|
137
|
+
return;
|
|
138
|
+
const value = root[key];
|
|
139
|
+
if (value == null) {
|
|
140
|
+
config[key] = null;
|
|
141
|
+
sources[key] = source;
|
|
120
142
|
}
|
|
121
143
|
else {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
else if (!known.includes(value)) {
|
|
130
|
-
warnings.push(`agents: "${value}" is not a known harness (${known.join(", ")}); ignoring it.`);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
valid.push(value);
|
|
134
|
-
}
|
|
144
|
+
const normalized = valid(value);
|
|
145
|
+
if (normalized === undefined)
|
|
146
|
+
warn(message(value));
|
|
147
|
+
else {
|
|
148
|
+
config[key] = normalized;
|
|
149
|
+
sources[key] = source;
|
|
135
150
|
}
|
|
136
|
-
agents = valid.length > 0 ? valid : null;
|
|
137
151
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
};
|
|
153
|
+
assignNullable("lead", (value) => typeof value === "string" && value.trim() ? value.trim() : undefined, (value) => `lead must be a non-empty command string; ignoring "${String(value)}".`);
|
|
154
|
+
assignNullable("placement", (value) => value === "split" || value === "window" ? value : undefined, (value) => `placement must be "split" or "window"; ignoring "${String(value)}".`);
|
|
155
|
+
assignNullable("layout", (value) => isWindowLayout(value) ? value : undefined, (value) => `layout must be one of ${WINDOW_LAYOUTS.join(", ")}; ignoring "${String(value)}".`);
|
|
156
|
+
assignNullable("profile", (value) => {
|
|
157
|
+
const name = String(value).trim();
|
|
158
|
+
return name !== "" && (name === NO_PROFILE || isValidProfileName(name)) ? name : undefined;
|
|
159
|
+
}, (value) => typeof value === "string" && value.trim() === ""
|
|
160
|
+
? "profile is empty; ignoring it."
|
|
161
|
+
: `profile "${String(value).trim()}" is not a valid profile name (letters, digits, dot, dash, underscore); ignoring it.`);
|
|
162
|
+
assignNullable("context_checkpoint_percent", (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100 ? value : undefined, () => "context_checkpoint_percent must be an integer from 1 through 100; ignoring it.");
|
|
163
|
+
if (has("lead_turn_budget")) {
|
|
164
|
+
const value = root.lead_turn_budget;
|
|
165
|
+
if (value == null) {
|
|
166
|
+
config.lead_turn_budget = null;
|
|
167
|
+
sources.lead_turn_budget = source;
|
|
168
|
+
}
|
|
169
|
+
else if (isMapping(value) && typeof value.warn === "number" && Number.isInteger(value.warn) && value.warn > 0 &&
|
|
170
|
+
typeof value.stop === "number" && Number.isInteger(value.stop) && value.stop > value.warn) {
|
|
171
|
+
config.lead_turn_budget = { warn: value.warn, stop: value.stop };
|
|
172
|
+
sources.lead_turn_budget = source;
|
|
143
173
|
}
|
|
144
174
|
else {
|
|
145
|
-
|
|
146
|
-
const value = typeof entry === "string" ? entry.trim() : "";
|
|
147
|
-
if (value === "") {
|
|
148
|
-
warnings.push("review_tags entry must be a non-empty tag; skipped.");
|
|
149
|
-
}
|
|
150
|
-
else if (!review_tags.includes(value)) {
|
|
151
|
-
review_tags.push(value);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
175
|
+
warn("lead_turn_budget must contain positive integer warn and stop values, with stop greater than warn; ignoring it.");
|
|
154
176
|
}
|
|
155
177
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.map((b) => b.trim());
|
|
162
|
-
if (branches && branches.length === raw.length) {
|
|
163
|
-
lead_branches = branches;
|
|
178
|
+
if (has("dashboard")) {
|
|
179
|
+
const value = root.dashboard;
|
|
180
|
+
if (value == null) {
|
|
181
|
+
config.dashboard = false;
|
|
182
|
+
sources.dashboard = source;
|
|
164
183
|
}
|
|
165
|
-
else {
|
|
166
|
-
|
|
184
|
+
else if (typeof value === "boolean") {
|
|
185
|
+
config.dashboard = value;
|
|
186
|
+
sources.dashboard = source;
|
|
167
187
|
}
|
|
188
|
+
else
|
|
189
|
+
warn(`dashboard must be true or false; ignoring "${String(value)}".`);
|
|
168
190
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
191
|
+
if (has("agents")) {
|
|
192
|
+
const value = root.agents;
|
|
193
|
+
if (value == null) {
|
|
194
|
+
config.agents = null;
|
|
195
|
+
sources.agents = source;
|
|
173
196
|
}
|
|
174
|
-
else {
|
|
175
|
-
|
|
197
|
+
else if (Array.isArray(value)) {
|
|
198
|
+
const known = harnessNames();
|
|
199
|
+
const valid = [];
|
|
200
|
+
for (const entry of value) {
|
|
201
|
+
const name = typeof entry === "string" ? entry.trim() : "";
|
|
202
|
+
if (!name)
|
|
203
|
+
warn("agents entry must be a non-empty harness name; skipped.");
|
|
204
|
+
else if (!known.includes(name))
|
|
205
|
+
warn(`agents: "${name}" is not a known harness (${known.join(", ")}); ignoring it.`);
|
|
206
|
+
else
|
|
207
|
+
valid.push(name);
|
|
208
|
+
}
|
|
209
|
+
config.agents = valid.length ? valid : null;
|
|
210
|
+
sources.agents = source;
|
|
176
211
|
}
|
|
212
|
+
else
|
|
213
|
+
warn("agents must be a list of harness names; ignoring it.");
|
|
177
214
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
215
|
+
if (has("lead_branches")) {
|
|
216
|
+
const value = root.lead_branches;
|
|
217
|
+
if (value == null) {
|
|
218
|
+
config.lead_branches = null;
|
|
219
|
+
sources.lead_branches = source;
|
|
182
220
|
}
|
|
183
|
-
else {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
warnings.push(`var "${key}" must be a scalar; skipped.`);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
vars[key] = String(value);
|
|
190
|
-
}
|
|
221
|
+
else if (Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim() !== "")) {
|
|
222
|
+
config.lead_branches = value.map((item) => item.trim());
|
|
223
|
+
sources.lead_branches = source;
|
|
191
224
|
}
|
|
225
|
+
else
|
|
226
|
+
warn("lead_branches must be a list of branch names; ignoring it.");
|
|
192
227
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
228
|
+
if (has("vars")) {
|
|
229
|
+
const value = root.vars;
|
|
230
|
+
if (value == null) {
|
|
231
|
+
for (const key of Object.keys(config.vars))
|
|
232
|
+
sources[`vars.${key}`] = source;
|
|
233
|
+
config.vars = {};
|
|
234
|
+
sources.vars = source;
|
|
198
235
|
}
|
|
199
|
-
else {
|
|
200
|
-
for (const [
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (value == null || typeof value !== "object") {
|
|
206
|
-
warnings.push(`Process "${name}" needs a command string; skipped.`);
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const p = value;
|
|
210
|
-
if (typeof p.command !== "string" || p.command.trim() === "") {
|
|
211
|
-
warnings.push(`Process "${name}" has no command; skipped.`);
|
|
212
|
-
continue;
|
|
236
|
+
else if (isMapping(value)) {
|
|
237
|
+
for (const [key, item] of Object.entries(value)) {
|
|
238
|
+
if (item == null) {
|
|
239
|
+
delete config.vars[key];
|
|
240
|
+
sources[`vars.${key}`] = source;
|
|
241
|
+
sources.vars = source;
|
|
213
242
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (p.visible != null) {
|
|
217
|
-
if (typeof p.visible === "boolean") {
|
|
218
|
-
visible = p.visible;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
warnings.push(`Process "${name}": visible must be true or false; ignoring "${String(p.visible)}".`);
|
|
222
|
-
}
|
|
243
|
+
else if (typeof item === "object") {
|
|
244
|
+
warn(`var "${key}" must be a scalar; skipped.`);
|
|
223
245
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
246
|
+
else {
|
|
247
|
+
config.vars[key] = String(item);
|
|
248
|
+
sources[`vars.${key}`] = source;
|
|
249
|
+
sources.vars = source;
|
|
229
250
|
}
|
|
230
|
-
processes[name] = {
|
|
231
|
-
command: p.command.trim(),
|
|
232
|
-
dir: typeof dirValue === "string" && dirValue.trim() !== "" ? dirValue.trim() : null,
|
|
233
|
-
auto_start: p.auto_start !== false,
|
|
234
|
-
visible,
|
|
235
|
-
env,
|
|
236
|
-
};
|
|
237
251
|
}
|
|
238
252
|
}
|
|
253
|
+
else
|
|
254
|
+
warn("vars must be a mapping of name to value; ignoring it.");
|
|
255
|
+
}
|
|
256
|
+
if (has("processes")) {
|
|
257
|
+
if (source === "global")
|
|
258
|
+
warn("global hive.yml cannot define `processes`; only project commands can be trusted.");
|
|
259
|
+
else if (root.processes != null) {
|
|
260
|
+
const processes = parseProcesses(root.processes, path, warnings);
|
|
261
|
+
if (processes != null) {
|
|
262
|
+
config.processes = processes;
|
|
263
|
+
sources.processes = source;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const key of ["attach", "autoAttach"]) {
|
|
268
|
+
if (source === "project" && has(key))
|
|
269
|
+
warn(`${key} is global-only; ignoring it in project hive.yml.`);
|
|
239
270
|
}
|
|
271
|
+
if (has("review_tags"))
|
|
272
|
+
warn("review_tags is no longer used by hive; remove it from hive.yml.");
|
|
273
|
+
}
|
|
274
|
+
export function resolveHiveConfig(projectPath) {
|
|
275
|
+
const global = readGlobalConfig();
|
|
276
|
+
const warnings = [...global.warnings];
|
|
277
|
+
const config = createBuiltInProjectYml();
|
|
278
|
+
const sources = Object.fromEntries(PROJECT_YML_KEYS.map((key) => [key, "built-in"]));
|
|
279
|
+
if (global.root)
|
|
280
|
+
applyLayer(global.root, globalConfigPath(), "global", config, sources, warnings);
|
|
281
|
+
let projectValid = false;
|
|
282
|
+
if (projectPath != null) {
|
|
283
|
+
const file = resolve(projectPath, "hive.yml");
|
|
284
|
+
const project = parseFile(file, warnings);
|
|
285
|
+
if (project) {
|
|
286
|
+
projectValid = true;
|
|
287
|
+
applyLayer(project, file, "project", config, sources, warnings);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const attachResult = resolveAttachSetting("attach", global.root, global.legacy, warnings);
|
|
291
|
+
const autoAttachResult = resolveAttachSetting("autoAttach", global.root, global.legacy, warnings);
|
|
240
292
|
return {
|
|
241
|
-
config:
|
|
242
|
-
lead,
|
|
243
|
-
placement,
|
|
244
|
-
layout,
|
|
245
|
-
profile,
|
|
246
|
-
agents,
|
|
247
|
-
review_tags,
|
|
248
|
-
lead_branches,
|
|
249
|
-
context_checkpoint_percent,
|
|
250
|
-
lead_turn_budget,
|
|
251
|
-
dashboard,
|
|
252
|
-
vars,
|
|
253
|
-
processes,
|
|
254
|
-
},
|
|
293
|
+
config: global.root || projectValid ? config : null,
|
|
255
294
|
warnings,
|
|
295
|
+
sources,
|
|
296
|
+
attach: attachResult,
|
|
297
|
+
autoAttach: autoAttachResult,
|
|
256
298
|
};
|
|
257
299
|
}
|
|
300
|
+
export function loadProjectYml(projectPath) {
|
|
301
|
+
return resolveHiveConfig(projectPath);
|
|
302
|
+
}
|
|
258
303
|
export function configHash(name, command, dir, env) {
|
|
259
304
|
return createHash("sha256")
|
|
260
305
|
.update(JSON.stringify([name, command, dir, env]))
|
package/dist/result.js
CHANGED
|
@@ -16,6 +16,23 @@ export function registrationNoticeText(notice) {
|
|
|
16
16
|
`project_select.`);
|
|
17
17
|
}
|
|
18
18
|
const reportedBuilds = new Set();
|
|
19
|
+
const pendingNotices = new WeakMap();
|
|
20
|
+
function addNotice(result, text, build) {
|
|
21
|
+
result.content.push({ type: "text", text });
|
|
22
|
+
const pending = pendingNotices.get(result) ?? { texts: [] };
|
|
23
|
+
pending.texts.push(text);
|
|
24
|
+
if (build)
|
|
25
|
+
pending.build = build;
|
|
26
|
+
pendingNotices.set(result, pending);
|
|
27
|
+
}
|
|
28
|
+
export function noticeFor(result) {
|
|
29
|
+
return pendingNotices.get(result)?.texts.join("\n");
|
|
30
|
+
}
|
|
31
|
+
export function releaseNotice(result) {
|
|
32
|
+
const build = pendingNotices.get(result)?.build;
|
|
33
|
+
if (build)
|
|
34
|
+
reportedBuilds.delete(build);
|
|
35
|
+
}
|
|
19
36
|
export async function appendRunningBuildNotice(result, session) {
|
|
20
37
|
try {
|
|
21
38
|
const change = runningBuildChange();
|
|
@@ -30,8 +47,8 @@ export async function appendRunningBuildNotice(result, session) {
|
|
|
30
47
|
}
|
|
31
48
|
if (reportedBuilds.has(change.disk.build_id))
|
|
32
49
|
return result;
|
|
33
|
-
result.content.push({ type: "text", text: runningBuildNotice(change, session) });
|
|
34
50
|
reportedBuilds.add(change.disk.build_id);
|
|
51
|
+
addNotice(result, runningBuildNotice(change, session), change.disk.build_id);
|
|
35
52
|
}
|
|
36
53
|
catch {
|
|
37
54
|
}
|
|
@@ -52,7 +69,7 @@ export async function run(fn) {
|
|
|
52
69
|
}
|
|
53
70
|
const notice = takeRegistrationNotice();
|
|
54
71
|
if (notice) {
|
|
55
|
-
result
|
|
72
|
+
addNotice(result, registrationNoticeText(notice));
|
|
56
73
|
}
|
|
57
74
|
return appendRunningBuildNotice(result);
|
|
58
75
|
}
|
package/dist/scheduler.js
CHANGED
|
@@ -1460,6 +1460,12 @@ function watchedStates(timer, snapshot) {
|
|
|
1460
1460
|
};
|
|
1461
1461
|
});
|
|
1462
1462
|
}
|
|
1463
|
+
function idleConditionMet(timer, snapshot) {
|
|
1464
|
+
const states = watchedStates(timer, snapshot);
|
|
1465
|
+
return timer.kind === "idle_any"
|
|
1466
|
+
? states.some((s) => s.gone || (s.idle && s.since != null && s.since >= timer.created_at))
|
|
1467
|
+
: states.length > 0 && states.every((s) => s.idle);
|
|
1468
|
+
}
|
|
1463
1469
|
async function maybeFireIdle(timer, snapshot, now, choices) {
|
|
1464
1470
|
const timedOut = timer.max_wait_at != null && timer.max_wait_at <= now;
|
|
1465
1471
|
if (isStandingWatch(timer)) {
|
|
@@ -1476,24 +1482,24 @@ async function maybeFireIdle(timer, snapshot, now, choices) {
|
|
|
1476
1482
|
return;
|
|
1477
1483
|
}
|
|
1478
1484
|
let ready = false;
|
|
1485
|
+
let idleMet = false;
|
|
1479
1486
|
if (timedOut) {
|
|
1480
1487
|
ready = true;
|
|
1488
|
+
if (snapshot !== null)
|
|
1489
|
+
idleMet = idleConditionMet(timer, snapshot);
|
|
1481
1490
|
}
|
|
1482
1491
|
else {
|
|
1483
1492
|
if (snapshot === null)
|
|
1484
1493
|
return;
|
|
1485
|
-
|
|
1486
|
-
ready =
|
|
1487
|
-
timer.kind === "idle_any"
|
|
1488
|
-
? states.some((s) => s.gone || (s.idle && s.since != null && s.since >= timer.created_at))
|
|
1489
|
-
: states.length > 0 && states.every((s) => s.idle);
|
|
1494
|
+
idleMet = idleConditionMet(timer, snapshot);
|
|
1495
|
+
ready = idleMet;
|
|
1490
1496
|
if (!ready)
|
|
1491
1497
|
noteBlockedWatched(timer, snapshot, choices);
|
|
1492
1498
|
}
|
|
1493
1499
|
if (ready) {
|
|
1494
1500
|
const decision = deliverable(timer, snapshot, choices);
|
|
1495
1501
|
if (decision.ok && claimOneShot(timer)) {
|
|
1496
|
-
await deliver(timer, timedOut ? "max wait reached" : "", choices, decision.typedSeen, decision.firstHeldAt);
|
|
1502
|
+
await deliver(timer, timedOut && !idleMet ? "max wait reached" : "", choices, decision.typedSeen, decision.firstHeldAt);
|
|
1497
1503
|
}
|
|
1498
1504
|
}
|
|
1499
1505
|
}
|
package/dist/spawn.js
CHANGED
|
@@ -16,6 +16,17 @@ export function withWindowClaim(claim) {
|
|
|
16
16
|
}
|
|
17
17
|
return db.transaction(claim).immediate();
|
|
18
18
|
}
|
|
19
|
+
export function storedLaunchOptions(value) {
|
|
20
|
+
if (value == null)
|
|
21
|
+
return { args: [], readOnly: false };
|
|
22
|
+
const stored = JSON.parse(value);
|
|
23
|
+
if (Array.isArray(stored))
|
|
24
|
+
return { args: stored, readOnly: false };
|
|
25
|
+
if (stored?.read_only === true && Array.isArray(stored.args)) {
|
|
26
|
+
return { args: stored.args, readOnly: true };
|
|
27
|
+
}
|
|
28
|
+
throw new Error("Invalid stored worker launch options");
|
|
29
|
+
}
|
|
19
30
|
export function splitTargetWindow(session, projectId, parentActor) {
|
|
20
31
|
const parent = db
|
|
21
32
|
.prepare("SELECT tmux_target, tmux_socket FROM agents WHERE actor_id = ? AND status = 'running' ORDER BY id DESC LIMIT 1")
|
|
@@ -30,7 +41,7 @@ export function splitTargetWindow(session, projectId, parentActor) {
|
|
|
30
41
|
export function buildEnvFlags(env) {
|
|
31
42
|
return Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
32
43
|
}
|
|
33
|
-
function agentIdentityEnv(actorId, name, projectPath) {
|
|
44
|
+
export function agentIdentityEnv(actorId, name, projectPath) {
|
|
34
45
|
return {
|
|
35
46
|
HIVE_AGENT_ID: actorId,
|
|
36
47
|
HIVE_AGENT_NAME: name,
|
|
@@ -152,9 +163,11 @@ export function launchAgent(spec) {
|
|
|
152
163
|
let info;
|
|
153
164
|
try {
|
|
154
165
|
info = db
|
|
155
|
-
.prepare("INSERT INTO agents (project_id, name, command, cwd, kind, parent_actor_id, tmux_socket, session_id, codex_home) " +
|
|
156
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
157
|
-
.run(spec.projectId, spec.name, typeof spec.commandString === "string" ? spec.commandString : "", spec.cwd, spec.kind, spec.parentActor, socket, spec.sessionId ?? "", spec.codexHome ?? ""
|
|
166
|
+
.prepare("INSERT INTO agents (project_id, name, command, cwd, kind, parent_actor_id, tmux_socket, session_id, codex_home, model, extra_args) " +
|
|
167
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
168
|
+
.run(spec.projectId, spec.name, typeof spec.commandString === "string" ? spec.commandString : "", spec.cwd, spec.kind, spec.parentActor, socket, spec.sessionId ?? "", spec.codexHome ?? "", spec.model ?? null, spec.readOnly
|
|
169
|
+
? JSON.stringify({ args: spec.extraArgs ?? [], read_only: true })
|
|
170
|
+
: spec.extraArgs ? JSON.stringify(spec.extraArgs) : null);
|
|
158
171
|
}
|
|
159
172
|
catch (e) {
|
|
160
173
|
throw asNameClash(e, spec.name);
|
package/dist/strictInput.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { noticeFor, releaseNotice } from "./result.js";
|
|
2
3
|
function isBuiltSchema(value) {
|
|
3
4
|
return typeof value === "object" && value !== null && ("_zod" in value || "_def" in value);
|
|
4
5
|
}
|
|
@@ -14,11 +15,15 @@ function strictSchemaFor(toolName, inputSchema) {
|
|
|
14
15
|
}
|
|
15
16
|
// The SDK requires structuredContent whenever outputSchema is declared but never populates it itself,
|
|
16
17
|
// so this stays scoped to declaring tools rather than attached blanket in src/result.ts's ok().
|
|
18
|
+
// Claude Code shows the model structuredContent and drops content for these tools, so a notice
|
|
19
|
+
// appended to content reaches it only as structuredContent.hive_notice.
|
|
17
20
|
function withStructuredContent(name, cb) {
|
|
18
21
|
return async (...args) => {
|
|
19
22
|
const result = (await cb(...args));
|
|
20
|
-
if (result.isError
|
|
23
|
+
if (result.isError)
|
|
21
24
|
return result;
|
|
25
|
+
if (result.structuredContent)
|
|
26
|
+
return withNotice(result);
|
|
22
27
|
const first = result.content[0];
|
|
23
28
|
const text = first?.type === "text" ? first.text : undefined;
|
|
24
29
|
let parsed;
|
|
@@ -30,8 +35,9 @@ function withStructuredContent(name, cb) {
|
|
|
30
35
|
}
|
|
31
36
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
32
37
|
result.structuredContent = parsed;
|
|
33
|
-
return result;
|
|
38
|
+
return withNotice(result);
|
|
34
39
|
}
|
|
40
|
+
releaseNotice(result);
|
|
35
41
|
return {
|
|
36
42
|
content: [
|
|
37
43
|
{
|
|
@@ -45,8 +51,23 @@ function withStructuredContent(name, cb) {
|
|
|
45
51
|
};
|
|
46
52
|
};
|
|
47
53
|
}
|
|
54
|
+
function withNotice(result) {
|
|
55
|
+
const notice = noticeFor(result);
|
|
56
|
+
if (notice && result.structuredContent)
|
|
57
|
+
result.structuredContent = { ...result.structuredContent, hive_notice: notice };
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function withNoticeField(outputSchema) {
|
|
61
|
+
return outputSchema === undefined || isBuiltSchema(outputSchema)
|
|
62
|
+
? outputSchema
|
|
63
|
+
: { ...outputSchema, hive_notice: z.string().optional() };
|
|
64
|
+
}
|
|
48
65
|
export function enforceStrictInput(server) {
|
|
49
66
|
const register = server.registerTool.bind(server);
|
|
50
|
-
server.registerTool = ((name, config, cb) => register(name, {
|
|
67
|
+
server.registerTool = ((name, config, cb) => register(name, {
|
|
68
|
+
...config,
|
|
69
|
+
inputSchema: strictSchemaFor(name, config.inputSchema),
|
|
70
|
+
outputSchema: withNoticeField(config.outputSchema),
|
|
71
|
+
}, (config.outputSchema ? withStructuredContent(name, cb) : cb)));
|
|
51
72
|
return server;
|
|
52
73
|
}
|