@onepointfour-packs/flaredeck-agent 0.1.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.md +14 -0
- package/README.md +102 -0
- package/dist/agent.d.ts +104 -0
- package/dist/agent.js +329 -0
- package/dist/agent.js.map +1 -0
- package/dist/approval.d.ts +72 -0
- package/dist/approval.js +161 -0
- package/dist/approval.js.map +1 -0
- package/dist/approvals.d.ts +2 -0
- package/dist/approvals.js +148 -0
- package/dist/approvals.js.map +1 -0
- package/dist/auth.d.ts +79 -0
- package/dist/auth.js +181 -0
- package/dist/auth.js.map +1 -0
- package/dist/automode.d.ts +33 -0
- package/dist/automode.js +69 -0
- package/dist/automode.js.map +1 -0
- package/dist/branches.d.ts +25 -0
- package/dist/branches.js +81 -0
- package/dist/branches.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -0
- package/dist/codesearch.d.ts +27 -0
- package/dist/codesearch.js +111 -0
- package/dist/codesearch.js.map +1 -0
- package/dist/concurrency.d.ts +14 -0
- package/dist/concurrency.js +52 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config.d.ts +106 -0
- package/dist/config.js +119 -0
- package/dist/config.js.map +1 -0
- package/dist/engine-antigravity.d.ts +23 -0
- package/dist/engine-antigravity.js +357 -0
- package/dist/engine-antigravity.js.map +1 -0
- package/dist/engine-claude.d.ts +18 -0
- package/dist/engine-claude.js +64 -0
- package/dist/engine-claude.js.map +1 -0
- package/dist/engine.d.ts +122 -0
- package/dist/engine.js +36 -0
- package/dist/engine.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/environment.d.ts +39 -0
- package/dist/environment.js +100 -0
- package/dist/environment.js.map +1 -0
- package/dist/flaredeck-check.d.ts +21 -0
- package/dist/flaredeck-check.js +12 -0
- package/dist/flaredeck-check.js.map +1 -0
- package/dist/flaredeck-proxy.d.ts +60 -0
- package/dist/flaredeck-proxy.js +186 -0
- package/dist/flaredeck-proxy.js.map +1 -0
- package/dist/flaredeck.d.ts +132 -0
- package/dist/flaredeck.js +106 -0
- package/dist/flaredeck.js.map +1 -0
- package/dist/gemini.d.ts +21 -0
- package/dist/gemini.js +59 -0
- package/dist/gemini.js.map +1 -0
- package/dist/gitguard.d.ts +2 -0
- package/dist/gitguard.js +90 -0
- package/dist/gitguard.js.map +1 -0
- package/dist/github.d.ts +38 -0
- package/dist/github.js +153 -0
- package/dist/github.js.map +1 -0
- package/dist/githubauth.d.ts +21 -0
- package/dist/githubauth.js +90 -0
- package/dist/githubauth.js.map +1 -0
- package/dist/intake.d.ts +124 -0
- package/dist/intake.js +383 -0
- package/dist/intake.js.map +1 -0
- package/dist/jam.d.ts +45 -0
- package/dist/jam.js +385 -0
- package/dist/jam.js.map +1 -0
- package/dist/jev.d.ts +31 -0
- package/dist/jev.js +75 -0
- package/dist/jev.js.map +1 -0
- package/dist/jevguard.d.ts +28 -0
- package/dist/jevguard.js +213 -0
- package/dist/jevguard.js.map +1 -0
- package/dist/mailbox.d.ts +22 -0
- package/dist/mailbox.js +51 -0
- package/dist/mailbox.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +764 -0
- package/dist/main.js.map +1 -0
- package/dist/markdown.d.ts +5 -0
- package/dist/markdown.js +81 -0
- package/dist/markdown.js.map +1 -0
- package/dist/oauth.d.ts +30 -0
- package/dist/oauth.js +216 -0
- package/dist/oauth.js.map +1 -0
- package/dist/permissions.d.ts +43 -0
- package/dist/permissions.js +57 -0
- package/dist/permissions.js.map +1 -0
- package/dist/preflight.d.ts +71 -0
- package/dist/preflight.js +116 -0
- package/dist/preflight.js.map +1 -0
- package/dist/providers.d.ts +26 -0
- package/dist/providers.js +27 -0
- package/dist/providers.js.map +1 -0
- package/dist/repopicker.d.ts +3 -0
- package/dist/repopicker.js +111 -0
- package/dist/repopicker.js.map +1 -0
- package/dist/runs.d.ts +59 -0
- package/dist/runs.js +211 -0
- package/dist/runs.js.map +1 -0
- package/dist/sandbox.d.ts +13 -0
- package/dist/sandbox.js +110 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schedules.d.ts +11 -0
- package/dist/schedules.js +85 -0
- package/dist/schedules.js.map +1 -0
- package/dist/secretguard.d.ts +4 -0
- package/dist/secretguard.js +115 -0
- package/dist/secretguard.js.map +1 -0
- package/dist/secrets.d.ts +27 -0
- package/dist/secrets.js +51 -0
- package/dist/secrets.js.map +1 -0
- package/dist/serve.d.ts +17 -0
- package/dist/serve.js +1206 -0
- package/dist/serve.js.map +1 -0
- package/dist/status.d.ts +73 -0
- package/dist/status.js +98 -0
- package/dist/status.js.map +1 -0
- package/dist/stream.d.ts +91 -0
- package/dist/stream.js +103 -0
- package/dist/stream.js.map +1 -0
- package/dist/streamtoken.d.ts +2 -0
- package/dist/streamtoken.js +30 -0
- package/dist/streamtoken.js.map +1 -0
- package/dist/team.d.ts +61 -0
- package/dist/team.js +136 -0
- package/dist/team.js.map +1 -0
- package/dist/ui.d.ts +110 -0
- package/dist/ui.js +348 -0
- package/dist/ui.js.map +1 -0
- package/dist/updater.d.ts +15 -0
- package/dist/updater.js +178 -0
- package/dist/updater.js.map +1 -0
- package/dist/workspace.d.ts +115 -0
- package/dist/workspace.js +247 -0
- package/dist/workspace.js.map +1 -0
- package/dist/worktree.d.ts +42 -0
- package/dist/worktree.js +140 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +52 -0
- package/python/sidecar.py +178 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Licence
|
|
2
|
+
|
|
3
|
+
Copyright © Phiresky. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is provided to let you run an agent against the FlareDeck service. You may
|
|
6
|
+
install and run it, on as many machines as you like, for that purpose.
|
|
7
|
+
|
|
8
|
+
You may not redistribute it, sell it, or offer it as part of another service, and you may not
|
|
9
|
+
use it to build a competing product. It is published here so that anyone with a FlareDeck
|
|
10
|
+
workspace can install it easily — not as open source.
|
|
11
|
+
|
|
12
|
+
No warranty of any kind is given, and no liability is accepted for any loss arising from its
|
|
13
|
+
use. An agent runs commands on the machine you put it on; what it does there is yours to
|
|
14
|
+
supervise.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @onepointfour-packs/flaredeck-agent
|
|
2
|
+
|
|
3
|
+
Runs a FlareDeck agent on your own machine. It watches the board for work assigned to it,
|
|
4
|
+
picks each card up, does it in a checkout of its own, reports back on the card, and asks a
|
|
5
|
+
person when it genuinely needs one.
|
|
6
|
+
|
|
7
|
+
You need a [FlareDeck](https://app.flaredeck.app) workspace and an agent account in it
|
|
8
|
+
(Users → Add Agent). The agent works as that account: its comments, its branches and its
|
|
9
|
+
pull requests are its own, and you can see everything it did on the card.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @anthropic-ai/claude-code @onepointfour-packs/flaredeck-agent
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Node 20 or newer. The Claude CLI is what the agent runs on, and it carries its own login — a
|
|
18
|
+
Claude subscription rather than metered API billing, which matters when an agent runs for hours.
|
|
19
|
+
|
|
20
|
+
## Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd /path/to/your/repo
|
|
24
|
+
flaredeck-agent login # once per machine — asks four short questions
|
|
25
|
+
flaredeck-agent # run it; leave it in tmux or a service
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`login` asks which FlareDeck account this machine acts as, authorises GitHub so it can publish
|
|
29
|
+
branches, works out which repositories it may touch, and offers to set an OpenRouter key for the
|
|
30
|
+
command guard. It detects an SSH session and gives you a code to paste instead of opening a
|
|
31
|
+
browser, so a headless box is fine.
|
|
32
|
+
|
|
33
|
+
## What it does with a card
|
|
34
|
+
|
|
35
|
+
1. **Works out where the work belongs.** A board can name a repository per component and a
|
|
36
|
+
branch per environment; the card's own fields then say which repository and which branch.
|
|
37
|
+
Where a card says nothing, the agent reads it and works it out, and asks a person rather than
|
|
38
|
+
guessing between several.
|
|
39
|
+
2. **Takes a checkout of its own.** A git worktree per card, on its own branch, cut from the
|
|
40
|
+
branch that card's environment means. Two agents never share a working tree.
|
|
41
|
+
3. **Does the work and reports on the card.** What it found, what it changed, what it could not
|
|
42
|
+
do. The account that comments is the account you created.
|
|
43
|
+
4. **Publishes through tools, never the shell.** `push_branch` pushes the branch this card made
|
|
44
|
+
and nothing else; `open_pull_request` targets the branch the board declared.
|
|
45
|
+
5. **Hands the checkout back** when the card is finished and nothing is left on it.
|
|
46
|
+
|
|
47
|
+
## Where you point it
|
|
48
|
+
|
|
49
|
+
Start it **in a repository** and it works that repository. Start it in a **folder of
|
|
50
|
+
repositories** and it works all of them, choosing per card — `login` shows you what it found and
|
|
51
|
+
you tick which ones it may touch. There is no list to maintain: the folder is the declaration.
|
|
52
|
+
|
|
53
|
+
## What it will not do
|
|
54
|
+
|
|
55
|
+
These are enforced, not asked for in a prompt:
|
|
56
|
+
|
|
57
|
+
- **It cannot push from the shell.** `origin` is fetch-only inside each card's worktree, so
|
|
58
|
+
`git push` fails however it is spelled. Publishing goes through the tools, which can only
|
|
59
|
+
publish that card's own branch.
|
|
60
|
+
- **It cannot write outside its own checkout.** It may read the neighbouring repositories —
|
|
61
|
+
that is often how a change is understood — but editing them is refused.
|
|
62
|
+
- **It will not run an irreversible command.** With an OpenRouter key it judges each risky
|
|
63
|
+
command and refuses what cannot be undone; without one, every risky command stops and asks a
|
|
64
|
+
person. The guard is never off.
|
|
65
|
+
- **It will not quietly guess.** A card whose repository or branch it cannot establish goes to a
|
|
66
|
+
person, with what is missing, rather than landing somewhere plausible.
|
|
67
|
+
|
|
68
|
+
None of this is a substitute for branch protection on your remote. It is the door, not the fence.
|
|
69
|
+
|
|
70
|
+
## Asking a person
|
|
71
|
+
|
|
72
|
+
When an agent genuinely needs something only a person has — an access it does not hold, a
|
|
73
|
+
decision about what the product should do, permission for something irreversible — it flags the
|
|
74
|
+
card and the people who can answer are notified, on their phone if they have the app. The ask
|
|
75
|
+
arrives as one short question with the answers as buttons, so it can be answered in a tap.
|
|
76
|
+
|
|
77
|
+
Before that happens the harness checks whether the agent could have finished the job itself, and
|
|
78
|
+
refuses to interrupt anyone over work it was capable of doing.
|
|
79
|
+
|
|
80
|
+
## Engines
|
|
81
|
+
|
|
82
|
+
Claude by default. `flaredeck-agent --agy` runs the same harness on Gemini through Google's
|
|
83
|
+
Antigravity SDK — same tools, same guards, same reporting, so you can compare them on the same
|
|
84
|
+
board. The Python sidecar it needs is installed on first use.
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
| | |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `flaredeck-agent` | Run, and keep running |
|
|
91
|
+
| `flaredeck-agent login` | Set this machine up |
|
|
92
|
+
| `flaredeck-agent whoami` | Which account, which repositories, what is switched on |
|
|
93
|
+
| `flaredeck-agent run "<task>"` | One task, right now, without a card |
|
|
94
|
+
| `flaredeck-agent approvals` | Answer what your agents are waiting on |
|
|
95
|
+
| `flaredeck-agent providers` | Keys for the optional small models |
|
|
96
|
+
| `flaredeck-agent secret` | Store a credential the agent may use but never see |
|
|
97
|
+
|
|
98
|
+
## Where things are kept
|
|
99
|
+
|
|
100
|
+
`~/.config/flaredeck-agent/` — one file per machine for logins and keys, one per workspace for
|
|
101
|
+
what that agent works on. Nothing is stored in your repository, and no credential is written
|
|
102
|
+
into a config you might copy to another box.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { query, type HookCallbackMatcher, type HookEvent, type Options } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import type { Config } from './config.js';
|
|
3
|
+
import { type Decider } from './permissions.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one place a session is configured.
|
|
6
|
+
*
|
|
7
|
+
* Everything still to come — hooks for the supervisor, the event-stream connector, per-role tool
|
|
8
|
+
* sets, budgets — is another field on the object this builds. Keeping that in a single function
|
|
9
|
+
* is most of what "a foundation" means here: no second place to remember.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* What the agent is told about itself before it does anything.
|
|
13
|
+
*
|
|
14
|
+
* An agent that has to be ASKED who it is will sometimes not ask. Stating it up front means
|
|
15
|
+
* identity and workload are facts of the session rather than something the model has to remember
|
|
16
|
+
* to look up — and `get-outstanding` is named explicitly so the first thing it does is find its
|
|
17
|
+
* own work rather than wait to be handed some.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AGENT_BRIEF: string;
|
|
20
|
+
/** In-process tool sets, mounted only when the caller has a use for them. */
|
|
21
|
+
export type ToolSet = {
|
|
22
|
+
server: unknown;
|
|
23
|
+
toolNames: string[];
|
|
24
|
+
};
|
|
25
|
+
export declare function buildOptions(config: Config, decider: Decider, extra?: Partial<Options>,
|
|
26
|
+
/** The card's worktree — the only place this turn may write. See sandbox.ts. */
|
|
27
|
+
sandbox?: string,
|
|
28
|
+
/** Publishing tools, when a job is running. Absent for one-off `run` and `whoami`. */
|
|
29
|
+
git?: ToolSet,
|
|
30
|
+
/** The harness's own FlareDeck token, handed to the MCP server so the model's tools act as
|
|
31
|
+
* the SAME account the harness listens as. */
|
|
32
|
+
token?: string,
|
|
33
|
+
/** Present only for a LEAD. A worker never receives these tools, so "a worker cannot reach
|
|
34
|
+
* across the team" holds because there is nothing to call, not because a prompt asked nicely. */
|
|
35
|
+
team?: ToolSet,
|
|
36
|
+
/** FlareDeck's tools, served in-process from what the server just listed. */
|
|
37
|
+
flaredeck?: ToolSet,
|
|
38
|
+
/** Production runs, where this machine names a target. Asking is harmless; running needs a person. */
|
|
39
|
+
runs?: ToolSet,
|
|
40
|
+
/** Recurring tasks. Asking is harmless: a person's approval is what starts one. */
|
|
41
|
+
schedules?: ToolSet,
|
|
42
|
+
/** The Jev gate, when this machine has a key for it. See jevguard.ts. */
|
|
43
|
+
guard?: Partial<Record<HookEvent, HookCallbackMatcher[]>>): Options;
|
|
44
|
+
/** process.env minus the one variable that would silently switch us to metered billing. */
|
|
45
|
+
export declare function subscriptionEnv(): Record<string, string | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* A tool call the agent made or tried to make. The supervisor will read these; for now they are
|
|
48
|
+
* what the CLI prints. Deriving them here rather than logging inline keeps the stream one thing
|
|
49
|
+
* that several consumers can read.
|
|
50
|
+
*/
|
|
51
|
+
export type AgentEvent = {
|
|
52
|
+
kind: 'ready';
|
|
53
|
+
tools: string[];
|
|
54
|
+
mcp: {
|
|
55
|
+
name: string;
|
|
56
|
+
status: string;
|
|
57
|
+
}[];
|
|
58
|
+
apiKeySource: string;
|
|
59
|
+
permissionMode: string;
|
|
60
|
+
/** Needed to resume this session later, so an agent picking a card back up remembers it. */
|
|
61
|
+
sessionId: string;
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'text';
|
|
64
|
+
text: string;
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'tool';
|
|
67
|
+
name: string;
|
|
68
|
+
}
|
|
69
|
+
/** The agent's own plan, as it writes it. What a supervisor reads instead of asking. */
|
|
70
|
+
| {
|
|
71
|
+
kind: 'todo';
|
|
72
|
+
text: string;
|
|
73
|
+
status?: string;
|
|
74
|
+
id?: string;
|
|
75
|
+
created?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** `contextTokens` is the size of the last request — what the next turn on this session starts from. */
|
|
78
|
+
/** After each model reply: the size of the request that produced it. */
|
|
79
|
+
| {
|
|
80
|
+
kind: 'usage';
|
|
81
|
+
contextTokens: number;
|
|
82
|
+
}
|
|
83
|
+
/** The SDK compacted the context mid-turn. It keeps the session alive by throwing detail away. */
|
|
84
|
+
| {
|
|
85
|
+
kind: 'compacted';
|
|
86
|
+
} | {
|
|
87
|
+
kind: 'done';
|
|
88
|
+
ok: boolean;
|
|
89
|
+
result: string;
|
|
90
|
+
contextTokens?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Raw text a tool returned. Our own servers' output, so it can be parsed rather than read. */
|
|
93
|
+
| {
|
|
94
|
+
kind: 'result';
|
|
95
|
+
text: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Run one task, yielding structured events rather than printing.
|
|
99
|
+
*
|
|
100
|
+
* `onStart` hands back the live query. That is what lets someone type at a turn already in
|
|
101
|
+
* progress — steering it rather than interrupting and starting again, which would throw away
|
|
102
|
+
* whatever it had worked out so far.
|
|
103
|
+
*/
|
|
104
|
+
export declare function runTask(prompt: string, options: Options, onStart?: (q: ReturnType<typeof query>) => void): AsyncGenerator<AgentEvent>;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { writeAutoModeSettings } from './automode.js';
|
|
3
|
+
import { gitGuard } from './gitguard.js';
|
|
4
|
+
import { secretGuard } from './secretguard.js';
|
|
5
|
+
import { sandboxGuard } from './sandbox.js';
|
|
6
|
+
import { readToolNames } from './permissions.js';
|
|
7
|
+
import { LEAD_BRIEF } from './team.js';
|
|
8
|
+
/**
|
|
9
|
+
* The one place a session is configured.
|
|
10
|
+
*
|
|
11
|
+
* Everything still to come — hooks for the supervisor, the event-stream connector, per-role tool
|
|
12
|
+
* sets, budgets — is another field on the object this builds. Keeping that in a single function
|
|
13
|
+
* is most of what "a foundation" means here: no second place to remember.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* What the agent is told about itself before it does anything.
|
|
17
|
+
*
|
|
18
|
+
* An agent that has to be ASKED who it is will sometimes not ask. Stating it up front means
|
|
19
|
+
* identity and workload are facts of the session rather than something the model has to remember
|
|
20
|
+
* to look up — and `get-outstanding` is named explicitly so the first thing it does is find its
|
|
21
|
+
* own work rather than wait to be handed some.
|
|
22
|
+
*/
|
|
23
|
+
export const AGENT_BRIEF = [
|
|
24
|
+
'You are a FlareDeck agent running unattended on your own machine, with your own FlareDeck',
|
|
25
|
+
'account. Nobody is watching this terminal.',
|
|
26
|
+
'',
|
|
27
|
+
'Two things already tell you what to do, and they win over anything here:',
|
|
28
|
+
' · your agent description in FlareDeck — who you are and what you are for',
|
|
29
|
+
' · the board\'s agent behaviour rules — how cards move, who picks up from where, who merges',
|
|
30
|
+
'Read them and follow them. What follows is only how THIS MACHINE works, which FlareDeck',
|
|
31
|
+
'cannot know.',
|
|
32
|
+
'',
|
|
33
|
+
'You work alone. There are no sub-agents to delegate to — do the work yourself, in order.',
|
|
34
|
+
'',
|
|
35
|
+
'Before changing anything, work out what THIS task actually needs and check it is there. Some',
|
|
36
|
+
'cards need nothing; for those, just get on with it. If something you need is missing, do not',
|
|
37
|
+
'work around it and do not diagnose the code — nothing is running, so anything you conclude',
|
|
38
|
+
'would be wrong. Say what is missing, call `set-needs-human`, and stop.',
|
|
39
|
+
'',
|
|
40
|
+
'Keep your todo list in step with the work. It is what a supervisor reads to see how you are',
|
|
41
|
+
'getting on, so it should show the real state rather than a plan you have moved past.',
|
|
42
|
+
'',
|
|
43
|
+
'If FlareDeck calls start coming back "interrupted before a result was received" or "Stream',
|
|
44
|
+
'closed", that is this session\'s channel to FlareDeck, not FlareDeck: it will not recover',
|
|
45
|
+
'within the turn, and no health check will show otherwise. Do not retry or wait for it. Say',
|
|
46
|
+
'in one line what did not land, and end the turn — the next event starts a session whose',
|
|
47
|
+
'calls work, and it reads the card before touching anything.',
|
|
48
|
+
'',
|
|
49
|
+
'Your session ends when the card is done, and you will be asked for a handover before it does.',
|
|
50
|
+
'Nothing you remember survives that — only what you write down.',
|
|
51
|
+
'',
|
|
52
|
+
'You have no memory on this machine, on purpose: you may be started anywhere, and you must be',
|
|
53
|
+
'the same agent wherever that is. Your memory is on your FlareDeck account — `remember` writes',
|
|
54
|
+
'to it and it is read back to you at the start of every session. Progress goes on the card;',
|
|
55
|
+
'lessons go in memory.',
|
|
56
|
+
].join('\n');
|
|
57
|
+
/**
|
|
58
|
+
* FlareDeck, plus whichever in-process tool sets are mounted.
|
|
59
|
+
*
|
|
60
|
+
* FlareDeck is served from inside this process (see flaredeck-proxy.ts) whenever the caller has
|
|
61
|
+
* discovered it; the harness is then the MCP client, with a fresh token per call. The direct
|
|
62
|
+
* http entry remains only for a caller that has a token but did no discovery — it carries a
|
|
63
|
+
* bearer frozen at session start, which is exactly the weakness the proxy removes.
|
|
64
|
+
*/
|
|
65
|
+
function mcpServers(config, token, mounted, flaredeck) {
|
|
66
|
+
const servers = {};
|
|
67
|
+
if (flaredeck) {
|
|
68
|
+
servers[config.mcpServerName] = flaredeck.server;
|
|
69
|
+
}
|
|
70
|
+
else if (token) {
|
|
71
|
+
servers[config.mcpServerName] = {
|
|
72
|
+
type: 'http',
|
|
73
|
+
url: config.mcpUrl,
|
|
74
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
for (const [name, set] of Object.entries(mounted))
|
|
78
|
+
if (set)
|
|
79
|
+
servers[name] = set.server;
|
|
80
|
+
return servers;
|
|
81
|
+
}
|
|
82
|
+
/** Hook sets into one, keeping every matcher for an event instead of letting the last one win. */
|
|
83
|
+
function mergeHooks(...sets) {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const set of sets) {
|
|
86
|
+
if (!set)
|
|
87
|
+
continue;
|
|
88
|
+
for (const [event, matchers] of Object.entries(set)) {
|
|
89
|
+
out[event] = [...(out[event] ?? []), ...matchers];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
export function buildOptions(config, decider, extra = {},
|
|
95
|
+
/** The card's worktree — the only place this turn may write. See sandbox.ts. */
|
|
96
|
+
sandbox,
|
|
97
|
+
/** Publishing tools, when a job is running. Absent for one-off `run` and `whoami`. */
|
|
98
|
+
git,
|
|
99
|
+
/** The harness's own FlareDeck token, handed to the MCP server so the model's tools act as
|
|
100
|
+
* the SAME account the harness listens as. */
|
|
101
|
+
token,
|
|
102
|
+
/** Present only for a LEAD. A worker never receives these tools, so "a worker cannot reach
|
|
103
|
+
* across the team" holds because there is nothing to call, not because a prompt asked nicely. */
|
|
104
|
+
team,
|
|
105
|
+
/** FlareDeck's tools, served in-process from what the server just listed. */
|
|
106
|
+
flaredeck,
|
|
107
|
+
/** Production runs, where this machine names a target. Asking is harmless; running needs a person. */
|
|
108
|
+
runs,
|
|
109
|
+
/** Recurring tasks. Asking is harmless: a person's approval is what starts one. */
|
|
110
|
+
schedules,
|
|
111
|
+
/** The Jev gate, when this machine has a key for it. See jevguard.ts. */
|
|
112
|
+
guard) {
|
|
113
|
+
return {
|
|
114
|
+
systemPrompt: {
|
|
115
|
+
type: 'preset',
|
|
116
|
+
preset: 'claude_code',
|
|
117
|
+
append: team ? `${AGENT_BRIEF}\n${LEAD_BRIEF}` : AGENT_BRIEF,
|
|
118
|
+
},
|
|
119
|
+
cwd: config.repo,
|
|
120
|
+
// Run on the Claude subscription, never on metered API billing. The SDK prefers
|
|
121
|
+
// ANTHROPIC_API_KEY whenever it is present, and an agent left running all night on a
|
|
122
|
+
// per-token key is a bill nobody agreed to — so the key is removed from the environment the
|
|
123
|
+
// Claude process gets rather than merely warned about. `env` REPLACES the subprocess
|
|
124
|
+
// environment, so everything else is passed through deliberately.
|
|
125
|
+
env: subscriptionEnv(),
|
|
126
|
+
// Isolation. Without this the agent inherits whatever Claude Code is configured with on this
|
|
127
|
+
// box — on a developer's machine that meant ten other MCP servers and 473 tools, and Flaredeck
|
|
128
|
+
// itself then timed out competing with them to connect. An agent must behave the same on every
|
|
129
|
+
// machine, so it loads no ambient settings and gets exactly the servers named below.
|
|
130
|
+
// 'project' only: the repo's .mcp.json (where login registered Flaredeck, with its stored
|
|
131
|
+
// credential) and the repo's CLAUDE.md, which an agent working here should follow. User-level
|
|
132
|
+
// settings are excluded — that is where this box's ten unrelated MCP servers came from, and an
|
|
133
|
+
// agent has to behave the same on every machine.
|
|
134
|
+
/**
|
|
135
|
+
* Trusted agent on its own sandboxed machine: it should get on with the job, not ask about
|
|
136
|
+
* every file it edits. Auto mode does that — a classifier reviews each action instead of a
|
|
137
|
+
* human and blocks "anything that escalates beyond your request, targets unrecognized
|
|
138
|
+
* infrastructure, or appears driven by hostile content Claude read".
|
|
139
|
+
*
|
|
140
|
+
* That last clause is why this beats bypassPermissions here. An agent works from card
|
|
141
|
+
* comments and web pages written by other people, so prompt injection is a live risk rather
|
|
142
|
+
* than a theoretical one — and the classifier's own request has tool RESULTS stripped, so
|
|
143
|
+
* hostile text in a comment cannot talk to the classifier directly.
|
|
144
|
+
*
|
|
145
|
+
* Deny rules and hooks are still evaluated before the mode, so the hard guardrails hold.
|
|
146
|
+
*/
|
|
147
|
+
permissionMode: 'auto',
|
|
148
|
+
// Tell the classifier what this machine is. Without it the judge trusts only the working
|
|
149
|
+
// directory and the repo's remotes, so routine agent work looks like exfiltration. The file
|
|
150
|
+
// is written beside the agent's own config, never in the repo — see automode.ts.
|
|
151
|
+
settings: writeAutoModeSettings(config),
|
|
152
|
+
settingSources: ['project'],
|
|
153
|
+
// Filesystem settings are not the only ambient source: claude.ai account connectors
|
|
154
|
+
// which are fetched from the logged-in account. An agent should not inherit those either.
|
|
155
|
+
managedSettings: {
|
|
156
|
+
disableClaudeAiConnectors: true,
|
|
157
|
+
// Same reason as the tools above: a scheduled cloud run is work nobody on this machine is
|
|
158
|
+
// watching. Skills are not covered by disallowedTools, so they are turned off here.
|
|
159
|
+
skillOverrides: { schedule: 'off' },
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* FlareDeck, authenticated with the harness's OWN token.
|
|
163
|
+
*
|
|
164
|
+
* This replaces registering the server with the Claude CLI and authorising it separately.
|
|
165
|
+
* That needed a second OAuth, and worse it let the two drift: the tools acted as one account
|
|
166
|
+
* while the harness listened as another, which happened and took a while to spot. One token
|
|
167
|
+
* for both makes that disagreement impossible rather than merely detected.
|
|
168
|
+
*
|
|
169
|
+
* It also removes what that approach dragged in — `claude mcp add`, `claude mcp login`, a
|
|
170
|
+
* .mcp.json written into the repo, and pre-approving it in the CLI's own config, each of
|
|
171
|
+
* which broke in its own way first.
|
|
172
|
+
*/
|
|
173
|
+
mcpServers: mcpServers(config, token, { git, team, runs, schedules }, flaredeck),
|
|
174
|
+
// Only the servers named above. A repo's own .mcp.json, agent frontmatter or plugin could
|
|
175
|
+
// declare a server with the same name and shadow the one that carries the token; this makes
|
|
176
|
+
// that impossible rather than merely unlikely.
|
|
177
|
+
strictMcpConfig: true,
|
|
178
|
+
// Reads only. Writes are left off deliberately so they reach the decider — see permissions.ts.
|
|
179
|
+
allowedTools: [
|
|
180
|
+
...readToolNames(config.mcpServerName),
|
|
181
|
+
...(git?.toolNames ?? []),
|
|
182
|
+
// Looking at the team is a read. It cannot change anyone's work, so it does not
|
|
183
|
+
// need a human's nod every time the lead wants to know who is on what.
|
|
184
|
+
...(team?.toolNames ?? []),
|
|
185
|
+
// Requesting a run changes nothing: the person's Approve inside the call is the gate. Left
|
|
186
|
+
// to the classifier, "production" plus a script is exactly what it would block or bounce
|
|
187
|
+
// to the broker — a second approval for the act of asking.
|
|
188
|
+
...(runs?.toolNames ?? []),
|
|
189
|
+
...(schedules?.toolNames ?? []),
|
|
190
|
+
],
|
|
191
|
+
// Always on, not only while a job runs. A guard that a different entry point can skip is a
|
|
192
|
+
// guard with a way round it — and `run` was exactly that way round.
|
|
193
|
+
// Three guards, merged by event rather than one replacing another: gitGuard keeps publishing
|
|
194
|
+
// in the tools, secretGuard fills in {{secret:NAME}} on the way out and redacts any secret
|
|
195
|
+
// that comes back, and the optional Jev gate judges whatever could be irreversible.
|
|
196
|
+
hooks: mergeHooks(gitGuard(() => { }), secretGuard(() => { }),
|
|
197
|
+
// Write where your job is, read anywhere. Starting the agent IN its worktree was only
|
|
198
|
+
// placement — a shell can cd out of it — and with several repositories in one folder there
|
|
199
|
+
// is somewhere to go.
|
|
200
|
+
sandboxGuard(() => sandbox ?? null, () => { }), guard),
|
|
201
|
+
// No sub-agents. One thread that runs a card to completion is far easier to reason about
|
|
202
|
+
// than several sharing a checkout: no worktrees to juggle, nothing competing for the same
|
|
203
|
+
// ports and database, and one place to look when something stalls. A bare name here removes
|
|
204
|
+
// the tool from the model's context entirely, so it never plans around having it.
|
|
205
|
+
// RemoteTrigger and Workflow are worse than sub-agents here, not better: they start runs on
|
|
206
|
+
// OTHER machines, where this harness cannot see them, stop them, or notice them stalling.
|
|
207
|
+
// Asked what it could delegate to, the agent found these itself and offered them — so naming
|
|
208
|
+
// them is the difference between a rule and a suggestion.
|
|
209
|
+
// Everything that starts or steers WORK IN ANOTHER PROCESS. Deliberately not FlareDeck's own
|
|
210
|
+
// send-message or trigger-build: messaging another agent and kicking a CI build are ordinary
|
|
211
|
+
// product features, not this agent quietly forking itself.
|
|
212
|
+
disallowedTools: ['Agent', 'Task', 'RemoteTrigger', 'Workflow', 'SendMessage'],
|
|
213
|
+
canUseTool: async (toolName, input, { signal }) => {
|
|
214
|
+
const decision = await decider({ toolName, input }, { signal });
|
|
215
|
+
return decision.allow
|
|
216
|
+
? { behavior: 'allow', updatedInput: input }
|
|
217
|
+
: { behavior: 'deny', message: decision.reason };
|
|
218
|
+
},
|
|
219
|
+
...extra,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** process.env minus the one variable that would silently switch us to metered billing. */
|
|
223
|
+
export function subscriptionEnv() {
|
|
224
|
+
const { ANTHROPIC_API_KEY: _removed, ...rest } = process.env;
|
|
225
|
+
return {
|
|
226
|
+
...rest,
|
|
227
|
+
// Flaredeck is the one server that must be up before the first turn: an agent that starts
|
|
228
|
+
// without it looks healthy and can't touch a card. The default first-turn wait gave up on it
|
|
229
|
+
// after 5 seconds, so wait properly rather than racing a cold remote server.
|
|
230
|
+
CLAUDE_CODE_MCP_STARTUP_WAIT_MS: rest['CLAUDE_CODE_MCP_STARTUP_WAIT_MS'] ?? '60000',
|
|
231
|
+
MCP_TIMEOUT: rest['MCP_TIMEOUT'] ?? '60000',
|
|
232
|
+
// Current models track multi-step work without writing a list, so the task tools are OFF by
|
|
233
|
+
// default and nothing appears in the stream. We want them ON: the agent's own todo list is
|
|
234
|
+
// how a supervisor sees progress without asking, and TaskCreate/TaskUpdate arrive as
|
|
235
|
+
// tool_use blocks we can read. There is no todos file on disk to read instead.
|
|
236
|
+
CLAUDE_CODE_ENABLE_TODO_TOOLS: rest['CLAUDE_CODE_ENABLE_TODO_TOOLS'] ?? '1',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Run one task, yielding structured events rather than printing.
|
|
241
|
+
*
|
|
242
|
+
* `onStart` hands back the live query. That is what lets someone type at a turn already in
|
|
243
|
+
* progress — steering it rather than interrupting and starting again, which would throw away
|
|
244
|
+
* whatever it had worked out so far.
|
|
245
|
+
*/
|
|
246
|
+
export async function* runTask(prompt, options, onStart) {
|
|
247
|
+
const running = query({ prompt, options });
|
|
248
|
+
onStart?.(running);
|
|
249
|
+
let lastContext = 0;
|
|
250
|
+
for await (const message of running) {
|
|
251
|
+
const m = message;
|
|
252
|
+
if (m['type'] === 'assistant') {
|
|
253
|
+
const u = m['message']?.usage ?? {};
|
|
254
|
+
const n = (u['input_tokens'] ?? 0) + (u['cache_creation_input_tokens'] ?? 0) + (u['cache_read_input_tokens'] ?? 0);
|
|
255
|
+
if (n > 0) {
|
|
256
|
+
lastContext = n;
|
|
257
|
+
yield { kind: 'usage', contextTokens: n };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (m['type'] === 'system' && m['subtype'] === 'compact_boundary')
|
|
261
|
+
yield { kind: 'compacted' };
|
|
262
|
+
if (m['type'] === 'system' && m['subtype'] === 'init') {
|
|
263
|
+
yield {
|
|
264
|
+
kind: 'ready',
|
|
265
|
+
tools: (m['tools'] ?? []).filter((t) => t.startsWith('mcp__')),
|
|
266
|
+
mcp: (m['mcp_servers'] ?? []).map((s) => ({ name: s.name, status: s.status })),
|
|
267
|
+
// 'none' means no API key is in use — i.e. a claude.ai OAuth login. That is what we want.
|
|
268
|
+
apiKeySource: m['apiKeySource'] ?? 'unknown',
|
|
269
|
+
// Asked for 'auto', but a session that cannot have it silently starts in Manual instead,
|
|
270
|
+
// which for an unattended agent means prompting for work nobody will approve. Report what
|
|
271
|
+
// we actually got rather than what we asked for.
|
|
272
|
+
permissionMode: m['permissionMode'] ?? 'unknown',
|
|
273
|
+
sessionId: m['session_id'] ?? '',
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
else if (m['type'] === 'assistant') {
|
|
277
|
+
for (const block of m['message']?.content ?? []) {
|
|
278
|
+
if (block.type === 'text' && block.text.trim())
|
|
279
|
+
yield { kind: 'text', text: block.text };
|
|
280
|
+
if (block.type === 'tool_use') {
|
|
281
|
+
// Read the id defensively: Claude Code repairs close-but-wrong key names before
|
|
282
|
+
// executing, but the STREAM carries the model's raw spelling.
|
|
283
|
+
const i = block.input ?? {};
|
|
284
|
+
if (block.name === 'TaskCreate' || block.name === 'TodoWrite') {
|
|
285
|
+
yield {
|
|
286
|
+
kind: 'todo',
|
|
287
|
+
text: i.subject ?? i.content ?? JSON.stringify(i).slice(0, 120),
|
|
288
|
+
created: true,
|
|
289
|
+
};
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (block.name === 'TaskUpdate') {
|
|
293
|
+
const id = i.taskId ?? i.id ?? i.task_id;
|
|
294
|
+
yield {
|
|
295
|
+
kind: 'todo',
|
|
296
|
+
text: `${id ?? '?'} → ${i.status ?? '?'}`,
|
|
297
|
+
status: i.status,
|
|
298
|
+
...(id ? { id: String(id) } : {}),
|
|
299
|
+
};
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
yield { kind: 'tool', name: block.name };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (m['type'] === 'user') {
|
|
307
|
+
for (const block of m['message']?.content ?? []) {
|
|
308
|
+
if (block.type !== 'tool_result')
|
|
309
|
+
continue;
|
|
310
|
+
const c = block.content;
|
|
311
|
+
const text = typeof c === 'string' ? c : (c ?? []).map((b) => b?.text ?? '').join('');
|
|
312
|
+
if (text)
|
|
313
|
+
yield { kind: 'result', text };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (m['type'] === 'result') {
|
|
317
|
+
// The result's own usage is the SUM over every request in the turn — a fifteen-request
|
|
318
|
+
// turn reads as a million tokens. The context the next turn starts from is the size of
|
|
319
|
+
// the LAST request, which is what its assistant message recorded.
|
|
320
|
+
yield {
|
|
321
|
+
kind: 'done',
|
|
322
|
+
ok: m['subtype'] === 'success',
|
|
323
|
+
result: m['result'] ?? '',
|
|
324
|
+
...(lastContext > 0 ? { contextTokens: lastContext } : {}),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA2E,MAAM,gCAAgC,CAAC;AAEhI,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAgB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,2FAA2F;IAC3F,4CAA4C;IAC5C,EAAE;IACF,0EAA0E;IAC1E,4EAA4E;IAC5E,8FAA8F;IAC9F,yFAAyF;IACzF,cAAc;IACd,EAAE;IACF,0FAA0F;IAC1F,EAAE;IACF,8FAA8F;IAC9F,8FAA8F;IAC9F,4FAA4F;IAC5F,wEAAwE;IACxE,EAAE;IACF,6FAA6F;IAC7F,sFAAsF;IACtF,EAAE;IACF,4FAA4F;IAC5F,2FAA2F;IAC3F,4FAA4F;IAC5F,yFAAyF;IACzF,6DAA6D;IAC7D,EAAE;IACF,+FAA+F;IAC/F,gEAAgE;IAChE,EAAE;IACF,8FAA8F;IAC9F,+FAA+F;IAC/F,4FAA4F;IAC5F,uBAAuB;CACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAKb;;;;;;;GAOG;AACH,SAAS,UAAU,CACjB,MAAc,EACd,KAAyB,EACzB,OAA4C,EAC5C,SAAmB;IAEnB,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC;IACnD,CAAC;SAAM,IAAI,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG;YAC9B,IAAI,EAAE,MAAM;YACZ,GAAG,EAAE,MAAM,CAAC,MAAM;YAClB,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,IAAI,GAAG;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;IACvF,OAAO,OAAgC,CAAC;AAC1C,CAAC;AAED,kGAAkG;AAClG,SAAS,UAAU,CACjB,GAAG,IAAuE;IAE1E,MAAM,GAAG,GAAsD,EAAE,CAAC;IAClE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAyC,EAAE,CAAC;YAC5F,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,OAAgB,EAChB,QAA0B,EAAE;AAC5B,gFAAgF;AAChF,OAAgB;AAChB,sFAAsF;AACtF,GAAa;AACb;+CAC+C;AAC/C,KAAc;AACd;kGACkG;AAClG,IAAc;AACd,6EAA6E;AAC7E,SAAmB;AACnB,sGAAsG;AACtG,IAAc;AACd,mFAAmF;AACnF,SAAmB;AACnB,yEAAyE;AACzE,KAAyD;IAEzD,OAAO;QACL,YAAY,EAAE;YACZ,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,WAAW,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,WAAW;SAC7D;QACD,GAAG,EAAE,MAAM,CAAC,IAAI;QAChB,gFAAgF;QAChF,qFAAqF;QACrF,4FAA4F;QAC5F,qFAAqF;QACrF,kEAAkE;QAClE,GAAG,EAAE,eAAe,EAAE;QACtB,6FAA6F;QAC7F,+FAA+F;QAC/F,+FAA+F;QAC/F,qFAAqF;QACrF,0FAA0F;QAC1F,8FAA8F;QAC9F,+FAA+F;QAC/F,iDAAiD;QACjD;;;;;;;;;;;;WAYG;QACH,cAAc,EAAE,MAAM;QACtB,yFAAyF;QACzF,4FAA4F;QAC5F,iFAAiF;QACjF,QAAQ,EAAE,qBAAqB,CAAC,MAAM,CAAC;QACvC,cAAc,EAAE,CAAC,SAAS,CAAC;QAC3B,oFAAoF;QACpF,0FAA0F;QAC1F,eAAe,EAAE;YACf,yBAAyB,EAAE,IAAI;YAC/B,0FAA0F;YAC1F,oFAAoF;YACpF,cAAc,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;SACpC;QACD;;;;;;;;;;;WAWG;QACH,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,CAAC;QAChF,0FAA0F;QAC1F,4FAA4F;QAC5F,+CAA+C;QAC/C,eAAe,EAAE,IAAI;QACrB,+FAA+F;QAC/F,YAAY,EAAE;YACZ,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC;YACtC,GAAG,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,CAAC;YACzB,gFAAgF;YAChF,uEAAuE;YACvE,GAAG,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC;YAC1B,2FAA2F;YAC3F,yFAAyF;YACzF,2DAA2D;YAC3D,GAAG,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC;YAC1B,GAAG,CAAC,SAAS,EAAE,SAAS,IAAI,EAAE,CAAC;SAChC;QACD,2FAA2F;QAC3F,oEAAoE;QACpE,6FAA6F;QAC7F,2FAA2F;QAC3F,oFAAoF;QACpF,KAAK,EAAE,UAAU,CACf,QAAQ,CAAC,GAAG,EAAE,GAAuC,CAAC,CAAC,EACvD,WAAW,CAAC,GAAG,EAAE,GAAuC,CAAC,CAAC;QAC1D,sFAAsF;QACtF,2FAA2F;QAC3F,sBAAsB;QACtB,YAAY,CAAC,GAAG,EAAE,CAAC,OAAO,IAAI,IAAI,EAAE,GAAG,EAAE,GAAuC,CAAC,CAAC,EAClF,KAAK,CACN;QACD,yFAAyF;QACzF,0FAA0F;QAC1F,4FAA4F;QAC5F,kFAAkF;QAClF,4FAA4F;QAC5F,0FAA0F;QAC1F,6FAA6F;QAC7F,0DAA0D;QAC1D,6FAA6F;QAC7F,6FAA6F;QAC7F,2DAA2D;QAC3D,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,CAAC;QAC9E,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YAChD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAChE,OAAO,QAAQ,CAAC,KAAK;gBACnB,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;gBAC5C,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrD,CAAC;QACD,GAAG,KAAK;KACT,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAC7D,OAAO;QACL,GAAG,IAAI;QACP,0FAA0F;QAC1F,6FAA6F;QAC7F,6EAA6E;QAC7E,+BAA+B,EAAE,IAAI,CAAC,iCAAiC,CAAC,IAAI,OAAO;QACnF,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,OAAO;QAC3C,4FAA4F;QAC5F,2FAA2F;QAC3F,qFAAqF;QACrF,+EAA+E;QAC/E,6BAA6B,EAAE,IAAI,CAAC,+BAA+B,CAAC,IAAI,GAAG;KAC5E,CAAC;AACJ,CAAC;AA8BD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,OAAO,CAC5B,MAAc,EACd,OAAgB,EAChB,OAA+C;IAE/C,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,OAAoC,EAAE,CAAC;QACjE,MAAM,CAAC,GAAG,OAA8B,CAAC;QACzC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,WAAW,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,6BAA6B,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC;YACnH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAC,WAAW,GAAG,CAAC,CAAC;gBAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;YAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,kBAAkB;YAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC/F,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,CAAC;YACtD,MAAM;gBACJ,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACtE,GAAG,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;gBACnF,0FAA0F;gBAC1F,YAAY,EAAE,CAAC,CAAC,cAAc,CAAC,IAAI,SAAS;gBAC5C,yFAAyF;gBACzF,0FAA0F;gBAC1F,iDAAiD;gBACjD,cAAc,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,SAAS;gBAChD,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE;aACjC,CAAC;QACJ,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,WAAW,EAAE,CAAC;YACrC,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;oBAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;gBACzF,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBAC9B,gFAAgF;oBAChF,8DAA8D;oBAC9D,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;oBAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;wBAC9D,MAAM;4BACJ,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;4BAC/D,OAAO,EAAE,IAAI;yBACd,CAAC;wBACF,SAAS;oBACX,CAAC;oBACD,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;wBAChC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC;wBACzC,MAAM;4BACJ,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,MAAM,IAAI,GAAG,EAAE;4BACzC,MAAM,EAAE,CAAC,CAAC,MAAM;4BAChB,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAClC,CAAC;wBACF,SAAS;oBACX,CAAC;oBACD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YAChC,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa;oBAAE,SAAS;gBAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;gBACxB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC3F,IAAI,IAAI;oBAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAC3C,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YAClC,uFAAuF;YACvF,uFAAuF;YACvF,kEAAkE;YAClE,MAAM;gBACJ,IAAI,EAAE,MAAM;gBACZ,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,SAAS;gBAC9B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE;gBACzB,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3D,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Flaredeck } from './flaredeck.js';
|
|
2
|
+
import type { Decider } from './permissions.js';
|
|
3
|
+
/**
|
|
4
|
+
* The wire shape is Claude Code's own permission-relay protocol, deliberately, even though this
|
|
5
|
+
* harness talks to FlareDeck directly rather than through a channel MCP server.
|
|
6
|
+
*
|
|
7
|
+
* A channel would add a second process and a research-preview dependency to get something
|
|
8
|
+
* `canUseTool` already does better for an unattended agent — the channel relay has no deadline,
|
|
9
|
+
* so a session with nobody at the terminal waits for an answer forever. Going direct keeps the
|
|
10
|
+
* timeout.
|
|
11
|
+
*
|
|
12
|
+
* But the PAYLOAD costs nothing to share, and sharing it means the FlareDeck side — the approval
|
|
13
|
+
* record, the Approve/Deny buttons, the mobile widget — is built once against a shape that is
|
|
14
|
+
* already proven and already spoken by every Claude Code session. If a channel ever becomes the
|
|
15
|
+
* better transport, only the transport changes.
|
|
16
|
+
*/
|
|
17
|
+
export type PermissionRequest = {
|
|
18
|
+
/** Five lowercase letters, no `l`. Echoed by the verdict. */
|
|
19
|
+
request_id: string;
|
|
20
|
+
tool_name: string;
|
|
21
|
+
/** What this call does, in words — never the command itself. */
|
|
22
|
+
description: string;
|
|
23
|
+
/** The arguments as display text. Redacted and truncated before it leaves here. */
|
|
24
|
+
input_preview: string;
|
|
25
|
+
};
|
|
26
|
+
export type PermissionVerdict = {
|
|
27
|
+
request_id: string;
|
|
28
|
+
behavior: 'allow' | 'deny';
|
|
29
|
+
};
|
|
30
|
+
/** How long a request waits before it is treated as a refusal. */
|
|
31
|
+
export declare const APPROVAL_TIMEOUT_MS: number;
|
|
32
|
+
/**
|
|
33
|
+
* Verdicts arrive on the event stream, not by polling the card.
|
|
34
|
+
*
|
|
35
|
+
* The server queues a `decided` event to the requester the moment anyone answers — lead, phone,
|
|
36
|
+
* web or inbox — and the stream the harness already holds delivers it. A request waiting here
|
|
37
|
+
* registers under its id; the stream loop hands the event in; the wait resolves. Nothing is
|
|
38
|
+
* fetched in between, so an hour-long wait costs nothing and an answer lands within a second.
|
|
39
|
+
*/
|
|
40
|
+
export type Decided = {
|
|
41
|
+
outcome: 'endorsed' | 'rejected' | 'allow' | 'deny';
|
|
42
|
+
by: string;
|
|
43
|
+
note?: string;
|
|
44
|
+
};
|
|
45
|
+
/** Called by the stream loop for every `decided` event. Returns true when a wait took it. */
|
|
46
|
+
export declare function deliverDecision(message: string): boolean;
|
|
47
|
+
/** True while any request is waiting — the stream loop uses it to know a `decided` event is expected. */
|
|
48
|
+
export declare const awaitingDecision: (requestId: string) => boolean;
|
|
49
|
+
export declare const redact: (text: string) => string;
|
|
50
|
+
/**
|
|
51
|
+
* Turn a permission request into a question on the card.
|
|
52
|
+
*
|
|
53
|
+
* In auto mode the classifier has already handled everything routine, so whatever arrives here is
|
|
54
|
+
* the unusual kind — an explicit `ask` rule, or a tool that needs a person. Rather than refusing
|
|
55
|
+
* and losing the work, the harness posts it to the card and waits: anyone on the board can answer
|
|
56
|
+
* from their phone, and the reply arrives as an ordinary comment.
|
|
57
|
+
*
|
|
58
|
+
* It cannot hang. The wait ends at the timeout, or when the SDK aborts, and both mean "no".
|
|
59
|
+
*/
|
|
60
|
+
export declare function flaredeckDecider(deck: Flaredeck, card: string, timeoutMs?: number,
|
|
61
|
+
/** Routing the request carries with it: a lead who must endorse it first, and who may answer. */
|
|
62
|
+
route?: {
|
|
63
|
+
review_by?: string;
|
|
64
|
+
approvers?: string[];
|
|
65
|
+
},
|
|
66
|
+
/** Called once the request is on the card, with what a local answerer needs to find it. */
|
|
67
|
+
onPosted?: (posted: {
|
|
68
|
+
request_id: string;
|
|
69
|
+
comment_id: string;
|
|
70
|
+
}) => void,
|
|
71
|
+
/** Called when the lead endorses it — the wait continues, now on a person. */
|
|
72
|
+
onReviewed?: (d: Decided) => void): Decider;
|