@bubblebrain-ai/bubble 0.0.53 → 0.0.55
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 +43 -0
- package/dist/agent/input-controller.d.ts +3 -0
- package/dist/agent/input-controller.js +13 -0
- package/dist/agent.js +7 -1
- package/dist/model-catalog.js +7 -2
- package/dist/model-pricing.d.ts +1 -5
- package/dist/model-pricing.js +0 -0
- package/dist/provider-ai-sdk.js +5 -0
- package/dist/provider-registry.js +34 -10
- package/dist/sdk/index.d.ts +97 -6
- package/dist/sdk/index.js +445 -34
- package/dist/sdk/replay-event-log.d.ts +19 -0
- package/dist/sdk/replay-event-log.js +72 -0
- package/dist/sdk/session-turn-coordinator.d.ts +45 -0
- package/dist/sdk/session-turn-coordinator.js +227 -0
- package/dist/tui/app.js +5 -3
- package/dist/tui/controller/controller.js +12 -6
- package/dist/tui/fullscreen.js +2 -1
- package/dist/types.d.ts +11 -1
- package/dist/update/index.d.ts +1 -0
- package/dist/update/index.js +22 -17
- package/package.json +5 -3
- /package/{packages/pi-tui/node_modules → node_modules}/marked/LICENSE +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/README.md +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/bin/main.js +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/bin/marked.js +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/lib/marked.d.ts +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/lib/marked.esm.js +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/lib/marked.esm.js.map +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/lib/marked.umd.js +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/lib/marked.umd.js.map +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/man/marked.1 +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/man/marked.1.md +0 -0
- /package/{packages/pi-tui/node_modules → node_modules}/marked/package.json +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small append-only event log for SDK streams. Producers never wait for a
|
|
3
|
+
* consumer, and consumers can attach again with their last seen sequence.
|
|
4
|
+
*/
|
|
5
|
+
export class ReplayEventLog {
|
|
6
|
+
entries = [];
|
|
7
|
+
signal = deferredSignal();
|
|
8
|
+
closed = false;
|
|
9
|
+
failure;
|
|
10
|
+
get length() {
|
|
11
|
+
return this.entries.length;
|
|
12
|
+
}
|
|
13
|
+
append(value) {
|
|
14
|
+
if (this.closed)
|
|
15
|
+
return;
|
|
16
|
+
this.entries.push(value);
|
|
17
|
+
this.wake();
|
|
18
|
+
}
|
|
19
|
+
close(error) {
|
|
20
|
+
if (this.closed)
|
|
21
|
+
return;
|
|
22
|
+
this.closed = true;
|
|
23
|
+
this.failure = error;
|
|
24
|
+
this.wake();
|
|
25
|
+
}
|
|
26
|
+
iterate(options = {}) {
|
|
27
|
+
const start = Math.max(0, options.from ?? 0);
|
|
28
|
+
return this.readFrom(start, options.signal);
|
|
29
|
+
}
|
|
30
|
+
async *readFrom(start, abortSignal) {
|
|
31
|
+
let index = start;
|
|
32
|
+
while (true) {
|
|
33
|
+
while (index < this.entries.length)
|
|
34
|
+
yield this.entries[index++];
|
|
35
|
+
if (this.closed) {
|
|
36
|
+
if (this.failure !== undefined)
|
|
37
|
+
throw this.failure;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (abortSignal?.aborted)
|
|
41
|
+
return;
|
|
42
|
+
const currentSignal = this.signal.promise;
|
|
43
|
+
await waitForSignal(currentSignal, abortSignal);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
wake() {
|
|
47
|
+
const previous = this.signal;
|
|
48
|
+
this.signal = deferredSignal();
|
|
49
|
+
previous.resolve();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function deferredSignal() {
|
|
53
|
+
let resolve;
|
|
54
|
+
const promise = new Promise((done) => {
|
|
55
|
+
resolve = done;
|
|
56
|
+
});
|
|
57
|
+
return { promise, resolve };
|
|
58
|
+
}
|
|
59
|
+
function waitForSignal(signal, abortSignal) {
|
|
60
|
+
if (!abortSignal)
|
|
61
|
+
return signal;
|
|
62
|
+
if (abortSignal.aborted)
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const done = () => {
|
|
66
|
+
abortSignal.removeEventListener("abort", done);
|
|
67
|
+
resolve();
|
|
68
|
+
};
|
|
69
|
+
abortSignal.addEventListener("abort", done, { once: true });
|
|
70
|
+
signal.then(done);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type SessionTurnPhase = "queued" | "reserved" | "starting" | "active" | "stopping" | "settled";
|
|
2
|
+
export interface SessionTurnQueueState {
|
|
3
|
+
active: boolean;
|
|
4
|
+
queued: number;
|
|
5
|
+
phase: Exclude<SessionTurnPhase, "queued" | "settled"> | "idle" | "deleted";
|
|
6
|
+
}
|
|
7
|
+
export interface SessionTurnReservation {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
readonly phase: SessionTurnPhase;
|
|
12
|
+
readonly completion: Promise<void>;
|
|
13
|
+
waitForStart(): Promise<void>;
|
|
14
|
+
markActive(): void;
|
|
15
|
+
cancel(error?: Error): void;
|
|
16
|
+
finish(): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One synchronous state machine owns the complete per-session turn lifecycle.
|
|
20
|
+
* A reservation and its AbortController are created when runTurn() is called,
|
|
21
|
+
* before its async iterator is consumed, so stop/delete never have a handoff
|
|
22
|
+
* window in which a turn is invisible.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SessionTurnCoordinator {
|
|
25
|
+
private readonly sessions;
|
|
26
|
+
private readonly deletedSessions;
|
|
27
|
+
private readonly turnIdPrefix;
|
|
28
|
+
private nextTurnId;
|
|
29
|
+
reserve(sessionId: string, signal?: AbortSignal): SessionTurnReservation;
|
|
30
|
+
getCurrent(sessionId: string): SessionTurnReservation | undefined;
|
|
31
|
+
getState(sessionId: string): SessionTurnQueueState;
|
|
32
|
+
stop(sessionId: string, message?: string): number;
|
|
33
|
+
stopCurrent(sessionId: string, message?: string): number;
|
|
34
|
+
clearQueue(sessionId: string, message?: string): number;
|
|
35
|
+
delete(sessionId: string): Promise<void>;
|
|
36
|
+
isDeleted(sessionId: string): boolean;
|
|
37
|
+
revive(sessionId: string): void;
|
|
38
|
+
private reservationFor;
|
|
39
|
+
private cancelTurn;
|
|
40
|
+
private finishTurn;
|
|
41
|
+
private settleDetached;
|
|
42
|
+
private promoteNext;
|
|
43
|
+
private deleteEmptySession;
|
|
44
|
+
private detachExternalAbort;
|
|
45
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { AgentAbortError, throwIfAborted } from "../agent/abort-errors.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* One synchronous state machine owns the complete per-session turn lifecycle.
|
|
5
|
+
* A reservation and its AbortController are created when runTurn() is called,
|
|
6
|
+
* before its async iterator is consumed, so stop/delete never have a handoff
|
|
7
|
+
* window in which a turn is invisible.
|
|
8
|
+
*/
|
|
9
|
+
export class SessionTurnCoordinator {
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
deletedSessions = new Set();
|
|
12
|
+
turnIdPrefix = `sdk-turn-${randomUUID().slice(0, 6)}`;
|
|
13
|
+
nextTurnId = 0;
|
|
14
|
+
reserve(sessionId, signal) {
|
|
15
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
16
|
+
throw new Error(`Session is deleted: ${sessionId}`);
|
|
17
|
+
}
|
|
18
|
+
throwIfAborted(signal);
|
|
19
|
+
const turn = {
|
|
20
|
+
id: `${this.turnIdPrefix}-${++this.nextTurnId}`,
|
|
21
|
+
sessionId,
|
|
22
|
+
phase: "queued",
|
|
23
|
+
abort: new AbortController(),
|
|
24
|
+
start: deferred(),
|
|
25
|
+
completion: deferred(),
|
|
26
|
+
externalSignal: signal,
|
|
27
|
+
};
|
|
28
|
+
// A reservation can legally be stopped before its iterator is consumed.
|
|
29
|
+
// Keep that rejected start promise observed until waitForStart() reads it.
|
|
30
|
+
void turn.start.promise.catch(() => undefined);
|
|
31
|
+
let session = this.sessions.get(sessionId);
|
|
32
|
+
if (!session) {
|
|
33
|
+
session = { queued: [] };
|
|
34
|
+
this.sessions.set(sessionId, session);
|
|
35
|
+
}
|
|
36
|
+
if (!session.current) {
|
|
37
|
+
session.current = turn;
|
|
38
|
+
turn.phase = "reserved";
|
|
39
|
+
turn.start.resolve(undefined);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
session.queued.push(turn);
|
|
43
|
+
}
|
|
44
|
+
if (signal) {
|
|
45
|
+
turn.onExternalAbort = () => {
|
|
46
|
+
this.cancelTurn(turn, abortReason(signal, "SDK turn cancelled."));
|
|
47
|
+
};
|
|
48
|
+
signal.addEventListener("abort", turn.onExternalAbort, { once: true });
|
|
49
|
+
}
|
|
50
|
+
return this.reservationFor(turn);
|
|
51
|
+
}
|
|
52
|
+
getCurrent(sessionId) {
|
|
53
|
+
const turn = this.sessions.get(sessionId)?.current;
|
|
54
|
+
return turn ? this.reservationFor(turn) : undefined;
|
|
55
|
+
}
|
|
56
|
+
getState(sessionId) {
|
|
57
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
58
|
+
return { active: false, queued: 0, phase: "deleted" };
|
|
59
|
+
}
|
|
60
|
+
const session = this.sessions.get(sessionId);
|
|
61
|
+
const currentPhase = session?.current?.phase;
|
|
62
|
+
return {
|
|
63
|
+
active: Boolean(session?.current),
|
|
64
|
+
queued: session?.queued.length ?? 0,
|
|
65
|
+
phase: currentPhase && currentPhase !== "queued" && currentPhase !== "settled"
|
|
66
|
+
? currentPhase
|
|
67
|
+
: "idle",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
stop(sessionId, message = "SDK turn stopped.") {
|
|
71
|
+
const session = this.sessions.get(sessionId);
|
|
72
|
+
if (!session)
|
|
73
|
+
return 0;
|
|
74
|
+
const turns = [session.current, ...session.queued].filter((turn) => Boolean(turn));
|
|
75
|
+
for (const turn of turns)
|
|
76
|
+
this.cancelTurn(turn, new AgentAbortError(message));
|
|
77
|
+
return turns.length;
|
|
78
|
+
}
|
|
79
|
+
stopCurrent(sessionId, message = "SDK turn stopped.") {
|
|
80
|
+
const current = this.sessions.get(sessionId)?.current;
|
|
81
|
+
if (!current)
|
|
82
|
+
return 0;
|
|
83
|
+
this.cancelTurn(current, new AgentAbortError(message));
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
clearQueue(sessionId, message = "Queued SDK turn cancelled.") {
|
|
87
|
+
const queued = [...(this.sessions.get(sessionId)?.queued ?? [])];
|
|
88
|
+
for (const turn of queued)
|
|
89
|
+
this.cancelTurn(turn, new AgentAbortError(message));
|
|
90
|
+
return queued.length;
|
|
91
|
+
}
|
|
92
|
+
async delete(sessionId) {
|
|
93
|
+
this.deletedSessions.add(sessionId);
|
|
94
|
+
const session = this.sessions.get(sessionId);
|
|
95
|
+
const turns = session
|
|
96
|
+
? [session.current, ...session.queued].filter((turn) => Boolean(turn))
|
|
97
|
+
: [];
|
|
98
|
+
for (const turn of turns) {
|
|
99
|
+
this.cancelTurn(turn, new AgentAbortError("SDK session deleted."));
|
|
100
|
+
}
|
|
101
|
+
await Promise.all(turns.map((turn) => turn.completion.promise));
|
|
102
|
+
}
|
|
103
|
+
isDeleted(sessionId) {
|
|
104
|
+
return this.deletedSessions.has(sessionId);
|
|
105
|
+
}
|
|
106
|
+
revive(sessionId) {
|
|
107
|
+
const session = this.sessions.get(sessionId);
|
|
108
|
+
if (session?.current || session?.queued.length) {
|
|
109
|
+
throw new Error(`Cannot recreate session while turns are still settling: ${sessionId}`);
|
|
110
|
+
}
|
|
111
|
+
this.deletedSessions.delete(sessionId);
|
|
112
|
+
}
|
|
113
|
+
reservationFor(turn) {
|
|
114
|
+
return {
|
|
115
|
+
id: turn.id,
|
|
116
|
+
sessionId: turn.sessionId,
|
|
117
|
+
signal: turn.abort.signal,
|
|
118
|
+
get phase() {
|
|
119
|
+
return turn.phase;
|
|
120
|
+
},
|
|
121
|
+
completion: turn.completion.promise,
|
|
122
|
+
waitForStart: async () => {
|
|
123
|
+
await turn.start.promise;
|
|
124
|
+
if (turn.phase === "settled")
|
|
125
|
+
throw turn.terminalError ?? new AgentAbortError("SDK turn cancelled.");
|
|
126
|
+
if (turn.phase === "reserved")
|
|
127
|
+
turn.phase = "starting";
|
|
128
|
+
throwIfAborted(turn.abort.signal);
|
|
129
|
+
},
|
|
130
|
+
markActive: () => {
|
|
131
|
+
throwIfAborted(turn.abort.signal);
|
|
132
|
+
if (turn.phase === "starting")
|
|
133
|
+
turn.phase = "active";
|
|
134
|
+
},
|
|
135
|
+
cancel: (error = new AgentAbortError("SDK turn cancelled.")) => {
|
|
136
|
+
this.cancelTurn(turn, error);
|
|
137
|
+
},
|
|
138
|
+
finish: () => this.finishTurn(turn),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
cancelTurn(turn, error) {
|
|
142
|
+
if (turn.phase === "settled")
|
|
143
|
+
return;
|
|
144
|
+
turn.terminalError = error;
|
|
145
|
+
turn.abort.abort(error);
|
|
146
|
+
const session = this.sessions.get(turn.sessionId);
|
|
147
|
+
if (!session) {
|
|
148
|
+
this.settleDetached(turn);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (turn.phase === "queued") {
|
|
152
|
+
const index = session.queued.indexOf(turn);
|
|
153
|
+
if (index >= 0)
|
|
154
|
+
session.queued.splice(index, 1);
|
|
155
|
+
this.settleDetached(turn);
|
|
156
|
+
this.deleteEmptySession(turn.sessionId, session);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (turn.phase === "reserved") {
|
|
160
|
+
if (session.current === turn)
|
|
161
|
+
session.current = undefined;
|
|
162
|
+
this.settleDetached(turn);
|
|
163
|
+
this.promoteNext(turn.sessionId, session);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
turn.phase = "stopping";
|
|
167
|
+
}
|
|
168
|
+
finishTurn(turn) {
|
|
169
|
+
if (turn.phase === "settled")
|
|
170
|
+
return;
|
|
171
|
+
const session = this.sessions.get(turn.sessionId);
|
|
172
|
+
if (session?.current === turn)
|
|
173
|
+
session.current = undefined;
|
|
174
|
+
this.settleDetached(turn);
|
|
175
|
+
if (session)
|
|
176
|
+
this.promoteNext(turn.sessionId, session);
|
|
177
|
+
}
|
|
178
|
+
settleDetached(turn) {
|
|
179
|
+
this.detachExternalAbort(turn);
|
|
180
|
+
const error = turn.terminalError;
|
|
181
|
+
turn.phase = "settled";
|
|
182
|
+
if (error)
|
|
183
|
+
turn.start.reject(error);
|
|
184
|
+
turn.completion.resolve(undefined);
|
|
185
|
+
}
|
|
186
|
+
promoteNext(sessionId, session) {
|
|
187
|
+
if (this.deletedSessions.has(sessionId)) {
|
|
188
|
+
for (const queued of [...session.queued]) {
|
|
189
|
+
this.cancelTurn(queued, new AgentAbortError("SDK session deleted."));
|
|
190
|
+
}
|
|
191
|
+
this.deleteEmptySession(sessionId, session);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
while (!session.current && session.queued.length > 0) {
|
|
195
|
+
const next = session.queued.shift();
|
|
196
|
+
if (!next || next.phase === "settled")
|
|
197
|
+
continue;
|
|
198
|
+
session.current = next;
|
|
199
|
+
next.phase = "reserved";
|
|
200
|
+
next.start.resolve(undefined);
|
|
201
|
+
}
|
|
202
|
+
this.deleteEmptySession(sessionId, session);
|
|
203
|
+
}
|
|
204
|
+
deleteEmptySession(sessionId, session) {
|
|
205
|
+
if (!session.current && session.queued.length === 0)
|
|
206
|
+
this.sessions.delete(sessionId);
|
|
207
|
+
}
|
|
208
|
+
detachExternalAbort(turn) {
|
|
209
|
+
if (turn.externalSignal && turn.onExternalAbort) {
|
|
210
|
+
turn.externalSignal.removeEventListener("abort", turn.onExternalAbort);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function deferred() {
|
|
215
|
+
let resolve;
|
|
216
|
+
let reject;
|
|
217
|
+
const promise = new Promise((onResolve, onReject) => {
|
|
218
|
+
resolve = onResolve;
|
|
219
|
+
reject = onReject;
|
|
220
|
+
});
|
|
221
|
+
return { promise, resolve, reject };
|
|
222
|
+
}
|
|
223
|
+
function abortReason(signal, fallback) {
|
|
224
|
+
if (signal.reason instanceof Error)
|
|
225
|
+
return signal.reason;
|
|
226
|
+
return new AgentAbortError(typeof signal.reason === "string" ? signal.reason : fallback);
|
|
227
|
+
}
|
package/dist/tui/app.js
CHANGED
|
@@ -483,8 +483,8 @@ export class PiTuiApp {
|
|
|
483
483
|
// into a text steer.
|
|
484
484
|
if (payload.images.length > 0)
|
|
485
485
|
controller.queueInput(payload);
|
|
486
|
-
else
|
|
487
|
-
controller.
|
|
486
|
+
else if (!controller.steer(trimmed))
|
|
487
|
+
controller.queueInput(payload);
|
|
488
488
|
this.renderSnapshot();
|
|
489
489
|
}
|
|
490
490
|
else if (controller.queueAfterCommand?.(payload)) {
|
|
@@ -520,7 +520,9 @@ export class PiTuiApp {
|
|
|
520
520
|
this.pushUserRow({ text: command, images: [] });
|
|
521
521
|
const controller = this.options.controller;
|
|
522
522
|
if (controller.isRunning()) {
|
|
523
|
-
controller.steer(skillInvocation.actualPrompt)
|
|
523
|
+
if (!controller.steer(skillInvocation.actualPrompt)) {
|
|
524
|
+
controller.queueInput(skillInvocation.actualPrompt);
|
|
525
|
+
}
|
|
524
526
|
this.renderSnapshot();
|
|
525
527
|
}
|
|
526
528
|
else {
|
|
@@ -449,7 +449,9 @@ export class BubbleTuiController {
|
|
|
449
449
|
steer(content) {
|
|
450
450
|
if (!this.runActive || !this.activeInputController)
|
|
451
451
|
return false;
|
|
452
|
-
const input = this.activeInputController.
|
|
452
|
+
const input = this.activeInputController.tryEnqueue(content);
|
|
453
|
+
if (!input)
|
|
454
|
+
return false;
|
|
453
455
|
const displayKey = nextDisplayMessageKey("steer");
|
|
454
456
|
beginSteer(this.queue, {
|
|
455
457
|
id: input.id,
|
|
@@ -539,11 +541,15 @@ export class BubbleTuiController {
|
|
|
539
541
|
throw new Error("A command is already running.");
|
|
540
542
|
}
|
|
541
543
|
if (this.runActive) {
|
|
542
|
-
if (typeof input === "string")
|
|
543
|
-
this.steer(input)
|
|
544
|
+
if (typeof input === "string") {
|
|
545
|
+
if (!this.steer(input))
|
|
546
|
+
this.queueInput(input);
|
|
547
|
+
}
|
|
544
548
|
else if (isSubmitPayload(input)) {
|
|
545
|
-
if (input.images.length === 0)
|
|
546
|
-
this.steer(input.text)
|
|
549
|
+
if (input.images.length === 0) {
|
|
550
|
+
if (!this.steer(input.text))
|
|
551
|
+
this.queueInput(input);
|
|
552
|
+
}
|
|
547
553
|
else
|
|
548
554
|
this.queueInput(input);
|
|
549
555
|
}
|
|
@@ -716,7 +722,7 @@ export class BubbleTuiController {
|
|
|
716
722
|
this.notify();
|
|
717
723
|
}
|
|
718
724
|
finally {
|
|
719
|
-
const leftoverSteers = inputController.
|
|
725
|
+
const leftoverSteers = inputController.closePendingInputs();
|
|
720
726
|
// The signal is the source of truth. A provider may observe cancellation
|
|
721
727
|
// and end its iterator without throwing, but the user's pending steers
|
|
722
728
|
// must still be dropped exactly as they are in the Ink implementation.
|
package/dist/tui/fullscreen.js
CHANGED
|
@@ -118,7 +118,8 @@ export class FullscreenApp {
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
if (this.options.controller.isRunning()) {
|
|
121
|
-
this.options.controller.steer(trimmed)
|
|
121
|
+
if (!this.options.controller.steer(trimmed))
|
|
122
|
+
this.options.controller.queueInput(trimmed);
|
|
122
123
|
}
|
|
123
124
|
else if (this.options.controller.queueAfterCommand?.(trimmed)) {
|
|
124
125
|
this.render();
|
package/dist/types.d.ts
CHANGED
|
@@ -368,10 +368,14 @@ export interface AgentRunInput {
|
|
|
368
368
|
submittedAt?: number;
|
|
369
369
|
}
|
|
370
370
|
export interface AgentInputController {
|
|
371
|
+
/** Enqueue only while the current run can still accept boundary input. */
|
|
372
|
+
tryEnqueue?(content: string): AgentRunInput | undefined;
|
|
371
373
|
drainPendingInputs(): AgentRunInput[];
|
|
372
374
|
pendingInputCount(): number;
|
|
375
|
+
/** Atomically stop accepting input and return everything still pending. */
|
|
376
|
+
closePendingInputs?(): AgentRunInput[];
|
|
373
377
|
}
|
|
374
|
-
export type AgentInputRejectedReason = "no_continuation";
|
|
378
|
+
export type AgentInputRejectedReason = "no_continuation" | "turn_failed" | "turn_cancelled";
|
|
375
379
|
export type AgentEvent = {
|
|
376
380
|
type: "turn_start";
|
|
377
381
|
} | {
|
|
@@ -455,6 +459,12 @@ export type AgentEvent = {
|
|
|
455
459
|
id: string;
|
|
456
460
|
content: string;
|
|
457
461
|
target: "current_turn";
|
|
462
|
+
} | {
|
|
463
|
+
type: "input_queued";
|
|
464
|
+
id: string;
|
|
465
|
+
content: string;
|
|
466
|
+
turnId: string;
|
|
467
|
+
target: "next_turn";
|
|
458
468
|
} | {
|
|
459
469
|
type: "input_rejected";
|
|
460
470
|
id: string;
|
package/dist/update/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface InstallInfo {
|
|
|
22
22
|
isLocalCheckout: boolean;
|
|
23
23
|
installPath: string;
|
|
24
24
|
}
|
|
25
|
+
export declare function detectInstallFromPath(installPath: string): InstallInfo;
|
|
25
26
|
/**
|
|
26
27
|
* Figure out how this copy of Bubble was installed by inspecting the real path
|
|
27
28
|
* of the package directory (two levels up from this module: dist/update -> pkg).
|
package/dist/update/index.js
CHANGED
|
@@ -81,28 +81,15 @@ export function compareVersions(a, b) {
|
|
|
81
81
|
return -1;
|
|
82
82
|
return pa.pre > pb.pre ? 1 : -1;
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
* Figure out how this copy of Bubble was installed by inspecting the real path
|
|
86
|
-
* of the package directory (two levels up from this module: dist/update -> pkg).
|
|
87
|
-
*/
|
|
88
|
-
export function detectInstall() {
|
|
89
|
-
const rawRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
90
|
-
let installPath = rawRoot;
|
|
91
|
-
try {
|
|
92
|
-
installPath = realpathSync(rawRoot);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
// keep rawRoot
|
|
96
|
-
}
|
|
84
|
+
export function detectInstallFromPath(installPath) {
|
|
97
85
|
const lower = installPath.replace(/\\/g, "/").toLowerCase();
|
|
98
86
|
const isUnderNodeModules = lower.includes("/node_modules/");
|
|
99
87
|
// A dev/source checkout has src/ alongside dist/ and isn't under node_modules.
|
|
100
88
|
const isLocalCheckout = !isUnderNodeModules;
|
|
101
89
|
let manager = "unknown";
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
else if (lower.includes("/.bun/") || lower.includes("/bun/install/")) {
|
|
90
|
+
// Package-manager-specific node_modules layouts take precedence over a
|
|
91
|
+
// parent directory name (for example /opt/homebrew/lib/node_modules is npm).
|
|
92
|
+
if (lower.includes("/.bun/") || lower.includes("/bun/install/")) {
|
|
106
93
|
manager = "bun";
|
|
107
94
|
}
|
|
108
95
|
else if (lower.includes("/pnpm/") || lower.includes("/.pnpm/")) {
|
|
@@ -114,8 +101,26 @@ export function detectInstall() {
|
|
|
114
101
|
else if (isUnderNodeModules) {
|
|
115
102
|
manager = "npm";
|
|
116
103
|
}
|
|
104
|
+
else if (lower.includes("/cellar/") || lower.includes("/homebrew/")) {
|
|
105
|
+
manager = "homebrew";
|
|
106
|
+
}
|
|
117
107
|
return { manager, isGlobal: isUnderNodeModules, isLocalCheckout, installPath };
|
|
118
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Figure out how this copy of Bubble was installed by inspecting the real path
|
|
111
|
+
* of the package directory (two levels up from this module: dist/update -> pkg).
|
|
112
|
+
*/
|
|
113
|
+
export function detectInstall() {
|
|
114
|
+
const rawRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
115
|
+
let installPath = rawRoot;
|
|
116
|
+
try {
|
|
117
|
+
installPath = realpathSync(rawRoot);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// keep rawRoot
|
|
121
|
+
}
|
|
122
|
+
return detectInstallFromPath(installPath);
|
|
123
|
+
}
|
|
119
124
|
export function upgradeCommandFor(manager) {
|
|
120
125
|
const spec = `${PACKAGE_NAME}@latest`;
|
|
121
126
|
switch (manager) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebrain-ai/bubble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"description": "A terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"build": "npm run build:vendor && rm -rf dist && tsc && chmod +x dist/bin.js dist/main.js",
|
|
32
32
|
"build:vendor": "tsc -p packages/pi-tui/tsconfig.build.json",
|
|
33
33
|
"test:vendor": "cd packages/pi-tui && node --test --experimental-strip-types --no-warnings \"test/*.test.ts\"",
|
|
34
|
+
"test:package": "node scripts/verify-packed-install.mjs",
|
|
34
35
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
35
36
|
"dev": "tsc && node dist/main.js",
|
|
36
37
|
"refresh-models": "NODE_USE_ENV_PROXY=1 node scripts/generate-model-data.mjs",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"diff": "^9.0.0",
|
|
54
55
|
"get-east-asian-width": "1.6.0",
|
|
55
56
|
"js-tiktoken": "^1.0.21",
|
|
56
|
-
"marked": "
|
|
57
|
+
"marked": "18.0.5",
|
|
57
58
|
"openai": "^4.77.0",
|
|
58
59
|
"picomatch": "^4.0.4",
|
|
59
60
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
"node-pty@1.1.0": true
|
|
91
92
|
},
|
|
92
93
|
"bundleDependencies": [
|
|
93
|
-
"@bubblebrain-ai/pi-tui"
|
|
94
|
+
"@bubblebrain-ai/pi-tui",
|
|
95
|
+
"marked"
|
|
94
96
|
]
|
|
95
97
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|