@robota-sdk/agent-transport 3.0.0-beta.78 → 3.0.0-beta.81
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 +84 -26
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -0
- package/dist/browser/index.d.ts +520 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
- package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
- package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +766 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +765 -27
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/node.cjs +1 -0
- package/dist/node/node.d.cts +109 -0
- package/dist/node/node.d.cts.map +1 -0
- package/dist/node/node.d.ts +109 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +2 -0
- package/dist/node/node.js.map +1 -0
- package/package.json +68 -47
- package/src/__tests__/access-token-verifier.test.ts +672 -0
- package/src/__tests__/admission.test.ts +130 -0
- package/src/__tests__/channel-frames.test.ts +214 -0
- package/src/__tests__/driver-attribution-capability.test.ts +83 -0
- package/src/__tests__/handoff-chunking.test.ts +215 -0
- package/src/__tests__/handoff-manifest.test.ts +52 -0
- package/src/__tests__/message-decoders.test.ts +249 -0
- package/src/__tests__/outbound-delivery.test.ts +558 -0
- package/src/__tests__/peer-message-ledger.test.ts +200 -0
- package/src/__tests__/personal-usage-report.test.ts +193 -0
- package/src/__tests__/resume-buffer.test.ts +93 -0
- package/src/__tests__/session-event-delivery.test.ts +101 -0
- package/src/__tests__/session-message-handler-e5.test.ts +99 -0
- package/src/__tests__/session-message-handler.test.ts +590 -0
- package/src/__tests__/session-resume-bridge.test.ts +406 -0
- package/src/__tests__/submit-prompt-shape.test.ts +78 -0
- package/src/__tests__/usage-report-carrier.test.ts +98 -0
- package/src/__tests__/ws-broadcast-events.test.ts +84 -0
- package/src/background-messages.ts +169 -0
- package/src/channel-frames.ts +182 -0
- package/src/client.ts +9 -0
- package/src/handoff-chunking.ts +235 -0
- package/src/index.ts +112 -3
- package/src/message-decoders.ts +229 -0
- package/src/message-parser.ts +33 -0
- package/src/node/access-token-verifier.ts +363 -0
- package/src/node/admission.ts +153 -0
- package/src/node/handoff-manifest.ts +77 -0
- package/src/node/index.ts +10 -0
- package/src/outbound-delivery.ts +231 -0
- package/src/peer-message-ledger.ts +193 -0
- package/src/protocol-session.ts +28 -0
- package/src/resume-buffer.ts +123 -0
- package/src/session-events.ts +198 -0
- package/src/session-message-handler.ts +282 -0
- package/src/session-query-messages.ts +42 -0
- package/src/session-resume-bridge.ts +252 -0
- package/src/usage-messages.ts +195 -0
- package/src/wire-messages.ts +165 -0
- package/dist/node/headless/index.cjs +0 -1
- package/dist/node/headless/index.d.ts +0 -2
- package/dist/node/headless/index.js +0 -1
- package/dist/node/headless-DG_MEbf0.js +0 -19
- package/dist/node/headless-DG_MEbf0.js.map +0 -1
- package/dist/node/headless-xq77JBq0.cjs +0 -18
- package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
- package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
- package/dist/node/index-JIvzrtaA.d.ts +0 -52
- package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
- package/dist/node/index-JTD_xtEQ.d.ts +0 -96
- package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
- package/dist/node/index-y_HBOfzO.d.ts +0 -52
- package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
- package/dist/node/programmatic/index.cjs +0 -1
- package/dist/node/programmatic/index.d.ts +0 -2
- package/dist/node/programmatic/index.js +0 -1
- package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
- package/dist/node/programmatic-70dxIXtS.js +0 -2
- package/dist/node/programmatic-70dxIXtS.js.map +0 -1
- package/dist/node/testing/index.cjs +0 -1
- package/dist/node/testing/index.d.ts +0 -2
- package/dist/node/testing/index.js +0 -1
- package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
- package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
- package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
- package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
- package/src/headless/HeadlessInteractionChannel.ts +0 -129
- package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
- package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
- package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
- package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
- package/src/headless/__tests__/headless-runner.test.ts +0 -508
- package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
- package/src/headless/__tests__/headless-transport.test.ts +0 -267
- package/src/headless/cli-input.ts +0 -50
- package/src/headless/headless-runner.ts +0 -230
- package/src/headless/headless-stream-json.ts +0 -148
- package/src/headless/headless-transport.ts +0 -43
- package/src/headless/index.ts +0 -8
- package/src/headless/print-terminal.ts +0 -57
- package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
- package/src/programmatic/createProgrammaticAgent.ts +0 -71
- package/src/programmatic/index.ts +0 -5
- package/src/testing/index.ts +0 -10
- package/src/transport-registry.ts +0 -103
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent session-resume bridge for the reconnectable WebRTC path (REMOTE-013 Stage E4).
|
|
3
|
+
*
|
|
4
|
+
* Created ONCE per remote session and OUTLIVES individual data channels. It subscribes to the session's
|
|
5
|
+
* events exactly once, stamps every outbound `TServerMessage` with a **monotonic `seq` that is continuous
|
|
6
|
+
* across channel drops**, and retains the un-acked tail in a {@link ResumeBuffer}. The data channel is a
|
|
7
|
+
* SWAPPABLE sink:
|
|
8
|
+
*
|
|
9
|
+
* - `attach(sink)` — set the current sink (a new channel after a reconnect). Does NOT auto-replay; replay is
|
|
10
|
+
* driven by the client's `resume`.
|
|
11
|
+
* - `detach()` — clear the sink but KEEP buffering, so output produced while no channel is attached (the gap)
|
|
12
|
+
* is captured and replayable.
|
|
13
|
+
* - `onClientMessage(data)` — `resume{lastSeq}` replays the buffered tail (or `resume_gap` on overrun); `ack{seq}`
|
|
14
|
+
* frees the buffer; everything else routes to the session (reusing the shared `handleClientMessage`).
|
|
15
|
+
* - `dispose()` — unsubscribe (host reconnect-window ceiling / teardown).
|
|
16
|
+
*
|
|
17
|
+
* This is the fix for the placement defect: because the seq counter + buffer live here (not in the per-channel
|
|
18
|
+
* `createSessionMessageHandler`), `seq` does not reset when a reconnect builds a new channel, and gap output is not lost.
|
|
19
|
+
* The WS localhost path does NOT use this bridge — it keeps `createSessionMessageHandler` unchanged.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createOutboundDelivery } from './outbound-delivery.js';
|
|
23
|
+
import { ResumeBuffer, type IResumeBufferOptions } from './resume-buffer.js';
|
|
24
|
+
import { subscribeSessionEvents } from './session-events.js';
|
|
25
|
+
import { handleClientMessage, parseClientMessage } from './session-message-handler.js';
|
|
26
|
+
|
|
27
|
+
import type { TOutboundDeliver } from './outbound-delivery.js';
|
|
28
|
+
import type { IProtocolSession } from './protocol-session.js';
|
|
29
|
+
import type { IUsageQueryReporters } from './usage-messages.js';
|
|
30
|
+
import type { TSeqServerMessage, TServerMessage } from './wire-messages.js';
|
|
31
|
+
import type { TUsageSurface } from '@robota-sdk/agent-interface-analytics';
|
|
32
|
+
import type { TDriverId } from '@robota-sdk/agent-interface-session';
|
|
33
|
+
|
|
34
|
+
/** The current channel sink — receives a serialized JSON frame to put on the wire. */
|
|
35
|
+
export type TResumeSink = (data: string) => void;
|
|
36
|
+
|
|
37
|
+
/** Options for {@link SessionResumeBridge.attach}. */
|
|
38
|
+
export interface IAttachOptions {
|
|
39
|
+
/**
|
|
40
|
+
* REMOTE-013 E4: on a RECONNECT, HOLD live sink-forwarding until the client's `resume` has replayed the
|
|
41
|
+
* buffered tail — otherwise a live frame emitted between attach and the inbound `resume` would leapfrog the
|
|
42
|
+
* gap frames on the wire, and the client's seq-dedup would then discard the (older, lower-seq) gap. Every
|
|
43
|
+
* live event is already appended to the buffer, so holding loses nothing; `replay()` flushes in order and
|
|
44
|
+
* un-holds. First-pair attach does NOT hold (the client starts at seq 0, frames arrive in order).
|
|
45
|
+
*/
|
|
46
|
+
readonly awaitResume?: boolean;
|
|
47
|
+
/** Sink/carrier-owned failure lifecycle for this attachment only. */
|
|
48
|
+
readonly onDeliveryError: (error: Error, event: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* ARCH-030: this attachment's carrier backpressure reading, if it has one.
|
|
51
|
+
*
|
|
52
|
+
* Threaded through because the replay path is the burst this budget exists for — a reconnecting
|
|
53
|
+
* peer is handed the whole retained tail at once, and without a reading the boundary here would
|
|
54
|
+
* have no budget at all while the per-channel boundary did.
|
|
55
|
+
*/
|
|
56
|
+
readonly pendingBytes?: () => number | undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ISessionResumeBridgeOptions {
|
|
60
|
+
readonly session: IProtocolSession;
|
|
61
|
+
readonly buffer?: IResumeBufferOptions;
|
|
62
|
+
/** REMOTE-014 E5: the SERVER-ASSIGNED driver id for this surface, injected into inbound submit/command/prompt-response. */
|
|
63
|
+
readonly driverId?: TDriverId;
|
|
64
|
+
readonly surface?: TUsageSurface;
|
|
65
|
+
readonly personalUsageReporter?: IUsageQueryReporters['personalUsageReporter'];
|
|
66
|
+
readonly usageReporter?: IUsageQueryReporters['usageReporter'];
|
|
67
|
+
readonly storedSessionUsageReporter?: IUsageQueryReporters['storedSessionUsageReporter'];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class SessionResumeBridge {
|
|
71
|
+
private readonly session: IProtocolSession;
|
|
72
|
+
private readonly buffer: ResumeBuffer;
|
|
73
|
+
private driverId?: TDriverId;
|
|
74
|
+
private readonly reporters: IUsageQueryReporters;
|
|
75
|
+
private readonly surface?: TUsageSurface;
|
|
76
|
+
private readonly unsubscribe: () => void;
|
|
77
|
+
private onDeliveryError?: IAttachOptions['onDeliveryError'];
|
|
78
|
+
/**
|
|
79
|
+
* ARCH-030: the attachment's outbound boundary — and the only reference this class keeps to the current
|
|
80
|
+
* channel, which is why `dispose()` goes through `detach()` rather than clearing a separate field.
|
|
81
|
+
* Built per `attach` because a boundary LATCHES on its first failure, so a fresh one is exactly what
|
|
82
|
+
* un-latches the session for the next channel. It holds the only try/catch on the frame path.
|
|
83
|
+
*/
|
|
84
|
+
private outbound?: TOutboundDeliver;
|
|
85
|
+
/**
|
|
86
|
+
* The session-lifetime entry every frame takes — event fan-out AND every reply to an inbound frame —
|
|
87
|
+
* before it is seq-stamped and buffered by {@link emit}. Distinct from {@link outbound}, which is the
|
|
88
|
+
* per-attachment exit to the current channel; this one outlives channels because the subscription does.
|
|
89
|
+
*
|
|
90
|
+
* It guards the BUFFERING step, not a carrier, and it latches for the life of the session on purpose:
|
|
91
|
+
* the failure it is placed against is the buffer refusing a frame on capacity, and a resume buffer that
|
|
92
|
+
* cannot accept frames cannot honour a later `resume` either, so there is nothing a reconnect recovers.
|
|
93
|
+
* That is the opposite of {@link outbound}, where the next channel IS the recovery — the two scopes
|
|
94
|
+
* differ because the failures do. (A per-frame serialization failure would leave the buffer intact, but
|
|
95
|
+
* such a frame is undeliverable on every carrier, since each one stringifies it too; it is reported and
|
|
96
|
+
* the carrier torn down, never silently dropped.)
|
|
97
|
+
*/
|
|
98
|
+
private readonly emitBoundary: TOutboundDeliver;
|
|
99
|
+
private disposed = false;
|
|
100
|
+
/** REMOTE-013 E4: while true, live emits are buffered but NOT forwarded — released by `replay()` on reconnect. */
|
|
101
|
+
private holding = false;
|
|
102
|
+
|
|
103
|
+
public constructor(options: ISessionResumeBridgeOptions) {
|
|
104
|
+
this.session = options.session;
|
|
105
|
+
this.buffer = new ResumeBuffer(options.buffer);
|
|
106
|
+
this.driverId = options.driverId;
|
|
107
|
+
this.surface = options.surface;
|
|
108
|
+
this.reporters = {
|
|
109
|
+
personalUsageReporter: options.personalUsageReporter,
|
|
110
|
+
usageReporter: options.usageReporter,
|
|
111
|
+
storedSessionUsageReporter: options.storedSessionUsageReporter,
|
|
112
|
+
};
|
|
113
|
+
// ONE subscription for the whole session — outlives every channel. Every event → seq-stamped + buffered.
|
|
114
|
+
// CMD-004 Stage D: `ui_intent` is requester-routed against the LATE-BOUND driver id (bound by
|
|
115
|
+
// `setDriverId` after pairing) — routing happens BEFORE buffering, so a foreign surface's intent
|
|
116
|
+
// consumes no seq and can never leak through a later `resume` replay.
|
|
117
|
+
//
|
|
118
|
+
// ARCH-030: built through the factory, never cast — the brand is only worth having if this class
|
|
119
|
+
// does not spell its way around it. `emit` buffers and then hands the stamped frame to whatever
|
|
120
|
+
// boundary is currently ATTACHED, so it does not itself throw on a carrier failure; this boundary
|
|
121
|
+
// exists so that if it ever did, the failure would be reported rather than escaping the session's
|
|
122
|
+
// event listener.
|
|
123
|
+
this.emitBoundary = createOutboundDelivery(
|
|
124
|
+
(message) => this.emit(message),
|
|
125
|
+
(error, event) => this.reportDeliveryError(error, event),
|
|
126
|
+
);
|
|
127
|
+
this.unsubscribe = subscribeSessionEvents(this.session, this.emitBoundary, {
|
|
128
|
+
getSurfaceDriverId: () => this.driverId,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Set the current channel sink (on connect / reconnect). Live messages reach the client; replay is `resume`-driven. */
|
|
133
|
+
public attach(sink: TResumeSink, options: IAttachOptions): void {
|
|
134
|
+
if (this.disposed) return;
|
|
135
|
+
this.onDeliveryError = options.onDeliveryError;
|
|
136
|
+
this.outbound = createOutboundDelivery(
|
|
137
|
+
(message) => sink(JSON.stringify(message)),
|
|
138
|
+
(error, event) => this.reportDeliveryError(error, event),
|
|
139
|
+
options.pendingBytes,
|
|
140
|
+
);
|
|
141
|
+
// On a reconnect, hold live forwarding until `resume` flushes the buffered tail (avoids the live-vs-replay race).
|
|
142
|
+
this.holding = options.awaitResume === true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Clear the sink (channel drop). Buffering continues so gap output is retained for the next `resume`. */
|
|
146
|
+
public detach(): void {
|
|
147
|
+
this.outbound = undefined;
|
|
148
|
+
this.onDeliveryError = undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* REMOTE-014 E5: bind the SERVER-ASSIGNED driver id (the E3 `deviceId`) once the peer pairs — the bridge
|
|
153
|
+
* injects it into every subsequent inbound submit/command/prompt-response for co-drive attribution.
|
|
154
|
+
*/
|
|
155
|
+
public setDriverId(driverId: TDriverId): void {
|
|
156
|
+
this.driverId = driverId;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Route one inbound channel frame: `resume`/`ack` handled here; everything else → the session. */
|
|
160
|
+
public onClientMessage(data: string): void {
|
|
161
|
+
if (this.disposed) return;
|
|
162
|
+
const msg = parseClientMessage(data, this.emitBoundary);
|
|
163
|
+
if (!msg) return;
|
|
164
|
+
if (msg.type === 'resume') {
|
|
165
|
+
this.replay(msg.lastSeq);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (msg.type === 'ack') {
|
|
169
|
+
this.buffer.ackThrough(msg.seq);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Session control/query/background/prompt-response — responses funnel back through `emit` (seq'd + buffered).
|
|
173
|
+
// REMOTE-014 E5: inject the server-assigned driver id (submit/prompt-response attribution).
|
|
174
|
+
handleClientMessage(
|
|
175
|
+
this.session,
|
|
176
|
+
this.emitBoundary,
|
|
177
|
+
msg,
|
|
178
|
+
this.driverId,
|
|
179
|
+
this.reporters,
|
|
180
|
+
this.surface,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Unsubscribe from the session (host reconnect-window ceiling / teardown). Idempotent. */
|
|
185
|
+
public dispose(): void {
|
|
186
|
+
if (this.disposed) return;
|
|
187
|
+
this.disposed = true;
|
|
188
|
+
// Through `detach`, not a field assignment: the boundary's closure is what holds the channel, so
|
|
189
|
+
// clearing anything else released nothing and left teardown documented but not performed.
|
|
190
|
+
this.detach();
|
|
191
|
+
this.unsubscribe();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Diagnostics/tests: the last seq assigned. */
|
|
195
|
+
public get lastSeq(): number {
|
|
196
|
+
return this.buffer.lastSeq;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Stamp a seq, retain in the buffer, and forward to the current sink — UNLESS holding for a reconnect replay.
|
|
201
|
+
*
|
|
202
|
+
* ARCH-030: the `buffer.append` happens BEFORE the boundary, deliberately. The boundary latches on its
|
|
203
|
+
* first failure and drops everything after it, so a frame appended afterwards would be lost — appending
|
|
204
|
+
* first means a dropped frame is still in the un-acked tail and still replays on the next `resume`.
|
|
205
|
+
*/
|
|
206
|
+
private emit(message: TServerMessage): void {
|
|
207
|
+
if (this.disposed) return;
|
|
208
|
+
const seq = this.buffer.append(message);
|
|
209
|
+
if (this.holding) return; // buffered only; `replay()` will flush it in order and release the hold
|
|
210
|
+
this.deliverFrame({ ...message, seq } as TSeqServerMessage);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Replay the buffered tail after `lastSeq` (or `resume_gap` on overrun), then RELEASE the reconnect hold so
|
|
215
|
+
* subsequent live frames flow in order behind the flushed gap.
|
|
216
|
+
*
|
|
217
|
+
* A failing sink reports ONCE across the whole tail: the boundary latches on the first failure and every
|
|
218
|
+
* remaining frame is dropped silently. Those frames stay un-acked in the buffer and replay on the next
|
|
219
|
+
* attachment. Before ARCH-030 the single report was a side effect of `detach()` running mid-loop; now it
|
|
220
|
+
* is the boundary's stated contract.
|
|
221
|
+
*/
|
|
222
|
+
private replay(lastSeq: number): void {
|
|
223
|
+
const tail = this.buffer.tailAfter(lastSeq);
|
|
224
|
+
if (tail.kind === 'overrun') {
|
|
225
|
+
// `resume_gap` is neither stamped nor buffered: the client answers it with a full `get-messages`
|
|
226
|
+
// refresh, so there is nothing for it to resume from.
|
|
227
|
+
this.deliverFrame({ type: 'resume_gap' });
|
|
228
|
+
this.holding = false; // client will full-refresh via get-messages; let live frames flow
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
for (const frame of tail.frames) {
|
|
232
|
+
this.deliverFrame({ ...frame.message, seq: frame.seq } as TSeqServerMessage);
|
|
233
|
+
}
|
|
234
|
+
this.holding = false; // gap flushed in order — resume live forwarding
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The ONE outbound exit from this class. No frame reaches the sink except through the attachment's boundary. */
|
|
238
|
+
private deliverFrame(message: TServerMessage): void {
|
|
239
|
+
this.outbound?.(message);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Detach the failed sink but retain the frame, then notify its attachment owner. */
|
|
243
|
+
private reportDeliveryError(error: Error, event: string): void {
|
|
244
|
+
const onDeliveryError = this.onDeliveryError;
|
|
245
|
+
this.detach();
|
|
246
|
+
try {
|
|
247
|
+
onDeliveryError?.(error, event);
|
|
248
|
+
} catch {
|
|
249
|
+
// Carrier diagnostics cannot corrupt the retained buffer or the committed operation.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { MAX_INBOUND_FRAME_BYTES } from './message-decoders.js';
|
|
2
|
+
|
|
3
|
+
import type { TOutboundDeliver } from './outbound-delivery.js';
|
|
4
|
+
import type { IProtocolSession } from './protocol-session.js';
|
|
5
|
+
import type { TClientMessage } from './wire-messages.js';
|
|
6
|
+
import type {
|
|
7
|
+
IPersonalUsageReport,
|
|
8
|
+
IPersonalUsageRequest,
|
|
9
|
+
IUsageBySourceReport,
|
|
10
|
+
} from '@robota-sdk/agent-interface-analytics';
|
|
11
|
+
|
|
12
|
+
type TJsonValue =
|
|
13
|
+
string | number | boolean | null | readonly TJsonValue[] | { readonly [key: string]: TJsonValue };
|
|
14
|
+
|
|
15
|
+
export interface IUsageQueryReporters {
|
|
16
|
+
readonly personalUsageReporter:
|
|
17
|
+
| ((request: IPersonalUsageRequest) => IPersonalUsageReport | Promise<IPersonalUsageReport>)
|
|
18
|
+
| undefined;
|
|
19
|
+
readonly usageReporter:
|
|
20
|
+
| ((session: IProtocolSession) => IUsageBySourceReport | Promise<IUsageBySourceReport>)
|
|
21
|
+
| undefined;
|
|
22
|
+
readonly storedSessionUsageReporter:
|
|
23
|
+
((sessionId: string) => IUsageBySourceReport | Promise<IUsageBySourceReport>) | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TMalformedUsageRequest =
|
|
27
|
+
| { type: 'personal'; requestId: string }
|
|
28
|
+
| { type: 'stored-session'; requestId: string; sessionId: string };
|
|
29
|
+
|
|
30
|
+
function isJsonObject(value: TJsonValue): value is { readonly [key: string]: TJsonValue } {
|
|
31
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function optionalNonEmptyString(value: TJsonValue | undefined): string | undefined {
|
|
35
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function malformedUsageRequest(data: string): TMalformedUsageRequest | undefined {
|
|
39
|
+
if (new TextEncoder().encode(data).byteLength > MAX_INBOUND_FRAME_BYTES) return undefined;
|
|
40
|
+
try {
|
|
41
|
+
const value = JSON.parse(data) as TJsonValue;
|
|
42
|
+
if (!isJsonObject(value)) return undefined;
|
|
43
|
+
const requestId = optionalNonEmptyString(value['requestId']);
|
|
44
|
+
if (!requestId) return undefined;
|
|
45
|
+
if (value['type'] === 'get-personal-usage-report') return { type: 'personal', requestId };
|
|
46
|
+
const sessionId = optionalNonEmptyString(value['sessionId']);
|
|
47
|
+
return value['type'] === 'get-stored-session-usage-report' && sessionId
|
|
48
|
+
? { type: 'stored-session', requestId, sessionId }
|
|
49
|
+
: undefined;
|
|
50
|
+
} catch {
|
|
51
|
+
// allow-fallback: malformed JSON has no trustworthy request id to correlate, so the caller emits
|
|
52
|
+
// the protocol's uncorrelated decode error without echoing attacker-controlled content.
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function handleUsageQueryMessage(
|
|
58
|
+
session: IProtocolSession,
|
|
59
|
+
deliver: TOutboundDeliver,
|
|
60
|
+
msg: TClientMessage,
|
|
61
|
+
reporters: IUsageQueryReporters,
|
|
62
|
+
): boolean {
|
|
63
|
+
if (msg.type === 'get-personal-usage-report') {
|
|
64
|
+
handlePersonalUsageQuery(deliver, msg, reporters.personalUsageReporter);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (msg.type === 'get-stored-session-usage-report') {
|
|
68
|
+
handleStoredSessionUsageQuery(deliver, msg, reporters.storedSessionUsageReporter);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (msg.type === 'get-usage-report') {
|
|
72
|
+
handleCurrentUsageQuery(session, deliver, reporters.usageReporter);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleCurrentUsageQuery(
|
|
79
|
+
session: IProtocolSession,
|
|
80
|
+
deliver: TOutboundDeliver,
|
|
81
|
+
reporter: IUsageQueryReporters['usageReporter'],
|
|
82
|
+
): void {
|
|
83
|
+
if (!reporter) {
|
|
84
|
+
deliver({
|
|
85
|
+
type: 'protocol_error',
|
|
86
|
+
message: 'Per-session usage reporting is not available on this host.',
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
void Promise.resolve(reporter(session)).then(
|
|
92
|
+
(report) => deliver({ type: 'usage_report', report }),
|
|
93
|
+
(error) => deliver({ type: 'protocol_error', message: failureMessage(error) }),
|
|
94
|
+
);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
deliver({
|
|
97
|
+
type: 'protocol_error',
|
|
98
|
+
message: failureMessage(error instanceof Error ? error : String(error)),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleStoredSessionUsageQuery(
|
|
104
|
+
deliver: TOutboundDeliver,
|
|
105
|
+
msg: Extract<TClientMessage, { type: 'get-stored-session-usage-report' }>,
|
|
106
|
+
reporter: IUsageQueryReporters['storedSessionUsageReporter'],
|
|
107
|
+
): void {
|
|
108
|
+
if (!reporter) {
|
|
109
|
+
deliverStoredSessionUsageError(
|
|
110
|
+
deliver,
|
|
111
|
+
msg,
|
|
112
|
+
'not_available',
|
|
113
|
+
'Stored-session usage reporting is not available on this host.',
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
void Promise.resolve(reporter(msg.sessionId)).then(
|
|
119
|
+
(report) =>
|
|
120
|
+
deliver({
|
|
121
|
+
type: 'stored_session_usage_report',
|
|
122
|
+
requestId: msg.requestId,
|
|
123
|
+
sessionId: msg.sessionId,
|
|
124
|
+
report,
|
|
125
|
+
}),
|
|
126
|
+
(error) =>
|
|
127
|
+
deliverStoredSessionUsageError(deliver, msg, 'report_failed', failureMessage(error)),
|
|
128
|
+
);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
deliverStoredSessionUsageError(
|
|
131
|
+
deliver,
|
|
132
|
+
msg,
|
|
133
|
+
'report_failed',
|
|
134
|
+
failureMessage(error instanceof Error ? error : String(error)),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function deliverStoredSessionUsageError(
|
|
140
|
+
deliver: TOutboundDeliver,
|
|
141
|
+
msg: Extract<TClientMessage, { type: 'get-stored-session-usage-report' }>,
|
|
142
|
+
code: 'not_available' | 'report_failed',
|
|
143
|
+
message: string,
|
|
144
|
+
): void {
|
|
145
|
+
deliver({
|
|
146
|
+
type: 'stored_session_usage_report_error',
|
|
147
|
+
requestId: msg.requestId,
|
|
148
|
+
sessionId: msg.sessionId,
|
|
149
|
+
code,
|
|
150
|
+
message,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handlePersonalUsageQuery(
|
|
155
|
+
deliver: TOutboundDeliver,
|
|
156
|
+
msg: Extract<TClientMessage, { type: 'get-personal-usage-report' }>,
|
|
157
|
+
reporter: IUsageQueryReporters['personalUsageReporter'],
|
|
158
|
+
): void {
|
|
159
|
+
if (!reporter) {
|
|
160
|
+
deliverPersonalUsageError(
|
|
161
|
+
deliver,
|
|
162
|
+
msg.requestId,
|
|
163
|
+
'not_available',
|
|
164
|
+
'Personal usage reporting is not available on this host.',
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
void Promise.resolve(reporter({ period: msg.period, timezone: msg.timezone })).then(
|
|
170
|
+
(report) => deliver({ type: 'personal_usage_report', requestId: msg.requestId, report }),
|
|
171
|
+
(error) =>
|
|
172
|
+
deliverPersonalUsageError(deliver, msg.requestId, 'report_failed', failureMessage(error)),
|
|
173
|
+
);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
deliverPersonalUsageError(
|
|
176
|
+
deliver,
|
|
177
|
+
msg.requestId,
|
|
178
|
+
'report_failed',
|
|
179
|
+
failureMessage(error instanceof Error ? error : String(error)),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function deliverPersonalUsageError(
|
|
185
|
+
deliver: TOutboundDeliver,
|
|
186
|
+
requestId: string,
|
|
187
|
+
code: 'not_available' | 'report_failed',
|
|
188
|
+
message: string,
|
|
189
|
+
): void {
|
|
190
|
+
deliver({ type: 'personal_usage_report_error', requestId, code, message });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function failureMessage(error: Error | TJsonValue | undefined): string {
|
|
194
|
+
return error instanceof Error ? error.message : String(error);
|
|
195
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IPersonalUsageReport,
|
|
3
|
+
IPersonalUsageRequest,
|
|
4
|
+
IUsageBySourceReport,
|
|
5
|
+
} from '@robota-sdk/agent-interface-analytics';
|
|
6
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
7
|
+
import type {
|
|
8
|
+
IBackgroundJobGroupState,
|
|
9
|
+
IExecutionWorkspaceSnapshot,
|
|
10
|
+
TBackgroundJobGroupEvent,
|
|
11
|
+
} from '@robota-sdk/agent-interface-execution';
|
|
12
|
+
import type {
|
|
13
|
+
IBackgroundTaskInput,
|
|
14
|
+
IBackgroundTaskListFilter,
|
|
15
|
+
IBackgroundTaskLogCursor,
|
|
16
|
+
IBackgroundTaskLogPage,
|
|
17
|
+
IBackgroundTaskState,
|
|
18
|
+
TBackgroundTaskEvent,
|
|
19
|
+
} from '@robota-sdk/agent-interface-execution';
|
|
20
|
+
import type {
|
|
21
|
+
IAskRequestEvent,
|
|
22
|
+
IBranchEvent,
|
|
23
|
+
IExecutionResult,
|
|
24
|
+
IContextFileRefreshedEvent,
|
|
25
|
+
IPermissionRequestEvent,
|
|
26
|
+
IPromptResolvedEvent,
|
|
27
|
+
IPlanApprovalEvent,
|
|
28
|
+
ISessionRenamedEvent,
|
|
29
|
+
IToolState,
|
|
30
|
+
IUiIntentEvent,
|
|
31
|
+
TPermissionResultValue,
|
|
32
|
+
} from '@robota-sdk/agent-interface-session';
|
|
33
|
+
import type { ISessionConversationRead, TDriverId } from '@robota-sdk/agent-interface-session';
|
|
34
|
+
import type { TActionResponse } from '@robota-sdk/agent-interface-transport';
|
|
35
|
+
|
|
36
|
+
export type TBackgroundControlAction = 'cancel' | 'close' | 'send';
|
|
37
|
+
|
|
38
|
+
/** Inbound message from client to server. */
|
|
39
|
+
export type TClientMessage =
|
|
40
|
+
| { type: 'submit'; prompt: string }
|
|
41
|
+
| { type: 'command'; name: string; args?: string }
|
|
42
|
+
| { type: 'abort' }
|
|
43
|
+
| { type: 'cancel-queue' }
|
|
44
|
+
| { type: 'get-messages' }
|
|
45
|
+
| { type: 'get-context' }
|
|
46
|
+
// SELFHOST-004: request the assembled trace/cost read-model (spans + cost-by-source) for the run.
|
|
47
|
+
| { type: 'get-usage-report' }
|
|
48
|
+
| {
|
|
49
|
+
type: 'get-personal-usage-report';
|
|
50
|
+
requestId: string;
|
|
51
|
+
period: IPersonalUsageRequest['period'];
|
|
52
|
+
timezone: string;
|
|
53
|
+
}
|
|
54
|
+
| { type: 'get-stored-session-usage-report'; requestId: string; sessionId: string }
|
|
55
|
+
| { type: 'get-executing' }
|
|
56
|
+
| { type: 'get-pending' }
|
|
57
|
+
| { type: 'get-execution-workspace' }
|
|
58
|
+
| { type: 'get-background-tasks'; filter?: IBackgroundTaskListFilter }
|
|
59
|
+
| { type: 'get-background-task'; taskId: string }
|
|
60
|
+
| { type: 'get-background-job-groups' }
|
|
61
|
+
| { type: 'get-background-job-group'; groupId: string }
|
|
62
|
+
| { type: 'wait-background-job-group'; groupId: string }
|
|
63
|
+
| { type: 'cancel-background-task'; taskId: string; reason?: string }
|
|
64
|
+
| { type: 'close-background-task'; taskId: string }
|
|
65
|
+
| { type: 'send-background-task'; taskId: string; input: IBackgroundTaskInput }
|
|
66
|
+
| { type: 'read-background-task-log'; taskId: string; cursor?: IBackgroundTaskLogCursor }
|
|
67
|
+
// REMOTE-007: a driving client answers a pending permission/ask prompt by id (first answer wins).
|
|
68
|
+
| { type: 'permission-response'; id: string; result: TPermissionResultValue }
|
|
69
|
+
| { type: 'ask-response'; id: string; response: TActionResponse }
|
|
70
|
+
// REMOTE-013 E4 session-resume: `resume` asks the host to replay the tail after `lastSeq` (the last seq the
|
|
71
|
+
// client applied); `ack` lets the host free its un-acked buffer up to `seq`. Only meaningful post-E3-accept.
|
|
72
|
+
| { type: 'resume'; lastSeq: number }
|
|
73
|
+
| { type: 'ack'; seq: number };
|
|
74
|
+
|
|
75
|
+
/** Outbound message from server to client. */
|
|
76
|
+
export type TServerMessage =
|
|
77
|
+
// REMOTE-014 E5: turn-authored events optionally carry the ACTIVE turn's `driverId` (co-drive authorship,
|
|
78
|
+
// display-only). Stamped at `subscribeSessionEvents` from `getActiveDriverId()`; background/goal/memory/
|
|
79
|
+
// execution-workspace events are NEVER stamped (they are not authored by a driver turn).
|
|
80
|
+
| { type: 'text_delta'; delta: string; driverId?: TDriverId }
|
|
81
|
+
| { type: 'user_message'; content: string; driverId?: TDriverId }
|
|
82
|
+
| { type: 'tool_start'; state: IToolState; driverId?: TDriverId }
|
|
83
|
+
| { type: 'tool_end'; state: IToolState; driverId?: TDriverId }
|
|
84
|
+
| { type: 'thinking'; isThinking: boolean; driverId?: TDriverId }
|
|
85
|
+
| { type: 'complete'; result: IExecutionResult; driverId?: TDriverId }
|
|
86
|
+
| { type: 'interrupted'; result: IExecutionResult; driverId?: TDriverId }
|
|
87
|
+
| { type: 'error'; message: string; driverId?: TDriverId }
|
|
88
|
+
| {
|
|
89
|
+
type: 'command_result';
|
|
90
|
+
name: string;
|
|
91
|
+
message: string;
|
|
92
|
+
success: boolean;
|
|
93
|
+
data?: ICommandResult['data'];
|
|
94
|
+
}
|
|
95
|
+
| { type: 'messages'; messages: ReturnType<ISessionConversationRead['getMessages']> }
|
|
96
|
+
| { type: 'context'; state: ReturnType<ISessionConversationRead['getContextState']> }
|
|
97
|
+
// SELFHOST-004 (P5, TC-08): carry the assembled trace/cost read-model (per-op span timeline +
|
|
98
|
+
// cost-by-source) across the sidecar boundary — no existing variant carries per-op `durationMs` or
|
|
99
|
+
// per-source `costUsd`. The GUI renders it renderer-side.
|
|
100
|
+
| { type: 'usage_report'; report: IUsageBySourceReport }
|
|
101
|
+
| { type: 'personal_usage_report'; requestId: string; report: IPersonalUsageReport }
|
|
102
|
+
| {
|
|
103
|
+
type: 'personal_usage_report_error';
|
|
104
|
+
requestId: string;
|
|
105
|
+
code: 'not_available' | 'report_failed';
|
|
106
|
+
message: string;
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: 'stored_session_usage_report';
|
|
110
|
+
requestId: string;
|
|
111
|
+
sessionId: string;
|
|
112
|
+
report: IUsageBySourceReport;
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
type: 'stored_session_usage_report_error';
|
|
116
|
+
requestId: string;
|
|
117
|
+
sessionId: string;
|
|
118
|
+
code: 'not_available' | 'report_failed';
|
|
119
|
+
message: string;
|
|
120
|
+
}
|
|
121
|
+
| { type: 'executing'; executing: boolean }
|
|
122
|
+
| { type: 'pending'; pending: string | null }
|
|
123
|
+
| { type: 'execution_workspace_event'; snapshot: IExecutionWorkspaceSnapshot }
|
|
124
|
+
| { type: 'background_task_event'; event: TBackgroundTaskEvent }
|
|
125
|
+
| { type: 'background_job_group_event'; event: TBackgroundJobGroupEvent }
|
|
126
|
+
| { type: 'plan_event'; event: IPlanApprovalEvent }
|
|
127
|
+
| { type: 'context_file_refreshed'; event: IContextFileRefreshedEvent }
|
|
128
|
+
| { type: 'branch_event'; event: IBranchEvent }
|
|
129
|
+
| { type: 'background_tasks'; tasks: IBackgroundTaskState[] }
|
|
130
|
+
| { type: 'background_task'; taskId: string; task: IBackgroundTaskState | null }
|
|
131
|
+
| { type: 'background_job_groups'; groups: IBackgroundJobGroupState[] }
|
|
132
|
+
| { type: 'background_job_group'; groupId: string; group: IBackgroundJobGroupState | null }
|
|
133
|
+
| { type: 'background_task_log'; taskId: string; page: IBackgroundTaskLogPage }
|
|
134
|
+
// REMOTE-007: forward the session's transport-neutral prompt events so a remote surface can render +
|
|
135
|
+
// answer the SAME prompt (permission/ask). `prompt_resolved` dismisses it when another surface won.
|
|
136
|
+
| { type: 'permission_request'; event: IPermissionRequestEvent }
|
|
137
|
+
| { type: 'ask_request'; event: IAskRequestEvent }
|
|
138
|
+
| { type: 'prompt_resolved'; event: IPromptResolvedEvent }
|
|
139
|
+
// CMD-004 Phase 2: forward command-issued UI intents (same pattern as `ask_request`). The event is
|
|
140
|
+
// requester-routed — a client renders it only when `event.requesterDriverId` is its own driver id;
|
|
141
|
+
// an unsupported intent yields an explicit "not available on this surface" notice, never a silent drop.
|
|
142
|
+
| { type: 'ui_intent'; event: IUiIntentEvent }
|
|
143
|
+
// CMD-004 Phase 2 Stage E: BROADCAST session events — every attached surface receives them
|
|
144
|
+
// (co-driving titles/transcripts follow the host-executed rename/clear; never requester-filtered).
|
|
145
|
+
| { type: 'session_renamed'; event: ISessionRenamedEvent }
|
|
146
|
+
| { type: 'history_cleared' }
|
|
147
|
+
| {
|
|
148
|
+
type: 'background_task_control_result';
|
|
149
|
+
action: TBackgroundControlAction;
|
|
150
|
+
taskId: string;
|
|
151
|
+
success: boolean;
|
|
152
|
+
message?: string;
|
|
153
|
+
}
|
|
154
|
+
| { type: 'protocol_error'; message: string }
|
|
155
|
+
// REMOTE-013 E4: sent instead of a replay when the client's `lastSeq` predates the host's retained buffer
|
|
156
|
+
// (overrun) — the client must do a full `get-messages` refresh rather than accept a silent gap.
|
|
157
|
+
| { type: 'resume_gap' };
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* REMOTE-013 E4: a server message stamped with its monotonic session sequence number (added by the
|
|
161
|
+
* {@link SessionResumeBridge} on the reconnectable WebRTC path). Intersecting over the union distributes the
|
|
162
|
+
* `seq` field onto every variant. The WS localhost path never stamps it (a `type`-dispatching client ignores
|
|
163
|
+
* an absent/extra `seq`).
|
|
164
|
+
*/
|
|
165
|
+
export type TSeqServerMessage = TServerMessage & { seq: number };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../headless-xq77JBq0.cjs");exports.HeadlessInteractionChannel=e.t,exports.PrintTerminal=e.a,exports.createHeadlessRunner=e.r,exports.createHeadlessTransport=e.n,exports.promptInput=e.i;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as IHeadlessRunnerOptions, c as promptInput, i as createHeadlessTransport, l as PrintTerminal, n as IHeadlessInteractionChannelOptions, o as TOutputFormat, r as IHeadlessTransportOptions, s as createHeadlessRunner, t as HeadlessInteractionChannel } from "../index-JTD_xtEQ.js";
|
|
2
|
-
export { HeadlessInteractionChannel, type IHeadlessInteractionChannelOptions, type IHeadlessRunnerOptions, type IHeadlessTransportOptions, PrintTerminal, type TOutputFormat, createHeadlessRunner, createHeadlessTransport, promptInput };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,i as t,n,r,t as i}from"../headless-DG_MEbf0.js";export{i as HeadlessInteractionChannel,e as PrintTerminal,r as createHeadlessRunner,n as createHeadlessTransport,t as promptInput};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import*as e from"node:readline";import{randomUUID as t}from"node:crypto";import{execSync as n}from"node:child_process";import{InteractiveSession as r}from"@robota-sdk/agent-framework";var i=class{write(e){process.stdout.write(e)}writeLine(e){process.stdout.write(e+`
|
|
2
|
-
`)}writeMarkdown(e){process.stdout.write(e)}writeError(e){process.stderr.write(e+`
|
|
3
|
-
`)}prompt(t){return new Promise(n=>{let r=e.createInterface({input:process.stdin,output:process.stdout,terminal:!1,historySize:0});r.question(t,e=>{r.close(),n(e)})})}async select(e,t=0){for(let n=0;n<e.length;n++){let r=n===t?`>`:` `;process.stdout.write(` ${r} ${n+1}) ${e[n]}\n`)}let n=(await this.prompt(` Choose [1-${e.length}] (default: ${e[t]}): `)).trim().toLowerCase();if(n===``)return t;let r=parseInt(n,10);return!isNaN(r)&&r>=1&&r<=e.length?r-1:t}spinner(e){return{stop(){},update(){}}}};const a=(e,t=!1)=>new Promise((n,r)=>{process.stdout.write(e);let i=``,a=process.stdin,o=a.isRaw;if(!a.isTTY){r(Error(`Cannot prompt for input: stdin is not a TTY.
|
|
4
|
-
Set your API key via environment variable instead:
|
|
5
|
-
ANTHROPIC_API_KEY=<key> robota
|
|
6
|
-
OPENAI_API_KEY=<key> robota`));return}a.setRawMode(!0),a.resume(),a.setEncoding(`utf8`);let s=e=>{for(let r of e)if(r===`\r`||r===`
|
|
7
|
-
`){a.removeListener(`data`,s),a.setRawMode(o??!1),a.pause(),process.stdout.write(`
|
|
8
|
-
`),n(i.trim());return}else r===``||r===`\b`?i.length>0&&(i=i.slice(0,-1),process.stdout.write(`\b \b`)):r===``?(a.removeListener(`data`,s),a.setRawMode(o??!1),a.pause(),process.stdout.write(`
|
|
9
|
-
`),process.exit(0)):r.charCodeAt(0)>=32&&(i+=r,process.stdout.write(t?`*`:r))};a.on(`data`,s)});function o(e){let t=e.trimStart();if(!t.startsWith(`/`))return null;let[n=``,...r]=t.slice(1).split(/\s+/);return n.length===0?null:{name:n,args:r.join(` `)}}async function s(e,t){let n=o(t);if(!n)return{kind:`not-slash`};let r=await e.executeCommand(n.name,n.args);return r?r.effects?.some(e=>e.type===`session-execution-started`)?{kind:`session-execution`}:{kind:`command-result`,result:r}:{kind:`command-result`,result:{message:`Unknown command "/${n.name}".`,success:!1}}}function c(e,n,r){let i=JSON.stringify({type:`stream_event`,event:r,session_id:n(e),uuid:t()});process.stdout.write(i+`
|
|
10
|
-
`)}function l(e,t,n,r){let i=n=>c(e,t,n),a=e=>i({type:`content_block_delta`,delta:{type:`text_delta`,text:e}}),o=e=>i({type:`background_task_event`,background_task_event:e}),s=e=>i({type:`background_job_group_event`,background_job_group_event:e}),l=()=>u(e,{onTextDelta:a,onBackgroundTaskEvent:o,onBackgroundJobGroupEvent:s,onComplete:d,onInterrupted:f,onError:p}),d=i=>{l(),n(t(e),i.response,`success`),r(0)},f=i=>{l(),n(t(e),i.response,`success`),r(0)},p=i=>{l(),n(t(e),``,`error`,i),r(1)};return e.on(`text_delta`,a),e.on(`background_task_event`,o),e.on(`background_job_group_event`,s),e.on(`complete`,d),e.on(`interrupted`,f),e.on(`error`,p),l}function u(e,t){e.off(`text_delta`,t.onTextDelta),e.off(`background_task_event`,t.onBackgroundTaskEvent),e.off(`background_job_group_event`,t.onBackgroundJobGroupEvent),e.off(`complete`,t.onComplete),e.off(`interrupted`,t.onInterrupted),e.off(`error`,t.onError)}function d(e){let{session:t,outputFormat:n}=e;return{run:e=>n===`text`?g(t,e):n===`json`?_(t,e):v(t,e),runGoal:(e,r={})=>f(t,e,r,n)}}function f(e,t,n,r){return new Promise(i=>{let a=()=>{e.off(`complete`,o),e.off(`error`,s),e.off(`goal_event`,c)},o=e=>{e.response&&process.stdout.write(e.response+`
|
|
11
|
-
`)},s=t=>{a(),r===`text`?process.stderr.write(t.message+`
|
|
12
|
-
`):m(h(e),``,`error`,t),i(1)},c=t=>{if(t.type!==`goal_stopped`)return;a();let n=t.goal,o=n.stopReason===`satisfied`,s=o?`Goal satisfied after ${n.iterations} iteration(s).`:`Goal stopped: ${n.stopReason} (after ${n.iterations} iteration(s)).`;r===`text`?(o?process.stdout:process.stderr).write(s+`
|
|
13
|
-
`):m(h(e),s,o?`success`:`error`),i(o?0:2)};e.on(`complete`,o),e.on(`error`,s),e.on(`goal_event`,c),e.setGoal(t,n.maxIterations?{maxIterations:n.maxIterations}:{})})}function p(e){let t=e.message.toLowerCase();return t.includes(`api key`)||t.includes(`no provider`)||t.includes(`provider`)?`config_error`:t.includes(`tool`)||t.includes(`execution`)?`tool_error`:`api_error`}function m(e,t,n,r){let i={type:`result`,result:t,session_id:e,subtype:n};n===`error`&&r!==void 0&&(i.error_code=p(r));let a=JSON.stringify(i);process.stdout.write(a+`
|
|
14
|
-
`)}function h(e){try{return e.getSession().getSessionId()}catch{return``}}function g(e,t){return new Promise(n=>{let r=()=>{e.off(`complete`,i),e.off(`interrupted`,a),e.off(`error`,o)},i=e=>{r(),process.stdout.write(e.response+`
|
|
15
|
-
`),n(0)},a=e=>{r(),e.response&&process.stdout.write(e.response+`
|
|
16
|
-
`),n(0)},o=e=>{r(),process.stderr.write(e.message+`
|
|
17
|
-
`),n(1)};e.on(`complete`,i),e.on(`interrupted`,a),e.on(`error`,o),s(e,t).then(i=>{if(i.kind===`command-result`){r(),process.stdout.write(i.result.message+`
|
|
18
|
-
`),n(+!i.result.success);return}i.kind!==`session-execution`&&e.submit(t)})})}function _(e,t){return new Promise(n=>{let r=()=>{e.off(`complete`,i),e.off(`interrupted`,a),e.off(`error`,o)},i=t=>{r(),m(h(e),t.response,`success`),n(0)},a=t=>{r(),m(h(e),t.response,`success`),n(0)},o=t=>{r(),m(h(e),``,`error`,t),n(1)};e.on(`complete`,i),e.on(`interrupted`,a),e.on(`error`,o),s(e,t).then(i=>{if(i.kind===`command-result`){r(),m(h(e),i.result.message,i.result.success?`success`:`error`),n(+!i.result.success);return}i.kind!==`session-execution`&&e.submit(t)})})}function v(e,t){return new Promise(n=>{let r=l(e,h,m,n);s(e,t).then(i=>{if(i.kind===`command-result`){r(),m(h(e),i.result.message,i.result.success?`success`:`error`),n(+!i.result.success);return}i.kind!==`session-execution`&&e.submit(t)})})}function y(e){let t=null,n=0;return{name:`headless`,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);n=await d({session:t,outputFormat:e.outputFormat}).run(e.prompt)},async stop(){},getExitCode(){return n}}}var b=class{opts;exitCode=0;constructor(e){this.opts=e}async run(e){let t=this.createSession(),n=d({session:t,outputFormat:this.opts.outputFormat});this.exitCode=await n.run(e),await t.shutdown({reason:`prompt_input_exit`,message:`Headless transport complete`})}async runGoal(e,t={}){let n=this.createSession(),r=d({session:n,outputFormat:this.opts.outputFormat});this.exitCode=await r.runGoal(e,t),await n.shutdown({reason:`prompt_input_exit`,message:`Headless goal complete`})}createSession(){let e=this.opts.shellExec??(e=>n(e,{timeout:5e3,encoding:`utf-8`,stdio:`pipe`}).trimEnd());return new r({cwd:this.opts.cwd,provider:this.opts.provider,permissionMode:this.opts.permissionMode??`bypassPermissions`,maxTurns:this.opts.maxTurns,sessionStore:this.opts.sessionStore,resumeSessionId:this.opts.resumeSessionId,forkSession:this.opts.forkSession,sessionName:this.opts.sessionName,bare:this.opts.bare||void 0,allowedTools:this.opts.allowedTools,deniedTools:this.opts.deniedTools,appendSystemPrompt:this.opts.appendSystemPrompt,...this.opts.persona===void 0?{}:{persona:this.opts.persona},...this.opts.systemPrompt?{systemPrompt:this.opts.systemPrompt}:{},backgroundTaskRunners:this.opts.backgroundTaskRunners,subagentRunnerFactory:this.opts.subagentRunnerFactory,commandModules:this.opts.commandModules,commandHostAdapters:this.opts.commandHostAdapters,shellExec:e,agentName:this.opts.agentName,...this.opts.activePresetId===void 0?{}:{activePresetId:this.opts.activePresetId},...this.opts.enableParallelSubagents===void 0?{}:{enableParallelSubagents:this.opts.enableParallelSubagents},...this.opts.selfVerification===void 0?{}:{selfVerification:this.opts.selfVerification}})}getExitCode(){return this.exitCode}};export{i as a,a as i,y as n,d as r,b as t};
|
|
19
|
-
//# sourceMappingURL=headless-DG_MEbf0.js.map
|