flowviant 0.6.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/LICENSE +21 -0
- package/README.md +71 -0
- package/bin/cli.mjs +78 -0
- package/bin/lib/claude.mjs +151 -0
- package/bin/lib/config.mjs +55 -0
- package/bin/lib/fleet.mjs +363 -0
- package/bin/lib/git.mjs +44 -0
- package/bin/lib/live.mjs +483 -0
- package/bin/lib/login.mjs +84 -0
- package/bin/lib/preflight.mjs +49 -0
- package/bin/lib/preview.mjs +169 -0
- package/bin/lib/single.mjs +66 -0
- package/bin/lib/ui.mjs +26 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flowviant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# flowviant
|
|
2
|
+
|
|
3
|
+
Run your own [Claude Code](https://claude.com/claude-code) as headless build agents for [Flowviant](https://flowviant.com). You manage a team of agents in the app; this daemon runs them on your machine, on your own credentials — Flowviant never sees your Claude or GitHub logins.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx flowviant login # approve the code in Flowviant → connected
|
|
7
|
+
npx flowviant # run your fleet
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
You create named agents in Flowviant and dispatch work to them. This daemon, running on a machine you control, gives each agent its own git worktree and drives **your** locally-authenticated `claude` to do the work — it claims a task, works it, captures evidence for each acceptance criterion, opens a pull request, and routes any question it can't answer back to you as a blocker. You review and merge in the app.
|
|
13
|
+
|
|
14
|
+
Because it drives the CLIs you're already logged into, **the cost is yours** (your Claude subscription, your GitHub) and **the daemon never handles a credential** — it shells out to tools you authenticated yourself.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
On the machine that runs the daemon:
|
|
19
|
+
|
|
20
|
+
- **[Claude Code](https://claude.com/claude-code)** installed and signed in (`claude`)
|
|
21
|
+
- **[GitHub CLI](https://cli.github.com)** authenticated (`gh auth login`) — for opening PRs
|
|
22
|
+
- **git**, and **Node 20+**
|
|
23
|
+
- run it from inside the git repository you want worked
|
|
24
|
+
|
|
25
|
+
## Connecting
|
|
26
|
+
|
|
27
|
+
The easy way — device login, like `gh auth login`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx flowviant login
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It shows a short code; enter it in Flowviant under **Agents → Connect a machine**. The credential is stored at `~/.flowviant/credentials.json`, and from then on `npx flowviant` just runs.
|
|
34
|
+
|
|
35
|
+
Prefer an explicit token? Create a fleet credential in the app and pass it directly:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
FLOWVIANT_FLEET=fva_… npx flowviant
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Live mode (opt-in)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
FLOWVIANT_LIVE=1 npx flowviant
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each task runs a **persistent** Claude session you can talk to mid-task from the app: the agent streams its work into the task's conversation, you `@`-mention it to steer or answer questions, and it resumes in place. Blockers park the session at zero cost until you answer.
|
|
48
|
+
|
|
49
|
+
### Live previews
|
|
50
|
+
|
|
51
|
+
For UI/API tasks, the daemon can start the branch's dev server in the agent's worktree and open a [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) quick tunnel so you can drive the real running change during review — no Cloudflare account needed (it's auto-fetched if missing). Configure it once per repo, or let it infer common setups:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
// .flowviant/preview.json
|
|
55
|
+
{ "ui": { "cmd": "npm run dev", "port": 5173 } }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Flowviant only stores the tunnel URL; your browser talks to it directly.
|
|
59
|
+
|
|
60
|
+
## Modes
|
|
61
|
+
|
|
62
|
+
| Env | What runs |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| _(stored login)_ or `FLOWVIANT_FLEET` | **Fleet daemon** — one worktree + worker per agent on your roster, managed in the app |
|
|
65
|
+
| `FLOWVIANT_TOKEN` | a single agent in the current checkout |
|
|
66
|
+
| `FLOWVIANT_TOKENS=a,b,c` | a static fleet, one worktree each |
|
|
67
|
+
| `FLOWVIANT_SAFE=1` | restrict the toolset instead of running unattended |
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT — see [LICENSE](./LICENSE).
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* flowviant — run your own Claude Code as headless Flowviant build agents.
|
|
4
|
+
*
|
|
5
|
+
* Three modes, picked by which env var is set:
|
|
6
|
+
*
|
|
7
|
+
* FLOWVIANT_TOKEN=fva_… npx flowviant # 1 worker, current checkout
|
|
8
|
+
* FLOWVIANT_TOKENS=a,b,c npx flowviant # static fleet, 1 worktree each
|
|
9
|
+
* FLOWVIANT_FLEET=fft_… npx flowviant # FLEET DAEMON (recommended)
|
|
10
|
+
*
|
|
11
|
+
* Fleet daemon: install ONCE with a fleet credential, then manage everything
|
|
12
|
+
* from Flowviant. The daemon polls GET /api/v2/fleet/agents, reconciles one
|
|
13
|
+
* persistent git worktree + worker loop per roster agent, rotates each worker's
|
|
14
|
+
* short-lived MCP token, and only spawns Claude when an agent has work. Add/remove
|
|
15
|
+
* agents in the app — the daemon picks up the change on its next poll. Each worker
|
|
16
|
+
* claims its work, resets its worktree to base per task (fresh Claude conversation),
|
|
17
|
+
* opens one PR per intent, and routes questions back as blockers.
|
|
18
|
+
*
|
|
19
|
+
* Env:
|
|
20
|
+
* FLOWVIANT_TOKEN / FLOWVIANT_TOKENS / FLOWVIANT_FLEET (one of) credentials.
|
|
21
|
+
* FLOWVIANT_API_URL default https://api.flowviant.com/api/v2
|
|
22
|
+
* FLOWVIANT_MCP_URL default <API_URL>/mcp
|
|
23
|
+
* FLOWVIANT_FLEET_URL default <API_URL>/fleet/agents
|
|
24
|
+
* POLL_SECONDS gap between turns while waiting on a blocker (default 20)
|
|
25
|
+
* IDLE_SECONDS gap between work checks when idle (default 30)
|
|
26
|
+
* RECONCILE_SECONDS fleet roster poll cadence (default 10)
|
|
27
|
+
* FLOWVIANT_SAFE=1 restrict the toolset instead of running unattended.
|
|
28
|
+
*
|
|
29
|
+
* Requires the `claude` CLI (and `gh` for PRs) on PATH; run from inside the git
|
|
30
|
+
* repo you want worked. Fleet & static-fleet modes also require `git`.
|
|
31
|
+
*
|
|
32
|
+
* Implementation lives in ./lib/: config, ui, claude, git, fleet, single.
|
|
33
|
+
*/
|
|
34
|
+
import { FLEET_TOKEN, tokens, SAFE, MCP_URL } from './lib/config.mjs';
|
|
35
|
+
import { runFleetDaemon } from './lib/fleet.mjs';
|
|
36
|
+
import { runWorker, runStaticFleet } from './lib/single.mjs';
|
|
37
|
+
import { runLogin } from './lib/login.mjs';
|
|
38
|
+
import { preflight } from './lib/preflight.mjs';
|
|
39
|
+
|
|
40
|
+
// `flowviant login` — device auth (recommended): approve a code in the
|
|
41
|
+
// app, the credential is stored locally, then plain `flowviant` just runs.
|
|
42
|
+
if (process.argv[2] === 'login') {
|
|
43
|
+
await runLogin();
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!FLEET_TOKEN && tokens.length === 0) {
|
|
48
|
+
console.error(
|
|
49
|
+
'error: no credential found. Easiest:\n' +
|
|
50
|
+
' flowviant login (approve in the app — recommended)\n' +
|
|
51
|
+
'Or set one of:\n' +
|
|
52
|
+
' FLOWVIANT_FLEET=fva_… (fleet token, manage agents in Flowviant)\n' +
|
|
53
|
+
' FLOWVIANT_TOKEN=fva_… (one agent, current checkout)\n' +
|
|
54
|
+
' FLOWVIANT_TOKENS=a,b,… (static fleet)'
|
|
55
|
+
);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
if (FLEET_TOKEN) {
|
|
61
|
+
await runFleetDaemon();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(
|
|
65
|
+
SAFE
|
|
66
|
+
? '» safe mode: restricted toolset (unset FLOWVIANT_SAFE for full autonomy).'
|
|
67
|
+
: '» unattended mode: permission prompts skipped so the agent runs hands-off.'
|
|
68
|
+
);
|
|
69
|
+
preflight({ needGit: tokens.length > 1 });
|
|
70
|
+
if (tokens.length === 1) {
|
|
71
|
+
console.log(`» flowviant → ${MCP_URL} (1 worker · token fva_…${tokens[0].slice(-4)})`);
|
|
72
|
+
await runWorker({ token: tokens[0], cwd: process.cwd(), label: '' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await runStaticFleet();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await main();
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driving Claude Code: the operating-contract system prompts, the permission
|
|
3
|
+
* posture, and one `claude -p` turn. The hard rule baked into both prompts:
|
|
4
|
+
* there is no interactive user — the only channel to a human is the blocker loop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { SAFE } from './config.mjs';
|
|
12
|
+
|
|
13
|
+
// Multi-task loop (TOKEN / TOKENS modes): drain the whole queue in one session.
|
|
14
|
+
export const SYSTEM_MULTI = `You are a Flowviant build agent running FULLY AUTONOMOUSLY via the "flowviant" MCP
|
|
15
|
+
server. There is NO interactive user and NO terminal to ask in. The ONLY way to
|
|
16
|
+
reach a human is the blocker loop. Never ask the user directly; never wait on stdin.
|
|
17
|
+
|
|
18
|
+
Operate this loop:
|
|
19
|
+
1. Call claim_next_intent. If it returns claimed:false, output exactly ALL_CLEAR on
|
|
20
|
+
its own line and stop.
|
|
21
|
+
2. Read the brief. If it has an existing "branch" (a REVISION the human bounced back),
|
|
22
|
+
\`git checkout <branch>\` to resume your prior work and address the review feedback in
|
|
23
|
+
the description (under "— Review feedback —"). Use get_module_files / search_wiki /
|
|
24
|
+
list_related_intents for context. Call report_progress as you go.
|
|
25
|
+
3. If you hit ANYTHING only a human can decide, call report_blocker with a clear
|
|
26
|
+
question (and options when you can), then call get_blocker_resolution. If it is
|
|
27
|
+
not yet resolved, output exactly BLOCKED:<blockerId> on its own line and STOP.
|
|
28
|
+
4. Before finishing: for EACH acceptance criterion, call attach_evidence with concrete
|
|
29
|
+
proof it is met. This is what the human reviews instead of the diff.
|
|
30
|
+
5. Ship: on a revision, \`git push\` to the SAME existing branch (the PR updates in place)
|
|
31
|
+
and re-call attach_pr with that PR URL; otherwise open ONE draft PR (git push +
|
|
32
|
+
\`gh pr create --draft\`) and call attach_pr. Then complete. NEVER merge — the human
|
|
33
|
+
approves the PR in Flowviant.
|
|
34
|
+
6. Return to step 1.
|
|
35
|
+
|
|
36
|
+
Keep every change scoped to the claimed intent. If a tool errors, report_progress with
|
|
37
|
+
the error, then retry or report_blocker.`;
|
|
38
|
+
|
|
39
|
+
// Single-task turn (FLEET mode): claim EXACTLY ONE intent, then stop. The daemon
|
|
40
|
+
// owns the loop so it can reset the worktree + start a fresh conversation per task.
|
|
41
|
+
export const SYSTEM_SINGLE = `You are a Flowviant build agent running FULLY AUTONOMOUSLY via the "flowviant" MCP
|
|
42
|
+
server. There is NO interactive user and NO terminal to ask in. The ONLY way to
|
|
43
|
+
reach a human is the blocker loop. Never ask the user directly; never wait on stdin.
|
|
44
|
+
|
|
45
|
+
Do EXACTLY ONE task this turn:
|
|
46
|
+
1. Call claim_next_intent. If it returns claimed:false, output exactly NOTHING on its
|
|
47
|
+
own line and stop. Do NOT retry.
|
|
48
|
+
2. Read the brief. If it has an existing "branch" (a REVISION the human bounced back),
|
|
49
|
+
first \`git fetch && git checkout <branch>\` to resume YOUR prior work, and address
|
|
50
|
+
the review feedback in the description (under "— Review feedback —"). Otherwise work
|
|
51
|
+
from the clean base checkout. Use get_module_files / search_wiki /
|
|
52
|
+
list_related_intents for context. report_progress as you go.
|
|
53
|
+
3. If you hit ANYTHING only a human can decide, call report_blocker (with options when
|
|
54
|
+
you can), then get_blocker_resolution. If unresolved, output exactly
|
|
55
|
+
BLOCKED:<blockerId> on its own line and STOP. Do NOT guess past a real decision.
|
|
56
|
+
4. Before finishing: for EACH acceptance criterion call attach_evidence with concrete
|
|
57
|
+
proof it is met.
|
|
58
|
+
5. Ship: if this is a revision, \`git push\` to the SAME existing branch (the open PR
|
|
59
|
+
updates in place) and re-call attach_pr with that same PR URL. Otherwise open ONE
|
|
60
|
+
draft PR (git push + \`gh pr create --draft\`) and call attach_pr. Then complete.
|
|
61
|
+
NEVER merge. Then output exactly DONE on its own line and stop.
|
|
62
|
+
|
|
63
|
+
Do NOT claim a second intent — exactly one per turn. Keep every change scoped to the
|
|
64
|
+
claimed intent. If a tool errors, report_progress with the error, then retry or
|
|
65
|
+
report_blocker.`;
|
|
66
|
+
|
|
67
|
+
export const KICKOFF =
|
|
68
|
+
'Begin the loop: claim and complete all dispatched Flowviant intents per your instructions.';
|
|
69
|
+
export const RESUME =
|
|
70
|
+
'Resume. First call get_blocker_resolution for any blocker you reported; if resolved, ' +
|
|
71
|
+
'apply the human’s answer and continue. Otherwise keep claiming and completing ' +
|
|
72
|
+
'dispatched intents per your instructions.';
|
|
73
|
+
export const SINGLE_KICKOFF =
|
|
74
|
+
'Claim and complete exactly ONE dispatched Flowviant intent per your instructions, then stop.';
|
|
75
|
+
export const SINGLE_RESUME =
|
|
76
|
+
'Resume your current task. Call get_blocker_resolution for the blocker you reported; ' +
|
|
77
|
+
'if resolved, apply the human’s answer and finish this one intent, then stop.';
|
|
78
|
+
|
|
79
|
+
// Unattended (default) skips prompts so the agent never stalls with no terminal;
|
|
80
|
+
// FLOWVIANT_SAFE=1 restricts to a curated toolset instead.
|
|
81
|
+
const PERM = SAFE
|
|
82
|
+
? [
|
|
83
|
+
'--allowedTools',
|
|
84
|
+
'mcp__flowviant',
|
|
85
|
+
'Edit',
|
|
86
|
+
'Write',
|
|
87
|
+
'Read',
|
|
88
|
+
'Grep',
|
|
89
|
+
'Glob',
|
|
90
|
+
'Bash(git:*)',
|
|
91
|
+
'Bash(gh:*)',
|
|
92
|
+
'Bash(npm:*)',
|
|
93
|
+
'Bash(bun:*)',
|
|
94
|
+
]
|
|
95
|
+
: ['--dangerously-skip-permissions'];
|
|
96
|
+
|
|
97
|
+
export const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
|
|
98
|
+
|
|
99
|
+
// Sentinels must appear on their OWN line (the prompts require it). Substring
|
|
100
|
+
// matching falsely fired when an agent merely *mentioned* the word in prose
|
|
101
|
+
// (e.g. "I won't fabricate a BLOCKED:<id> line"), trapping the worker in a fake
|
|
102
|
+
// blocked loop. Anchor to a full line instead.
|
|
103
|
+
export const sawSentinel = (out, name) => new RegExp(`^\\s*${name}\\s*$`, 'm').test(out);
|
|
104
|
+
export const blockedId = (out) => {
|
|
105
|
+
const m = out.match(/^\s*BLOCKED:(\S+)\s*$/m);
|
|
106
|
+
return m ? m[1] : null;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export function mcpConfigFor(token, mcpUrl) {
|
|
110
|
+
const dir = mkdtempSync(join(tmpdir(), 'flowviant-'));
|
|
111
|
+
const p = join(dir, 'mcp.json');
|
|
112
|
+
writeFileSync(
|
|
113
|
+
p,
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
mcpServers: {
|
|
116
|
+
flowviant: { type: 'http', url: mcpUrl, headers: { Authorization: `Bearer ${token}` } },
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
return { dir, path: p };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// One Claude Code turn. Output is captured (for sentinel detection) and streamed
|
|
124
|
+
// through, line-prefixed with the worker label so a fleet stays legible.
|
|
125
|
+
export function runTurn({ prompt, resume, system, cwd, mcpConfig, label, onSpawn }) {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
const args = [];
|
|
128
|
+
if (resume) args.push('--continue');
|
|
129
|
+
args.push('-p', prompt, '--mcp-config', mcpConfig, '--append-system-prompt', system, ...PERM);
|
|
130
|
+
const child = spawn('claude', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
131
|
+
onSpawn?.(child);
|
|
132
|
+
let out = '';
|
|
133
|
+
const pfx = label ? `${label} ` : '';
|
|
134
|
+
const onChunk = (d) => {
|
|
135
|
+
const s = d.toString();
|
|
136
|
+
out += s;
|
|
137
|
+
process.stdout.write(pfx ? s.replace(/\n/g, `\n${pfx}`) : s);
|
|
138
|
+
};
|
|
139
|
+
child.stdout.on('data', onChunk);
|
|
140
|
+
child.stderr.on('data', onChunk);
|
|
141
|
+
child.on('error', (e) => {
|
|
142
|
+
if (e.code === 'ENOENT') {
|
|
143
|
+
console.error("\nerror: 'claude' CLI not found on PATH. Install Claude Code first.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
console.error(e);
|
|
147
|
+
resolve(out);
|
|
148
|
+
});
|
|
149
|
+
child.on('close', () => resolve(out));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Parsed configuration: env vars, CLI flags, and the chosen credentials. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
|
|
7
|
+
export const VERSION = '0.6.0';
|
|
8
|
+
|
|
9
|
+
// Credential stored by `flowviant login` (device auth) — the no-token,
|
|
10
|
+
// no-env-var path. An explicit --fleet flag or FLOWVIANT_FLEET env still wins.
|
|
11
|
+
function readStoredCredential() {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(join(homedir(), '.flowviant', 'credentials.json'), 'utf8'));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const stored = readStoredCredential();
|
|
19
|
+
|
|
20
|
+
function argFlag(name) {
|
|
21
|
+
const i = process.argv.indexOf(name);
|
|
22
|
+
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const API_BASE = process.env.FLOWVIANT_API_URL || 'https://api.flowviant.com/api/v2';
|
|
26
|
+
export const MCP_URL = process.env.FLOWVIANT_MCP_URL || `${API_BASE}/mcp`;
|
|
27
|
+
export const FLEET_URL = process.env.FLOWVIANT_FLEET_URL || `${API_BASE}/fleet/agents`;
|
|
28
|
+
export const POLL_SECONDS = Number(process.env.POLL_SECONDS || 20);
|
|
29
|
+
export const IDLE_SECONDS = Number(process.env.IDLE_SECONDS || 30);
|
|
30
|
+
// Live mode: after this long idle-parked on a blocker, tear the session down to
|
|
31
|
+
// free the Claude process (the intent stays claimed; it resumes when answered).
|
|
32
|
+
export const PARK_TIMEOUT_SECONDS = Number(process.env.PARK_TIMEOUT_SECONDS || 900);
|
|
33
|
+
export const RECONCILE_SECONDS = Number(process.env.RECONCILE_SECONDS || 10);
|
|
34
|
+
// Proactively refresh a worker token this many seconds before its lease lapses,
|
|
35
|
+
// so a long-lived daemon never silently 401s on an expired token.
|
|
36
|
+
export const REFRESH_BEFORE_SECONDS = Number(process.env.REFRESH_BEFORE_SECONDS || 3600);
|
|
37
|
+
export const SAFE = process.env.FLOWVIANT_SAFE === '1';
|
|
38
|
+
// Opt-in phase-2 live mode: persistent Agent-SDK session per task (streams into
|
|
39
|
+
// the task channel, injectable, blocker-parks in place) instead of one-shot
|
|
40
|
+
// `claude -p` turns. Off = the proven poll/sentinel path, untouched.
|
|
41
|
+
export const LIVE = process.env.FLOWVIANT_LIVE === '1';
|
|
42
|
+
// Sent on the daemon's own HTTP calls so Cloudflare Bot Fight Mode doesn't 403
|
|
43
|
+
// them (Node's default UA is treated as a bot). Claude Code sends its own UA.
|
|
44
|
+
export const USER_AGENT = `flowviant/${VERSION}`;
|
|
45
|
+
|
|
46
|
+
// One of: a fleet credential (recommended), one worker token, or a comma list.
|
|
47
|
+
export const FLEET_TOKEN =
|
|
48
|
+
argFlag('--fleet') || process.env.FLOWVIANT_FLEET || stored?.fleetToken || '';
|
|
49
|
+
const rawTokens =
|
|
50
|
+
argFlag('--tokens') ||
|
|
51
|
+
process.env.FLOWVIANT_TOKENS ||
|
|
52
|
+
argFlag('--token') ||
|
|
53
|
+
process.env.FLOWVIANT_TOKEN ||
|
|
54
|
+
'';
|
|
55
|
+
export const tokens = rawTokens.split(',').map((t) => t.trim()).filter(Boolean);
|