hive-lite 0.2.0 → 0.2.1
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/docs/cli-semantics.md
CHANGED
|
@@ -193,9 +193,9 @@ Saved handoffs are written under:
|
|
|
193
193
|
.hive/state/intents/<intent_id>/start-intent.json
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
The validator requires `type: hive-lite.start-intent`, `version: 1`, `status: ready_for_hive`, a safe `intent_id`,
|
|
196
|
+
The validator requires `type: hive-lite.start-intent`, `version: 1`, `status: ready_for_hive`, a safe `intent_id`, and the minimal Markdown sections `Original Idea`, `Agreed Direction`, and `User-Confirmed Decisions`. `Blocking Questions` is optional, but if present for a ready handoff it must be `None`.
|
|
197
197
|
It rejects execution-only content such as `Direct Writable`, final file scope, implementation plans, validation plans, files-to-modify sections, and direct instructions that an agent should edit specific files.
|
|
198
|
-
`show --json` includes a derived `findIntent` for `$hive-lite-start-prompt`.
|
|
198
|
+
`show --json` includes a derived `findIntent` for `$hive-lite-start-prompt`; it is target-oriented and derived from `Agreed Direction`.
|
|
199
199
|
The human-readable `intent save` output recommends starting the operator runner with `hive-lite operator run --intent <intent_id>` and also prints the equivalent saved handoff path form.
|
|
200
200
|
|
|
201
201
|
## `skills`
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hive-lite-discovery
|
|
3
|
-
description: Use this skill when a user
|
|
3
|
+
description: Use this skill when a user wants to discuss and clarify a product/code requirement before coding. It behaves like a no-code requirements conversation, may inspect the repository read-only to compare the idea with the current project, asks follow-up questions when the requirement is unclear, saves a short Start Intent Handoff with `hive-lite intent save`, and stops before code execution, Project Map repair, or operator-run automation.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Hive Lite Discovery
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
9
9
|
|
|
10
|
-
Turn a
|
|
10
|
+
Turn a requirements conversation into a durable Start Intent Handoff that is ready for Hive Lite to search.
|
|
11
11
|
|
|
12
12
|
Discovery makes an idea ready for Hive Lite. Start makes that intent ready for code.
|
|
13
13
|
|
|
14
|
+
The mental model is simple: the user is saying, "do not write code yet; discuss this requirement with me." If the requirement is unclear, ask natural follow-up questions. If repo context would help, inspect it read-only. When the direction is clear enough, save the agreed result for the next skill.
|
|
15
|
+
|
|
14
16
|
## Boundaries
|
|
15
17
|
|
|
16
18
|
- This skill is pre-Hive and pre-operator.
|
|
@@ -18,16 +20,15 @@ Discovery makes an idea ready for Hive Lite. Start makes that intent ready for c
|
|
|
18
20
|
- Do not run `hive-lite find`, `check`, `validate`, `accept`, `operator run`, or coding-agent execution.
|
|
19
21
|
- Do not produce `Direct Writable`, final file scope, an implementation plan, a validation plan, files to modify, or instructions that an agent should edit specific files.
|
|
20
22
|
- Do not turn vague ideas into coding permission. The handoff is search-ready, not execution-ready.
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
+
- Keep the handoff plain and short. Do not add sections merely to sound complete.
|
|
24
|
+
- Optional non-goals, acceptance signals, unknowns, or hints are for unusual cases only; omit them by default.
|
|
23
25
|
|
|
24
26
|
## Completion Contract
|
|
25
27
|
|
|
26
28
|
End with exactly one clear outcome:
|
|
27
29
|
|
|
28
30
|
- `ready_for_hive`: a valid Start Intent Handoff is saved, `intent_id` and artifact paths are reported, and the next terminal `hive-lite operator run --intent ...` command is shown.
|
|
29
|
-
- `needs_user_decision`: one to three
|
|
30
|
-
- `skip_discovery`: the requirement is already clear; provide the exact `$hive-lite-start-prompt` handoff and stop.
|
|
31
|
+
- `needs_user_decision`: one to three product questions are asked because the direction is not clear enough; no `ready_for_hive` handoff is saved.
|
|
31
32
|
- `blocked`: CLI, repo, or artifact validation prevents saving; report the exact blocker and next action.
|
|
32
33
|
|
|
33
34
|
Do not produce a coding prompt, implementation plan, or file-edit permission as a discovery outcome.
|
|
@@ -38,20 +39,21 @@ Gather only enough facts to make the idea ready for Hive Lite search.
|
|
|
38
39
|
|
|
39
40
|
Stop searching once these are known:
|
|
40
41
|
|
|
41
|
-
- the user-approved product direction
|
|
42
|
+
- the user-approved product direction;
|
|
42
43
|
- confirmed product decisions versus open blocking questions;
|
|
43
|
-
-
|
|
44
|
+
- whether the request is best understood as a bug, new capability, behavior change, or replacement of existing behavior;
|
|
45
|
+
- only the minimal repo vocabulary or existing capability hints needed to phrase the direction in project terms;
|
|
44
46
|
- whether the handoff can be saved as `status: ready_for_hive`.
|
|
45
47
|
|
|
46
48
|
Do not inspect the repo to choose final files, architecture, validation commands, or Direct Writable scope. Those belong to `$hive-lite-start-prompt` and the Context Packet.
|
|
47
49
|
|
|
48
50
|
## Workflow
|
|
49
51
|
|
|
50
|
-
1. Understand the
|
|
51
|
-
2. Inspect the repo read-only only when project vocabulary or
|
|
52
|
-
3. Ask only
|
|
52
|
+
1. Understand the user's starting idea and preserve it as provenance.
|
|
53
|
+
2. Inspect the repo read-only only when project vocabulary, current behavior, or the difference between the idea and the current project needs clarification. Prefer `rg` and `rg --files`; do not write files during exploration.
|
|
54
|
+
3. Ask follow-up questions only when the answer affects the product direction. If product decisions remain unresolved, do not save a `ready_for_hive` handoff.
|
|
53
55
|
4. Separate confirmed decisions from ideas that were merely discussed.
|
|
54
|
-
5. Draft a Start Intent Handoff using the required structure below.
|
|
56
|
+
5. Draft a short Start Intent Handoff using the required structure below.
|
|
55
57
|
6. Save it with one of:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
@@ -59,7 +61,8 @@ hive-lite intent save --from <draft-start-intent.md> --json
|
|
|
59
61
|
hive-lite intent save --stdin --json
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
7. If the CLI reports errors, revise the draft
|
|
64
|
+
7. If the CLI reports errors, revise the draft only when the error is about this draft's content. Treat execution-only content errors as real scope violations, not formatting nits.
|
|
65
|
+
- If the CLI reports `MISSING_SECTION` for optional legacy sections such as `Non-Goals`, `Acceptance Signals`, `Unknowns For Hive Lite To Resolve`, `Pre-Hive Observations / Hints`, or `Blocking Questions`, the installed CLI is stale relative to this skill. Do not add those sections just to satisfy the old CLI. Stop and tell the user to update or reinstall Hive Lite CLI, then rerun discovery.
|
|
63
66
|
8. Return the `intent_id`, `start-intent.md` path, and the next terminal command for `hive-lite operator run --intent`.
|
|
64
67
|
|
|
65
68
|
## Required Handoff
|
|
@@ -90,37 +93,32 @@ created_at: 2026-05-15T00:00:00+08:00
|
|
|
90
93
|
|
|
91
94
|
- <Decision actually confirmed by the user.>
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- <Scope boundary.>
|
|
96
|
-
- Do not commit to exact files, Direct Writable scope, or implementation architecture before Hive Lite context discovery.
|
|
97
|
-
|
|
98
|
-
## Acceptance Signals
|
|
99
|
-
|
|
100
|
-
- <User-level signal that the direction would feel complete. This is not a validation plan.>
|
|
101
|
-
|
|
102
|
-
## Unknowns For Hive Lite To Resolve
|
|
103
|
-
|
|
104
|
-
- <Codebase/context question, such as where an existing provider abstraction lives.>
|
|
96
|
+
```
|
|
105
97
|
|
|
106
|
-
|
|
98
|
+
The sections above are enough for most handoffs. Add optional sections only when they remove real ambiguity:
|
|
107
99
|
|
|
108
|
-
|
|
100
|
+
- `## Blocking Questions`: use only when the handoff is not ready; for `ready_for_hive`, omit it or set it to `None`.
|
|
101
|
+
- `## Non-Goals`: use only for a user-confirmed scope boundary that would otherwise be easy to misunderstand.
|
|
102
|
+
- `## Pre-Hive Observations / Hints`: use only for non-authoritative repo observations that materially help wording.
|
|
103
|
+
- Avoid `## Acceptance Signals` and `## Unknowns For Hive Lite To Resolve` by default. They easily become noisy pseudo-specs.
|
|
109
104
|
|
|
110
|
-
|
|
105
|
+
## Target-Only Routing Text
|
|
111
106
|
|
|
112
|
-
|
|
107
|
+
`Agreed Direction` is used to derive the Hive Lite `findIntent`. Keep it target-only.
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
- Put only the capability or behavior that should be searched next.
|
|
110
|
+
- Do not name reference, existing, non-regression, or "do not replace" capabilities in `Agreed Direction`.
|
|
111
|
+
- If the user mentions an existing capability only as context, keep that in `Original Idea`.
|
|
112
|
+
- If an additive/non-replacement boundary is important, phrase it generically in `User-Confirmed Decisions`, such as `This is additive, not a replacement of existing LLM proxy capability`, without naming unrelated providers.
|
|
113
|
+
- Before saving, mentally check whether `Agreed Direction` could match multiple providers, products, modules, or target identities. If yes, rewrite it around the intended target only.
|
|
116
114
|
|
|
117
115
|
## Validation Rules
|
|
118
116
|
|
|
119
|
-
- A `ready_for_hive` handoff must have `Blocking Questions`
|
|
120
|
-
-
|
|
117
|
+
- A `ready_for_hive` handoff must have no unresolved product questions. If `Blocking Questions` exists, it must be `None`.
|
|
118
|
+
- Do not invent non-goals, acceptance criteria, codebase unknowns, or hints just to fill a template.
|
|
121
119
|
- Put possible file paths only in `Pre-Hive Observations / Hints`, and phrase them as non-authoritative hints.
|
|
122
120
|
- If the user asks for implementation planning, explain that `$hive-lite-start-prompt` and the Context Packet define execution boundaries after discovery.
|
|
123
|
-
- If the
|
|
121
|
+
- If the requirement is already clear, save a short handoff without extra questions. Do not skip to `$hive-lite-start-prompt` unless the user explicitly asks to bypass discovery.
|
|
124
122
|
|
|
125
123
|
## Final Output
|
|
126
124
|
|
|
@@ -42,7 +42,7 @@ If the invocation includes `Step result path`, `Artifacts directory`, and `Step-
|
|
|
42
42
|
- Write copyable artifacts under the provided `Artifacts directory`, then write `step-result.json` to the exact `Step result path`.
|
|
43
43
|
- For `prompt_ready`, write the coding prompt artifact and set `recommended_action` to `copy_coding_prompt`.
|
|
44
44
|
- For `split_required`, write a ready-phase handoff prompt artifact and set `recommended_action` to `start_ready_phase`.
|
|
45
|
-
- For map-maintainer handoffs, use `map_update_required` with `recommended_action: map_update_required`.
|
|
45
|
+
- For map-maintainer handoffs or map repairs that must be committed/stashed before continuing, write `Artifacts directory/map-update-required.md`, use `map_update_required` with `recommended_action: map_update_required`, and set `artifacts.map_update_required_path`.
|
|
46
46
|
- For setup, preflight, validation, or irrecoverable start blockers, use `blocked` with `recommended_action: revise_required`, or `failed` for unexpected tool/runtime failures.
|
|
47
47
|
- After writing the structured result, stop. Do not wait for human input inside the operator-agent session.
|
|
48
48
|
|
|
@@ -179,12 +179,13 @@ hive-lite intent validate <intent_id_or_path> --json
|
|
|
179
179
|
hive-lite intent show <intent_id_or_path> --json
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
Use the JSON output for decisions. Refuse and send the user back to `$hive-lite-discovery` if validation is not valid, `status` is not `ready_for_hive`, or `fields.blockingQuestions` is not `None`.
|
|
182
|
+
Use the JSON output for decisions. Refuse and send the user back to `$hive-lite-discovery` if validation is not valid, `status` is not `ready_for_hive`, or `fields.blockingQuestions` exists and is not `None`.
|
|
183
183
|
|
|
184
184
|
For a valid handoff:
|
|
185
185
|
|
|
186
|
-
- Set `originalRequirement` from the Start Intent Handoff title
|
|
187
|
-
- Set `findIntent` from `show.findIntent` when present. If unavailable, derive it from `Agreed Direction
|
|
186
|
+
- Set `originalRequirement` from the Start Intent Handoff title, `fields.agreedDirection`, and `fields.userConfirmedDecisions`; keep `fields.originalIdea` as provenance only.
|
|
187
|
+
- Set `findIntent` from `show.findIntent` when present. If unavailable, derive it only from `Agreed Direction`.
|
|
188
|
+
- Treat `User-Confirmed Decisions` as coding-prompt requirement context, not routing text for `hive-lite find`.
|
|
188
189
|
- Treat `Pre-Hive Observations / Hints` as non-authoritative hints only. Hive Lite must verify them through Project Map and Context Packet discovery.
|
|
189
190
|
- Do not add Direct Writable files, final file scope, implementation plan, validation plan, or files to modify from the handoff. Those are execution boundaries produced later by `hive-lite find`.
|
|
190
191
|
- Do not ask new discovery questions inside this skill. If product questions are still unresolved, stop with an explicit `$hive-lite-discovery` handoff.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -80,8 +80,8 @@ Usage:
|
|
|
80
80
|
hive-lite intent save --stdin --json
|
|
81
81
|
hive-lite intent validate <intent_id_or_path> --json
|
|
82
82
|
hive-lite intent show <intent_id_or_path> --json
|
|
83
|
-
hive-lite operator run "<clear requirement>" [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
|
|
84
|
-
hive-lite operator run --intent <intent_id_or_path> [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
|
|
83
|
+
hive-lite operator run "<clear requirement>" [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file] [--operator-agent-timeout-ms 600000]
|
|
84
|
+
hive-lite operator run --intent <intent_id_or_path> [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file] [--operator-agent-timeout-ms 600000]
|
|
85
85
|
hive-lite find "<intent>" [--area <id>] [--max-files 8] [--explain] [--json]
|
|
86
86
|
hive-lite check [changeId] [--context ctx_id] [--json]
|
|
87
87
|
hive-lite validate [changeId] [--cmd "..."] [--profile <id>]
|
|
@@ -997,6 +997,7 @@ async function main(argv) {
|
|
|
997
997
|
intent: parsed.flags.intent || null,
|
|
998
998
|
operatorAgentCommand: parsed.flags.operatorAgentCommand || parsed.flags.agentCommand,
|
|
999
999
|
operatorAgentMode: parsed.flags.operatorAgentMode || parsed.flags.agentMode,
|
|
1000
|
+
operatorAgentTimeoutMs: parsed.flags.operatorAgentTimeoutMs || parsed.flags.agentTimeoutMs,
|
|
1000
1001
|
}, {
|
|
1001
1002
|
stdin: process.stdin,
|
|
1002
1003
|
stdout: process.stdout,
|
package/src/lib/intent.js
CHANGED
|
@@ -6,41 +6,47 @@ const { parseYaml } = require('./yaml');
|
|
|
6
6
|
const SCHEMA_VERSION = 'hive.start_intent.v1';
|
|
7
7
|
const INTENT_ID_PATTERN = /^int_[a-z0-9][a-z0-9_-]*$/;
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const INTENT_SECTIONS = [
|
|
10
10
|
{
|
|
11
11
|
key: 'originalIdea',
|
|
12
12
|
title: 'Original Idea',
|
|
13
13
|
aliases: ['original idea'],
|
|
14
|
+
required: true,
|
|
14
15
|
allowNone: false,
|
|
15
16
|
},
|
|
16
17
|
{
|
|
17
18
|
key: 'agreedDirection',
|
|
18
19
|
title: 'Agreed Direction',
|
|
19
20
|
aliases: ['agreed direction'],
|
|
21
|
+
required: true,
|
|
20
22
|
allowNone: false,
|
|
21
23
|
},
|
|
22
24
|
{
|
|
23
25
|
key: 'userConfirmedDecisions',
|
|
24
26
|
title: 'User-Confirmed Decisions',
|
|
25
27
|
aliases: ['user confirmed decisions', 'user-confirmed decisions'],
|
|
28
|
+
required: true,
|
|
26
29
|
allowNone: true,
|
|
27
30
|
},
|
|
28
31
|
{
|
|
29
32
|
key: 'nonGoals',
|
|
30
33
|
title: 'Non-Goals',
|
|
31
34
|
aliases: ['non goals', 'non-goals'],
|
|
35
|
+
required: false,
|
|
32
36
|
allowNone: false,
|
|
33
37
|
},
|
|
34
38
|
{
|
|
35
39
|
key: 'acceptanceSignals',
|
|
36
40
|
title: 'Acceptance Signals',
|
|
37
41
|
aliases: ['acceptance signals'],
|
|
42
|
+
required: false,
|
|
38
43
|
allowNone: false,
|
|
39
44
|
},
|
|
40
45
|
{
|
|
41
46
|
key: 'unknownsForHiveLiteToResolve',
|
|
42
47
|
title: 'Unknowns For Hive Lite To Resolve',
|
|
43
48
|
aliases: ['unknowns for hive lite to resolve'],
|
|
49
|
+
required: false,
|
|
44
50
|
allowNone: false,
|
|
45
51
|
},
|
|
46
52
|
{
|
|
@@ -54,18 +60,20 @@ const REQUIRED_SECTIONS = [
|
|
|
54
60
|
'pre hive hints',
|
|
55
61
|
'pre-hive hints',
|
|
56
62
|
],
|
|
63
|
+
required: false,
|
|
57
64
|
allowNone: true,
|
|
58
65
|
},
|
|
59
66
|
{
|
|
60
67
|
key: 'blockingQuestions',
|
|
61
68
|
title: 'Blocking Questions',
|
|
62
69
|
aliases: ['blocking questions'],
|
|
70
|
+
required: false,
|
|
63
71
|
allowNone: true,
|
|
64
72
|
},
|
|
65
73
|
];
|
|
66
74
|
|
|
67
75
|
const SECTION_BY_NORMALIZED = new Map();
|
|
68
|
-
for (const section of
|
|
76
|
+
for (const section of INTENT_SECTIONS) {
|
|
69
77
|
for (const alias of section.aliases) SECTION_BY_NORMALIZED.set(normalizeHeading(alias), section);
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -286,7 +294,7 @@ function validateSections(parsed, frontmatter, errors, warnings) {
|
|
|
286
294
|
}
|
|
287
295
|
}
|
|
288
296
|
|
|
289
|
-
for (const required of
|
|
297
|
+
for (const required of INTENT_SECTIONS.filter((section) => section.required)) {
|
|
290
298
|
const section = parsed.sections[required.key];
|
|
291
299
|
if (!section) {
|
|
292
300
|
addError(errors, 'MISSING_SECTION', `Missing required section: ${required.title}.`, { section: required.title });
|
|
@@ -307,11 +315,6 @@ function validateSections(parsed, frontmatter, errors, warnings) {
|
|
|
307
315
|
const unknowns = parsed.sections.unknownsForHiveLiteToResolve;
|
|
308
316
|
if (unknowns) {
|
|
309
317
|
const items = contentItems(unknowns);
|
|
310
|
-
if (items.length === 0) {
|
|
311
|
-
addError(errors, 'EMPTY_CODEBASE_UNKNOWNS', 'Unknowns For Hive Lite To Resolve must list codebase/context unknowns.', {
|
|
312
|
-
section: 'Unknowns For Hive Lite To Resolve',
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
318
|
for (const item of items) {
|
|
316
319
|
if (PRODUCT_DECISION_PATTERN.test(item)) {
|
|
317
320
|
addError(errors, 'PRODUCT_DECISION_IN_UNKNOWNS', 'Product decisions belong in User-Confirmed Decisions or Blocking Questions, not Unknowns For Hive Lite To Resolve.', {
|
|
@@ -367,10 +370,6 @@ function validateExecutionContent(markdown, parsed, errors, warnings) {
|
|
|
367
370
|
function deriveFindIntent(fields) {
|
|
368
371
|
const parts = [
|
|
369
372
|
['Agreed Direction', fields.agreedDirection],
|
|
370
|
-
['User-Confirmed Decisions', fields.userConfirmedDecisions],
|
|
371
|
-
['Non-Goals', fields.nonGoals],
|
|
372
|
-
['Acceptance Signals', fields.acceptanceSignals],
|
|
373
|
-
['Codebase Unknowns For Hive Lite To Resolve', fields.unknownsForHiveLiteToResolve],
|
|
374
373
|
].filter(([, value]) => value && !isNone(value));
|
|
375
374
|
|
|
376
375
|
return parts.map(([label, value]) => `${label}:\n${value}`).join('\n\n');
|
|
@@ -380,7 +379,7 @@ function parseStartIntentMarkdown(markdown) {
|
|
|
380
379
|
const front = parseFrontmatter(markdown);
|
|
381
380
|
const parsed = parseMarkdownSections(front.body);
|
|
382
381
|
const fields = {};
|
|
383
|
-
for (const section of
|
|
382
|
+
for (const section of INTENT_SECTIONS) {
|
|
384
383
|
fields[section.key] = parsed.sections[section.key] ? parsed.sections[section.key].content : '';
|
|
385
384
|
}
|
|
386
385
|
|
package/src/lib/operator.js
CHANGED
|
@@ -49,8 +49,11 @@ const ARTIFACT_FILENAMES = {
|
|
|
49
49
|
review_packet_path: 'review-packet.md',
|
|
50
50
|
handoff_prompt_path: 'handoff-prompt.md',
|
|
51
51
|
start_intent_path: 'start-intent.md',
|
|
52
|
+
map_update_required_path: 'map-update-required.md',
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
const DEFAULT_AGENT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
56
|
+
|
|
54
57
|
function rel(root, file) {
|
|
55
58
|
return path.relative(root, file).replace(/\\/g, '/');
|
|
56
59
|
}
|
|
@@ -99,6 +102,19 @@ function splitCommand(commandText) {
|
|
|
99
102
|
return parts;
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
function parsePositiveInteger(value, label) {
|
|
106
|
+
if (value === undefined || value === null || value === '') return null;
|
|
107
|
+
const parsed = Number(value);
|
|
108
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
109
|
+
throw new Error(`${label} must be a non-negative integer number of milliseconds.`);
|
|
110
|
+
}
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function firstConfigured(values) {
|
|
115
|
+
return values.find((value) => value !== undefined && value !== null && value !== '');
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
function resolveAgentConfig(options = {}, env = process.env) {
|
|
103
119
|
const commandText = options.operatorAgentCommand
|
|
104
120
|
|| options.agentCommand
|
|
@@ -120,11 +136,25 @@ function resolveAgentConfig(options = {}, env = process.env) {
|
|
|
120
136
|
if (!['stdin', 'prompt_file'].includes(mode)) {
|
|
121
137
|
throw new Error(`Unsupported operator agent mode: ${mode}. Use stdin or prompt_file.`);
|
|
122
138
|
}
|
|
139
|
+
const commandName = path.basename(argv[0]);
|
|
140
|
+
if (mode === 'stdin' && commandName === 'codex' && !argv.slice(1).some((arg) => arg === 'exec' || arg === 'e')) {
|
|
141
|
+
throw new Error('Codex interactive CLI requires a terminal when stdin is piped. Use HIVE_LITE_OPERATOR_AGENT_COMMAND="codex -a never -s workspace-write exec" with HIVE_LITE_OPERATOR_AGENT_MODE="stdin", or pass --operator-agent-command "codex -a never -s workspace-write exec".');
|
|
142
|
+
}
|
|
143
|
+
const timeoutMs = parsePositiveInteger(
|
|
144
|
+
firstConfigured([
|
|
145
|
+
options.operatorAgentTimeoutMs,
|
|
146
|
+
options.agentTimeoutMs,
|
|
147
|
+
env.HIVE_LITE_OPERATOR_AGENT_TIMEOUT_MS,
|
|
148
|
+
env.HIVE_OPERATOR_AGENT_TIMEOUT_MS,
|
|
149
|
+
]),
|
|
150
|
+
'operator agent timeout',
|
|
151
|
+
) ?? DEFAULT_AGENT_TIMEOUT_MS;
|
|
123
152
|
return {
|
|
124
153
|
commandText,
|
|
125
154
|
command: argv[0],
|
|
126
155
|
args: argv.slice(1),
|
|
127
156
|
mode,
|
|
157
|
+
timeoutMs,
|
|
128
158
|
};
|
|
129
159
|
}
|
|
130
160
|
|
|
@@ -305,6 +335,26 @@ function baseInvocation(run, paths, skill, body) {
|
|
|
305
335
|
'- accepted',
|
|
306
336
|
'- done',
|
|
307
337
|
'',
|
|
338
|
+
'Required result shapes:',
|
|
339
|
+
'- For `map_update_required`, write a human-readable artifact first, then write `step-result.json` like:',
|
|
340
|
+
'```json',
|
|
341
|
+
JSON.stringify({
|
|
342
|
+
schema_version: STEP_RESULT_SCHEMA_VERSION,
|
|
343
|
+
skill,
|
|
344
|
+
status: 'map_update_required',
|
|
345
|
+
recommended_action: 'map_update_required',
|
|
346
|
+
artifacts: {
|
|
347
|
+
map_update_required_path: rel(run.root, path.join(paths.artifactsDir, 'map-update-required.md')),
|
|
348
|
+
},
|
|
349
|
+
map_update: {
|
|
350
|
+
changed_files: ['.hive/map/areas.yaml'],
|
|
351
|
+
reason: 'Project Map changed and must be committed or stashed before continuing.',
|
|
352
|
+
},
|
|
353
|
+
}, null, 2),
|
|
354
|
+
'```',
|
|
355
|
+
'- For `blocked`, write `recommended_action: revise_required` and include `blockers` with concrete next steps.',
|
|
356
|
+
'- For `failed`, use only unexpected tool/runtime failures that prevent a normal structured result.',
|
|
357
|
+
'',
|
|
308
358
|
'Artifact paths in step-result.json must be under .hive/state/operator/.',
|
|
309
359
|
'',
|
|
310
360
|
].join('\n');
|
|
@@ -506,6 +556,20 @@ function runSubprocess(command, args, options) {
|
|
|
506
556
|
const stdout = fs.createWriteStream(options.stdoutPath);
|
|
507
557
|
const stderr = fs.createWriteStream(options.stderrPath);
|
|
508
558
|
let spawnError = null;
|
|
559
|
+
let timedOut = false;
|
|
560
|
+
let killTimer = null;
|
|
561
|
+
const timeoutMs = Number.isInteger(options.timeoutMs) ? options.timeoutMs : DEFAULT_AGENT_TIMEOUT_MS;
|
|
562
|
+
const timeoutTimer = timeoutMs > 0
|
|
563
|
+
? setTimeout(() => {
|
|
564
|
+
timedOut = true;
|
|
565
|
+
child.kill('SIGTERM');
|
|
566
|
+
killTimer = setTimeout(() => {
|
|
567
|
+
child.kill('SIGKILL');
|
|
568
|
+
}, Math.min(2000, timeoutMs));
|
|
569
|
+
if (killTimer.unref) killTimer.unref();
|
|
570
|
+
}, timeoutMs)
|
|
571
|
+
: null;
|
|
572
|
+
if (timeoutTimer && timeoutTimer.unref) timeoutTimer.unref();
|
|
509
573
|
child.on('error', (error) => {
|
|
510
574
|
spawnError = error;
|
|
511
575
|
});
|
|
@@ -517,9 +581,11 @@ function runSubprocess(command, args, options) {
|
|
|
517
581
|
child.stdin.end();
|
|
518
582
|
}
|
|
519
583
|
child.on('close', (code, signal) => {
|
|
584
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
585
|
+
if (killTimer) clearTimeout(killTimer);
|
|
520
586
|
stdout.end();
|
|
521
587
|
stderr.end();
|
|
522
|
-
resolve({ code, signal, error: spawnError });
|
|
588
|
+
resolve({ code, signal, error: spawnError, timedOut, timeoutMs });
|
|
523
589
|
});
|
|
524
590
|
});
|
|
525
591
|
}
|
|
@@ -545,6 +611,7 @@ async function runAgentStep(run, baseStep, skill, input, agentConfig, io) {
|
|
|
545
611
|
}
|
|
546
612
|
|
|
547
613
|
io.stdout.write('[OK] agent session started\n');
|
|
614
|
+
io.stdout.write(`[..] waiting for structured result (logs: ${rel(run.root, paths.stdoutPath)}, ${rel(run.root, paths.stderrPath)})\n`);
|
|
548
615
|
const before = statusSnapshot(run.root);
|
|
549
616
|
const proc = await runSubprocess(agentConfig.command, args, {
|
|
550
617
|
cwd: run.root,
|
|
@@ -554,8 +621,8 @@ async function runAgentStep(run, baseStep, skill, input, agentConfig, io) {
|
|
|
554
621
|
resultPath: paths.resultPath,
|
|
555
622
|
artifactsDir: paths.artifactsDir,
|
|
556
623
|
runId: run.id,
|
|
624
|
+
timeoutMs: agentConfig.timeoutMs,
|
|
557
625
|
});
|
|
558
|
-
io.stdout.write('[..] waiting for result\n');
|
|
559
626
|
|
|
560
627
|
const after = statusSnapshot(run.root);
|
|
561
628
|
validateSkillAuthority(run.root, skill, before, after);
|
|
@@ -563,6 +630,9 @@ async function runAgentStep(run, baseStep, skill, input, agentConfig, io) {
|
|
|
563
630
|
if (proc.error) {
|
|
564
631
|
throw new Error(`Operator agent failed to start: ${proc.error.message}`);
|
|
565
632
|
}
|
|
633
|
+
if (proc.timedOut) {
|
|
634
|
+
throw new Error(`Operator agent timed out after ${proc.timeoutMs}ms before completing the step. Transcript logs: ${rel(run.root, paths.stdoutPath)}, ${rel(run.root, paths.stderrPath)}. Expected result: ${rel(run.root, paths.resultPath)}`);
|
|
635
|
+
}
|
|
566
636
|
if (proc.code !== 0) {
|
|
567
637
|
throw new Error(`Operator agent exited with code ${proc.code}. Transcript logs: ${rel(run.root, paths.stdoutPath)}, ${rel(run.root, paths.stderrPath)}`);
|
|
568
638
|
}
|
|
@@ -597,7 +667,7 @@ function validateArtifactHashes(root, result, pathFields) {
|
|
|
597
667
|
|
|
598
668
|
function validateAndLoadStepResult(run, paths, expectedSkill) {
|
|
599
669
|
if (!exists(paths.resultPath)) {
|
|
600
|
-
throw new Error(`Operator step failed: missing step-result.json at ${rel(run.root, paths.resultPath)}`);
|
|
670
|
+
throw new Error(`Operator step failed: missing step-result.json at ${rel(run.root, paths.resultPath)}. Transcript logs: ${rel(run.root, paths.stdoutPath)}, ${rel(run.root, paths.stderrPath)}`);
|
|
601
671
|
}
|
|
602
672
|
let result;
|
|
603
673
|
try {
|
|
@@ -668,6 +738,10 @@ function validateRequiredArtifacts(result) {
|
|
|
668
738
|
throw new Error('Operator step failed: split_required requires split.ready_phase.handoff_prompt_path');
|
|
669
739
|
}
|
|
670
740
|
}
|
|
741
|
+
if (result.status === 'map_update_required') {
|
|
742
|
+
const mapUpdatePath = result.artifacts && result.artifacts.map_update_required_path;
|
|
743
|
+
if (!mapUpdatePath) throw new Error('Operator step failed: map_update_required requires artifacts.map_update_required_path');
|
|
744
|
+
}
|
|
671
745
|
}
|
|
672
746
|
|
|
673
747
|
function syncCurrentArtifacts(root, result, resultPath) {
|
|
@@ -947,8 +1021,11 @@ async function renderBlocked(run, result, agentConfig, input, io) {
|
|
|
947
1021
|
return runAgentStep(run, 'finish', SKILLS.finish, {}, agentConfig, io);
|
|
948
1022
|
}
|
|
949
1023
|
|
|
950
|
-
async function renderMapUpdateRequired(result, input, io) {
|
|
1024
|
+
async function renderMapUpdateRequired(run, result, input, io) {
|
|
951
1025
|
io.stdout.write('\nMap Update Required\n\n');
|
|
1026
|
+
const details = displayArtifact(run.root, result, 'map_update_required_path');
|
|
1027
|
+
io.stdout.write('Details\n');
|
|
1028
|
+
io.stdout.write(` ${details}\n\n`);
|
|
952
1029
|
const changed = result.map_update && Array.isArray(result.map_update.changed_files)
|
|
953
1030
|
? result.map_update.changed_files
|
|
954
1031
|
: [];
|
|
@@ -960,10 +1037,22 @@ async function renderMapUpdateRequired(result, input, io) {
|
|
|
960
1037
|
io.stdout.write('Reason\n');
|
|
961
1038
|
io.stdout.write(' Project Map was updated while preparing this requirement.\n');
|
|
962
1039
|
io.stdout.write(' Commit or stash the map update before generating the coding prompt.\n\n');
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1040
|
+
for (;;) {
|
|
1041
|
+
io.stdout.write('Actions\n');
|
|
1042
|
+
io.stdout.write(' [o] Open details\n');
|
|
1043
|
+
io.stdout.write(' [q] Quit\n');
|
|
1044
|
+
const choice = (await input.ask('> ', 'q')).trim().toLowerCase();
|
|
1045
|
+
if (choice === 'o') {
|
|
1046
|
+
const file = artifactPath(run.root, result, 'map_update_required_path');
|
|
1047
|
+
const text = readText(file);
|
|
1048
|
+
io.stdout.write('\n');
|
|
1049
|
+
io.stdout.write(text);
|
|
1050
|
+
if (!text.endsWith('\n')) io.stdout.write('\n');
|
|
1051
|
+
io.stdout.write('\n');
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
967
1056
|
}
|
|
968
1057
|
|
|
969
1058
|
async function handleStepResult(run, result, agentConfig, input, io) {
|
|
@@ -984,7 +1073,7 @@ async function handleStepResult(run, result, agentConfig, input, io) {
|
|
|
984
1073
|
} else if (current.status === 'blocked' || current.recommended_action === 'revise_required') {
|
|
985
1074
|
current = await renderBlocked(run, current, agentConfig, input, io);
|
|
986
1075
|
} else if (current.status === 'map_update_required') {
|
|
987
|
-
current = await renderMapUpdateRequired(current, input, io);
|
|
1076
|
+
current = await renderMapUpdateRequired(run, current, input, io);
|
|
988
1077
|
} else if (current.status === 'accepted' || current.status === 'done') {
|
|
989
1078
|
io.stdout.write('\nOperator step complete.\n');
|
|
990
1079
|
current = null;
|