@hachej/boring-agent 0.1.10 → 0.1.12
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/dist/front/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ReactNode, ComponentType, HTMLAttributes, ComponentProps, FormEvent, El
|
|
|
4
4
|
import * as ai from 'ai';
|
|
5
5
|
import { UIMessage, FileUIPart, ChatStatus } from 'ai';
|
|
6
6
|
import * as _ai_sdk_react from '@ai-sdk/react';
|
|
7
|
-
import { S as SendMessageInput, d as SessionSummary } from '../harness-
|
|
7
|
+
import { S as SendMessageInput, d as SessionSummary } from '../harness-DT3ZzdAN.js';
|
|
8
8
|
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, InputGroupButton, TooltipContent, InputGroupAddon, InputGroupTextarea } from '@hachej/boring-ui-kit';
|
|
9
9
|
import { Streamdown } from 'streamdown';
|
|
10
10
|
import { StickToBottom } from 'use-stick-to-bottom';
|
package/dist/front/index.js
CHANGED
|
@@ -344,32 +344,61 @@ function useAgentChat(opts) {
|
|
|
344
344
|
|
|
345
345
|
// src/front/splitFollowUp.ts
|
|
346
346
|
function splitFollowUp(msgs, consumed, genId) {
|
|
347
|
-
|
|
347
|
+
let targetIdx = msgs.findIndex(
|
|
348
348
|
(m) => m.role === "assistant" && m.parts?.some((p) => {
|
|
349
349
|
const part = p;
|
|
350
350
|
return part.type === "data-followup-consumed" || part.id?.startsWith("turn-");
|
|
351
351
|
})
|
|
352
352
|
);
|
|
353
|
+
if (targetIdx < 0) {
|
|
354
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
355
|
+
if (msgs[i]?.role === "assistant") {
|
|
356
|
+
targetIdx = i;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const displayTextFromMarker = msgs.flatMap((m) => m.parts ?? []).map((p) => p).find((p) => p.type === "data-followup-consumed" && typeof p.data?.text === "string")?.data?.text;
|
|
353
362
|
const userMsg = {
|
|
354
363
|
id: genId(),
|
|
355
364
|
role: "user",
|
|
356
|
-
parts: [...consumed.files, { type: "text", text: consumed.text }]
|
|
365
|
+
parts: [...consumed.files, { type: "text", text: displayTextFromMarker ?? consumed.text }]
|
|
357
366
|
};
|
|
358
367
|
if (targetIdx < 0) {
|
|
359
368
|
return [...msgs, userMsg];
|
|
360
369
|
}
|
|
361
370
|
const target = msgs[targetIdx];
|
|
362
|
-
const
|
|
371
|
+
const parts = target.parts ?? [];
|
|
372
|
+
let markerIdx = parts.findIndex((p) => {
|
|
363
373
|
const part = p;
|
|
364
374
|
return part.type === "data-followup-consumed" || part.id?.startsWith("turn-");
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
375
|
+
});
|
|
376
|
+
let markerIsPart = markerIdx >= 0 && parts[markerIdx].type === "data-followup-consumed";
|
|
377
|
+
if (markerIdx < 0) {
|
|
378
|
+
const textPartIndexes = parts.map((p, index) => p.type === "text" ? index : -1).filter((index) => index >= 0);
|
|
379
|
+
if (textPartIndexes.length > 1) {
|
|
380
|
+
markerIdx = textPartIndexes[textPartIndexes.length - 1];
|
|
381
|
+
markerIsPart = false;
|
|
382
|
+
} else {
|
|
383
|
+
return [...msgs.slice(0, targetIdx), userMsg, target, ...msgs.slice(targetIdx + 1)];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const turn1Parts = markerIdx >= 0 ? parts.slice(0, markerIdx) : parts;
|
|
387
|
+
const turn2Parts = markerIdx >= 0 ? parts.slice(markerIsPart ? markerIdx + 1 : markerIdx) : [];
|
|
369
388
|
const asst1 = { ...target, parts: turn1Parts };
|
|
370
389
|
const asst2 = { ...target, id: genId(), parts: turn2Parts };
|
|
371
390
|
return [...msgs.slice(0, targetIdx), asst1, userMsg, asst2, ...msgs.slice(targetIdx + 1)];
|
|
372
391
|
}
|
|
392
|
+
function splitFollowUpForDisplay(msgs, consumed, pending, ids) {
|
|
393
|
+
const draft = consumed ?? pending;
|
|
394
|
+
if (!draft || !ids) return msgs;
|
|
395
|
+
const hasFollowUpBoundary = msgs.some(
|
|
396
|
+
(m) => m.role === "assistant" && (m.parts?.some((p) => p.id?.startsWith("turn-") || p.type === "data-followup-consumed") || (m.parts?.filter((p) => p.type === "text").length ?? 0) > 1)
|
|
397
|
+
);
|
|
398
|
+
if (!hasFollowUpBoundary) return msgs;
|
|
399
|
+
let n = 0;
|
|
400
|
+
return splitFollowUp(msgs, draft, () => n++ === 0 ? ids.userId : ids.assistantId);
|
|
401
|
+
}
|
|
373
402
|
|
|
374
403
|
// src/front/DebugDrawer.tsx
|
|
375
404
|
import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
@@ -3778,6 +3807,8 @@ function ChatPanel(props) {
|
|
|
3778
3807
|
const [debugWidth, setDebugWidth] = useState13(440);
|
|
3779
3808
|
const [pendingMessage, setPendingMessage] = useState13(null);
|
|
3780
3809
|
const pendingMessageRef = useRef10(null);
|
|
3810
|
+
const followUpPostedRef = useRef10(false);
|
|
3811
|
+
const followUpPostTimerRef = useRef10(null);
|
|
3781
3812
|
const consumedFollowUpRef = useRef10(null);
|
|
3782
3813
|
const followUpSplitIdsRef = useRef10(null);
|
|
3783
3814
|
const {
|
|
@@ -3793,7 +3824,8 @@ function ChatPanel(props) {
|
|
|
3793
3824
|
onData: (part) => {
|
|
3794
3825
|
if (part?.type === "data-followup-consumed") {
|
|
3795
3826
|
const pending = pendingMessageRef.current;
|
|
3796
|
-
|
|
3827
|
+
const serverText = part?.data?.text;
|
|
3828
|
+
consumedFollowUpRef.current = pending ? { text: pending.text, files: pending.files } : typeof serverText === "string" ? { text: serverText, files: [] } : null;
|
|
3797
3829
|
followUpSplitIdsRef.current = {
|
|
3798
3830
|
userId: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-followup-user`,
|
|
3799
3831
|
assistantId: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-followup-assistant`
|
|
@@ -3923,17 +3955,35 @@ function ChatPanel(props) {
|
|
|
3923
3955
|
return () => globalThis.removeEventListener?.("boring:model-change", onChange);
|
|
3924
3956
|
}, []);
|
|
3925
3957
|
const isStreaming = status === "submitted" || status === "streaming";
|
|
3926
|
-
const displayMessages = useMemo10(
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
)
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3958
|
+
const displayMessages = useMemo10(
|
|
3959
|
+
() => splitFollowUpForDisplay(
|
|
3960
|
+
messages,
|
|
3961
|
+
consumedFollowUpRef.current,
|
|
3962
|
+
pendingMessageRef.current,
|
|
3963
|
+
followUpSplitIdsRef.current
|
|
3964
|
+
),
|
|
3965
|
+
[messages]
|
|
3966
|
+
);
|
|
3967
|
+
const postPendingFollowUp = useCallback12((pending) => {
|
|
3968
|
+
if (followUpPostedRef.current) return;
|
|
3969
|
+
followUpPostedRef.current = true;
|
|
3970
|
+
fetch(`/api/v1/agent/chat/${encodeURIComponent(sessionId)}/followup`, {
|
|
3971
|
+
method: "POST",
|
|
3972
|
+
headers: {
|
|
3973
|
+
"Content-Type": "application/json",
|
|
3974
|
+
...requestHeaders
|
|
3975
|
+
},
|
|
3976
|
+
body: JSON.stringify({ message: pending.serverMessage, displayText: pending.text, attachments: pending.attachments })
|
|
3977
|
+
}).catch(() => {
|
|
3978
|
+
followUpPostedRef.current = false;
|
|
3979
|
+
});
|
|
3980
|
+
}, [sessionId, requestHeaders]);
|
|
3981
|
+
useEffect11(() => {
|
|
3982
|
+
if (status !== "streaming") return;
|
|
3983
|
+
const pending = pendingMessageRef.current;
|
|
3984
|
+
if (!pending) return;
|
|
3985
|
+
postPendingFollowUp(pending);
|
|
3986
|
+
}, [status, postPendingFollowUp]);
|
|
3937
3987
|
const prevStatusForQueue = useRef10(status);
|
|
3938
3988
|
useEffect11(() => {
|
|
3939
3989
|
const prev = prevStatusForQueue.current;
|
|
@@ -3946,6 +3996,9 @@ function ChatPanel(props) {
|
|
|
3946
3996
|
consumedFollowUpRef.current = null;
|
|
3947
3997
|
followUpSplitIdsRef.current = null;
|
|
3948
3998
|
pendingMessageRef.current = null;
|
|
3999
|
+
followUpPostedRef.current = false;
|
|
4000
|
+
if (followUpPostTimerRef.current) clearTimeout(followUpPostTimerRef.current);
|
|
4001
|
+
followUpPostTimerRef.current = null;
|
|
3949
4002
|
setPendingMessage(null);
|
|
3950
4003
|
let n = 0;
|
|
3951
4004
|
const genId = () => ids ? n++ === 0 ? ids.userId : ids.assistantId : globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
@@ -3966,6 +4019,9 @@ function ChatPanel(props) {
|
|
|
3966
4019
|
consumedFollowUpRef.current = null;
|
|
3967
4020
|
followUpSplitIdsRef.current = null;
|
|
3968
4021
|
pendingMessageRef.current = null;
|
|
4022
|
+
followUpPostedRef.current = false;
|
|
4023
|
+
if (followUpPostTimerRef.current) clearTimeout(followUpPostTimerRef.current);
|
|
4024
|
+
followUpPostTimerRef.current = null;
|
|
3969
4025
|
setPendingMessage(null);
|
|
3970
4026
|
fetch(`/api/v1/agent/chat/${encodeURIComponent(sessionId)}/followup`, {
|
|
3971
4027
|
method: "DELETE",
|
|
@@ -4155,16 +4211,17 @@ ${content}
|
|
|
4155
4211
|
attachments: resolvedAttachments
|
|
4156
4212
|
};
|
|
4157
4213
|
pendingMessageRef.current = nextPending;
|
|
4214
|
+
followUpPostedRef.current = false;
|
|
4158
4215
|
setPendingMessage(nextPending);
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
}
|
|
4216
|
+
if (status === "streaming") {
|
|
4217
|
+
postPendingFollowUp(nextPending);
|
|
4218
|
+
} else {
|
|
4219
|
+
if (followUpPostTimerRef.current) clearTimeout(followUpPostTimerRef.current);
|
|
4220
|
+
followUpPostTimerRef.current = setTimeout(() => {
|
|
4221
|
+
const pending = pendingMessageRef.current;
|
|
4222
|
+
if (pending) postPendingFollowUp(pending);
|
|
4223
|
+
}, 1e3);
|
|
4224
|
+
}
|
|
4168
4225
|
}
|
|
4169
4226
|
return;
|
|
4170
4227
|
}
|
|
@@ -51,7 +51,7 @@ interface AgentHarness {
|
|
|
51
51
|
* A `data-followup-consumed` chunk is emitted before the follow-up turn so
|
|
52
52
|
* the client can clear its pending-message bubble immediately.
|
|
53
53
|
*/
|
|
54
|
-
followUp?(sessionId: string, text: string, attachments?: MessageAttachment[]): void
|
|
54
|
+
followUp?(sessionId: string, text: string, attachments?: MessageAttachment[], displayText?: string): void | Promise<void>;
|
|
55
55
|
/**
|
|
56
56
|
* Discard any queued follow-up for this session (called by the Stop button).
|
|
57
57
|
*/
|
package/dist/server/index.js
CHANGED
|
@@ -2242,13 +2242,25 @@ function timeoutResult(durationMs) {
|
|
|
2242
2242
|
stderrEncoding: "utf-8"
|
|
2243
2243
|
};
|
|
2244
2244
|
}
|
|
2245
|
+
function toRemotePath(value) {
|
|
2246
|
+
if (value === VERCEL_SANDBOX_WORKSPACE_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
|
|
2247
|
+
if (value.startsWith(`${VERCEL_SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
2248
|
+
return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_WORKSPACE_ROOT.length)}`;
|
|
2249
|
+
}
|
|
2250
|
+
return value;
|
|
2251
|
+
}
|
|
2245
2252
|
function toRemoteCwd(cwd) {
|
|
2246
2253
|
if (!cwd) return cwd;
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2254
|
+
return toRemotePath(cwd);
|
|
2255
|
+
}
|
|
2256
|
+
function toRemoteCommand(command) {
|
|
2257
|
+
return command.replaceAll(VERCEL_SANDBOX_WORKSPACE_ROOT, VERCEL_SANDBOX_REMOTE_ROOT);
|
|
2258
|
+
}
|
|
2259
|
+
function toRemoteEnv(env) {
|
|
2260
|
+
if (!env) return void 0;
|
|
2261
|
+
return Object.fromEntries(
|
|
2262
|
+
Object.entries(env).map(([key, value]) => [key, toRemotePath(value)])
|
|
2263
|
+
);
|
|
2252
2264
|
}
|
|
2253
2265
|
function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
2254
2266
|
return {
|
|
@@ -2294,9 +2306,9 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
2294
2306
|
}
|
|
2295
2307
|
const result = await sandbox.runCommand({
|
|
2296
2308
|
cmd: "sh",
|
|
2297
|
-
args: ["-c", cmd],
|
|
2309
|
+
args: ["-c", toRemoteCommand(cmd)],
|
|
2298
2310
|
cwd: toRemoteCwd(opts?.cwd),
|
|
2299
|
-
env: opts?.env,
|
|
2311
|
+
env: toRemoteEnv(opts?.env),
|
|
2300
2312
|
signal: controller.signal,
|
|
2301
2313
|
stdout: createStreamWritable(stdoutCollector, captureState, opts?.onStdout),
|
|
2302
2314
|
stderr: createStreamWritable(stderrCollector, captureState, opts?.onStderr)
|
|
@@ -3689,6 +3701,16 @@ function mergePiPackageSources(base = [], additional = []) {
|
|
|
3689
3701
|
}
|
|
3690
3702
|
|
|
3691
3703
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
3704
|
+
function extractUserMessageText(message) {
|
|
3705
|
+
const record = message;
|
|
3706
|
+
if (record?.role !== "user") return "";
|
|
3707
|
+
if (typeof record.content === "string") return record.content;
|
|
3708
|
+
if (!Array.isArray(record.content)) return "";
|
|
3709
|
+
return record.content.map((part) => {
|
|
3710
|
+
const p = part;
|
|
3711
|
+
return p.type === "text" && typeof p.text === "string" ? p.text : "";
|
|
3712
|
+
}).join("");
|
|
3713
|
+
}
|
|
3692
3714
|
function extractAssistantMessageText(message) {
|
|
3693
3715
|
const record = message;
|
|
3694
3716
|
const role = typeof record?.role === "string" ? record.role : void 0;
|
|
@@ -3874,16 +3896,18 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3874
3896
|
await originalDelete(ctx, sessionId);
|
|
3875
3897
|
disposePiSession(sessionId);
|
|
3876
3898
|
};
|
|
3877
|
-
const
|
|
3899
|
+
const nativeFollowUpPending = /* @__PURE__ */ new Set();
|
|
3878
3900
|
return {
|
|
3879
3901
|
id: "pi-coding-agent",
|
|
3880
3902
|
placement: "server",
|
|
3881
3903
|
sessions: sessionStore,
|
|
3882
|
-
followUp(sessionId, text,
|
|
3883
|
-
|
|
3904
|
+
async followUp(sessionId, text, _attachments, _displayText = text) {
|
|
3905
|
+
const handle = piSessions.get(sessionId);
|
|
3906
|
+
if (!handle) return;
|
|
3907
|
+
nativeFollowUpPending.add(sessionId);
|
|
3908
|
+
await handle.piSession.followUp(text);
|
|
3884
3909
|
},
|
|
3885
|
-
clearFollowUp(
|
|
3886
|
-
harnessFollowUpQueues.delete(sessionId);
|
|
3910
|
+
clearFollowUp(_sessionId) {
|
|
3887
3911
|
},
|
|
3888
3912
|
/**
|
|
3889
3913
|
* Pi exposes the resolved system prompt as a getter on AgentSession.
|
|
@@ -3900,7 +3924,6 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3900
3924
|
input,
|
|
3901
3925
|
ctx
|
|
3902
3926
|
);
|
|
3903
|
-
harnessFollowUpQueues.delete(input.sessionId);
|
|
3904
3927
|
const chunks = [];
|
|
3905
3928
|
let done = false;
|
|
3906
3929
|
let streamError = null;
|
|
@@ -3960,7 +3983,6 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3960
3983
|
return chunk2;
|
|
3961
3984
|
});
|
|
3962
3985
|
}
|
|
3963
|
-
let pendingFollowUp;
|
|
3964
3986
|
let promptSettled = false;
|
|
3965
3987
|
let promptPromise = Promise.resolve();
|
|
3966
3988
|
const unsubscribe = piSession.subscribe((event) => {
|
|
@@ -3982,7 +4004,19 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3982
4004
|
activeTools.delete(event.toolCallId);
|
|
3983
4005
|
if (activeTools.size === 0) stopHeartbeat();
|
|
3984
4006
|
}
|
|
3985
|
-
let converted
|
|
4007
|
+
let converted;
|
|
4008
|
+
if (event.type === "message_start" && event.message?.role === "user") {
|
|
4009
|
+
const text = extractUserMessageText(event.message);
|
|
4010
|
+
converted = text && text !== input.message ? [{ type: "data-followup-consumed", data: { text } }] : [];
|
|
4011
|
+
if (text && text !== input.message) {
|
|
4012
|
+
inlineTurnIndex += 1;
|
|
4013
|
+
nativeFollowUpPending.delete(input.sessionId);
|
|
4014
|
+
}
|
|
4015
|
+
} else if (event.type === "message_end" && event.message?.role === "user") {
|
|
4016
|
+
converted = [];
|
|
4017
|
+
} else {
|
|
4018
|
+
converted = namespaceInlinePartIds(dedupStartChunks(piEventToChunks(event)));
|
|
4019
|
+
}
|
|
3986
4020
|
if (event.type === "message_update") {
|
|
3987
4021
|
const ame = event.assistantMessageEvent;
|
|
3988
4022
|
if (ame.type === "text_end" && typeof ame.content === "string" && ame.content.length > 0 && !textDeltaSeen.has(ame.contentIndex)) {
|
|
@@ -4042,16 +4076,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4042
4076
|
assistantText += text;
|
|
4043
4077
|
}
|
|
4044
4078
|
}
|
|
4045
|
-
|
|
4046
|
-
if (followUp !== void 0 && !ctx.abortSignal.aborted) {
|
|
4047
|
-
harnessFollowUpQueues.delete(input.sessionId);
|
|
4048
|
-
chunks.push({ type: "data-followup-consumed" });
|
|
4049
|
-
inlineTurnIndex += 1;
|
|
4050
|
-
sawTextChunk = false;
|
|
4051
|
-
textStartSeen.clear();
|
|
4052
|
-
textDeltaSeen.clear();
|
|
4053
|
-
assistantText = "";
|
|
4054
|
-
pendingFollowUp = followUp;
|
|
4079
|
+
if (nativeFollowUpPending.has(input.sessionId) && !ctx.abortSignal.aborted) {
|
|
4055
4080
|
} else {
|
|
4056
4081
|
done = true;
|
|
4057
4082
|
stopHeartbeat();
|
|
@@ -4107,11 +4132,6 @@ ${attachmentNotes.join("\n")}` : message,
|
|
|
4107
4132
|
const prepared = await prepareTurn(message, attachments);
|
|
4108
4133
|
return piSession.prompt(prepared.message, prepared.promptOpts).then(() => {
|
|
4109
4134
|
promptSettled = true;
|
|
4110
|
-
if (pendingFollowUp !== void 0 && !ctx.abortSignal.aborted) {
|
|
4111
|
-
const next = pendingFollowUp;
|
|
4112
|
-
pendingFollowUp = void 0;
|
|
4113
|
-
promptPromise = startTurn(next.message, next.attachments);
|
|
4114
|
-
}
|
|
4115
4135
|
}).catch((err) => {
|
|
4116
4136
|
promptSettled = true;
|
|
4117
4137
|
streamError = err;
|
|
@@ -4535,12 +4555,25 @@ function boundFs(workspaceRoot) {
|
|
|
4535
4555
|
|
|
4536
4556
|
// src/server/tools/operations/vercel.ts
|
|
4537
4557
|
import { relative as relative5 } from "path";
|
|
4558
|
+
function rootAliases(workspace) {
|
|
4559
|
+
const aliases = [workspace.root];
|
|
4560
|
+
if (workspace.root === VERCEL_SANDBOX_WORKSPACE_ROOT) aliases.push(VERCEL_SANDBOX_REMOTE_ROOT);
|
|
4561
|
+
if (workspace.root === VERCEL_SANDBOX_REMOTE_ROOT) aliases.push(VERCEL_SANDBOX_WORKSPACE_ROOT);
|
|
4562
|
+
return aliases;
|
|
4563
|
+
}
|
|
4538
4564
|
function toRelPath(workspace, absolutePath) {
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4541
|
-
|
|
4565
|
+
for (const root of rootAliases(workspace)) {
|
|
4566
|
+
const rel = relative5(root, absolutePath);
|
|
4567
|
+
if (!rel.startsWith("..") && !rel.startsWith("/")) return rel;
|
|
4568
|
+
}
|
|
4569
|
+
const skillMarker = "/.agents/skills/";
|
|
4570
|
+
const skillIndex = absolutePath.indexOf(skillMarker);
|
|
4571
|
+
if (skillIndex >= 0) {
|
|
4572
|
+
return `.agents/skills/${absolutePath.slice(skillIndex + skillMarker.length)}`;
|
|
4542
4573
|
}
|
|
4543
|
-
|
|
4574
|
+
throw new Error(
|
|
4575
|
+
`path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${rootAliases(workspace).join(" / ")}`
|
|
4576
|
+
);
|
|
4544
4577
|
}
|
|
4545
4578
|
function vercelBashOps(sandbox) {
|
|
4546
4579
|
return {
|
|
@@ -4599,6 +4632,39 @@ function vercelEditOps(workspace) {
|
|
|
4599
4632
|
}
|
|
4600
4633
|
};
|
|
4601
4634
|
}
|
|
4635
|
+
function toRemotePath2(value) {
|
|
4636
|
+
if (value === VERCEL_SANDBOX_WORKSPACE_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
|
|
4637
|
+
if (value.startsWith(`${VERCEL_SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
4638
|
+
return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_WORKSPACE_ROOT.length)}`;
|
|
4639
|
+
}
|
|
4640
|
+
return value;
|
|
4641
|
+
}
|
|
4642
|
+
function findPredicate(pattern) {
|
|
4643
|
+
const isPathShaped = pattern.includes("/") || pattern.includes("**");
|
|
4644
|
+
if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
|
|
4645
|
+
let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
|
|
4646
|
+
if (!translated.startsWith("*")) translated = `*${translated}`;
|
|
4647
|
+
return `-path ${shellEscape(translated)}`;
|
|
4648
|
+
}
|
|
4649
|
+
function findIgnoreArgs(cwd, ignore) {
|
|
4650
|
+
return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
|
|
4651
|
+
}
|
|
4652
|
+
function fallbackFindCommand(pattern, cwd, options) {
|
|
4653
|
+
return [
|
|
4654
|
+
"find",
|
|
4655
|
+
shellEscape(cwd),
|
|
4656
|
+
"-maxdepth 20",
|
|
4657
|
+
"-type f",
|
|
4658
|
+
findIgnoreArgs(cwd, options.ignore),
|
|
4659
|
+
findPredicate(pattern),
|
|
4660
|
+
`| head -n ${Math.max(1, Math.trunc(options.limit))}`
|
|
4661
|
+
].filter(Boolean).join(" ");
|
|
4662
|
+
}
|
|
4663
|
+
function isFdMissing(result) {
|
|
4664
|
+
if (result.exitCode !== 127) return false;
|
|
4665
|
+
const stderr = Buffer.from(result.stderr).toString("utf-8");
|
|
4666
|
+
return /fd: not found|fd: command not found|not found/i.test(stderr);
|
|
4667
|
+
}
|
|
4602
4668
|
function vercelFindOps(sandbox, workspace) {
|
|
4603
4669
|
return {
|
|
4604
4670
|
async exists(absolutePath) {
|
|
@@ -4617,18 +4683,25 @@ function vercelFindOps(sandbox, workspace) {
|
|
|
4617
4683
|
return result.exitCode === 0;
|
|
4618
4684
|
},
|
|
4619
4685
|
async glob(pattern, cwd, options) {
|
|
4686
|
+
const remoteCwd = toRemotePath2(cwd);
|
|
4620
4687
|
const args = ["fd", "--glob", "--no-require-git", "--max-results", String(options.limit)];
|
|
4621
4688
|
for (const ig of options.ignore) {
|
|
4622
4689
|
args.push("--exclude", ig);
|
|
4623
4690
|
}
|
|
4624
|
-
args.push(pattern,
|
|
4625
|
-
|
|
4691
|
+
args.push(pattern, remoteCwd);
|
|
4692
|
+
let result = await sandbox.exec(args.map(shellEscape).join(" "), {
|
|
4626
4693
|
timeoutMs: 3e4,
|
|
4627
4694
|
maxOutputBytes: 1048576
|
|
4628
4695
|
});
|
|
4696
|
+
if (isFdMissing(result)) {
|
|
4697
|
+
result = await sandbox.exec(fallbackFindCommand(pattern, remoteCwd, options), {
|
|
4698
|
+
timeoutMs: 3e4,
|
|
4699
|
+
maxOutputBytes: 1048576
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4629
4702
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
4630
4703
|
const stderr = Buffer.from(result.stderr).toString("utf-8").trim();
|
|
4631
|
-
throw new Error(`
|
|
4704
|
+
throw new Error(`file search failed (exit ${result.exitCode}): ${stderr}`);
|
|
4632
4705
|
}
|
|
4633
4706
|
const stdout = Buffer.from(result.stdout).toString("utf-8");
|
|
4634
4707
|
return stdout.split("\n").filter(Boolean);
|
|
@@ -6158,7 +6231,12 @@ function chatRoutes(app, opts, done) {
|
|
|
6158
6231
|
});
|
|
6159
6232
|
}
|
|
6160
6233
|
const runtime = await resolveRuntime(request);
|
|
6161
|
-
runtime.harness.followUp?.(
|
|
6234
|
+
await runtime.harness.followUp?.(
|
|
6235
|
+
sessionId,
|
|
6236
|
+
body.message,
|
|
6237
|
+
parsedAttachments.data,
|
|
6238
|
+
typeof body.displayText === "string" && body.displayText.length > 0 ? body.displayText : body.message
|
|
6239
|
+
);
|
|
6162
6240
|
return reply.code(202).send({ queued: true });
|
|
6163
6241
|
}
|
|
6164
6242
|
);
|
|
@@ -6281,9 +6359,16 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
6281
6359
|
import { z as z2 } from "zod";
|
|
6282
6360
|
var DEFAULT_SESSION_TITLE2 = "New session";
|
|
6283
6361
|
var DEFAULT_WORKSPACE_ID2 = "default";
|
|
6362
|
+
var MAX_ANALYSIS_TRANSCRIPT_CHARS = 12e4;
|
|
6284
6363
|
var createSessionBodySchema = z2.object({
|
|
6285
6364
|
title: z2.string().min(1).max(200).optional()
|
|
6286
6365
|
}).optional();
|
|
6366
|
+
var analyzeSessionBodySchema = z2.object({
|
|
6367
|
+
instructions: z2.string().max(4e3).optional(),
|
|
6368
|
+
title: z2.string().min(1).max(200).optional(),
|
|
6369
|
+
run: z2.boolean().optional(),
|
|
6370
|
+
includeTranscript: z2.boolean().optional()
|
|
6371
|
+
}).optional();
|
|
6287
6372
|
var SessionNotFoundError = class extends Error {
|
|
6288
6373
|
constructor(sessionId) {
|
|
6289
6374
|
super(`Session not found: ${sessionId}`);
|
|
@@ -6380,13 +6465,141 @@ function classifySessionError(err, reply) {
|
|
|
6380
6465
|
}
|
|
6381
6466
|
});
|
|
6382
6467
|
}
|
|
6468
|
+
function stableStringify(value) {
|
|
6469
|
+
if (typeof value === "string") return value;
|
|
6470
|
+
try {
|
|
6471
|
+
return JSON.stringify(value, null, 2);
|
|
6472
|
+
} catch {
|
|
6473
|
+
return String(value);
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
function partText(part) {
|
|
6477
|
+
const value = part;
|
|
6478
|
+
if (typeof value.text === "string") return value.text;
|
|
6479
|
+
if (typeof value.delta === "string") return value.delta;
|
|
6480
|
+
if (typeof value.content === "string") return value.content;
|
|
6481
|
+
return "";
|
|
6482
|
+
}
|
|
6483
|
+
function toolName(part) {
|
|
6484
|
+
if (typeof part.toolName === "string") return part.toolName;
|
|
6485
|
+
if (typeof part.type === "string" && part.type.startsWith("tool-")) {
|
|
6486
|
+
return part.type.slice("tool-".length);
|
|
6487
|
+
}
|
|
6488
|
+
return "tool";
|
|
6489
|
+
}
|
|
6490
|
+
function formatToolPart(part) {
|
|
6491
|
+
const name = toolName(part);
|
|
6492
|
+
const lines = [`[tool:${name}]`];
|
|
6493
|
+
if ("input" in part) {
|
|
6494
|
+
lines.push("input:", stableStringify(part.input));
|
|
6495
|
+
}
|
|
6496
|
+
if ("output" in part) {
|
|
6497
|
+
lines.push("output:", stableStringify(part.output));
|
|
6498
|
+
}
|
|
6499
|
+
if (typeof part.errorText === "string" && part.errorText.length > 0) {
|
|
6500
|
+
lines.push("error:", part.errorText);
|
|
6501
|
+
}
|
|
6502
|
+
return lines.join("\n");
|
|
6503
|
+
}
|
|
6504
|
+
function formatMessageForTranscript(message, index) {
|
|
6505
|
+
const msg = message;
|
|
6506
|
+
const role = String(msg.role ?? "unknown").toUpperCase();
|
|
6507
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
6508
|
+
const lines = [`## ${index + 1}. ${role}`];
|
|
6509
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
6510
|
+
lines.push(msg.content.trim());
|
|
6511
|
+
}
|
|
6512
|
+
for (const part of parts) {
|
|
6513
|
+
const record = part;
|
|
6514
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
6515
|
+
if (type === "text") {
|
|
6516
|
+
const text2 = partText(record).trim();
|
|
6517
|
+
if (text2) lines.push(text2);
|
|
6518
|
+
continue;
|
|
6519
|
+
}
|
|
6520
|
+
if (type === "reasoning") {
|
|
6521
|
+
const text2 = partText(record).trim();
|
|
6522
|
+
if (text2) lines.push(`[reasoning]
|
|
6523
|
+
${text2}`);
|
|
6524
|
+
continue;
|
|
6525
|
+
}
|
|
6526
|
+
if (type.startsWith("tool-")) {
|
|
6527
|
+
lines.push(formatToolPart(record));
|
|
6528
|
+
continue;
|
|
6529
|
+
}
|
|
6530
|
+
const text = partText(record).trim();
|
|
6531
|
+
if (text) lines.push(`[${type || "part"}]
|
|
6532
|
+
${text}`);
|
|
6533
|
+
}
|
|
6534
|
+
if (lines.length === 1) lines.push("(no visible content)");
|
|
6535
|
+
return lines.join("\n\n");
|
|
6536
|
+
}
|
|
6537
|
+
function formatSessionTranscript(session) {
|
|
6538
|
+
const header = [
|
|
6539
|
+
`# Agent session transcript: ${session.title}`,
|
|
6540
|
+
"",
|
|
6541
|
+
`- Session: ${session.id}`,
|
|
6542
|
+
`- Created: ${session.createdAt}`,
|
|
6543
|
+
`- Updated: ${session.updatedAt}`,
|
|
6544
|
+
`- User turns: ${session.turnCount}`
|
|
6545
|
+
];
|
|
6546
|
+
const body = session.messages.map(formatMessageForTranscript);
|
|
6547
|
+
return [...header, "", ...body].join("\n");
|
|
6548
|
+
}
|
|
6549
|
+
function truncateMiddle(value, maxChars) {
|
|
6550
|
+
if (value.length <= maxChars) return value;
|
|
6551
|
+
const keep = Math.floor((maxChars - 200) / 2);
|
|
6552
|
+
return `${value.slice(0, keep)}
|
|
6553
|
+
|
|
6554
|
+
[... transcript truncated: ${value.length - keep * 2} chars omitted ...]
|
|
6555
|
+
|
|
6556
|
+
${value.slice(-keep)}`;
|
|
6557
|
+
}
|
|
6558
|
+
function buildAnalysisPrompt(session, transcript, instructions) {
|
|
6559
|
+
const boundedTranscript = truncateMiddle(transcript, MAX_ANALYSIS_TRANSCRIPT_CHARS);
|
|
6560
|
+
return [
|
|
6561
|
+
"You are an agent-session analyst. Analyze the transcript below and explain what is going on.",
|
|
6562
|
+
"",
|
|
6563
|
+
"Return a concise, evidence-backed report with these sections:",
|
|
6564
|
+
"1. User goal",
|
|
6565
|
+
"2. Current state",
|
|
6566
|
+
"3. What the agent did",
|
|
6567
|
+
"4. Failures, confusing behavior, or likely root causes",
|
|
6568
|
+
"5. Files/tools/commands that matter",
|
|
6569
|
+
"6. Risks and recommended next actions",
|
|
6570
|
+
"",
|
|
6571
|
+
"Rules:",
|
|
6572
|
+
"- Cite concrete transcript evidence when possible.",
|
|
6573
|
+
'- Say "unknown" when the transcript does not prove something.',
|
|
6574
|
+
"- Do not modify files unless explicitly asked in follow-up.",
|
|
6575
|
+
instructions ? `- Extra user instructions: ${instructions}` : "",
|
|
6576
|
+
"",
|
|
6577
|
+
`Source session: ${session.id} (${session.title})`,
|
|
6578
|
+
"",
|
|
6579
|
+
"<transcript>",
|
|
6580
|
+
boundedTranscript,
|
|
6581
|
+
"</transcript>"
|
|
6582
|
+
].filter(Boolean).join("\n");
|
|
6583
|
+
}
|
|
6584
|
+
function analysisTextFromChunks(chunks) {
|
|
6585
|
+
return chunks.map((chunk2) => {
|
|
6586
|
+
const record = chunk2;
|
|
6587
|
+
return record.type === "text-delta" && typeof record.delta === "string" ? record.delta : "";
|
|
6588
|
+
}).join("").trim();
|
|
6589
|
+
}
|
|
6383
6590
|
function sessionRoutes(app, opts, done) {
|
|
6384
6591
|
const sessionStore = opts.sessionStore ?? new InMemorySessionStore();
|
|
6385
6592
|
const validateCreateBody = createBodyValidator(createSessionBodySchema);
|
|
6593
|
+
const validateAnalyzeBody = createBodyValidator(analyzeSessionBodySchema);
|
|
6386
6594
|
async function resolveSessionStore(request) {
|
|
6387
6595
|
if (opts.getSessionStore) return await opts.getSessionStore(request);
|
|
6388
6596
|
return sessionStore;
|
|
6389
6597
|
}
|
|
6598
|
+
async function resolveRuntime(request) {
|
|
6599
|
+
if (opts.getRuntime) return await opts.getRuntime(request);
|
|
6600
|
+
if (opts.harness && opts.workdir) return { harness: opts.harness, workdir: opts.workdir };
|
|
6601
|
+
throw new Error("session analysis requires harness/workdir or getRuntime");
|
|
6602
|
+
}
|
|
6390
6603
|
app.get("/api/v1/agent/sessions", async (request, reply) => {
|
|
6391
6604
|
try {
|
|
6392
6605
|
const store = await resolveSessionStore(request);
|
|
@@ -6421,6 +6634,113 @@ function sessionRoutes(app, opts, done) {
|
|
|
6421
6634
|
return classifySessionError(err, reply);
|
|
6422
6635
|
}
|
|
6423
6636
|
});
|
|
6637
|
+
app.get("/api/v1/agent/sessions/:id/transcript", async (request, reply) => {
|
|
6638
|
+
const params = request.params;
|
|
6639
|
+
const sessionId = requireSessionId(params.id, reply);
|
|
6640
|
+
if (sessionId === null) return;
|
|
6641
|
+
const query = request.query;
|
|
6642
|
+
const format = query.format === "json" ? "json" : "markdown";
|
|
6643
|
+
try {
|
|
6644
|
+
const store = await resolveSessionStore(request);
|
|
6645
|
+
const session = await store.load(getSessionCtx(request), sessionId);
|
|
6646
|
+
const transcript = formatSessionTranscript(session);
|
|
6647
|
+
if (format === "json") {
|
|
6648
|
+
return {
|
|
6649
|
+
session: {
|
|
6650
|
+
id: session.id,
|
|
6651
|
+
title: session.title,
|
|
6652
|
+
createdAt: session.createdAt,
|
|
6653
|
+
updatedAt: session.updatedAt,
|
|
6654
|
+
turnCount: session.turnCount
|
|
6655
|
+
},
|
|
6656
|
+
transcript,
|
|
6657
|
+
messages: session.messages
|
|
6658
|
+
};
|
|
6659
|
+
}
|
|
6660
|
+
return reply.header("Content-Type", "text/markdown; charset=utf-8").send(transcript);
|
|
6661
|
+
} catch (err) {
|
|
6662
|
+
return classifySessionError(err, reply);
|
|
6663
|
+
}
|
|
6664
|
+
});
|
|
6665
|
+
app.post(
|
|
6666
|
+
"/api/v1/agent/sessions/:id/analysis",
|
|
6667
|
+
{ preHandler: validateAnalyzeBody },
|
|
6668
|
+
async (request, reply) => {
|
|
6669
|
+
const params = request.params;
|
|
6670
|
+
const sourceSessionId = requireSessionId(params.id, reply);
|
|
6671
|
+
if (sourceSessionId === null) return;
|
|
6672
|
+
const body = request.body;
|
|
6673
|
+
const run2 = body?.run === true;
|
|
6674
|
+
const sessionCtx = getSessionCtx(request);
|
|
6675
|
+
try {
|
|
6676
|
+
const store = await resolveSessionStore(request);
|
|
6677
|
+
const sourceSession = await store.load(sessionCtx, sourceSessionId);
|
|
6678
|
+
const transcript = formatSessionTranscript(sourceSession);
|
|
6679
|
+
const prompt = buildAnalysisPrompt(sourceSession, transcript, body?.instructions);
|
|
6680
|
+
let runtime = null;
|
|
6681
|
+
if (run2) {
|
|
6682
|
+
try {
|
|
6683
|
+
runtime = await resolveRuntime(request);
|
|
6684
|
+
} catch {
|
|
6685
|
+
return reply.code(501).send({
|
|
6686
|
+
error: {
|
|
6687
|
+
code: ERROR_CODE_NOT_IMPLEMENTED,
|
|
6688
|
+
message: "session analysis runtime is not configured"
|
|
6689
|
+
}
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
const analysisSession = await store.create(sessionCtx, {
|
|
6694
|
+
title: body?.title ?? `Analysis: ${sourceSession.title}`.slice(0, 200)
|
|
6695
|
+
});
|
|
6696
|
+
if (!run2) {
|
|
6697
|
+
return {
|
|
6698
|
+
sourceSession: {
|
|
6699
|
+
id: sourceSession.id,
|
|
6700
|
+
title: sourceSession.title,
|
|
6701
|
+
createdAt: sourceSession.createdAt,
|
|
6702
|
+
updatedAt: sourceSession.updatedAt,
|
|
6703
|
+
turnCount: sourceSession.turnCount
|
|
6704
|
+
},
|
|
6705
|
+
analysisSession,
|
|
6706
|
+
prompt,
|
|
6707
|
+
transcriptUrl: `/api/v1/agent/sessions/${encodeURIComponent(sourceSession.id)}/transcript`,
|
|
6708
|
+
...body?.includeTranscript ? { transcript } : {}
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6711
|
+
if (!runtime) {
|
|
6712
|
+
throw new Error("session analysis runtime is not configured");
|
|
6713
|
+
}
|
|
6714
|
+
const abortController = new AbortController();
|
|
6715
|
+
const chunks = [];
|
|
6716
|
+
for await (const chunk2 of runtime.harness.sendMessage(
|
|
6717
|
+
{ sessionId: analysisSession.id, message: prompt },
|
|
6718
|
+
{
|
|
6719
|
+
abortSignal: abortController.signal,
|
|
6720
|
+
workdir: runtime.workdir
|
|
6721
|
+
}
|
|
6722
|
+
)) {
|
|
6723
|
+
chunks.push(chunk2);
|
|
6724
|
+
}
|
|
6725
|
+
return {
|
|
6726
|
+
sourceSession: {
|
|
6727
|
+
id: sourceSession.id,
|
|
6728
|
+
title: sourceSession.title,
|
|
6729
|
+
createdAt: sourceSession.createdAt,
|
|
6730
|
+
updatedAt: sourceSession.updatedAt,
|
|
6731
|
+
turnCount: sourceSession.turnCount
|
|
6732
|
+
},
|
|
6733
|
+
analysisSession,
|
|
6734
|
+
prompt,
|
|
6735
|
+
transcriptUrl: `/api/v1/agent/sessions/${encodeURIComponent(sourceSession.id)}/transcript`,
|
|
6736
|
+
analysisText: analysisTextFromChunks(chunks),
|
|
6737
|
+
...body?.includeTranscript ? { transcript } : {}
|
|
6738
|
+
};
|
|
6739
|
+
} catch (err) {
|
|
6740
|
+
return classifySessionError(err, reply);
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
);
|
|
6424
6744
|
app.delete("/api/v1/agent/sessions/:id", async (request, reply) => {
|
|
6425
6745
|
const params = request.params;
|
|
6426
6746
|
const sessionId = requireSessionId(params.id, reply);
|
|
@@ -6741,7 +7061,9 @@ async function createAgentApp(opts = {}) {
|
|
|
6741
7061
|
sessionChangesTracker
|
|
6742
7062
|
});
|
|
6743
7063
|
await app.register(sessionRoutes, {
|
|
6744
|
-
sessionStore: harness.sessions
|
|
7064
|
+
sessionStore: harness.sessions,
|
|
7065
|
+
harness,
|
|
7066
|
+
workdir: runtimeBundle.workspace.root
|
|
6745
7067
|
});
|
|
6746
7068
|
await app.register(systemPromptRoutes, { harness });
|
|
6747
7069
|
await app.register(modelsRoutes);
|
|
@@ -7193,6 +7515,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
7193
7515
|
getSessionStore: async (request) => {
|
|
7194
7516
|
const binding = await getBindingForRequest(request);
|
|
7195
7517
|
return binding.harness.sessions;
|
|
7518
|
+
},
|
|
7519
|
+
getRuntime: async (request) => {
|
|
7520
|
+
const binding = await getBindingForRequest(request);
|
|
7521
|
+
return {
|
|
7522
|
+
harness: binding.harness,
|
|
7523
|
+
workdir: binding.runtimeBundle.workspace.root
|
|
7524
|
+
};
|
|
7196
7525
|
}
|
|
7197
7526
|
});
|
|
7198
7527
|
await app.register(systemPromptRoutes, {
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AgentHarness, R as RunContext, S as SendMessageInput, a as SessionCtx, b as SessionDetail, c as SessionStore, d as SessionSummary } from '../harness-
|
|
1
|
+
export { A as AgentHarness, R as RunContext, S as SendMessageInput, a as SessionCtx, b as SessionDetail, c as SessionStore, d as SessionSummary } from '../harness-DT3ZzdAN.js';
|
|
2
2
|
import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../sandbox-handle-store-DCNEJJ6b.js';
|
|
3
3
|
export { E as Entry, a as ExecOptions, b as ExecResult, I as IsolatedCodeInput, c as IsolatedCodeOutput, J as JSONSchema, d as SandboxCapability, e as SandboxHandleRecord, f as SandboxHandleStore, g as Stat, T as ToolExecContext, h as ToolResult } from '../sandbox-handle-store-DCNEJJ6b.js';
|
|
4
4
|
export { UIMessage, UIMessageChunk } from 'ai';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"use-stick-to-bottom": "^1.1.3",
|
|
75
75
|
"yaml": "^2.8.3",
|
|
76
76
|
"zod": "^3.25.76",
|
|
77
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
77
|
+
"@hachej/boring-ui-kit": "0.1.12"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@opentelemetry/api": "^1.9.1",
|