@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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The harness's own line to FlareDeck, over the REST API.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not the MCP server: that is the MODEL's identity, and the harness is a different
|
|
5
|
+
* actor. An approval must be posted and read by the supervisor even when the model is wedged,
|
|
6
|
+
* mid-compaction, or refusing to call tools — so it cannot go through the model's tools.
|
|
7
|
+
*/
|
|
8
|
+
export type Approval = {
|
|
9
|
+
request_id: string;
|
|
10
|
+
tool_name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
input_preview: string;
|
|
13
|
+
state: 'pending' | 'allow' | 'deny' | 'expired';
|
|
14
|
+
expires_at: string;
|
|
15
|
+
decided_by?: string;
|
|
16
|
+
decided_by_name?: string;
|
|
17
|
+
decided_at?: string;
|
|
18
|
+
};
|
|
19
|
+
/** One open request, as the inbox lists it: the question plus where it was asked. */
|
|
20
|
+
export type PendingApproval = {
|
|
21
|
+
comment_id: string;
|
|
22
|
+
request_id: string;
|
|
23
|
+
tool_name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
input_preview: string;
|
|
26
|
+
state: 'pending' | 'allow' | 'deny' | 'expired';
|
|
27
|
+
expires_at: string;
|
|
28
|
+
created_at: string | null;
|
|
29
|
+
sender_key: string | null;
|
|
30
|
+
sender_name: string | null;
|
|
31
|
+
/** Present when a lead must endorse it before anyone is asked. */
|
|
32
|
+
review?: {
|
|
33
|
+
by: string;
|
|
34
|
+
state: 'pending' | 'endorsed' | 'rejected';
|
|
35
|
+
note?: string;
|
|
36
|
+
decided_by_name?: string;
|
|
37
|
+
};
|
|
38
|
+
approvers?: string[];
|
|
39
|
+
ran_at?: string;
|
|
40
|
+
decided_by_name?: string;
|
|
41
|
+
card: {
|
|
42
|
+
key: string;
|
|
43
|
+
code: string | null;
|
|
44
|
+
name: string | null;
|
|
45
|
+
board_key: string;
|
|
46
|
+
board_name: string | null;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export type Comment = {
|
|
50
|
+
id: string;
|
|
51
|
+
message: string;
|
|
52
|
+
sender_key: string | null;
|
|
53
|
+
sender_name: string | null;
|
|
54
|
+
created_at: string | null;
|
|
55
|
+
/** Present when this comment IS a permission request — the widget people tap. */
|
|
56
|
+
approval?: Approval;
|
|
57
|
+
};
|
|
58
|
+
export declare class Flaredeck {
|
|
59
|
+
private readonly tenantId;
|
|
60
|
+
private readonly auth;
|
|
61
|
+
private readonly baseUrl;
|
|
62
|
+
/**
|
|
63
|
+
* `auth` returns the agent's own access token — a function because it is refreshed as it
|
|
64
|
+
* expires, and this client outlives any one token. No API key: the agent already has an
|
|
65
|
+
* identity, and a second tenant-wide credential would be both more to look after and less
|
|
66
|
+
* accurate, posting work as "API (default)" rather than as the agent that did it.
|
|
67
|
+
*/
|
|
68
|
+
constructor(tenantId: string, auth: () => Promise<string>, baseUrl?: string);
|
|
69
|
+
private call;
|
|
70
|
+
/** Returns the stored comment, so the caller knows exactly which one is its own. */
|
|
71
|
+
comment(card: string, message: string, approval?: {
|
|
72
|
+
request_id: string;
|
|
73
|
+
tool_name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
input_preview: string;
|
|
76
|
+
expires_in_seconds: number;
|
|
77
|
+
review_by?: string;
|
|
78
|
+
approvers?: string[];
|
|
79
|
+
}): Promise<Comment>;
|
|
80
|
+
/** Every request still waiting for an answer in this workspace, newest first. */
|
|
81
|
+
pendingApprovals(): Promise<PendingApproval[]>;
|
|
82
|
+
/** This account's approved requests that were never executed — what a restarted harness owes. */
|
|
83
|
+
approvedUnrun(): Promise<PendingApproval[]>;
|
|
84
|
+
/** Report an approved request as executed, so no harness runs it again. */
|
|
85
|
+
markRan(commentId: string, outcome: string): Promise<void>;
|
|
86
|
+
/** One request by its comment id, in the inbox's shape — or null when it is not pending. */
|
|
87
|
+
approval(commentId: string): Promise<PendingApproval | null>;
|
|
88
|
+
/** The named lead's review of a worker's request: endorse it on to the approvers, or refuse it. */
|
|
89
|
+
review(commentId: string, state: 'endorsed' | 'rejected', note?: string): Promise<void>;
|
|
90
|
+
/** A person's verdict on a request. Writes the same fields the app's button does. */
|
|
91
|
+
decide(commentId: string, state: 'allow' | 'deny'): Promise<void>;
|
|
92
|
+
comments(card: string): Promise<Comment[]>;
|
|
93
|
+
/** The whole card as the REST API returns it — fields, description as markdown, the thread. */
|
|
94
|
+
card(idOrCode: string): Promise<any>;
|
|
95
|
+
/** Every board with its lanes (key, name, type, order), as the REST API lists them. */
|
|
96
|
+
boards(): Promise<Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
lanes: Array<{
|
|
100
|
+
key: string;
|
|
101
|
+
name: string;
|
|
102
|
+
type: string | null;
|
|
103
|
+
order: number | null;
|
|
104
|
+
}>;
|
|
105
|
+
}>>;
|
|
106
|
+
/** Move a card to a lane by key. `keepAssignee` stops the lane's assign-to-role rule from taking the card away. */
|
|
107
|
+
move(card: string, lane: string, keepAssignee?: boolean): Promise<void>;
|
|
108
|
+
/** Every scheduled agent task in the workspace. */
|
|
109
|
+
schedules(): Promise<ScheduleRow[]>;
|
|
110
|
+
/** Register one. From an agent it comes back `pending`, with an approval request on `card`. */
|
|
111
|
+
createSchedule(body: {
|
|
112
|
+
name: string;
|
|
113
|
+
cron: string;
|
|
114
|
+
timezone: string;
|
|
115
|
+
prompt: string;
|
|
116
|
+
card: string;
|
|
117
|
+
agent?: string;
|
|
118
|
+
max_late_minutes?: number;
|
|
119
|
+
}): Promise<ScheduleRow>;
|
|
120
|
+
deleteSchedule(id: string): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
export type ScheduleRow = {
|
|
123
|
+
id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
cron: string;
|
|
126
|
+
timezone: string;
|
|
127
|
+
state: 'pending' | 'active' | 'denied' | string;
|
|
128
|
+
agent_uid: string;
|
|
129
|
+
agent_name: string | null;
|
|
130
|
+
next_run_at: string | null;
|
|
131
|
+
last_run_at: string | null;
|
|
132
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The harness's own line to FlareDeck, over the REST API.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not the MCP server: that is the MODEL's identity, and the harness is a different
|
|
5
|
+
* actor. An approval must be posted and read by the supervisor even when the model is wedged,
|
|
6
|
+
* mid-compaction, or refusing to call tools — so it cannot go through the model's tools.
|
|
7
|
+
*/
|
|
8
|
+
export class Flaredeck {
|
|
9
|
+
tenantId;
|
|
10
|
+
auth;
|
|
11
|
+
baseUrl;
|
|
12
|
+
/**
|
|
13
|
+
* `auth` returns the agent's own access token — a function because it is refreshed as it
|
|
14
|
+
* expires, and this client outlives any one token. No API key: the agent already has an
|
|
15
|
+
* identity, and a second tenant-wide credential would be both more to look after and less
|
|
16
|
+
* accurate, posting work as "API (default)" rather than as the agent that did it.
|
|
17
|
+
*/
|
|
18
|
+
constructor(tenantId, auth, baseUrl = 'https://mcp.flaredeck.app/api') {
|
|
19
|
+
this.tenantId = tenantId;
|
|
20
|
+
this.auth = auth;
|
|
21
|
+
this.baseUrl = baseUrl;
|
|
22
|
+
}
|
|
23
|
+
async call(path, init = {}) {
|
|
24
|
+
const res = await fetch(`${this.baseUrl}/v1/${this.tenantId}${path}`, {
|
|
25
|
+
...init,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${await this.auth()}`,
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
...(init.headers ?? {}),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const body = await res.json().catch(() => ({}));
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`FlareDeck ${res.status}: ${body?.message ?? body?.error ?? 'request failed'}`);
|
|
35
|
+
}
|
|
36
|
+
return body;
|
|
37
|
+
}
|
|
38
|
+
/** Returns the stored comment, so the caller knows exactly which one is its own. */
|
|
39
|
+
async comment(card, message, approval) {
|
|
40
|
+
return (await this.call(`/cards/${encodeURIComponent(card)}/comments`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify({ message, ...(approval ? { approval } : {}) }),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
/** Every request still waiting for an answer in this workspace, newest first. */
|
|
46
|
+
async pendingApprovals() {
|
|
47
|
+
const body = await this.call('/approvals?state=pending');
|
|
48
|
+
return Array.isArray(body?.approvals) ? body.approvals : [];
|
|
49
|
+
}
|
|
50
|
+
/** This account's approved requests that were never executed — what a restarted harness owes. */
|
|
51
|
+
async approvedUnrun() {
|
|
52
|
+
const body = await this.call('/approvals?state=allow&mine=1&unrun=1');
|
|
53
|
+
return Array.isArray(body?.approvals) ? body.approvals : [];
|
|
54
|
+
}
|
|
55
|
+
/** Report an approved request as executed, so no harness runs it again. */
|
|
56
|
+
async markRan(commentId, outcome) {
|
|
57
|
+
await this.call(`/approvals/${encodeURIComponent(commentId)}/ran`, { method: 'POST', body: JSON.stringify({ outcome }) });
|
|
58
|
+
}
|
|
59
|
+
/** One request by its comment id, in the inbox's shape — or null when it is not pending. */
|
|
60
|
+
async approval(commentId) {
|
|
61
|
+
const all = await this.pendingApprovals();
|
|
62
|
+
return all.find((a) => a.comment_id === commentId) ?? null;
|
|
63
|
+
}
|
|
64
|
+
/** The named lead's review of a worker's request: endorse it on to the approvers, or refuse it. */
|
|
65
|
+
async review(commentId, state, note) {
|
|
66
|
+
await this.call(`/approvals/${encodeURIComponent(commentId)}/review`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
body: JSON.stringify({ state, ...(note ? { note } : {}) }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** A person's verdict on a request. Writes the same fields the app's button does. */
|
|
72
|
+
async decide(commentId, state) {
|
|
73
|
+
await this.call(`/approvals/${encodeURIComponent(commentId)}/decide`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
body: JSON.stringify({ state }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async comments(card) {
|
|
79
|
+
const card_ = await this.call(`/cards/${encodeURIComponent(card)}`);
|
|
80
|
+
return (card_.comments ?? []);
|
|
81
|
+
}
|
|
82
|
+
/** The whole card as the REST API returns it — fields, description as markdown, the thread. */
|
|
83
|
+
async card(idOrCode) {
|
|
84
|
+
return this.call(`/cards/${encodeURIComponent(idOrCode)}`);
|
|
85
|
+
}
|
|
86
|
+
/** Every board with its lanes (key, name, type, order), as the REST API lists them. */
|
|
87
|
+
async boards() {
|
|
88
|
+
return ((await this.call('/boards')).boards ?? []);
|
|
89
|
+
}
|
|
90
|
+
/** Move a card to a lane by key. `keepAssignee` stops the lane's assign-to-role rule from taking the card away. */
|
|
91
|
+
async move(card, lane, keepAssignee = true) {
|
|
92
|
+
await this.call(`/cards/${encodeURIComponent(card)}/move`, { method: 'POST', body: JSON.stringify({ lane, keep_assignee: keepAssignee }) });
|
|
93
|
+
}
|
|
94
|
+
/** Every scheduled agent task in the workspace. */
|
|
95
|
+
async schedules() {
|
|
96
|
+
return ((await this.call('/schedules')).schedules ?? []);
|
|
97
|
+
}
|
|
98
|
+
/** Register one. From an agent it comes back `pending`, with an approval request on `card`. */
|
|
99
|
+
async createSchedule(body) {
|
|
100
|
+
return (await this.call('/schedules', { method: 'POST', body: JSON.stringify(body) }));
|
|
101
|
+
}
|
|
102
|
+
async deleteSchedule(id) {
|
|
103
|
+
await this.call(`/schedules/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=flaredeck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flaredeck.js","sourceRoot":"","sources":["../src/flaredeck.ts"],"names":[],"mappings":"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"}
|
package/dist/gemini.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini, through the Gemini Developer API (the AI Studio key — the one with a free tier).
|
|
3
|
+
*
|
|
4
|
+
* The same brief Haiku writes, from a model that costs nothing at this volume. Flash-Lite is
|
|
5
|
+
* the default: a few thousand tokens of card thread do not need a bigger one, and its free
|
|
6
|
+
* quota is counted in requests per day, not tokens. The key comes from aistudio.google.com/apikey.
|
|
7
|
+
*
|
|
8
|
+
* Not Vertex and not OpenRouter on purpose: Vertex has no free tier and wants a GCP project,
|
|
9
|
+
* OpenRouter bills Gemini per token and does not expose the free quota.
|
|
10
|
+
*/
|
|
11
|
+
export declare const GEMINI_DEFAULT_MODEL = "gemini-3.5-flash-lite";
|
|
12
|
+
export type GeminiUsage = {
|
|
13
|
+
input_tokens: number;
|
|
14
|
+
output_tokens: number;
|
|
15
|
+
};
|
|
16
|
+
/** Returns the raw JSON text of the brief (parsed by the caller, same as Haiku's) and the token usage. */
|
|
17
|
+
export declare function geminiBriefText(prompt: string, apiKey: string, model?: string): Promise<{
|
|
18
|
+
text: string;
|
|
19
|
+
usage: GeminiUsage;
|
|
20
|
+
model: string;
|
|
21
|
+
}>;
|
package/dist/gemini.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini, through the Gemini Developer API (the AI Studio key — the one with a free tier).
|
|
3
|
+
*
|
|
4
|
+
* The same brief Haiku writes, from a model that costs nothing at this volume. Flash-Lite is
|
|
5
|
+
* the default: a few thousand tokens of card thread do not need a bigger one, and its free
|
|
6
|
+
* quota is counted in requests per day, not tokens. The key comes from aistudio.google.com/apikey.
|
|
7
|
+
*
|
|
8
|
+
* Not Vertex and not OpenRouter on purpose: Vertex has no free tier and wants a GCP project,
|
|
9
|
+
* OpenRouter bills Gemini per token and does not expose the free quota.
|
|
10
|
+
*/
|
|
11
|
+
export const GEMINI_DEFAULT_MODEL = 'gemini-3.5-flash-lite';
|
|
12
|
+
const BRIEF_SCHEMA = {
|
|
13
|
+
type: 'OBJECT',
|
|
14
|
+
properties: {
|
|
15
|
+
ask: { type: 'STRING' },
|
|
16
|
+
repro: { type: 'ARRAY', items: { type: 'STRING' } },
|
|
17
|
+
area: { type: 'STRING' },
|
|
18
|
+
acceptance: { type: 'ARRAY', items: { type: 'STRING' } },
|
|
19
|
+
evidence: { type: 'ARRAY', items: { type: 'STRING' } },
|
|
20
|
+
open_questions: { type: 'ARRAY', items: { type: 'STRING' } },
|
|
21
|
+
complexity: { type: 'STRING', enum: ['small', 'medium', 'large'] },
|
|
22
|
+
touches_production_data: { type: 'BOOLEAN' },
|
|
23
|
+
confidence: { type: 'NUMBER' },
|
|
24
|
+
},
|
|
25
|
+
required: ['ask', 'repro', 'area', 'acceptance', 'evidence', 'open_questions', 'complexity', 'touches_production_data', 'confidence'],
|
|
26
|
+
};
|
|
27
|
+
/** Returns the raw JSON text of the brief (parsed by the caller, same as Haiku's) and the token usage. */
|
|
28
|
+
export async function geminiBriefText(prompt, apiKey, model = GEMINI_DEFAULT_MODEL) {
|
|
29
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`;
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey },
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
35
|
+
generationConfig: {
|
|
36
|
+
temperature: 0.2,
|
|
37
|
+
responseMimeType: 'application/json',
|
|
38
|
+
responseSchema: BRIEF_SCHEMA,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
signal: AbortSignal.timeout(60_000),
|
|
42
|
+
});
|
|
43
|
+
const body = await res.json().catch(() => ({}));
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const err = new Error(`Gemini ${res.status}: ${body?.error?.message ?? 'request failed'}`);
|
|
46
|
+
err.status = res.status;
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
const text = (body.candidates?.[0]?.content?.parts ?? []).map((p) => p.text ?? '').join('');
|
|
50
|
+
return {
|
|
51
|
+
text,
|
|
52
|
+
usage: {
|
|
53
|
+
input_tokens: Number(body.usageMetadata?.promptTokenCount ?? 0),
|
|
54
|
+
output_tokens: Number(body.usageMetadata?.candidatesTokenCount ?? 0),
|
|
55
|
+
},
|
|
56
|
+
model: String(body.modelVersion ?? model),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../src/gemini.ts"],"names":[],"mappings":"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"}
|
package/dist/gitguard.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep publishing to the tools, and out of the shell.
|
|
3
|
+
*
|
|
4
|
+
* Denying `git push` used to be a rule with no alternative, which is what makes a model creative
|
|
5
|
+
* — it would find `git -C`, or write a script, not out of mischief but because it had a job to
|
|
6
|
+
* finish and one door was shut. Now there is a door: push_branch and open_pull_request. So this
|
|
7
|
+
* refuses the shell route and says where to go instead.
|
|
8
|
+
*
|
|
9
|
+
* A PreToolUse hook rather than a deny rule because it sees the command TEXT. `Bash(git push *)`
|
|
10
|
+
* matches `git push origin x` and misses `git -C /tmp/wt push`, `/usr/bin/git push`, and every
|
|
11
|
+
* push inside a script. This still is not airtight — nothing that reads strings can be — which
|
|
12
|
+
* is why the boundary that actually holds is branch protection on the remote. This is fast
|
|
13
|
+
* feedback and a closed front door, not the fence.
|
|
14
|
+
*/
|
|
15
|
+
/** Publishing, merging and anything that rewrites shared history. */
|
|
16
|
+
const BLOCKED = [
|
|
17
|
+
{ re: /(^|[\s;&|(])git\b[^\n;&|]*\bpush\b/, why: 'push_branch publishes your branch — it takes no arguments, so it cannot push the wrong one.' },
|
|
18
|
+
{ re: /(^|[\s;&|(])git\b[^\n;&|]*\bremote\b[^\n;&|]*\b(add|set-url)\b/, why: 'Pointing the repo at another remote is not something a job does.' },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* READING GitHub is not publishing to it.
|
|
22
|
+
*
|
|
23
|
+
* This used to block every `gh`, which meant an agent could not see its own pull request's checks
|
|
24
|
+
* — it had the tools to open a PR and then no way to look at anything the tools do not cover, so
|
|
25
|
+
* it reported itself blocked and stopped. Nothing here can change anything on GitHub: these are
|
|
26
|
+
* the read verbs, and the write ones (merge, create, close, edit, review, comment, run, delete,
|
|
27
|
+
* rerun, set) are not listed, so they still go through the harness's tools.
|
|
28
|
+
*
|
|
29
|
+
* An allowlist, not a denylist: an unrecognised `gh` subcommand is refused rather than waved
|
|
30
|
+
* through, so a verb added to the CLI later cannot arrive pre-approved.
|
|
31
|
+
*/
|
|
32
|
+
const GH_READ_ONLY = [
|
|
33
|
+
/^gh\s+(?:pr|issue)\s+(?:view|list|diff|checks|status)\b/,
|
|
34
|
+
/^gh\s+run\s+(?:view|list|watch)\b/,
|
|
35
|
+
/^gh\s+(?:repo|release|workflow|label|secret|variable|cache|ruleset)\s+(?:view|list)\b/,
|
|
36
|
+
/^gh\s+auth\s+status\b/,
|
|
37
|
+
/^gh\s+(?:search|status)\b/,
|
|
38
|
+
// `gh api` is a GET unless it is told otherwise. Any method flag, any field, any body makes it
|
|
39
|
+
// a write — including `-f` on graphql, which is how a mutation would be smuggled past this.
|
|
40
|
+
/^gh\s+api\b(?!.*(?:-X|--method)\b)(?!.*(?:-f|-F|--field|--raw-field|--input)\b)/,
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Every `gh …` invocation in a command line, so a pipeline or a chain is judged on each of its
|
|
44
|
+
* parts rather than on the first one that matches.
|
|
45
|
+
*/
|
|
46
|
+
function ghInvocations(command) {
|
|
47
|
+
return command
|
|
48
|
+
.split(/[\n;|&]+|\$\(|`/)
|
|
49
|
+
.map((part) => part.trim())
|
|
50
|
+
.filter((part) => /^gh(\s|$)/.test(part));
|
|
51
|
+
}
|
|
52
|
+
/** True when every `gh` in the line only reads. An empty list means there was no `gh` at all. */
|
|
53
|
+
function ghIsReadOnly(command) {
|
|
54
|
+
const calls = ghInvocations(command);
|
|
55
|
+
if (!calls.length)
|
|
56
|
+
return false;
|
|
57
|
+
return calls.every((call) => GH_READ_ONLY.some((re) => re.test(call)));
|
|
58
|
+
}
|
|
59
|
+
export function gitGuard(onNote) {
|
|
60
|
+
return {
|
|
61
|
+
PreToolUse: [
|
|
62
|
+
{
|
|
63
|
+
matcher: 'Bash',
|
|
64
|
+
hooks: [
|
|
65
|
+
async (input) => {
|
|
66
|
+
const command = String(input?.tool_input?.command ?? '');
|
|
67
|
+
// Reading GitHub is allowed; the write verbs fall through to the refusal below.
|
|
68
|
+
if (ghIsReadOnly(command) && !BLOCKED.some((b) => b.re.test(command)))
|
|
69
|
+
return { continue: true };
|
|
70
|
+
const hit = BLOCKED.find((b) => b.re.test(command))
|
|
71
|
+
?? (/(^|[\s;&|(])gh\b/.test(command)
|
|
72
|
+
? { why: 'open_pull_request, pull_request_status and merge_and_promote do what you need through the harness, which holds the credentials. Reading GitHub with gh (pr view/list/diff/checks, run view/list, api without a method or field) is allowed.' }
|
|
73
|
+
: undefined);
|
|
74
|
+
if (!hit)
|
|
75
|
+
return { continue: true };
|
|
76
|
+
onNote(`blocked a shell publish: ${command.slice(0, 80)}`);
|
|
77
|
+
return {
|
|
78
|
+
decision: 'block',
|
|
79
|
+
reason: `Do not publish from the shell. ${hit.why}\n\n` +
|
|
80
|
+
`Everything local is yours as normal — commit, branch, diff, merging the base ` +
|
|
81
|
+
`branch into yours to stay current. Only reaching GitHub goes through the tools, ` +
|
|
82
|
+
`so a job can publish its own work and nothing else.`,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=gitguard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitguard.js","sourceRoot":"","sources":["../src/gitguard.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,qEAAqE;AACrE,MAAM,OAAO,GAAG;IACd,EAAE,EAAE,EAAE,oCAAoC,EAAE,GAAG,EAAE,6FAA6F,EAAE;IAChJ,EAAE,EAAE,EAAE,gEAAgE,EAAE,GAAG,EAAE,kEAAkE,EAAE;CAClJ,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,YAAY,GAAa;IAC7B,yDAAyD;IACzD,mCAAmC;IACnC,uFAAuF;IACvF,uBAAuB;IACvB,2BAA2B;IAC3B,+FAA+F;IAC/F,4FAA4F;IAC5F,iFAAiF;CAClF,CAAC;AAEF;;;GAGG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,OAAO;SACX,KAAK,CAAC,iBAAiB,CAAC;SACxB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,iGAAiG;AACjG,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAChC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAA8B;IACrD,OAAO;QACL,UAAU,EAAE;YACV;gBACE,OAAO,EAAE,MAAM;gBACf,KAAK,EAAE;oBACL,KAAK,EAAE,KAAU,EAAE,EAAE;wBACnB,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;wBACzD,gFAAgF;wBAChF,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;4BAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;wBACjG,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;+BAC9C,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;gCAClC,CAAC,CAAC,EAAE,GAAG,EAAE,6OAA6O,EAAE;gCACxP,CAAC,CAAC,SAAS,CAAC,CAAC;wBACjB,IAAI,CAAC,GAAG;4BAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;wBAEpC,MAAM,CAAC,4BAA4B,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;wBAC3D,OAAO;4BACL,QAAQ,EAAE,OAAO;4BACjB,MAAM,EACJ,kCAAkC,GAAG,CAAC,GAAG,MAAM;gCAC/C,+EAA+E;gCAC/E,kFAAkF;gCAClF,qDAAqD;yBACxD,CAAC;oBACJ,CAAC;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
|
package/dist/github.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MergeConfig } from './preflight.js';
|
|
2
|
+
import type { Decider } from './permissions.js';
|
|
3
|
+
/**
|
|
4
|
+
* Publishing to GitHub, as tools the harness runs rather than commands the agent writes.
|
|
5
|
+
*
|
|
6
|
+
* A hook that inspects `git push` has to out-guess `git -C dir push`, `/usr/bin/git`, a push
|
|
7
|
+
* inside a script, an alias. You can get that 95% right and never learn about the rest. A tool
|
|
8
|
+
* has no string to be clever with: `push_branch` takes NO branch argument, so the branch is the
|
|
9
|
+
* one the job created and there is no way to name another.
|
|
10
|
+
*
|
|
11
|
+
* The same shape gives the boundary the lead needs. A worker publishes its own branch and opens
|
|
12
|
+
* a pull request; merging is simply not a tool it has, so "don't merge to dev" stops being a rule
|
|
13
|
+
* it must remember and becomes something it cannot express.
|
|
14
|
+
*/
|
|
15
|
+
export type Job = {
|
|
16
|
+
/** Supplies a valid GitHub token. A function, because it is refreshed as it expires. */
|
|
17
|
+
token: () => Promise<string>;
|
|
18
|
+
/** Where the work is. */
|
|
19
|
+
cwd: string;
|
|
20
|
+
/** The branch this job created — the only one it may publish. */
|
|
21
|
+
branch: string;
|
|
22
|
+
/** What a pull request targets, from the board's own configuration. */
|
|
23
|
+
base: string;
|
|
24
|
+
card: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function githubTools(job: () => Job | null,
|
|
27
|
+
/**
|
|
28
|
+
* Present only where a project declares a merge command AND this agent may merge. `config` is
|
|
29
|
+
* read per call, from the checkout the job is in: an agent working a folder of repos has a
|
|
30
|
+
* merge script per repo, and one project's promote script must never be handed another
|
|
31
|
+
* project's branch. Null means this job's repo declares none, so there is nothing to run.
|
|
32
|
+
*/
|
|
33
|
+
merge?: {
|
|
34
|
+
config: () => MergeConfig | null;
|
|
35
|
+
decider: Decider;
|
|
36
|
+
}): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
|
|
37
|
+
/** The tool names to pre-approve: publishing your own branch needs no one's permission. */
|
|
38
|
+
export declare const GIT_TOOL_NAMES: string[];
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
function run(command, args, cwd, token) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const child = spawn(command, args, {
|
|
7
|
+
cwd,
|
|
8
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
9
|
+
env: {
|
|
10
|
+
...process.env,
|
|
11
|
+
// The harness's own credential, handed to this one command. It is never in the agent's
|
|
12
|
+
// environment, so a model that wanted the token could not read it even from a shell —
|
|
13
|
+
// which is the difference between a boundary and a rule.
|
|
14
|
+
...(token ? { GH_TOKEN: token, GITHUB_TOKEN: token } : {}),
|
|
15
|
+
// Never stop for a credential prompt: an unattended push must fail rather than hang.
|
|
16
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
let out = '';
|
|
20
|
+
const cap = (d) => { if (out.length < 4000)
|
|
21
|
+
out += String(d); };
|
|
22
|
+
child.stdout.on('data', cap);
|
|
23
|
+
child.stderr.on('data', cap);
|
|
24
|
+
child.on('close', (c) => resolve({ code: c ?? 1, out: out.trim() }));
|
|
25
|
+
child.on('error', (e) => resolve({ code: 1, out: String(e) }));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The job's branch is the one the harness created, not whatever HEAD happens to be — so an agent
|
|
30
|
+
* cannot change which branch it may publish by checking out another one.
|
|
31
|
+
*
|
|
32
|
+
* But it CAN check out another one and commit there, and then publishing the job's branch would
|
|
33
|
+
* push something stale while the real work sat unpublished. That is not a refusal to punish; it
|
|
34
|
+
* is a reading nobody would notice was wrong. So: publishing requires HEAD to be where the job
|
|
35
|
+
* put it, and says so plainly when it is not.
|
|
36
|
+
*/
|
|
37
|
+
async function onJobBranch(j) {
|
|
38
|
+
const head = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], j.cwd);
|
|
39
|
+
if (head.code !== 0)
|
|
40
|
+
return `Could not read the current branch in ${j.cwd}.`;
|
|
41
|
+
if (head.out !== j.branch) {
|
|
42
|
+
return (`This job is on ${j.branch}, but the checkout is on ${head.out}. ` +
|
|
43
|
+
`Only ${j.branch} can be published from here — move your commits onto it ` +
|
|
44
|
+
`(git checkout ${j.branch} && git merge ${head.out}) and try again.`);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const say = (text, isError = false) => ({
|
|
49
|
+
content: [{ type: 'text', text }],
|
|
50
|
+
...(isError ? { isError: true } : {}),
|
|
51
|
+
});
|
|
52
|
+
export function githubTools(job,
|
|
53
|
+
/**
|
|
54
|
+
* Present only where a project declares a merge command AND this agent may merge. `config` is
|
|
55
|
+
* read per call, from the checkout the job is in: an agent working a folder of repos has a
|
|
56
|
+
* merge script per repo, and one project's promote script must never be handed another
|
|
57
|
+
* project's branch. Null means this job's repo declares none, so there is nothing to run.
|
|
58
|
+
*/
|
|
59
|
+
merge) {
|
|
60
|
+
const needJob = () => {
|
|
61
|
+
const j = job();
|
|
62
|
+
if (!j)
|
|
63
|
+
throw new Error('There is no job running, so there is nothing to publish.');
|
|
64
|
+
return j;
|
|
65
|
+
};
|
|
66
|
+
return createSdkMcpServer({
|
|
67
|
+
name: 'git',
|
|
68
|
+
version: '1.0.0',
|
|
69
|
+
tools: [
|
|
70
|
+
tool('push_branch', 'Publish this job\'s branch to GitHub. Takes no arguments: it pushes the branch this job ' +
|
|
71
|
+
'created, which is the only branch you may publish.', {}, async () => {
|
|
72
|
+
const j = needJob();
|
|
73
|
+
const wrong = await onJobBranch(j);
|
|
74
|
+
if (wrong)
|
|
75
|
+
return say(wrong, true);
|
|
76
|
+
const token = await j.token();
|
|
77
|
+
// Push over an authenticated URL for this call only, rather than rewriting the remote:
|
|
78
|
+
// the checkout keeps its normal origin and no credential is left behind in it.
|
|
79
|
+
const origin = await run('git', ['remote', 'get-url', 'origin'], j.cwd);
|
|
80
|
+
const url = origin.out.replace(/^https:\/\//, `https://x-access-token:${token}@`);
|
|
81
|
+
const { code, out } = await run('git', ['push', '-u', url, `${j.branch}:${j.branch}`], j.cwd, token);
|
|
82
|
+
// Redact before anything is shown: git echoes the remote URL on failure, and that URL
|
|
83
|
+
// now carries the token.
|
|
84
|
+
const safe = out.split(token).join('[REDACTED]');
|
|
85
|
+
return code === 0
|
|
86
|
+
? say(`Pushed ${j.branch}.\n${safe}`)
|
|
87
|
+
: say(`Could not push ${j.branch}:\n${safe}`, true);
|
|
88
|
+
}),
|
|
89
|
+
tool('open_pull_request', 'Open a pull request from this job\'s branch. The source and target branches are fixed by ' +
|
|
90
|
+
'the job and the board, so you choose only the title and the description.', {
|
|
91
|
+
title: z.string().describe('One line. What the change does, not which card it came from.'),
|
|
92
|
+
body: z.string().describe('What changed and why, for whoever reviews it.'),
|
|
93
|
+
}, async ({ title, body }) => {
|
|
94
|
+
const j = needJob();
|
|
95
|
+
const { code, out } = await run('gh', ['pr', 'create', '--base', j.base, '--head', j.branch,
|
|
96
|
+
'--title', title, '--body', `${body}\n\n---\nFlareDeck card: ${j.card}`], j.cwd, await j.token());
|
|
97
|
+
return code === 0 ? say(out) : say(`Could not open a pull request:\n${out}`, true);
|
|
98
|
+
}),
|
|
99
|
+
...(merge
|
|
100
|
+
? [
|
|
101
|
+
tool('merge_and_promote', 'Merge this job\'s branch and promote it, using the project\'s own merge script. ' +
|
|
102
|
+
'Takes no arguments: the branch is this job\'s, and a person approves the result ' +
|
|
103
|
+
'of a dry run before anything is pushed.', {}, async () => {
|
|
104
|
+
const j = needJob();
|
|
105
|
+
const wrong = await onJobBranch(j);
|
|
106
|
+
if (wrong)
|
|
107
|
+
return say(wrong, true);
|
|
108
|
+
const cfg = merge.config();
|
|
109
|
+
if (!cfg) {
|
|
110
|
+
return say(`${j.cwd} declares no merge command, so there is nothing to promote here. ` +
|
|
111
|
+
'Open a pull request instead and say on the card that it is ready.', true);
|
|
112
|
+
}
|
|
113
|
+
const token = await j.token();
|
|
114
|
+
const base = cfg.command.split(' ');
|
|
115
|
+
// ALWAYS the dry run first. Not "the agent should preview it" — it cannot skip
|
|
116
|
+
// this, because it is not composing the command.
|
|
117
|
+
const preview = await run(base[0], [...base.slice(1), j.branch, ...(cfg.dryRun ? [cfg.dryRun] : [])], j.cwd, token);
|
|
118
|
+
if (preview.code !== 0) {
|
|
119
|
+
return say(`The merge script refused before doing anything:\n${preview.out}`, true);
|
|
120
|
+
}
|
|
121
|
+
// A person sees what the dry run said and decides. The confirmation flag exists
|
|
122
|
+
// in these scripts precisely so a human says yes — an agent passing it for
|
|
123
|
+
// itself would be answering its own question.
|
|
124
|
+
const decision = await merge.decider({ toolName: 'merge_and_promote', input: { branch: j.branch, preview: preview.out } }, { signal: new AbortController().signal });
|
|
125
|
+
if (!decision.allow)
|
|
126
|
+
return say(`Not merged. ${decision.reason}`, true);
|
|
127
|
+
const done = await run(base[0], [...base.slice(1), j.branch, ...(cfg.confirm ? [cfg.confirm] : [])], j.cwd, token);
|
|
128
|
+
const safe = done.out.split(token).join('[REDACTED]');
|
|
129
|
+
// The script's own "NOT PROVED" lines come back verbatim. A merge that pushed but
|
|
130
|
+
// could not prove the deploy is not a success, and rewording it would lose that.
|
|
131
|
+
return done.code === 0 ? say(safe) : say(`The merge did not complete:\n${safe}`, true);
|
|
132
|
+
}),
|
|
133
|
+
]
|
|
134
|
+
: []),
|
|
135
|
+
tool('pull_request_status', 'How this job\'s pull request is doing: checks, reviews, whether it can merge.', {}, async () => {
|
|
136
|
+
const j = needJob();
|
|
137
|
+
const { code, out } = await run('gh', ['pr', 'view', j.branch, '--json',
|
|
138
|
+
'number,title,state,mergeable,reviewDecision,statusCheckRollup,url'], j.cwd, await j.token());
|
|
139
|
+
return code === 0 ? say(out) : say(`No pull request found for ${j.branch}.`, true);
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** The tool names to pre-approve: publishing your own branch needs no one's permission. */
|
|
145
|
+
export const GIT_TOOL_NAMES = [
|
|
146
|
+
'mcp__git__push_branch',
|
|
147
|
+
'mcp__git__open_pull_request',
|
|
148
|
+
'mcp__git__pull_request_status',
|
|
149
|
+
// Pre-approved as a TOOL, but it asks a person inside itself before it pushes anything. The
|
|
150
|
+
// approval is on the merge, not on the agent's right to attempt one.
|
|
151
|
+
'mcp__git__merge_and_promote',
|
|
152
|
+
];
|
|
153
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
|