@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.
Files changed (148) hide show
  1. package/LICENSE.md +14 -0
  2. package/README.md +102 -0
  3. package/dist/agent.d.ts +104 -0
  4. package/dist/agent.js +329 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/approval.d.ts +72 -0
  7. package/dist/approval.js +161 -0
  8. package/dist/approval.js.map +1 -0
  9. package/dist/approvals.d.ts +2 -0
  10. package/dist/approvals.js +148 -0
  11. package/dist/approvals.js.map +1 -0
  12. package/dist/auth.d.ts +79 -0
  13. package/dist/auth.js +181 -0
  14. package/dist/auth.js.map +1 -0
  15. package/dist/automode.d.ts +33 -0
  16. package/dist/automode.js +69 -0
  17. package/dist/automode.js.map +1 -0
  18. package/dist/branches.d.ts +25 -0
  19. package/dist/branches.js +81 -0
  20. package/dist/branches.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +12 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/codesearch.d.ts +27 -0
  25. package/dist/codesearch.js +111 -0
  26. package/dist/codesearch.js.map +1 -0
  27. package/dist/concurrency.d.ts +14 -0
  28. package/dist/concurrency.js +52 -0
  29. package/dist/concurrency.js.map +1 -0
  30. package/dist/config.d.ts +106 -0
  31. package/dist/config.js +119 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/engine-antigravity.d.ts +23 -0
  34. package/dist/engine-antigravity.js +357 -0
  35. package/dist/engine-antigravity.js.map +1 -0
  36. package/dist/engine-claude.d.ts +18 -0
  37. package/dist/engine-claude.js +64 -0
  38. package/dist/engine-claude.js.map +1 -0
  39. package/dist/engine.d.ts +122 -0
  40. package/dist/engine.js +36 -0
  41. package/dist/engine.js.map +1 -0
  42. package/dist/env.d.ts +1 -0
  43. package/dist/env.js +10 -0
  44. package/dist/env.js.map +1 -0
  45. package/dist/environment.d.ts +39 -0
  46. package/dist/environment.js +100 -0
  47. package/dist/environment.js.map +1 -0
  48. package/dist/flaredeck-check.d.ts +21 -0
  49. package/dist/flaredeck-check.js +12 -0
  50. package/dist/flaredeck-check.js.map +1 -0
  51. package/dist/flaredeck-proxy.d.ts +60 -0
  52. package/dist/flaredeck-proxy.js +186 -0
  53. package/dist/flaredeck-proxy.js.map +1 -0
  54. package/dist/flaredeck.d.ts +132 -0
  55. package/dist/flaredeck.js +106 -0
  56. package/dist/flaredeck.js.map +1 -0
  57. package/dist/gemini.d.ts +21 -0
  58. package/dist/gemini.js +59 -0
  59. package/dist/gemini.js.map +1 -0
  60. package/dist/gitguard.d.ts +2 -0
  61. package/dist/gitguard.js +90 -0
  62. package/dist/gitguard.js.map +1 -0
  63. package/dist/github.d.ts +38 -0
  64. package/dist/github.js +153 -0
  65. package/dist/github.js.map +1 -0
  66. package/dist/githubauth.d.ts +21 -0
  67. package/dist/githubauth.js +90 -0
  68. package/dist/githubauth.js.map +1 -0
  69. package/dist/intake.d.ts +124 -0
  70. package/dist/intake.js +383 -0
  71. package/dist/intake.js.map +1 -0
  72. package/dist/jam.d.ts +45 -0
  73. package/dist/jam.js +385 -0
  74. package/dist/jam.js.map +1 -0
  75. package/dist/jev.d.ts +31 -0
  76. package/dist/jev.js +75 -0
  77. package/dist/jev.js.map +1 -0
  78. package/dist/jevguard.d.ts +28 -0
  79. package/dist/jevguard.js +213 -0
  80. package/dist/jevguard.js.map +1 -0
  81. package/dist/mailbox.d.ts +22 -0
  82. package/dist/mailbox.js +51 -0
  83. package/dist/mailbox.js.map +1 -0
  84. package/dist/main.d.ts +2 -0
  85. package/dist/main.js +764 -0
  86. package/dist/main.js.map +1 -0
  87. package/dist/markdown.d.ts +5 -0
  88. package/dist/markdown.js +81 -0
  89. package/dist/markdown.js.map +1 -0
  90. package/dist/oauth.d.ts +30 -0
  91. package/dist/oauth.js +216 -0
  92. package/dist/oauth.js.map +1 -0
  93. package/dist/permissions.d.ts +43 -0
  94. package/dist/permissions.js +57 -0
  95. package/dist/permissions.js.map +1 -0
  96. package/dist/preflight.d.ts +71 -0
  97. package/dist/preflight.js +116 -0
  98. package/dist/preflight.js.map +1 -0
  99. package/dist/providers.d.ts +26 -0
  100. package/dist/providers.js +27 -0
  101. package/dist/providers.js.map +1 -0
  102. package/dist/repopicker.d.ts +3 -0
  103. package/dist/repopicker.js +111 -0
  104. package/dist/repopicker.js.map +1 -0
  105. package/dist/runs.d.ts +59 -0
  106. package/dist/runs.js +211 -0
  107. package/dist/runs.js.map +1 -0
  108. package/dist/sandbox.d.ts +13 -0
  109. package/dist/sandbox.js +110 -0
  110. package/dist/sandbox.js.map +1 -0
  111. package/dist/schedules.d.ts +11 -0
  112. package/dist/schedules.js +85 -0
  113. package/dist/schedules.js.map +1 -0
  114. package/dist/secretguard.d.ts +4 -0
  115. package/dist/secretguard.js +115 -0
  116. package/dist/secretguard.js.map +1 -0
  117. package/dist/secrets.d.ts +27 -0
  118. package/dist/secrets.js +51 -0
  119. package/dist/secrets.js.map +1 -0
  120. package/dist/serve.d.ts +17 -0
  121. package/dist/serve.js +1206 -0
  122. package/dist/serve.js.map +1 -0
  123. package/dist/status.d.ts +73 -0
  124. package/dist/status.js +98 -0
  125. package/dist/status.js.map +1 -0
  126. package/dist/stream.d.ts +91 -0
  127. package/dist/stream.js +103 -0
  128. package/dist/stream.js.map +1 -0
  129. package/dist/streamtoken.d.ts +2 -0
  130. package/dist/streamtoken.js +30 -0
  131. package/dist/streamtoken.js.map +1 -0
  132. package/dist/team.d.ts +61 -0
  133. package/dist/team.js +136 -0
  134. package/dist/team.js.map +1 -0
  135. package/dist/ui.d.ts +110 -0
  136. package/dist/ui.js +348 -0
  137. package/dist/ui.js.map +1 -0
  138. package/dist/updater.d.ts +15 -0
  139. package/dist/updater.js +178 -0
  140. package/dist/updater.js.map +1 -0
  141. package/dist/workspace.d.ts +115 -0
  142. package/dist/workspace.js +247 -0
  143. package/dist/workspace.js.map +1 -0
  144. package/dist/worktree.d.ts +42 -0
  145. package/dist/worktree.js +140 -0
  146. package/dist/worktree.js.map +1 -0
  147. package/package.json +52 -0
  148. 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.
@@ -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":"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"}
@@ -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;