@onepointfour-packs/flaredeck-agent 0.2.209 → 0.2.211

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 (94) hide show
  1. package/dist/agent.js +2 -1
  2. package/dist/agent.js.map +1 -1
  3. package/dist/assistant.d.ts +1 -1
  4. package/dist/assistant.js +4 -2
  5. package/dist/assistant.js.map +1 -1
  6. package/dist/check.d.ts +32 -0
  7. package/dist/check.js +251 -0
  8. package/dist/check.js.map +1 -0
  9. package/dist/config.d.ts +8 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/environment.d.ts +13 -3
  12. package/dist/environment.js +25 -15
  13. package/dist/environment.js.map +1 -1
  14. package/dist/envtools.d.ts +29 -0
  15. package/dist/envtools.js +67 -5
  16. package/dist/envtools.js.map +1 -1
  17. package/dist/features/card-folder.d.ts +69 -0
  18. package/dist/features/card-folder.js +305 -0
  19. package/dist/features/card-folder.js.map +1 -0
  20. package/dist/features/chat.d.ts +45 -0
  21. package/dist/features/chat.js +90 -0
  22. package/dist/features/chat.js.map +1 -0
  23. package/dist/features/dispatch.d.ts +55 -0
  24. package/dist/features/dispatch.js +101 -0
  25. package/dist/features/dispatch.js.map +1 -0
  26. package/dist/features/hand-back.d.ts +60 -0
  27. package/dist/features/hand-back.js +127 -0
  28. package/dist/features/hand-back.js.map +1 -0
  29. package/dist/features/manage-pane.d.ts +31 -0
  30. package/dist/features/manage-pane.js +60 -0
  31. package/dist/features/manage-pane.js.map +1 -0
  32. package/dist/features/release-wiring.d.ts +18 -0
  33. package/dist/features/release-wiring.js +56 -0
  34. package/dist/features/release-wiring.js.map +1 -0
  35. package/dist/features/services.d.ts +45 -0
  36. package/dist/features/services.js +147 -0
  37. package/dist/features/services.js.map +1 -0
  38. package/dist/features/sign-in-request.d.ts +81 -0
  39. package/dist/features/sign-in-request.js +91 -0
  40. package/dist/features/sign-in-request.js.map +1 -0
  41. package/dist/github.d.ts +14 -0
  42. package/dist/github.js +39 -8
  43. package/dist/github.js.map +1 -1
  44. package/dist/harness/events.d.ts +58 -0
  45. package/dist/harness/events.js +126 -0
  46. package/dist/harness/events.js.map +1 -0
  47. package/dist/harness/handover.d.ts +39 -0
  48. package/dist/harness/handover.js +63 -0
  49. package/dist/harness/handover.js.map +1 -0
  50. package/dist/harness/pause.d.ts +67 -0
  51. package/dist/harness/pause.js +128 -0
  52. package/dist/harness/pause.js.map +1 -0
  53. package/dist/harness/plan-usage.d.ts +49 -0
  54. package/dist/harness/plan-usage.js +69 -0
  55. package/dist/harness/plan-usage.js.map +1 -0
  56. package/dist/harness/prompt.d.ts +59 -0
  57. package/dist/harness/prompt.js +289 -0
  58. package/dist/harness/prompt.js.map +1 -0
  59. package/dist/harness/repo-notes.d.ts +1 -0
  60. package/dist/harness/repo-notes.js +26 -0
  61. package/dist/harness/repo-notes.js.map +1 -0
  62. package/dist/harness/restarter.d.ts +33 -0
  63. package/dist/harness/restarter.js +78 -0
  64. package/dist/harness/restarter.js.map +1 -0
  65. package/dist/harness/start-scripts.d.ts +7 -0
  66. package/dist/harness/start-scripts.js +44 -0
  67. package/dist/harness/start-scripts.js.map +1 -0
  68. package/dist/harness/tools.d.ts +37 -0
  69. package/dist/harness/tools.js +17 -0
  70. package/dist/harness/tools.js.map +1 -0
  71. package/dist/hydrate.d.ts +5 -0
  72. package/dist/hydrate.js +1 -0
  73. package/dist/hydrate.js.map +1 -1
  74. package/dist/main.js +32 -1
  75. package/dist/main.js.map +1 -1
  76. package/dist/profile.d.ts +80 -0
  77. package/dist/profile.js +77 -0
  78. package/dist/profile.js.map +1 -0
  79. package/dist/release.js +3 -0
  80. package/dist/release.js.map +1 -1
  81. package/dist/sandbox.js +3 -1
  82. package/dist/sandbox.js.map +1 -1
  83. package/dist/serve.d.ts +21 -115
  84. package/dist/serve.js +971 -1612
  85. package/dist/serve.js.map +1 -1
  86. package/dist/stream.d.ts +5 -0
  87. package/dist/stream.js.map +1 -1
  88. package/dist/workspace.d.ts +6 -0
  89. package/dist/workspace.js +19 -0
  90. package/dist/workspace.js.map +1 -1
  91. package/dist/worktree.d.ts +55 -2
  92. package/dist/worktree.js +130 -5
  93. package/dist/worktree.js.map +1 -1
  94. package/package.json +2 -2
