balladeer 1.0.0 → 1.0.1
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/README.md +31 -18
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +148 -9
- package/dist/client.d.ts +23 -1
- package/dist/client.js +29 -1
- package/dist/commands/affected.js +2 -1
- package/dist/commands/discover.js +12 -4
- package/dist/commands/explain.d.ts +1 -1
- package/dist/commands/explain.js +1 -1
- package/dist/commands/invite.js +2 -1
- package/dist/commands/prepare.d.ts +74 -0
- package/dist/commands/prepare.js +217 -0
- package/dist/commands/propose.d.ts +10 -0
- package/dist/commands/propose.js +25 -3
- package/dist/commands/session.d.ts +35 -0
- package/dist/commands/session.js +118 -0
- package/dist/commands/setup.d.ts +23 -0
- package/dist/commands/setup.js +157 -28
- package/dist/commands/status.d.ts +16 -0
- package/dist/commands/status.js +67 -7
- package/dist/commands/whoami.js +2 -1
- package/dist/conventions.d.ts +9 -1
- package/dist/conventions.js +9 -1
- package/dist/copy.d.ts +79 -3
- package/dist/copy.js +187 -5
- package/dist/desktop-config.d.ts +85 -0
- package/dist/desktop-config.js +217 -0
- package/dist/git.d.ts +15 -0
- package/dist/git.js +23 -0
- package/dist/legacy.d.ts +41 -0
- package/dist/legacy.js +143 -0
- package/dist/local-time.d.ts +66 -0
- package/dist/local-time.js +84 -0
- package/dist/mcp-config.d.ts +10 -0
- package/dist/mcp-config.js +8 -4
- package/dist/session.d.ts +84 -0
- package/dist/session.js +135 -0
- package/dist/store.d.ts +10 -0
- package/dist/store.js +15 -3
- package/dist/wire.d.ts +88 -2
- package/dist/wire.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The id that ties what an agent was told to what it then wrote.
|
|
3
|
+
*
|
|
4
|
+
* A retrieval read happens before the commit exists, so nothing joins the two
|
|
5
|
+
* on its own. The agent's own working session is what spans them: it reads the
|
|
6
|
+
* index under this id, and writes the same id into the commit trailer. That is
|
|
7
|
+
* the whole mechanism, and it lives here rather than on the server because the
|
|
8
|
+
* server never sees a commit message.
|
|
9
|
+
*
|
|
10
|
+
* The id is opaque and says nothing about the work. It is not a secret either:
|
|
11
|
+
* it is written into commit messages, which is exactly where people will read
|
|
12
|
+
* it. What it must be is stable across one piece of work and different across
|
|
13
|
+
* two, which is what the marker file below is for.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The shape of a session id, and the line that carries it into a commit.
|
|
17
|
+
*
|
|
18
|
+
* Written out here rather than imported. This command is published to npm on
|
|
19
|
+
* its own and depends on nothing, so the grammar it uses has to live inside it;
|
|
20
|
+
* a packaging test compares these three against the control plane's own
|
|
21
|
+
* definitions, so a drift fails here rather than as a token the server refuses
|
|
22
|
+
* out of a customer's commit message.
|
|
23
|
+
*/
|
|
24
|
+
export declare const AGENT_SESSION_ID_PATTERN: RegExp;
|
|
25
|
+
/** The trailer key an agent writes into the commit or the pull-request body. */
|
|
26
|
+
export declare const AGENT_SESSION_TRAILER = "Balladeer-Session";
|
|
27
|
+
/** The exact line to paste, so every carrier spells the trailer one way. */
|
|
28
|
+
export declare function agentSessionTrailerLine(sessionId: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The session id a commit message or pull-request body carries, if it carries
|
|
31
|
+
* one this release would recognise.
|
|
32
|
+
*
|
|
33
|
+
* A body carrying two different session ids is refused rather than resolved:
|
|
34
|
+
* two answers to "which session wrote this" is not one answer, and guessing
|
|
35
|
+
* which of them to record would put an invented join in front of a number.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readAgentSessionTrailer(text: string): string | undefined;
|
|
38
|
+
/** Where the current session for a repository is remembered. */
|
|
39
|
+
export declare const SESSION_FILE = "sessions.json";
|
|
40
|
+
/**
|
|
41
|
+
* How long one session id stays current.
|
|
42
|
+
*
|
|
43
|
+
* A working session is a sitting, not a calendar day. Twelve hours is long
|
|
44
|
+
* enough that a morning's work and the commit that ends it share an id, and
|
|
45
|
+
* short enough that a machine left running overnight starts the next day's work
|
|
46
|
+
* under a new one rather than attributing tomorrow's commits to yesterday's
|
|
47
|
+
* reads. `--new` is the manual answer for anyone whose sitting ends earlier.
|
|
48
|
+
*/
|
|
49
|
+
export declare const SESSION_LIFETIME_MS: number;
|
|
50
|
+
export type SessionMark = Readonly<{
|
|
51
|
+
/** Which repository this session belongs to, as the credential names it. */
|
|
52
|
+
repository: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
startedAt: string;
|
|
55
|
+
}>;
|
|
56
|
+
type SessionFile = {
|
|
57
|
+
version: 1;
|
|
58
|
+
sessions: SessionMark[];
|
|
59
|
+
};
|
|
60
|
+
/** A fresh id: the prefix a person can recognise, and 128 bits of nothing else. */
|
|
61
|
+
export declare function mintSessionId(): string;
|
|
62
|
+
export declare function isSessionId(value: string): boolean;
|
|
63
|
+
export declare function readSessionMarks(environment?: NodeJS.ProcessEnv): SessionFile;
|
|
64
|
+
export type CurrentSession = Readonly<{
|
|
65
|
+
sessionId: string;
|
|
66
|
+
startedAt: string;
|
|
67
|
+
/** True when this call minted it, false when it was already current. */
|
|
68
|
+
minted: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* The session id for this repository right now, minting one when there is none
|
|
72
|
+
* current.
|
|
73
|
+
*
|
|
74
|
+
* Reusing a live one is the point. An agent that asks twice in one sitting must
|
|
75
|
+
* get the same answer, or its reads and its commit end up under two ids and the
|
|
76
|
+
* join it exists to make is broken by the act of asking for it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function currentSession(input: Readonly<{
|
|
79
|
+
repository: string;
|
|
80
|
+
now: string;
|
|
81
|
+
forceNew?: boolean;
|
|
82
|
+
environment?: NodeJS.ProcessEnv;
|
|
83
|
+
}>): CurrentSession;
|
|
84
|
+
export {};
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { assertSafeStoreLocation, configHome, writeStoreFile } from "./store.js";
|
|
5
|
+
/**
|
|
6
|
+
* The id that ties what an agent was told to what it then wrote.
|
|
7
|
+
*
|
|
8
|
+
* A retrieval read happens before the commit exists, so nothing joins the two
|
|
9
|
+
* on its own. The agent's own working session is what spans them: it reads the
|
|
10
|
+
* index under this id, and writes the same id into the commit trailer. That is
|
|
11
|
+
* the whole mechanism, and it lives here rather than on the server because the
|
|
12
|
+
* server never sees a commit message.
|
|
13
|
+
*
|
|
14
|
+
* The id is opaque and says nothing about the work. It is not a secret either:
|
|
15
|
+
* it is written into commit messages, which is exactly where people will read
|
|
16
|
+
* it. What it must be is stable across one piece of work and different across
|
|
17
|
+
* two, which is what the marker file below is for.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The shape of a session id, and the line that carries it into a commit.
|
|
21
|
+
*
|
|
22
|
+
* Written out here rather than imported. This command is published to npm on
|
|
23
|
+
* its own and depends on nothing, so the grammar it uses has to live inside it;
|
|
24
|
+
* a packaging test compares these three against the control plane's own
|
|
25
|
+
* definitions, so a drift fails here rather than as a token the server refuses
|
|
26
|
+
* out of a customer's commit message.
|
|
27
|
+
*/
|
|
28
|
+
export const AGENT_SESSION_ID_PATTERN = /^bs_[0-9a-f]{32}$/;
|
|
29
|
+
/** The trailer key an agent writes into the commit or the pull-request body. */
|
|
30
|
+
export const AGENT_SESSION_TRAILER = "Balladeer-Session";
|
|
31
|
+
/** The exact line to paste, so every carrier spells the trailer one way. */
|
|
32
|
+
export function agentSessionTrailerLine(sessionId) {
|
|
33
|
+
return `${AGENT_SESSION_TRAILER}: ${sessionId}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The session id a commit message or pull-request body carries, if it carries
|
|
37
|
+
* one this release would recognise.
|
|
38
|
+
*
|
|
39
|
+
* A body carrying two different session ids is refused rather than resolved:
|
|
40
|
+
* two answers to "which session wrote this" is not one answer, and guessing
|
|
41
|
+
* which of them to record would put an invented join in front of a number.
|
|
42
|
+
*/
|
|
43
|
+
export function readAgentSessionTrailer(text) {
|
|
44
|
+
const pattern = new RegExp(`^[ \\t]*${AGENT_SESSION_TRAILER}[ \\t]*:[ \\t]*(\\S+)[ \\t]*$`, "gim");
|
|
45
|
+
const found = new Set();
|
|
46
|
+
for (const match of text.matchAll(pattern)) {
|
|
47
|
+
const value = match[1];
|
|
48
|
+
if (value !== undefined && isSessionId(value))
|
|
49
|
+
found.add(value);
|
|
50
|
+
}
|
|
51
|
+
if (found.size !== 1)
|
|
52
|
+
return undefined;
|
|
53
|
+
return [...found][0];
|
|
54
|
+
}
|
|
55
|
+
/** Where the current session for a repository is remembered. */
|
|
56
|
+
export const SESSION_FILE = "sessions.json";
|
|
57
|
+
/**
|
|
58
|
+
* How long one session id stays current.
|
|
59
|
+
*
|
|
60
|
+
* A working session is a sitting, not a calendar day. Twelve hours is long
|
|
61
|
+
* enough that a morning's work and the commit that ends it share an id, and
|
|
62
|
+
* short enough that a machine left running overnight starts the next day's work
|
|
63
|
+
* under a new one rather than attributing tomorrow's commits to yesterday's
|
|
64
|
+
* reads. `--new` is the manual answer for anyone whose sitting ends earlier.
|
|
65
|
+
*/
|
|
66
|
+
export const SESSION_LIFETIME_MS = 12 * 60 * 60 * 1000;
|
|
67
|
+
const EMPTY = { version: 1, sessions: [] };
|
|
68
|
+
/** A fresh id: the prefix a person can recognise, and 128 bits of nothing else. */
|
|
69
|
+
export function mintSessionId() {
|
|
70
|
+
return `bs_${randomBytes(16).toString("hex")}`;
|
|
71
|
+
}
|
|
72
|
+
export function isSessionId(value) {
|
|
73
|
+
return AGENT_SESSION_ID_PATTERN.test(value);
|
|
74
|
+
}
|
|
75
|
+
function sessionPath(environment) {
|
|
76
|
+
return join(configHome(environment), SESSION_FILE);
|
|
77
|
+
}
|
|
78
|
+
export function readSessionMarks(environment = process.env) {
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = readFileSync(sessionPath(environment), "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { version: 1, sessions: [] };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
const sessions = Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
89
|
+
// A marker whose id this build would not recognise is dropped rather than
|
|
90
|
+
// repaired. It can only have come from a copy of this command that spelled
|
|
91
|
+
// ids differently, and reusing one would put a token the server refuses
|
|
92
|
+
// into a commit message where nobody would ever look for the cause.
|
|
93
|
+
return { version: 1, sessions: sessions.filter((mark) => isSessionId(mark.sessionId)) };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { ...EMPTY, sessions: [] };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function writeSessionMarks(file, environment) {
|
|
100
|
+
assertSafeStoreLocation(configHome(environment), environment);
|
|
101
|
+
writeStoreFile(SESSION_FILE, `${JSON.stringify(file, null, 2)}\n`, environment);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The session id for this repository right now, minting one when there is none
|
|
105
|
+
* current.
|
|
106
|
+
*
|
|
107
|
+
* Reusing a live one is the point. An agent that asks twice in one sitting must
|
|
108
|
+
* get the same answer, or its reads and its commit end up under two ids and the
|
|
109
|
+
* join it exists to make is broken by the act of asking for it.
|
|
110
|
+
*/
|
|
111
|
+
export function currentSession(input) {
|
|
112
|
+
const environment = input.environment ?? process.env;
|
|
113
|
+
const file = readSessionMarks(environment);
|
|
114
|
+
const existing = file.sessions.find((mark) => mark.repository === input.repository);
|
|
115
|
+
const age = existing === undefined ? undefined : Date.parse(input.now) - Date.parse(existing.startedAt);
|
|
116
|
+
const usable = existing !== undefined &&
|
|
117
|
+
input.forceNew !== true &&
|
|
118
|
+
age !== undefined &&
|
|
119
|
+
Number.isFinite(age) &&
|
|
120
|
+
age >= 0 &&
|
|
121
|
+
age < SESSION_LIFETIME_MS;
|
|
122
|
+
if (usable && existing !== undefined) {
|
|
123
|
+
return { sessionId: existing.sessionId, startedAt: existing.startedAt, minted: false };
|
|
124
|
+
}
|
|
125
|
+
const minted = {
|
|
126
|
+
repository: input.repository,
|
|
127
|
+
sessionId: mintSessionId(),
|
|
128
|
+
startedAt: input.now,
|
|
129
|
+
};
|
|
130
|
+
writeSessionMarks({
|
|
131
|
+
version: 1,
|
|
132
|
+
sessions: [...file.sessions.filter((mark) => mark.repository !== input.repository), minted],
|
|
133
|
+
}, environment);
|
|
134
|
+
return { sessionId: minted.sessionId, startedAt: minted.startedAt, minted: true };
|
|
135
|
+
}
|
package/dist/store.d.ts
CHANGED
|
@@ -81,6 +81,16 @@ export declare function readCredentials(environment?: NodeJS.ProcessEnv): Creden
|
|
|
81
81
|
* its mode is set on the open descriptor, and it is flushed before the rename.
|
|
82
82
|
*/
|
|
83
83
|
export declare function writeCredentials(credentials: Credentials, environment?: NodeJS.ProcessEnv): void;
|
|
84
|
+
/**
|
|
85
|
+
* One private file in the store directory, written the same careful way the
|
|
86
|
+
* credential is.
|
|
87
|
+
*
|
|
88
|
+
* Pulled out of `writeCredentials` rather than copied beside it: the session
|
|
89
|
+
* marker written next to the credential is not a secret, but it is written into
|
|
90
|
+
* the same directory by the same command, and a second, sloppier writer there
|
|
91
|
+
* would be the one an attacker pre-plants a symlink for.
|
|
92
|
+
*/
|
|
93
|
+
export declare function writeStoreFile(fileName: string, contents: string, environment?: NodeJS.ProcessEnv): void;
|
|
84
94
|
/** Origins, not URL strings, so `https://host/../` cannot alias a stored entry. */
|
|
85
95
|
export declare function normalizeControlPlane(value: string): string;
|
|
86
96
|
/** At most one pending pairing per control plane, newest wins. */
|
package/dist/store.js
CHANGED
|
@@ -124,14 +124,26 @@ export function readCredentials(environment = process.env) {
|
|
|
124
124
|
* its mode is set on the open descriptor, and it is flushed before the rename.
|
|
125
125
|
*/
|
|
126
126
|
export function writeCredentials(credentials, environment = process.env) {
|
|
127
|
+
writeStoreFile("credentials.json", `${JSON.stringify(credentials, null, 2)}\n`, environment);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* One private file in the store directory, written the same careful way the
|
|
131
|
+
* credential is.
|
|
132
|
+
*
|
|
133
|
+
* Pulled out of `writeCredentials` rather than copied beside it: the session
|
|
134
|
+
* marker written next to the credential is not a secret, but it is written into
|
|
135
|
+
* the same directory by the same command, and a second, sloppier writer there
|
|
136
|
+
* would be the one an attacker pre-plants a symlink for.
|
|
137
|
+
*/
|
|
138
|
+
export function writeStoreFile(fileName, contents, environment = process.env) {
|
|
127
139
|
const directory = ensureStoreDirectory(environment);
|
|
128
|
-
const path = join(directory,
|
|
129
|
-
const temporary = join(directory,
|
|
140
|
+
const path = join(directory, fileName);
|
|
141
|
+
const temporary = join(directory, `.${fileName}.${randomBytes(8).toString("hex")}.tmp`);
|
|
130
142
|
let descriptor;
|
|
131
143
|
try {
|
|
132
144
|
descriptor = openSync(temporary, "wx", 0o600);
|
|
133
145
|
fchmodSync(descriptor, 0o600);
|
|
134
|
-
writeSync(descriptor,
|
|
146
|
+
writeSync(descriptor, contents);
|
|
135
147
|
fsyncSync(descriptor);
|
|
136
148
|
closeSync(descriptor);
|
|
137
149
|
descriptor = undefined;
|
package/dist/wire.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* runtime dependency at all: Node 22 builtins and global fetch, nothing else.
|
|
6
6
|
* A contract test compares the scope list below against the server's.
|
|
7
7
|
*/
|
|
8
|
-
export declare const CLI_VERSION = "1.0.
|
|
8
|
+
export declare const CLI_VERSION = "1.0.1";
|
|
9
9
|
export declare const CLIENT_HEADER = "x-balladeer-client";
|
|
10
|
-
export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.
|
|
10
|
+
export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.1";
|
|
11
11
|
export declare const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
|
|
12
12
|
export type DelegatedScope = "repository:enroll" | "agent:issue" | "ci:connect" | "workspace:invite" | "candidate:propose";
|
|
13
13
|
export declare const DELEGATED_SCOPES: readonly DelegatedScope[];
|
|
@@ -108,6 +108,21 @@ export type SetupInvitationResponse = Readonly<{
|
|
|
108
108
|
export type SetupCiRequest = Readonly<{
|
|
109
109
|
eventClasses: readonly ("push" | "pull_request")[];
|
|
110
110
|
}>;
|
|
111
|
+
/**
|
|
112
|
+
* The GitHub login this machine's `gh` is signed in as.
|
|
113
|
+
*
|
|
114
|
+
* Sent so a run started by this person can be shown under their name instead of
|
|
115
|
+
* a handle nobody has learned. No token is sent, here or anywhere: the login is
|
|
116
|
+
* read from the GitHub CLI they already signed in to, and Balladeer stores it as
|
|
117
|
+
* something they said rather than something anyone checked. It grants nothing.
|
|
118
|
+
* An empty string clears it.
|
|
119
|
+
*/
|
|
120
|
+
export type SetupGithubLoginRequest = Readonly<{
|
|
121
|
+
login: string;
|
|
122
|
+
}>;
|
|
123
|
+
export type SetupGithubLoginResponse = Readonly<{
|
|
124
|
+
claimedLogin: string | null;
|
|
125
|
+
}>;
|
|
111
126
|
export type SetupRepositoryView = Readonly<{
|
|
112
127
|
id: string;
|
|
113
128
|
displayName: string;
|
|
@@ -193,9 +208,32 @@ export type SetupCandidateResponse = Readonly<{
|
|
|
193
208
|
reviewUrl: string;
|
|
194
209
|
}>;
|
|
195
210
|
/** The step objects `--json` emits, one per line, and nothing else on stdout. */
|
|
211
|
+
/**
|
|
212
|
+
* What a run did about Claude desktop chat, on the two steps that can touch it.
|
|
213
|
+
*
|
|
214
|
+
* Its own type because both `agent` and `refresh` report it and they must report
|
|
215
|
+
* it identically: an agent reading either step reads the same three fields.
|
|
216
|
+
*/
|
|
217
|
+
export type ClaudeDesktopStep = Readonly<{
|
|
218
|
+
status: "connected" | "current" | "absent" | "unsupported" | "refused";
|
|
219
|
+
key?: string;
|
|
220
|
+
path?: string;
|
|
221
|
+
reason?: string;
|
|
222
|
+
}>;
|
|
196
223
|
export type JsonStep = Readonly<{
|
|
197
224
|
step: "explain";
|
|
198
225
|
version: string;
|
|
226
|
+
}> | Readonly<{
|
|
227
|
+
/**
|
|
228
|
+
* The GitHub login this machine was signed in as, handed over so a run
|
|
229
|
+
* this person starts can be shown under their name. `status` says what
|
|
230
|
+
* happened: `claimed` when it was recorded, `unavailable` when `gh` could
|
|
231
|
+
* not name a login, and `refused` when the control plane would not take
|
|
232
|
+
* it. None of the three stops setup.
|
|
233
|
+
*/
|
|
234
|
+
step: "github_login";
|
|
235
|
+
status: "claimed" | "unavailable" | "refused";
|
|
236
|
+
login?: string;
|
|
199
237
|
}> | Readonly<{
|
|
200
238
|
step: "pair";
|
|
201
239
|
status: "pending" | "paired" | "expired" | "denied";
|
|
@@ -258,6 +296,15 @@ export type JsonStep = Readonly<{
|
|
|
258
296
|
/** Present only where this command refused to change one of these files. */
|
|
259
297
|
mcpConfig?: string;
|
|
260
298
|
conventions?: string;
|
|
299
|
+
/**
|
|
300
|
+
* What happened to Claude desktop chat, present only where this run had
|
|
301
|
+
* something to say about it. `connected` and `current` carry the key this
|
|
302
|
+
* repository's server is named under and the file it was written to;
|
|
303
|
+
* `absent`, `unsupported` and `refused` carry the sentence saying why
|
|
304
|
+
* nothing was written, and are only ever reported where the flag asked for
|
|
305
|
+
* it or the file itself refused the merge.
|
|
306
|
+
*/
|
|
307
|
+
claudeDesktop?: ClaudeDesktopStep;
|
|
261
308
|
}> | Readonly<{
|
|
262
309
|
step: "ci";
|
|
263
310
|
/**
|
|
@@ -284,6 +331,20 @@ export type JsonStep = Readonly<{
|
|
|
284
331
|
reviewUrl?: string;
|
|
285
332
|
promiseUrl?: string;
|
|
286
333
|
}>
|
|
334
|
+
/**
|
|
335
|
+
* The one-time qualification setup, minted and written to disk.
|
|
336
|
+
*
|
|
337
|
+
* `supersededPackets` is the number of earlier packets this run invalidated,
|
|
338
|
+
* so a caller can tell an ordinary first preparation from a deliberate
|
|
339
|
+
* replacement without reading prose. It is zero on the ordinary case.
|
|
340
|
+
*/
|
|
341
|
+
| Readonly<{
|
|
342
|
+
step: "qualification";
|
|
343
|
+
status: "prepared";
|
|
344
|
+
promiseId: string;
|
|
345
|
+
metadataPath: string;
|
|
346
|
+
supersededPackets: number;
|
|
347
|
+
}>
|
|
287
348
|
/**
|
|
288
349
|
* A whole discovered catalog, filed in one run.
|
|
289
350
|
*
|
|
@@ -482,6 +543,30 @@ export type JsonStep = Readonly<{
|
|
|
482
543
|
changed: boolean;
|
|
483
544
|
exitCode: number;
|
|
484
545
|
}>
|
|
546
|
+
/**
|
|
547
|
+
* The working session an agent reads and commits under.
|
|
548
|
+
*
|
|
549
|
+
* `minted` is the difference between "here is a new session" and "you already
|
|
550
|
+
* have one open": an agent that asked twice must reuse the second answer
|
|
551
|
+
* rather than treat it as a second session, or its reads and its commit end
|
|
552
|
+
* up under two ids and the join breaks by the act of asking for it.
|
|
553
|
+
*/
|
|
554
|
+
| Readonly<{
|
|
555
|
+
step: "session";
|
|
556
|
+
sessionId: string;
|
|
557
|
+
startedAt: string;
|
|
558
|
+
minted: boolean;
|
|
559
|
+
/** The exact line to write into the commit or the pull-request body. */
|
|
560
|
+
trailer: string;
|
|
561
|
+
changed: boolean;
|
|
562
|
+
}>
|
|
563
|
+
/** One commit recorded as written by one session. */
|
|
564
|
+
| Readonly<{
|
|
565
|
+
step: "session_commit";
|
|
566
|
+
sessionId: string;
|
|
567
|
+
commitSha: string;
|
|
568
|
+
changed: boolean;
|
|
569
|
+
}>
|
|
485
570
|
/**
|
|
486
571
|
* One invited address, one line, whatever happened to it.
|
|
487
572
|
*
|
|
@@ -582,6 +667,7 @@ export type JsonStep = Readonly<{
|
|
|
582
667
|
previousConventionsVersion?: number;
|
|
583
668
|
reason?: string;
|
|
584
669
|
message?: string;
|
|
670
|
+
claudeDesktop?: ClaudeDesktopStep;
|
|
585
671
|
}> | Readonly<{
|
|
586
672
|
step: "whoami";
|
|
587
673
|
session: PairSessionSummary;
|
package/dist/wire.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* runtime dependency at all: Node 22 builtins and global fetch, nothing else.
|
|
6
6
|
* A contract test compares the scope list below against the server's.
|
|
7
7
|
*/
|
|
8
|
-
export const CLI_VERSION = "1.0.
|
|
8
|
+
export const CLI_VERSION = "1.0.1";
|
|
9
9
|
export const CLIENT_HEADER = "x-balladeer-client";
|
|
10
10
|
export const CLIENT_HEADER_VALUE = `balladeer/${CLI_VERSION}`;
|
|
11
11
|
export const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
|