planflow-ai 1.3.4 → 1.4.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/.claude/commands/create-plan.md +11 -0
- package/.claude/commands/discovery-plan.md +12 -0
- package/.claude/commands/execute-plan.md +114 -23
- package/.claude/commands/flow.md +30 -5
- package/.claude/commands/resume-work.md +261 -0
- package/.claude/commands/review-code.md +11 -0
- package/.claude/commands/review-pr.md +11 -0
- package/.claude/resources/core/_index.md +45 -2
- package/.claude/resources/core/atomic-commits.md +380 -0
- package/.claude/resources/core/autopilot-mode.md +3 -2
- package/.claude/resources/core/compaction-guide.md +15 -1
- package/.claude/resources/core/heartbeat.md +129 -1
- package/.claude/resources/core/model-routing.md +6 -2
- package/.claude/resources/core/per-task-verification.md +362 -0
- package/.claude/resources/core/phase-isolation.md +192 -4
- package/.claude/resources/core/session-scratchpad.md +1 -0
- package/.claude/resources/core/wave-execution.md +329 -0
- package/.claude/resources/patterns/plans-patterns.md +56 -0
- package/.claude/resources/patterns/plans-templates.md +152 -0
- package/.claude/resources/skills/_index.md +8 -6
- package/.claude/resources/skills/create-plan-skill.md +71 -5
- package/.claude/resources/skills/execute-plan-skill.md +357 -12
- package/.claude/resources/skills/resume-work-skill.md +159 -0
- package/.claude/rules/core/forbidden-patterns.md +38 -0
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/state.d.ts +12 -0
- package/dist/cli/commands/state.d.ts.map +1 -0
- package/dist/cli/commands/state.js +47 -0
- package/dist/cli/commands/state.js.map +1 -0
- package/dist/cli/daemon/desktop-notifier.d.ts +16 -0
- package/dist/cli/daemon/desktop-notifier.d.ts.map +1 -0
- package/dist/cli/daemon/desktop-notifier.js +53 -0
- package/dist/cli/daemon/desktop-notifier.js.map +1 -0
- package/dist/cli/daemon/event-writer.d.ts +22 -0
- package/dist/cli/daemon/event-writer.d.ts.map +1 -0
- package/dist/cli/daemon/event-writer.js +76 -0
- package/dist/cli/daemon/event-writer.js.map +1 -0
- package/dist/cli/daemon/heartbeat-daemon.js +81 -1
- package/dist/cli/daemon/heartbeat-daemon.js.map +1 -1
- package/dist/cli/daemon/log-writer.d.ts +17 -0
- package/dist/cli/daemon/log-writer.d.ts.map +1 -0
- package/dist/cli/daemon/log-writer.js +62 -0
- package/dist/cli/daemon/log-writer.js.map +1 -0
- package/dist/cli/daemon/notification-router.d.ts +17 -0
- package/dist/cli/daemon/notification-router.d.ts.map +1 -0
- package/dist/cli/daemon/notification-router.js +35 -0
- package/dist/cli/daemon/notification-router.js.map +1 -0
- package/dist/cli/daemon/prompt-manager.d.ts +27 -0
- package/dist/cli/daemon/prompt-manager.d.ts.map +1 -0
- package/dist/cli/daemon/prompt-manager.js +107 -0
- package/dist/cli/daemon/prompt-manager.js.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/state/flowconfig-parser.d.ts +16 -0
- package/dist/cli/state/flowconfig-parser.d.ts.map +1 -0
- package/dist/cli/state/flowconfig-parser.js +166 -0
- package/dist/cli/state/flowconfig-parser.js.map +1 -0
- package/dist/cli/state/heartbeat-state.d.ts +16 -0
- package/dist/cli/state/heartbeat-state.d.ts.map +1 -0
- package/dist/cli/state/heartbeat-state.js +97 -0
- package/dist/cli/state/heartbeat-state.js.map +1 -0
- package/dist/cli/state/model-router.d.ts +8 -0
- package/dist/cli/state/model-router.d.ts.map +1 -0
- package/dist/cli/state/model-router.js +36 -0
- package/dist/cli/state/model-router.js.map +1 -0
- package/dist/cli/state/plan-parser.d.ts +16 -0
- package/dist/cli/state/plan-parser.d.ts.map +1 -0
- package/dist/cli/state/plan-parser.js +124 -0
- package/dist/cli/state/plan-parser.js.map +1 -0
- package/dist/cli/state/session-state.d.ts +21 -0
- package/dist/cli/state/session-state.d.ts.map +1 -0
- package/dist/cli/state/session-state.js +36 -0
- package/dist/cli/state/session-state.js.map +1 -0
- package/dist/cli/state/state-md-parser.d.ts +18 -0
- package/dist/cli/state/state-md-parser.d.ts.map +1 -0
- package/dist/cli/state/state-md-parser.js +222 -0
- package/dist/cli/state/state-md-parser.js.map +1 -0
- package/dist/cli/state/types.d.ts +106 -0
- package/dist/cli/state/types.d.ts.map +1 -0
- package/dist/cli/state/types.js +8 -0
- package/dist/cli/state/types.js.map +1 -0
- package/dist/cli/state/wave-calculator.d.ts +18 -0
- package/dist/cli/state/wave-calculator.d.ts.map +1 -0
- package/dist/cli/state/wave-calculator.js +134 -0
- package/dist/cli/state/wave-calculator.js.map +1 -0
- package/dist/cli/types.d.ts +15 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/templates/shared/CLAUDE.md.template +4 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
# Resume Work Skill
|
|
3
|
+
|
|
4
|
+
## Purpose
|
|
5
|
+
|
|
6
|
+
Reconstruct full session context from `flow/STATE.md` after a context reset (compaction, new session, crash). This skill reads the unified state file, identifies the active work, cross-references with the plan file, and outputs a structured summary that enables the LLM to resume work seamlessly.
|
|
7
|
+
|
|
8
|
+
**This skill is read-only** — it does not create, modify, or delete any files.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Restrictions — READ-ONLY
|
|
13
|
+
|
|
14
|
+
| Allowed Action | Purpose |
|
|
15
|
+
|----------------|---------|
|
|
16
|
+
| Read `flow/STATE.md` | Parse execution state |
|
|
17
|
+
| Read plan files (`flow/plans/`) | Cross-reference progress |
|
|
18
|
+
| Read discovery files (`flow/discovery/`) | Context for active discovery |
|
|
19
|
+
| Read `flow/.scratchpad.md` | Restore ephemeral notes |
|
|
20
|
+
| Read `flow/tasklist.md` | Check task status |
|
|
21
|
+
|
|
22
|
+
| Forbidden Action | Reason |
|
|
23
|
+
|------------------|--------|
|
|
24
|
+
| Create/edit any files | Resume is observation-only |
|
|
25
|
+
| Run build or test commands | No execution during resume |
|
|
26
|
+
| Auto-invoke other skills | User decides next action |
|
|
27
|
+
| Modify STATE.md | State persists until actively changed by a skill |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
### Step 1: Read STATE.md
|
|
34
|
+
|
|
35
|
+
1. Check if `flow/STATE.md` exists — if not, inform user and stop
|
|
36
|
+
2. Read the file and parse all sections:
|
|
37
|
+
- `**Updated**` timestamp
|
|
38
|
+
- `## Execution State` — active skill, plan, phase, task, completed phases
|
|
39
|
+
- `## Decisions` — decisions with rationale
|
|
40
|
+
- `## Blockers` — issues with status
|
|
41
|
+
- `## Files Modified` — changed file paths
|
|
42
|
+
- `## Next Action` — immediate next step
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### Step 2: Staleness Check
|
|
47
|
+
|
|
48
|
+
Parse the `**Updated**` timestamp and compare to current time.
|
|
49
|
+
|
|
50
|
+
| Age | Behavior |
|
|
51
|
+
|-----|----------|
|
|
52
|
+
| < 24 hours | Continue silently |
|
|
53
|
+
| 24-72 hours | Warn: "State is {N} hours old. Context may be outdated." Ask: resume or start fresh? |
|
|
54
|
+
| > 72 hours | Strong warning: "State is {N} days old. Recommend starting fresh." Ask: resume or start fresh? |
|
|
55
|
+
|
|
56
|
+
If user chooses "start fresh":
|
|
57
|
+
1. Inform them to delete `flow/STATE.md` manually
|
|
58
|
+
2. Stop — do not proceed with resume
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### Step 3: Read Active Plan
|
|
63
|
+
|
|
64
|
+
If `Active Plan` references a plan file:
|
|
65
|
+
|
|
66
|
+
1. Read the plan file from `flow/plans/`
|
|
67
|
+
2. Parse phase structure and task checkboxes
|
|
68
|
+
3. Cross-reference:
|
|
69
|
+
- STATE.md `Completed Phases` → plan's `[x]` tasks should match
|
|
70
|
+
- STATE.md `Current Phase` → identify exact task position
|
|
71
|
+
4. If discrepancies found, note them and trust the plan file
|
|
72
|
+
|
|
73
|
+
If `Active Plan` is "none", skip this step.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Step 4: Identify Next Task
|
|
78
|
+
|
|
79
|
+
Based on STATE.md and plan cross-reference:
|
|
80
|
+
|
|
81
|
+
1. If `Next Action` is set → use it as the recommended next step
|
|
82
|
+
2. If `Current Task` is set → resume from that task
|
|
83
|
+
3. If `Current Phase` is set but no task → start at the first unchecked task in that phase
|
|
84
|
+
4. If no current phase → suggest continuing from the first unchecked phase
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### Step 5: Output Context Summary
|
|
89
|
+
|
|
90
|
+
Present a structured summary:
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
## Session Resumed
|
|
94
|
+
|
|
95
|
+
**Active Skill**: {skill name}
|
|
96
|
+
**Active Plan**: `{plan file path}`
|
|
97
|
+
**Progress**: Phase {current} of {total} — {phase name}
|
|
98
|
+
**Last Updated**: {timestamp} ({relative time})
|
|
99
|
+
|
|
100
|
+
### Completed Phases
|
|
101
|
+
| Phase | Name | Outcome |
|
|
102
|
+
|-------|------|---------|
|
|
103
|
+
| 1 | Types and Schemas | done |
|
|
104
|
+
| 2 | Backend Setup | done |
|
|
105
|
+
|
|
106
|
+
### Decisions Made
|
|
107
|
+
- {decision} — {choice} (reason: {reason})
|
|
108
|
+
|
|
109
|
+
### Blockers
|
|
110
|
+
| Issue | Status | Tried |
|
|
111
|
+
|-------|--------|-------|
|
|
112
|
+
| {issue} | {status} | {tried} |
|
|
113
|
+
|
|
114
|
+
### Files Modified ({count})
|
|
115
|
+
- `{file1}`
|
|
116
|
+
- `{file2}`
|
|
117
|
+
|
|
118
|
+
### Next Action
|
|
119
|
+
> {next action description}
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
Ready to continue. Suggested command: `/execute-plan @{plan-file}`
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Error Handling
|
|
129
|
+
|
|
130
|
+
| Scenario | Behavior |
|
|
131
|
+
|----------|----------|
|
|
132
|
+
| STATE.md missing | Inform user: "No active state found." Stop. |
|
|
133
|
+
| Plan file referenced but missing | Warn: "Plan file not found." Show state without plan cross-reference. |
|
|
134
|
+
| STATE.md malformed | Parse what's possible, warn about missing sections. |
|
|
135
|
+
| Active skill is unknown | Show state as-is, don't suggest a specific command. |
|
|
136
|
+
| Scratchpad exists | Mention: "Session scratchpad has notes — read `flow/.scratchpad.md` for context." |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Rules
|
|
141
|
+
|
|
142
|
+
1. **Read-only** — never modify any files during resume
|
|
143
|
+
2. **No auto-chaining** — present summary and stop
|
|
144
|
+
3. **Plan file is truth** — if STATE.md and plan disagree, trust the plan
|
|
145
|
+
4. **Graceful degradation** — parse what's available, skip missing sections
|
|
146
|
+
5. **Staleness gate** — always check timestamp before proceeding
|
|
147
|
+
6. **Complement scratchpad** — mention scratchpad if it exists, but don't merge content
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Related Files
|
|
152
|
+
|
|
153
|
+
| File | Purpose |
|
|
154
|
+
|------|---------|
|
|
155
|
+
| `.claude/commands/resume-work.md` | Command file for /resume-work |
|
|
156
|
+
| `.claude/resources/core/compaction-guide.md` | What to preserve during compaction |
|
|
157
|
+
| `.claude/resources/core/session-scratchpad.md` | Complementary session notes |
|
|
158
|
+
| `src/cli/state/state-md-parser.ts` | Programmatic parser for STATE.md |
|
|
159
|
+
| `src/cli/state/types.ts` | ExecutionState type definition |
|
|
@@ -256,4 +256,42 @@ const badExample = problematicCode()
|
|
|
256
256
|
|
|
257
257
|
## Project Anti-Patterns
|
|
258
258
|
|
|
259
|
+
### 6. DON'T Execute ORM/Database Migration Commands Directly
|
|
260
|
+
|
|
261
|
+
**Problem**: Running ORM tools (Prisma, Drizzle, TypeORM, Sequelize, Knex, Django ORM, Alembic, etc.) directly can cause irreversible data loss, schema corruption, or unintended migrations in production environments.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# BAD - Never run these directly
|
|
265
|
+
npx prisma migrate dev
|
|
266
|
+
npx prisma db push
|
|
267
|
+
npx prisma db seed
|
|
268
|
+
npx drizzle-kit push
|
|
269
|
+
npx drizzle-kit migrate
|
|
270
|
+
npx typeorm migration:run
|
|
271
|
+
python manage.py migrate
|
|
272
|
+
alembic upgrade head
|
|
273
|
+
npx knex migrate:latest
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Why This is Wrong**:
|
|
277
|
+
|
|
278
|
+
- Database migrations can be destructive and irreversible (dropping columns, tables, data)
|
|
279
|
+
- ORM commands may connect to production databases if environment is misconfigured
|
|
280
|
+
- Schema changes need human review before execution
|
|
281
|
+
- Seed commands can overwrite existing data
|
|
282
|
+
- The AI agent has no way to verify which database environment it's targeting
|
|
283
|
+
|
|
284
|
+
**Fix**: Always present the command to the user and ask them to execute it manually.
|
|
285
|
+
|
|
286
|
+
```markdown
|
|
287
|
+
<!-- GOOD - Ask the user to run it -->
|
|
288
|
+
"Please run the following command to apply the migration:"
|
|
289
|
+
|
|
290
|
+
`npx prisma migrate dev --name add_users_table`
|
|
291
|
+
|
|
292
|
+
"Review the generated migration SQL before confirming."
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Scope**: This applies to ALL ORM and database tools — Prisma, Drizzle, TypeORM, Sequelize, Knex, Django ORM, Alembic, SQLAlchemy, ActiveRecord, and any other database migration or seeding tool.
|
|
296
|
+
|
|
259
297
|
<!-- auto-captured anti-patterns below this line -->
|
|
@@ -101,7 +101,7 @@ function printNextSteps(platforms) {
|
|
|
101
101
|
log.blank();
|
|
102
102
|
log.header('Heartbeat Daemon');
|
|
103
103
|
log.info('The heartbeat daemon runs in the background to execute scheduled tasks.');
|
|
104
|
-
log.info('Manage it with: npx
|
|
104
|
+
log.info('Manage it with: npx planflow-ai heartbeat start|stop|status');
|
|
105
105
|
log.blank();
|
|
106
106
|
log.header('Brain & Obsidian Vault');
|
|
107
107
|
log.info('Your project brain lives at flow/brain/ — features, errors, and decisions are tracked here.');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State CLI command
|
|
3
|
+
*
|
|
4
|
+
* Outputs deterministic JSON representing current plan-flow state.
|
|
5
|
+
* Composes all parsers: config, session, heartbeat (always),
|
|
6
|
+
* and plan/waves/tiers (when --plan is provided).
|
|
7
|
+
*/
|
|
8
|
+
export declare function runState(options: {
|
|
9
|
+
plan?: string;
|
|
10
|
+
target: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State CLI command
|
|
3
|
+
*
|
|
4
|
+
* Outputs deterministic JSON representing current plan-flow state.
|
|
5
|
+
* Composes all parsers: config, session, heartbeat (always),
|
|
6
|
+
* and plan/waves/tiers (when --plan is provided).
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { parseFlowConfig } from '../state/flowconfig-parser.js';
|
|
10
|
+
import { getSessionState } from '../state/session-state.js';
|
|
11
|
+
import { getHeartbeatSummary } from '../state/heartbeat-state.js';
|
|
12
|
+
import { parseStateMd } from '../state/state-md-parser.js';
|
|
13
|
+
import { parsePlan } from '../state/plan-parser.js';
|
|
14
|
+
import { calculateWaves } from '../state/wave-calculator.js';
|
|
15
|
+
import { calculateModelTiers } from '../state/model-router.js';
|
|
16
|
+
export async function runState(options) {
|
|
17
|
+
try {
|
|
18
|
+
const flowDir = join(options.target, 'flow');
|
|
19
|
+
const config = parseFlowConfig(flowDir);
|
|
20
|
+
const session = getSessionState(flowDir);
|
|
21
|
+
const heartbeat = getHeartbeatSummary(flowDir);
|
|
22
|
+
const execution = parseStateMd(flowDir);
|
|
23
|
+
const output = {
|
|
24
|
+
config,
|
|
25
|
+
session,
|
|
26
|
+
heartbeat,
|
|
27
|
+
...(execution ? { execution } : {}),
|
|
28
|
+
};
|
|
29
|
+
if (options.plan) {
|
|
30
|
+
const phases = parsePlan(options.plan);
|
|
31
|
+
const waves = calculateWaves(phases);
|
|
32
|
+
const model_tiers = calculateModelTiers(phases, config.model_routing);
|
|
33
|
+
output.plan = {
|
|
34
|
+
phases,
|
|
35
|
+
waves,
|
|
36
|
+
model_tiers,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
+
process.stdout.write(JSON.stringify({ error: message }, null, 2) + '\n');
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/cli/commands/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAG9B;IACC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAE/C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAExC,MAAM,MAAM,GAAgB;YAC1B,MAAM;YACN,OAAO;YACP,SAAS;YACT,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC;QAEF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;YAEtE,MAAM,CAAC,IAAI,GAAG;gBACZ,MAAM;gBACN,KAAK;gBACL,WAAW;aACZ,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACzE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop notifier — thin wrapper around node-notifier.
|
|
3
|
+
* Fire-and-forget: never throws, never blocks.
|
|
4
|
+
* Degrades silently on headless servers or when node-notifier is unavailable.
|
|
5
|
+
*/
|
|
6
|
+
import type { NotificationEvent } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Send a desktop notification for the given event.
|
|
9
|
+
*
|
|
10
|
+
* - Title: "Plan-Flow Heartbeat"
|
|
11
|
+
* - Message: "{task}: {message}"
|
|
12
|
+
* - Fire-and-forget — does not await the OS notification result
|
|
13
|
+
* - Catches ALL errors; logs to console.error but never throws
|
|
14
|
+
*/
|
|
15
|
+
export declare function sendDesktopNotification(event: NotificationEvent): void;
|
|
16
|
+
//# sourceMappingURL=desktop-notifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-notifier.d.ts","sourceRoot":"","sources":["../../../src/cli/daemon/desktop-notifier.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA6BrD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAetE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop notifier — thin wrapper around node-notifier.
|
|
3
|
+
* Fire-and-forget: never throws, never blocks.
|
|
4
|
+
* Degrades silently on headless servers or when node-notifier is unavailable.
|
|
5
|
+
*/
|
|
6
|
+
const NOTIFICATION_TITLE = 'Plan-Flow Heartbeat';
|
|
7
|
+
/**
|
|
8
|
+
* Lazily resolved notifier instance.
|
|
9
|
+
* `null` means the import failed (headless server, missing native deps, etc.)
|
|
10
|
+
*/
|
|
11
|
+
let cachedNotifier;
|
|
12
|
+
async function getNotifier() {
|
|
13
|
+
if (cachedNotifier !== undefined)
|
|
14
|
+
return cachedNotifier;
|
|
15
|
+
try {
|
|
16
|
+
// Dynamic import — may fail on headless systems
|
|
17
|
+
const mod = await import('node-notifier');
|
|
18
|
+
// node-notifier exports its instance as both default and named
|
|
19
|
+
cachedNotifier = (mod.default ?? mod);
|
|
20
|
+
return cachedNotifier;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// node-notifier unavailable — degrade silently
|
|
24
|
+
cachedNotifier = null;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Send a desktop notification for the given event.
|
|
30
|
+
*
|
|
31
|
+
* - Title: "Plan-Flow Heartbeat"
|
|
32
|
+
* - Message: "{task}: {message}"
|
|
33
|
+
* - Fire-and-forget — does not await the OS notification result
|
|
34
|
+
* - Catches ALL errors; logs to console.error but never throws
|
|
35
|
+
*/
|
|
36
|
+
export function sendDesktopNotification(event) {
|
|
37
|
+
// Intentionally not awaited — fire-and-forget
|
|
38
|
+
void (async () => {
|
|
39
|
+
try {
|
|
40
|
+
const notifier = await getNotifier();
|
|
41
|
+
if (!notifier)
|
|
42
|
+
return;
|
|
43
|
+
notifier.notify({
|
|
44
|
+
title: NOTIFICATION_TITLE,
|
|
45
|
+
message: `${event.task}: ${event.message}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Desktop notification failed (non-fatal):', error);
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=desktop-notifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-notifier.js","sourceRoot":"","sources":["../../../src/cli/daemon/desktop-notifier.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AAKjD;;;GAGG;AACH,IAAI,cAA+C,CAAC;AAEpD,KAAK,UAAU,WAAW;IACxB,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IAExD,IAAI,CAAC;QACH,gDAAgD;QAChD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC1C,+DAA+D;QAC/D,cAAc,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAiB,CAAC;QACtD,OAAO,cAAc,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;QAC/C,cAAc,GAAG,IAAI,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAwB;IAC9D,8CAA8C;IAC9C,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,QAAQ;gBAAE,OAAO;YAEtB,QAAQ,CAAC,MAAM,CAAC;gBACd,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;QACnE,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event writer — appends NotificationEvent records to a JSONL file.
|
|
3
|
+
* Uses atomic write (write-to-temp then rename) to prevent corruption.
|
|
4
|
+
*/
|
|
5
|
+
import type { NotificationEvent, NotificationLevel, NotificationType } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create a NotificationEvent with an auto-generated id and current timestamp.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createEvent(task: string, type: NotificationType, level: NotificationLevel, message: string, phase?: string): NotificationEvent;
|
|
10
|
+
/**
|
|
11
|
+
* Append a single event to the JSONL file using atomic write.
|
|
12
|
+
*
|
|
13
|
+
* Reads the existing file (if any), appends the new JSON line,
|
|
14
|
+
* writes to a temp file, then renames over the original.
|
|
15
|
+
*/
|
|
16
|
+
export declare function appendEvent(flowDir: string, event: NotificationEvent): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Read all events from the JSONL file. Returns an empty array if the file
|
|
19
|
+
* does not exist.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readEvents(flowDir: string): Promise<NotificationEvent[]>;
|
|
22
|
+
//# sourceMappingURL=event-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-writer.d.ts","sourceRoot":"","sources":["../../../src/cli/daemon/event-writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAW1F;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,iBAAiB,CAUnB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiB9E"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event writer — appends NotificationEvent records to a JSONL file.
|
|
3
|
+
* Uses atomic write (write-to-temp then rename) to prevent corruption.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, writeFile, rename, mkdir } from 'node:fs/promises';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
const EVENTS_FILENAME = '.heartbeat-events.jsonl';
|
|
9
|
+
/**
|
|
10
|
+
* Build the full path to the events file for a given flow directory.
|
|
11
|
+
*/
|
|
12
|
+
function eventsPath(flowDir) {
|
|
13
|
+
return join(flowDir, EVENTS_FILENAME);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a NotificationEvent with an auto-generated id and current timestamp.
|
|
17
|
+
*/
|
|
18
|
+
export function createEvent(task, type, level, message, phase) {
|
|
19
|
+
return {
|
|
20
|
+
id: randomUUID(),
|
|
21
|
+
timestamp: new Date(),
|
|
22
|
+
task,
|
|
23
|
+
type,
|
|
24
|
+
level,
|
|
25
|
+
phase,
|
|
26
|
+
message,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Append a single event to the JSONL file using atomic write.
|
|
31
|
+
*
|
|
32
|
+
* Reads the existing file (if any), appends the new JSON line,
|
|
33
|
+
* writes to a temp file, then renames over the original.
|
|
34
|
+
*/
|
|
35
|
+
export async function appendEvent(flowDir, event) {
|
|
36
|
+
const filePath = eventsPath(flowDir);
|
|
37
|
+
const dir = dirname(filePath);
|
|
38
|
+
await mkdir(dir, { recursive: true });
|
|
39
|
+
let existing = '';
|
|
40
|
+
try {
|
|
41
|
+
existing = await readFile(filePath, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// File does not exist yet — start fresh
|
|
45
|
+
}
|
|
46
|
+
const line = JSON.stringify({
|
|
47
|
+
...event,
|
|
48
|
+
timestamp: event.timestamp instanceof Date ? event.timestamp.toISOString() : event.timestamp,
|
|
49
|
+
});
|
|
50
|
+
const updated = existing ? `${existing.trimEnd()}\n${line}\n` : `${line}\n`;
|
|
51
|
+
const tempPath = `${filePath}.tmp`;
|
|
52
|
+
await writeFile(tempPath, updated, 'utf-8');
|
|
53
|
+
await rename(tempPath, filePath);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read all events from the JSONL file. Returns an empty array if the file
|
|
57
|
+
* does not exist.
|
|
58
|
+
*/
|
|
59
|
+
export async function readEvents(flowDir) {
|
|
60
|
+
const filePath = eventsPath(flowDir);
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = await readFile(filePath, 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return content
|
|
69
|
+
.split('\n')
|
|
70
|
+
.filter((line) => line.trim().length > 0)
|
|
71
|
+
.map((line) => {
|
|
72
|
+
const parsed = JSON.parse(line);
|
|
73
|
+
return { ...parsed, timestamp: new Date(parsed.timestamp) };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=event-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-writer.js","sourceRoot":"","sources":["../../../src/cli/daemon/event-writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAElD;;GAEG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,IAAsB,EACtB,KAAwB,EACxB,OAAe,EACf,KAAc;IAEd,OAAO;QACL,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,IAAI;QACJ,IAAI;QACJ,KAAK;QACL,KAAK;QACL,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,KAAwB;IAExB,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE9B,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,GAAG,KAAK;QACR,SAAS,EAAE,KAAK,CAAC,SAAS,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS;KAC7F,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;IAE5E,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;IACnC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe;IAC9C,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAErC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,OAAO;SACX,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
import { readFileSync, writeFileSync, existsSync, unlinkSync, watch, appendFileSync, statSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { spawn } from 'node:child_process';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
11
12
|
import { parseHeartbeatFile } from './heartbeat-parser.js';
|
|
13
|
+
import { notify } from './notification-router.js';
|
|
14
|
+
import { writePrompt } from './prompt-manager.js';
|
|
12
15
|
const target = process.argv[2] || process.cwd();
|
|
13
16
|
const heartbeatPath = join(target, 'flow', 'heartbeat.md');
|
|
14
17
|
const pidPath = join(target, 'flow', '.heartbeat.pid');
|
|
@@ -18,7 +21,47 @@ let taskRunning = false;
|
|
|
18
21
|
const ACTIVE_SESSION_ERROR = 'cannot be launched inside another Claude Code session';
|
|
19
22
|
const MAX_RETRIES = 5;
|
|
20
23
|
const RETRY_DELAY_MS = 60_000;
|
|
24
|
+
const TAIL_LINES = 20;
|
|
21
25
|
const retryCountMap = new Map();
|
|
26
|
+
const flowDir = join(target, 'flow');
|
|
27
|
+
/**
|
|
28
|
+
* Create a NotificationEvent with consistent structure.
|
|
29
|
+
*/
|
|
30
|
+
function createEvent(task, type, level, message, phase) {
|
|
31
|
+
return {
|
|
32
|
+
id: randomUUID(),
|
|
33
|
+
timestamp: new Date(),
|
|
34
|
+
task: task.name,
|
|
35
|
+
type,
|
|
36
|
+
level,
|
|
37
|
+
message,
|
|
38
|
+
...(phase ? { phase } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Return the last N lines from a string.
|
|
43
|
+
*/
|
|
44
|
+
function tailLines(text, n) {
|
|
45
|
+
const lines = text.split('\n');
|
|
46
|
+
return lines.slice(-n).join('\n');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the autopilot setting from flow/.flowconfig.
|
|
50
|
+
* Returns true if autopilot is enabled, false otherwise.
|
|
51
|
+
*/
|
|
52
|
+
function readAutopilot() {
|
|
53
|
+
try {
|
|
54
|
+
const configPath = join(flowDir, '.flowconfig');
|
|
55
|
+
if (!existsSync(configPath))
|
|
56
|
+
return false;
|
|
57
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
58
|
+
const match = content.match(/^autopilot:\s*(true|false)/m);
|
|
59
|
+
return match?.[1] === 'true';
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
22
65
|
function log(message) {
|
|
23
66
|
const timestamp = new Date().toISOString();
|
|
24
67
|
const line = `[${timestamp}] ${message}\n`;
|
|
@@ -86,6 +129,10 @@ function executeTask(task) {
|
|
|
86
129
|
}
|
|
87
130
|
taskRunning = true;
|
|
88
131
|
log(`Executing "${task.name}": ${task.command}`);
|
|
132
|
+
// Snapshot prompt file state before task runs
|
|
133
|
+
const promptExistedBefore = existsSync(join(flowDir, '.heartbeat-prompt.md'));
|
|
134
|
+
// Emit task_started notification (fire-and-forget)
|
|
135
|
+
void notify(createEvent(task, 'task_started', 'info', `Starting task: ${task.name}`), flowDir);
|
|
89
136
|
// Strip CLAUDECODE env var to avoid "nested session" detection.
|
|
90
137
|
// The daemon runs detached — it's not actually nested.
|
|
91
138
|
const cleanEnv = { ...process.env };
|
|
@@ -118,13 +165,42 @@ function executeTask(task) {
|
|
|
118
165
|
});
|
|
119
166
|
child.on('close', (code) => {
|
|
120
167
|
taskRunning = false;
|
|
168
|
+
const outputTail = tailLines(stdout, TAIL_LINES);
|
|
169
|
+
const errorTail = tailLines(stderr, TAIL_LINES);
|
|
121
170
|
if (code === 0) {
|
|
122
|
-
log(`Task "${task.name}" completed successfully`);
|
|
123
171
|
retryCountMap.delete(task.name);
|
|
172
|
+
// Check if the task created a NEW prompt file (blocked via file, not exit code)
|
|
173
|
+
const promptPath = join(flowDir, '.heartbeat-prompt.md');
|
|
174
|
+
if (!promptExistedBefore && existsSync(promptPath)) {
|
|
175
|
+
log(`Task "${task.name}" completed but created prompt file — treating as blocked`);
|
|
176
|
+
void notify(createEvent(task, 'task_blocked', 'error', `Task needs input: ${task.name}`), flowDir);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
log(`Task "${task.name}" completed successfully`);
|
|
180
|
+
// Emit task_complete notification (fire-and-forget)
|
|
181
|
+
void notify(createEvent(task, 'task_complete', 'info', `Task completed: ${task.name}`), flowDir);
|
|
182
|
+
}
|
|
124
183
|
if (task.oneShot) {
|
|
125
184
|
disableOneShotTask(task.name);
|
|
126
185
|
}
|
|
127
186
|
}
|
|
187
|
+
else if (code === 2) {
|
|
188
|
+
// Exit code 2 — task needs human input
|
|
189
|
+
log(`Task "${task.name}" blocked — needs input (exit code 2)`);
|
|
190
|
+
if (stderr)
|
|
191
|
+
log(` stderr: ${stderr.slice(0, 500)}`);
|
|
192
|
+
const isAutopilot = readAutopilot();
|
|
193
|
+
// Emit task_blocked notification (fire-and-forget)
|
|
194
|
+
void notify(createEvent(task, 'task_blocked', 'error', `Task blocked (needs input): ${task.name}${isAutopilot ? ' [autopilot ON]' : ''}`), flowDir);
|
|
195
|
+
if (!isAutopilot) {
|
|
196
|
+
void writePrompt(task.name, outputTail, errorTail, flowDir);
|
|
197
|
+
log(` prompt written to flow/${'.heartbeat-prompt.md'} — waiting for human input`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
log(` Autopilot ON — skipping prompt, continuing`);
|
|
201
|
+
}
|
|
202
|
+
log(` context (last ${TAIL_LINES} lines): ${outputTail.slice(0, 500)}`);
|
|
203
|
+
}
|
|
128
204
|
else if (stderr.includes(ACTIVE_SESSION_ERROR)) {
|
|
129
205
|
scheduleRetry(task);
|
|
130
206
|
}
|
|
@@ -132,6 +208,8 @@ function executeTask(task) {
|
|
|
132
208
|
log(`Task "${task.name}" failed with code ${code}`);
|
|
133
209
|
if (stderr)
|
|
134
210
|
log(` stderr: ${stderr.slice(0, 500)}`);
|
|
211
|
+
// Emit task_failed notification (fire-and-forget)
|
|
212
|
+
void notify(createEvent(task, 'task_failed', 'error', `Task failed (exit ${code}): ${task.name}${errorTail ? '\n' + errorTail.slice(0, 300) : ''}`), flowDir);
|
|
135
213
|
}
|
|
136
214
|
if (stdout)
|
|
137
215
|
log(` output: ${stdout.slice(0, 500)}`);
|
|
@@ -139,6 +217,8 @@ function executeTask(task) {
|
|
|
139
217
|
child.on('error', (err) => {
|
|
140
218
|
taskRunning = false;
|
|
141
219
|
log(`Task "${task.name}" error: ${err.message}`);
|
|
220
|
+
// Emit task_failed notification for spawn errors (fire-and-forget)
|
|
221
|
+
void notify(createEvent(task, 'task_failed', 'error', `Task spawn error: ${task.name} — ${err.message}`), flowDir);
|
|
142
222
|
});
|
|
143
223
|
}
|
|
144
224
|
function scheduleRetry(task) {
|