@@ -0,0 +1,60 @@
1
+ import { type Worktree } from '../worktree.js';
2
+ /** The card, as FlareDeck's REST API returns it — the fields the gate reads. */
3
+ export type GateCard = {
4
+ code?: string;
5
+ lane?: {
6
+ type?: string | null;
7
+ name?: string | null;
8
+ } | null;
9
+ releases?: unknown[];
10
+ deployedAt?: unknown;
11
+ assigned_to?: {
12
+ uid?: string;
13
+ } | null;
14
+ } | null;
15
+ /**
16
+ * Cards handed to the lead, and the branch head at the time — so a retried tool call does not
17
+ * assign or message twice, and a card the lead gives back unchanged is its "move it".
18
+ */
19
+ export declare function handBackMemory(): {
20
+ has: (code: string) => boolean;
21
+ headAt: (code: string) => string | undefined;
22
+ add: (code: string, head: string | null) => void;
23
+ forget: (code: string) => void;
24
+ };
25
+ export type HandBackMemory = ReturnType<typeof handBackMemory>;
26
+ /** What the gate reads from git; the real functions unless a test says otherwise. */
27
+ export type GateGit = {
28
+ tooEarlyToFinish: (tree: Worktree | null) => Promise<string | null>;
29
+ produced: (tree: Worktree) => Promise<{
30
+ commits: number;
31
+ pushed: boolean;
32
+ }>;
33
+ cardWorkOnBase: (path: string, base: string | undefined, code: string) => Promise<boolean>;
34
+ headOf: (tree: Worktree) => Promise<string | null>;
35
+ };
36
+ export type FinishGateDeps = {
37
+ /** The card from FlareDeck; absent when this harness has no FlareDeck REST access. */
38
+ card?: (ref: string) => Promise<GateCard>;
39
+ /** The card this turn is on: the code it resolved to, else the event's code or id. */
40
+ cardRef: () => string;
41
+ /** The card's code for messages, preferring the event's own. */
42
+ cardCode: () => string;
43
+ tree: Worktree | null;
44
+ /** Where this card's work starts: a card whose work is already merged there may move on. */
45
+ startFrom: string | undefined;
46
+ /** Has others' work in its care (a lead or manager): a card whose code is a worker's may move on. */
47
+ oversees: boolean;
48
+ /** Its lead, when it has one: finished work is handed there. */
49
+ lead: {
50
+ id: string;
51
+ name: string;
52
+ } | null;
53
+ selfId: string;
54
+ handedBack: HandBackMemory;
55
+ /** Assign the card to the lead and tell them, in one go. Throws when either fails. */
56
+ handToLead: (code: string, message: string) => Promise<void>;
57
+ log: (kind: 'tool' | 'warn', text: string) => void;
58
+ git?: GateGit;
59
+ };
60
+ export declare function finishGate(d: FinishGateDeps): (toLane?: string) => Promise<string | null>;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The finish gate: what happens when an agent calls a card finished (mark-card-done, or a move into
3
+ * a done or review lane — see CLAIMS_DONE in flaredeck-proxy.ts).
4
+ *
5
+ * Checked here rather than trusted, because a turn that produced nothing still reports confidently:
6
+ * · a card already released moves on — its work is on an environment;
7
+ * · otherwise the card must have commits of its own, published;
8
+ * · and an agent that reports to a lead does not decide its own work is finished: the card is
9
+ * handed to the lead for review, once, with the branch named.
10
+ *
11
+ * Returns null to let the call through, or the text the agent is told instead.
12
+ * Split out of serve.ts (docs/spec-harness-profiles.md, step 2).
13
+ */
14
+ import { isAfterReleaseMove, isReleasedClose } from '../flaredeck-proxy.js';
15
+ import { cardWorkOnBase, finishRefusalStands, headOf, leadGaveItBack, produced, tooEarlyToFinish } from '../worktree.js';
16
+ /**
17
+ * Cards handed to the lead, and the branch head at the time — so a retried tool call does not
18
+ * assign or message twice, and a card the lead gives back unchanged is its "move it".
19
+ */
20
+ export function handBackMemory() {
21
+ const handed = new Set();
22
+ const heads = new Map();
23
+ return {
24
+ has: (code) => handed.has(code),
25
+ headAt: (code) => heads.get(code),
26
+ add: (code, head) => { handed.add(code); if (head)
27
+ heads.set(code, head); },
28
+ forget: (code) => { handed.delete(code); heads.delete(code); },
29
+ };
30
+ }
31
+ const realGit = { tooEarlyToFinish, produced, cardWorkOnBase, headOf };
32
+ export function finishGate(d) {
33
+ const git = d.git ?? realGit;
34
+ return async (toLane) => {
35
+ // A released card moving on to testing or review is the next step of the board's flow.
36
+ if (d.card) {
37
+ const here = await d.card(d.cardRef()).catch(() => null);
38
+ const released = !!here && ((Array.isArray(here.releases) && here.releases.length > 0) || !!here.deployedAt);
39
+ if (here && toLane && isAfterReleaseMove(toLane, here.lane?.type, here.lane?.name ?? '', released)) {
40
+ d.log('tool', `moving ${here.code || d.cardRef()} on to ${toLane} — it was released already, so this is the next step, not a hand-back`);
41
+ return null;
42
+ }
43
+ if (here && isReleasedClose(toLane ?? '', released)) {
44
+ d.log('tool', `closing ${here.code || d.cardRef()} — its work was released already`);
45
+ return null;
46
+ }
47
+ }
48
+ const { tree } = d;
49
+ const why = await git.tooEarlyToFinish(tree);
50
+ /* A LEAD moving a card on with nothing of its own on the branch is dispatch, not a claim of
51
+ finished work: the code lives on the worker's branch, often already merged. The gate refused
52
+ JARVIS moving HR-0634 (PR #75 merged, on qa) to "On test site", and the lead then flagged a
53
+ person to do the move — an intervention nobody needed. Kept for a lead that HAS committed
54
+ work of its own: that still has to be published before the card moves. */
55
+ const cardCode = d.cardCode();
56
+ if (why && tree && cardCode && (await git.produced(tree)).commits === 0 && await git.cardWorkOnBase(tree.path, d.startFrom, cardCode)) {
57
+ d.log('tool', `moving ${cardCode} on — its work is already merged into ${d.startFrom ?? 'the base branch'}, on an earlier branch`);
58
+ return null;
59
+ }
60
+ if (why && tree && !finishRefusalStands(why, d.oversees, (await git.produced(tree)).commits)) {
61
+ d.log('tool', `moving ${cardCode || 'the card'} on as lead — the work is on a worker's branch, not mine`);
62
+ return null;
63
+ }
64
+ if (why) {
65
+ d.log('warn', `refused a "done" on an empty branch: ${tree?.branch ?? '(no branch)'}`);
66
+ return why;
67
+ }
68
+ // If this agent reports to someone, it does not decide that its own work is finished. The lead
69
+ // said "I'll check its saved work myself before it goes anywhere" and then was never told: the
70
+ // card moved itself to Awaiting Deployment. A report hands back; the lead moves it, or comments
71
+ // and sends it back, which is what a lead is for.
72
+ if (!d.lead)
73
+ return null;
74
+ const code = d.cardRef();
75
+ if (!code)
76
+ return null;
77
+ const lead = d.lead;
78
+ // Handed over before, and the lead has given it back with nothing new on the branch: the lead
79
+ // reviewed it and wants it moved on. Let the move through instead of refusing again.
80
+ if (d.handedBack.has(code) && tree && d.card) {
81
+ const [here, head] = await Promise.all([d.card(code).catch(() => null), git.headOf(tree)]);
82
+ if (leadGaveItBack(d.handedBack.headAt(code), head, here?.assigned_to?.uid, d.selfId)) {
83
+ d.log('tool', `moving ${code} on — ${lead.name} reviewed it and gave it back, nothing new since`);
84
+ return null;
85
+ }
86
+ // Back with this worker and changed since (or we cannot tell): it is new work, handed to the
87
+ // lead again like the first time. Remembering the old hand-back forever refused HAL's second
88
+ // "done" on HR-1074 with "JARVIS's to verify" while the card stayed assigned to HAL (20 Oct).
89
+ if (here?.assigned_to?.uid && here.assigned_to.uid === d.selfId)
90
+ d.handedBack.forget(code);
91
+ }
92
+ if (!d.handedBack.has(code)) {
93
+ d.handedBack.add(code, tree ? await git.headOf(tree) : null);
94
+ const made = tree ? await git.produced(tree) : null;
95
+ let where = tree
96
+ ? `Branch \`${tree.branch}\`${made ? ` — ${made.commits} commit(s), ${made.pushed ? 'pushed' : 'NOT pushed'}` : ''}.`
97
+ : 'No branch: nothing of mine is in a checkout for this card.';
98
+ // A card folder: each repository with work on it, by name — the lead reviews each one.
99
+ if (tree?.companions?.length) {
100
+ const lines = [];
101
+ for (const t of [tree, ...tree.companions]) {
102
+ const p = await git.produced(t);
103
+ if (p.commits)
104
+ lines.push(` · ${t.path.split("/").pop()}: \`${t.branch}\` — ${p.commits} commit(s), ${p.pushed ? 'pushed' : 'NOT pushed'}`);
105
+ }
106
+ where = lines.length ? `Changed:\n${lines.join('\n')}` : where;
107
+ }
108
+ try {
109
+ await d.handToLead(code, `**${code} is ready for your review.**\n\n${where}\n\n` +
110
+ `I have not moved it — it is assigned to you and still in its lane. ` +
111
+ `Check the work and move it on, or comment on the card and I will fix it.`);
112
+ d.log('tool', `handed ${code} to ${lead.name} for review`);
113
+ }
114
+ catch (e) {
115
+ // A handback that did not happen must not read as one that did.
116
+ d.handedBack.forget(code);
117
+ const text = e instanceof Error ? e.message : String(e);
118
+ d.log('warn', `could not hand ${code} to ${lead.name}: ${text}`);
119
+ return `Could not hand this card to ${lead.name} for review (${text}). Do not move it; say so on the card instead.`;
120
+ }
121
+ }
122
+ return (`${code} is now ${lead.name}'s to verify, not yours to close. It is assigned to them and ` +
123
+ `they have been told, with your branch. Leave the lane alone — they move it on, or comment ` +
124
+ `and send it back to you. Your part is done; say nothing further about it being finished.`);
125
+ };
126
+ }
127
+ //# sourceMappingURL=hand-back.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hand-back.js","sourceRoot":"","sources":["../../src/features/hand-back.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The `manage` feature at the pane: a manager's person can sit at its terminal and answer the
3
+ * questions meant for them here, instead of on their phone. `/local` holds those questions at the
4
+ * pane; `/remote` (or ten minutes with nobody answering) sends them to the phone as they stand on
5
+ * the card. An answer typed here is written on the card first — an answer that exists only in a
6
+ * terminal is one nobody else can find — then the flag comes off and the card goes back to the
7
+ * agent. See presence.ts for the holding itself.
8
+ * Split out of serve.ts (docs/spec-harness-profiles.md, step 2).
9
+ */
10
+ import type { HeldAsk, Mode, Presence } from '../presence.js';
11
+ export type ManagePaneDeps = {
12
+ presence: Presence;
13
+ /** Only a manager's pane decides where its person's questions are shown. */
14
+ manages: () => boolean;
15
+ /** A FlareDeck tool, as this agent. */
16
+ call: (tool: string, args: Record<string, unknown>) => Promise<string>;
17
+ /** The card goes back to the agent with this note, its question answered. */
18
+ resume: (ask: HeldAsk, text: string) => void;
19
+ /** The pane's list of held questions changed. */
20
+ show: () => void;
21
+ /** This agent's name, for the answer's comment. */
22
+ agent: () => string;
23
+ log: (kind: 'warn' | 'you', text: string) => void;
24
+ };
25
+ export declare function managePane(d: ManagePaneDeps): {
26
+ sendOn: (a: HeldAsk) => Promise<void>;
27
+ setMode(mode: Mode): void;
28
+ answer(a: HeldAsk, answer: string): Promise<void>;
29
+ /** Every 15 seconds: a question nobody answered here in ten minutes goes to the phone. */
30
+ tick(): void;
31
+ };
@@ -0,0 +1,60 @@
1
+ export function managePane(d) {
2
+ /** Nobody answered here: the held question goes to the phone, as it stands on the card. */
3
+ const sendOn = async (a) => {
4
+ const label = a.code || a.card;
5
+ try {
6
+ const out = await d.call('set-needs-human', { card_id: a.code || a.card, ...(a.board ? { board_id: a.board } : {}), push_held: true });
7
+ d.log('warn', `${label}: ${out.split('\n').find((l) => l.startsWith('**Told**'))?.replace(/\*\*/g, '') ?? out.split('\n')[0]}`);
8
+ }
9
+ catch (e) {
10
+ d.log('warn', `${label}: could not send the question on to ${a.person}'s phone (${e instanceof Error ? e.message : String(e)}) — it is still on the card`);
11
+ }
12
+ };
13
+ return {
14
+ sendOn,
15
+ setMode(mode) {
16
+ if (!d.manages()) {
17
+ d.log('warn', '/local and /remote are for a manager\'s pane: they decide where questions for the person the manager reports to are shown.');
18
+ return;
19
+ }
20
+ d.presence.set(mode);
21
+ if (mode === 'local') {
22
+ d.log('you', 'local — questions for you are shown here and kept off your phone. One left unanswered for ten minutes goes to your phone, and this goes back to remote.');
23
+ }
24
+ else {
25
+ const waiting = d.presence.release();
26
+ d.log('you', `remote — questions go to FlareDeck and your phone${waiting.length ? `, including the ${waiting.length} waiting here` : ''}.`);
27
+ for (const a of waiting)
28
+ void sendOn(a);
29
+ }
30
+ d.show();
31
+ },
32
+ async answer(a, answer) {
33
+ d.presence.take(a.card);
34
+ d.show();
35
+ const label = a.code || a.card;
36
+ d.log('you', `${label}: ${answer}`);
37
+ const ref = { card_id: a.code || a.card, ...(a.board ? { board_id: a.board } : {}) };
38
+ try {
39
+ await d.call('create-card-comment', { ...ref, comment: `**${a.person} answered at ${d.agent()}'s pane.**\n\n> ${a.question}\n\n${answer}` });
40
+ await d.call('clear-needs-human', ref);
41
+ }
42
+ catch (e) {
43
+ d.log('warn', `${label}: the answer could not be written to the card (${e instanceof Error ? e.message : String(e)}) — the agent still gets it`);
44
+ }
45
+ d.resume(a, `${a.person} answered your question at this pane.\n\nYou asked: ${a.question}\nThe answer: ${answer}\n\nIt is on the card as a comment and the flag is cleared. Carry on from where you stopped.`);
46
+ },
47
+ /** Every 15 seconds: a question nobody answered here in ten minutes goes to the phone. */
48
+ tick() {
49
+ const due = d.presence.expire();
50
+ if (due.length) {
51
+ d.log('warn', `nobody answered here in ten minutes — ${due.length === 1 ? 'that question has' : `${due.length} questions have`} gone to your phone, and this pane is remote again. /local when you are back.`);
52
+ for (const a of due)
53
+ void sendOn(a);
54
+ }
55
+ if (due.length || d.presence.held().length)
56
+ d.show();
57
+ },
58
+ };
59
+ }
60
+ //# sourceMappingURL=manage-pane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manage-pane.js","sourceRoot":"","sources":["../../src/features/manage-pane.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type ReleaseDeps } from '../release.js';
2
+ import type { Flaredeck } from '../flaredeck.js';
3
+ /** Who approves a release this agent may not make alone: its manager, or the person it works for. */
4
+ export type Approvers = {
5
+ manager?: string | null;
6
+ person?: string | null;
7
+ };
8
+ /** The repository a checkout came from. */
9
+ export declare const originOf: (cwd: string) => Promise<string>;
10
+ /** A card from FlareDeck's REST API, as the release tool needs it. */
11
+ export declare function releaseCardInfo(c: any, now?: number): Awaited<ReturnType<NonNullable<ReleaseDeps['cardInfo']>>>;
12
+ export declare function flaredeckReleaseDeps(o: {
13
+ mcpUrl: string;
14
+ tenant: string;
15
+ auth: () => Promise<string>;
16
+ deck: Flaredeck;
17
+ approvers: Approvers;
18
+ }): Pick<ReleaseDeps, 'ladder' | 'approve' | 'originOf' | 'cardInfo' | 'record' | 'recordReleased' | 'watch'>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The release tool's connection to FlareDeck: the board's ladder, approvals from the agent's
3
+ * manager or person, what a card looks like now, and recording where a release went. The tool
4
+ * itself — what may be released where — is release.ts; this is only how it reaches FlareDeck.
5
+ * Split out of serve.ts (docs/spec-harness-profiles.md, step 2).
6
+ */
7
+ import { spawn } from 'node:child_process';
8
+ import { fetchLadder } from '../release.js';
9
+ import { flaredeckDecider } from '../approval.js';
10
+ import { callFlaredeckTool } from '../flaredeck-proxy.js';
11
+ /** The repository a checkout came from. */
12
+ export const originOf = (cwd) => new Promise((res) => {
13
+ const c = spawn('git', ['remote', 'get-url', 'origin'], { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
14
+ let out = '';
15
+ c.stdout.on('data', (d) => (out += String(d)));
16
+ c.on('close', () => res(out.trim()));
17
+ c.on('error', () => res(''));
18
+ });
19
+ /** A card from FlareDeck's REST API, as the release tool needs it. */
20
+ export function releaseCardInfo(c, now = Date.now()) {
21
+ if (!c)
22
+ return null;
23
+ const since = c.release_since ? Date.parse(String(c.release_since)) : NaN;
24
+ return {
25
+ laneType: c.lane?.type ?? null,
26
+ minutesInReleaseLane: String(c.lane?.type || '').toLowerCase() === 'release' && Number.isFinite(since) ? (now - since) / 60_000 : null,
27
+ releases: (Array.isArray(c.releases) ? c.releases : []).map((r) => ({ environment: String(r.environment ?? ''), sha: String(r.sha ?? '') })),
28
+ };
29
+ }
30
+ export function flaredeckReleaseDeps(o) {
31
+ const api = (path) => `${new URL(o.mcpUrl).origin}/api/v1/${encodeURIComponent(o.tenant)}/${path}`;
32
+ const post = async (path, body, ms) => fetch(api(path), {
33
+ method: 'POST', headers: { authorization: `Bearer ${await o.auth()}`, 'content-type': 'application/json' },
34
+ body: JSON.stringify(body), signal: AbortSignal.timeout(ms),
35
+ });
36
+ return {
37
+ ladder: (boardId) => fetchLadder(o.mcpUrl, o.tenant, boardId, o.auth),
38
+ approve: async (card, who, what) => {
39
+ const approvers = [who === 'person' ? o.approvers.person : o.approvers.manager].filter((x) => !!x);
40
+ if (!approvers.length)
41
+ return { allow: false, reason: `nobody is set as your ${who === 'person' ? 'person (Works for / reports to)' : 'manager'} in FlareDeck to approve it` };
42
+ const ask = flaredeckDecider(o.deck, card, 60 * 60_000, { approvers });
43
+ const v = await ask({ toolName: 'release', input: what }, { signal: new AbortController().signal });
44
+ return v.allow ? { allow: true } : { allow: false, ...(v.reason ? { reason: v.reason } : {}) };
45
+ },
46
+ originOf,
47
+ cardInfo: async (code) => releaseCardInfo(await o.deck.card(code).catch(() => null)),
48
+ record: async (card, text) => { await callFlaredeckTool(o.mcpUrl, await o.auth(), 'create-card-comment', { card_id: card, comment: text }); },
49
+ recordReleased: async (r) => {
50
+ const res = await post('cards/released', r, 20_000);
51
+ return res.ok ? (await res.json()) : null;
52
+ },
53
+ watch: async (card) => { await post('deploy-watch', { card_key: card }, 15_000); },
54
+ };
55
+ }
56
+ //# sourceMappingURL=release-wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-wiring.js","sourceRoot":"","sources":["../../src/features/release-wiring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { StreamEvent } from '../stream.js';
2
+ import type { Started, StartWith } from '../environment.js';
3
+ type Repo = NonNullable<StreamEvent['baseBranches']>[number];
4
+ export type ServiceDeps = {
5
+ /** The harness's folder for service clones. */
6
+ root: string;
7
+ /** git, never prompting, with FlareDeck's GitHub connection. */
8
+ git: (args: string[], cwd: string) => Promise<{
9
+ code: number;
10
+ out: string;
11
+ }>;
12
+ /** The GitHub token to clone with, when there is one. */
13
+ token: () => Promise<string | null>;
14
+ /** The start script kept in FlareDeck for a repository (an absolute path), if any. */
15
+ storedScript: (repository: string) => string | undefined;
16
+ start: (dir: string, onStep: (t: string) => void, declared?: string, w?: StartWith) => Promise<Started>;
17
+ stop: (dir: string, declared?: string, stored?: string) => Promise<boolean>;
18
+ log: (kind: 'tool' | 'warn', text: string) => void;
19
+ now?: () => number;
20
+ };
21
+ /** `https://github.com/Owner/Name` → `owner__name`. */
22
+ export declare const serviceDir: (url: string) => string;
23
+ /** The variable a dependency's address is given to the card's start script under. */
24
+ export declare const envName: (s: string) => string;
25
+ /** The deployed address for an environment: by its name, case-insensitively, else the only one. */
26
+ export declare function deployedFor(sites: Record<string, string> | undefined, environment: string): string | undefined;
27
+ /** A service unused this long is stopped. */
28
+ export declare const SERVICE_IDLE_MS: number;
29
+ export declare function createServices(d: ServiceDeps): {
30
+ /** A clone to cut a card folder's worktree from, when the repository is not checked out here. */
31
+ clone: (repository: string) => Promise<string | null>;
32
+ /**
33
+ * Everything a card needs running beside it, as environment variables for its own start
34
+ * script, and a line each for the agent. Never throws: what cannot be had is said, and the card
35
+ * still starts — it can always be tested against what IS running.
36
+ */
37
+ ensure(alongside: Repo[], environment: string): Promise<{
38
+ env: Record<string, string>;
39
+ notes: string[];
40
+ }>;
41
+ /** Stop services nothing has needed for a while. */
42
+ stopIdle(): Promise<void>;
43
+ stopAll(): Promise<void>;
44
+ };
45
+ export {};
@@ -0,0 +1,147 @@
1
+ /**
2
+ * What a card needs running beside its own repository — a site's API and CMS — so the change can
3
+ * be tested (`alongside` on the event; docs/spec-workspace-repositories.md).
4
+ *
5
+ * Each is used DEPLOYED when the repository has an address for the card's environment: always-
6
+ * current data and nothing to build, which is what PayInc's `payinc-local-stack.sh --api=dev` did by
7
+ * hand and the right default for a site card. Otherwise it is started LOCALLY, once, from a clean
8
+ * clone in the harness's own folder (never somebody's working tree — C3PO would not touch those),
9
+ * on the branch the card's environment names, and shared by every card that needs it until it has
10
+ * gone unused for a while.
11
+ *
12
+ * Its address reaches the card's own start script as FLAREDECK_<TYPE>_URL (FLAREDECK_API_URL,
13
+ * FLAREDECK_CMS_URL) and FLAREDECK_<OWNER_NAME>_URL.
14
+ */
15
+ import { existsSync, mkdirSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ /** `https://github.com/Owner/Name` → `owner__name`. */
18
+ export const serviceDir = (url) => url.replace(/\.git$/i, '').replace(/\/+$/, '').split(/[/:]/).slice(-2).join('__').toLowerCase().replace(/[^\w.-]/g, '_');
19
+ /** The variable a dependency's address is given to the card's start script under. */
20
+ export const envName = (s) => `FLAREDECK_${s.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '')}_URL`;
21
+ /** The deployed address for an environment: by its name, case-insensitively, else the only one. */
22
+ export function deployedFor(sites, environment) {
23
+ const entries = Object.entries(sites ?? {});
24
+ const hit = entries.find(([k]) => k.toLowerCase() === environment.toLowerCase());
25
+ if (hit)
26
+ return hit[1];
27
+ // "*": the address for every environment (a bare address on Integrations → Repositories).
28
+ const any = entries.find(([k]) => k === '*');
29
+ if (any)
30
+ return any[1];
31
+ return entries.length === 1 ? entries[0][1] : undefined;
32
+ }
33
+ /** A service unused this long is stopped. */
34
+ export const SERVICE_IDLE_MS = 2 * 60 * 60 * 1000;
35
+ export function createServices(d) {
36
+ const now = d.now ?? Date.now;
37
+ const running = new Map();
38
+ /** A clone of the repository in the harness's folder, made if it is not there yet. */
39
+ const clone = async (r) => {
40
+ const dir = join(d.root, serviceDir(r.repository));
41
+ if (!existsSync(join(dir, '.git'))) {
42
+ const token = await d.token();
43
+ if (!token) {
44
+ d.log('warn', `${serviceDir(r.repository)}: not on this machine, and FlareDeck has no GitHub connection to clone it with`);
45
+ return null;
46
+ }
47
+ mkdirSync(d.root, { recursive: true });
48
+ const auth = `http.extraHeader=Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString('base64')}`;
49
+ d.log('tool', `cloning ${r.repository} to run beside this card`);
50
+ const c = await d.git(['-c', auth, 'clone', '--quiet', `${r.repository.replace(/\.git$/i, '')}.git`, dir], d.root);
51
+ if (c.code !== 0) {
52
+ d.log('warn', `could not clone ${r.repository}: ${c.out.split('\n').pop()}`);
53
+ return null;
54
+ }
55
+ }
56
+ return dir;
57
+ };
58
+ /** A clean clone of the repository on `branch`, in the harness's folder. */
59
+ const checkout = async (r) => {
60
+ const dir = await clone(r);
61
+ if (!dir)
62
+ return null;
63
+ const branch = r.branch || 'HEAD';
64
+ await d.git(['fetch', '--quiet', 'origin'], dir);
65
+ const co = await d.git(['checkout', '--quiet', '--detach', branch === 'HEAD' ? 'origin/HEAD' : `origin/${branch}`], dir);
66
+ if (co.code !== 0)
67
+ d.log('warn', `${serviceDir(r.repository)}: no branch ${branch} — using what is checked out`);
68
+ return dir;
69
+ };
70
+ return {
71
+ /** A clone to cut a card folder's worktree from, when the repository is not checked out here. */
72
+ clone: (repository) => clone({ repository }),
73
+ /**
74
+ * Everything a card needs running beside it, as environment variables for its own start
75
+ * script, and a line each for the agent. Never throws: what cannot be had is said, and the card
76
+ * still starts — it can always be tested against what IS running.
77
+ */
78
+ async ensure(alongside, environment) {
79
+ const env = {};
80
+ const notes = [];
81
+ const give = (r, url) => {
82
+ env[envName(r.component || 'service')] ??= url;
83
+ env[envName(serviceDir(r.repository))] = url;
84
+ };
85
+ for (const r of alongside) {
86
+ const name = serviceDir(r.repository);
87
+ const deployed = deployedFor(r.sites, environment);
88
+ if (deployed) {
89
+ give(r, deployed);
90
+ notes.push(`${name} (${r.component}): using the deployed one, ${deployed}`);
91
+ continue;
92
+ }
93
+ const up = running.get(name);
94
+ if (up && up.branch === r.branch && up.started.ok) {
95
+ up.usedAt = now();
96
+ const url = Object.values(up.started.endpoints)[0];
97
+ if (url) {
98
+ give(r, url);
99
+ notes.push(`${name} (${r.component}): already running, ${url}`);
100
+ continue;
101
+ }
102
+ }
103
+ const dir = await checkout(r);
104
+ if (!dir) {
105
+ notes.push(`${name} (${r.component}): not running — it could not be checked out here`);
106
+ continue;
107
+ }
108
+ const stored = d.storedScript(r.repository);
109
+ const started = await d.start(dir, (t) => d.log('tool', `${name}: ${t}`), r.environmentScript, stored ? { script: stored } : {});
110
+ if (!started.ran) {
111
+ notes.push(`${name} (${r.component}): not running — it has no start script; declare an address for ${environment || 'this environment'} on Integrations → Repositories, or a start script`);
112
+ continue;
113
+ }
114
+ if (!started.ok) {
115
+ notes.push(`${name} (${r.component}): did not start — ${started.detail.split('\n').pop()}`);
116
+ continue;
117
+ }
118
+ running.set(name, { dir, branch: r.branch, started, usedAt: now(), ...(r.environmentScript ? { declared: r.environmentScript } : {}), ...(stored ? { stored } : {}) });
119
+ const url = Object.values(started.endpoints)[0];
120
+ if (url) {
121
+ give(r, url);
122
+ notes.push(`${name} (${r.component}): started, ${url}`);
123
+ }
124
+ else
125
+ notes.push(`${name} (${r.component}): started, but printed no address`);
126
+ }
127
+ return { env, notes };
128
+ },
129
+ /** Stop services nothing has needed for a while. */
130
+ async stopIdle() {
131
+ for (const [name, s] of running) {
132
+ if (now() - s.usedAt < SERVICE_IDLE_MS)
133
+ continue;
134
+ running.delete(name);
135
+ await d.stop(s.dir, s.declared, s.stored).catch(() => false);
136
+ d.log('tool', `${name}: stopped — no card has needed it for ${Math.round(SERVICE_IDLE_MS / 3_600_000)}h`);
137
+ }
138
+ },
139
+ async stopAll() {
140
+ for (const [name, s] of running) {
141
+ running.delete(name);
142
+ await d.stop(s.dir, s.declared, s.stored).catch(() => false);
143
+ }
144
+ },
145
+ };
146
+ }
147
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/features/services.ts"],"names":[],"mappings":"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"}