runcastle 1.0.19 → 1.0.21
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/bin/runcastle.js +111 -10
- package/index.js +111 -10
- package/package.json +1 -1
- package/web/assets/{index-97_7db0H.js → index-B5UytNW_.js} +20 -20
- package/web/assets/index-BIDKY6fS.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-D2WcrzA3.css +0 -1
package/bin/runcastle.js
CHANGED
|
@@ -72,7 +72,7 @@ function resolveSandboxImage(config) {
|
|
|
72
72
|
function resolveModel(step, config, project, runOverride) {
|
|
73
73
|
return runOverride ?? config.stepModels[step] ?? project?.model ?? config.model;
|
|
74
74
|
}
|
|
75
|
-
var MODEL_STEPS, ModelStep, DEFAULT_SMOKE_MODEL = "claude-haiku-4-5
|
|
75
|
+
var MODEL_STEPS, ModelStep, DEFAULT_SMOKE_MODEL = "claude-haiku-4-5", foldLegacyModelConfig = (raw) => {
|
|
76
76
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
77
77
|
return raw;
|
|
78
78
|
const obj = { ...raw };
|
|
@@ -99,7 +99,7 @@ var init_config = __esm(() => {
|
|
|
99
99
|
ModelStep = z.enum(MODEL_STEPS);
|
|
100
100
|
RuncastleConfig = z.preprocess(foldLegacyModelConfig, z.object({
|
|
101
101
|
serverPort: z.number().default(4512),
|
|
102
|
-
model: z.string().default("claude-opus-
|
|
102
|
+
model: z.string().default("claude-opus-5"),
|
|
103
103
|
stepModels: z.partialRecord(ModelStep, z.string()).default({ smoke: DEFAULT_SMOKE_MODEL }),
|
|
104
104
|
sandbox: z.enum(["docker", "podman", "noSandbox"]).default("docker"),
|
|
105
105
|
mainBranch: z.string().default("main"),
|
|
@@ -1127,6 +1127,27 @@ var init_errors = __esm(() => {
|
|
|
1127
1127
|
};
|
|
1128
1128
|
});
|
|
1129
1129
|
|
|
1130
|
+
// src/services/bus.ts
|
|
1131
|
+
function subscribeLive(fn) {
|
|
1132
|
+
subscribers.add(fn);
|
|
1133
|
+
return () => {
|
|
1134
|
+
subscribers.delete(fn);
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function publishLive(signal) {
|
|
1138
|
+
for (const fn of [...subscribers]) {
|
|
1139
|
+
try {
|
|
1140
|
+
fn(signal);
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
console.error("live bus subscriber threw", err);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
var subscribers;
|
|
1147
|
+
var init_bus = __esm(() => {
|
|
1148
|
+
subscribers = new Set;
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1130
1151
|
// src/services/events.ts
|
|
1131
1152
|
import { and, asc, eq, gt } from "drizzle-orm";
|
|
1132
1153
|
function rowToEvent(row) {
|
|
@@ -1165,7 +1186,14 @@ function insertEvent(ctx, projectId, featureId, e) {
|
|
|
1165
1186
|
message: e.message,
|
|
1166
1187
|
data: e.data ?? null
|
|
1167
1188
|
}).returning().get();
|
|
1168
|
-
|
|
1189
|
+
const event = rowToEvent(row);
|
|
1190
|
+
publishLive({
|
|
1191
|
+
kind: "event",
|
|
1192
|
+
projectId: event.projectId,
|
|
1193
|
+
featureId: event.featureId,
|
|
1194
|
+
eventId: event.id
|
|
1195
|
+
});
|
|
1196
|
+
return event;
|
|
1169
1197
|
}
|
|
1170
1198
|
function listAfter(ctx, featureId, afterId = 0) {
|
|
1171
1199
|
const rows = ctx.db.select().from(events).where(and(eq(events.featureId, featureId), gt(events.id, afterId))).orderBy(asc(events.id)).all();
|
|
@@ -1178,6 +1206,7 @@ function listByProject(ctx, projectId, afterId = 0) {
|
|
|
1178
1206
|
var init_events = __esm(() => {
|
|
1179
1207
|
init_schema();
|
|
1180
1208
|
init_errors();
|
|
1209
|
+
init_bus();
|
|
1181
1210
|
});
|
|
1182
1211
|
|
|
1183
1212
|
// src/services/repo.ts
|
|
@@ -1516,6 +1545,9 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
1516
1545
|
function kickoffLineFor(kind, override) {
|
|
1517
1546
|
return override ?? KICKOFF_LINES[kind];
|
|
1518
1547
|
}
|
|
1548
|
+
function resumeKickoffLine(kind) {
|
|
1549
|
+
return RESUME_KICKOFF_PREFIX + KICKOFF_LINES[kind];
|
|
1550
|
+
}
|
|
1519
1551
|
function setKickoffOverride(sessionId, line) {
|
|
1520
1552
|
pendingKickoffOverrides.set(sessionId, line);
|
|
1521
1553
|
}
|
|
@@ -1564,15 +1596,15 @@ function markSessionEnded(ctx, id) {
|
|
|
1564
1596
|
ctx.db.update(sessions).set({ status: "ended" }).where(eq4(sessions.id, id)).run();
|
|
1565
1597
|
return getSessionRow(ctx, id);
|
|
1566
1598
|
}
|
|
1567
|
-
function mostRecentResumableSession(ctx, featureId) {
|
|
1568
|
-
const row = ctx.db.select().from(sessions).where(and4(eq4(sessions.featureId, featureId), eq4(sessions.status, "ended"), isNotNull(sessions.ccSessionId))).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1599
|
+
function mostRecentResumableSession(ctx, featureId, kind) {
|
|
1600
|
+
const row = ctx.db.select().from(sessions).where(and4(eq4(sessions.featureId, featureId), eq4(sessions.status, "ended"), isNotNull(sessions.ccSessionId), ...kind ? [eq4(sessions.kind, kind)] : [])).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1569
1601
|
return row ? rowToSession(row) : null;
|
|
1570
1602
|
}
|
|
1571
1603
|
function mostRecentLiveSession(ctx) {
|
|
1572
1604
|
const row = ctx.db.select().from(sessions).where(eq4(sessions.status, "live")).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1573
1605
|
return row ? rowToSession(row) : null;
|
|
1574
1606
|
}
|
|
1575
|
-
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, pendingKickoffOverrides;
|
|
1607
|
+
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides;
|
|
1576
1608
|
var init_sessions = __esm(() => {
|
|
1577
1609
|
init_src();
|
|
1578
1610
|
init_schema();
|
|
@@ -1588,6 +1620,7 @@ var init_sessions = __esm(() => {
|
|
|
1588
1620
|
converge: CONVERGE_KICKOFF_LINE,
|
|
1589
1621
|
revisit: "Proceed with your task: invoke the /runcastle:revisit skill and work through what the " + "human brings up."
|
|
1590
1622
|
};
|
|
1623
|
+
RESUME_KICKOFF_PREFIX = "We are picking this conversation back up \u2014 runcastle restarted and closed the terminal, " + "but this conversation is intact. Do NOT start over: tell me in one or two lines where we " + "left off and what is next, then carry on. Your original instruction was: ";
|
|
1591
1624
|
pendingKickoffOverrides = new Map;
|
|
1592
1625
|
});
|
|
1593
1626
|
|
|
@@ -2686,6 +2719,7 @@ var init_skills_root = __esm(() => {
|
|
|
2686
2719
|
// src/services/agent-stream.ts
|
|
2687
2720
|
function beginTranscript(ticketId) {
|
|
2688
2721
|
transcripts.set(ticketId, { live: true, chunks: [], nextIndex: 0, bytes: 0 });
|
|
2722
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2689
2723
|
}
|
|
2690
2724
|
function appendTranscript(ticketId, chunk) {
|
|
2691
2725
|
const t = transcripts.get(ticketId);
|
|
@@ -2701,11 +2735,13 @@ function appendTranscript(ticketId, chunk) {
|
|
|
2701
2735
|
break;
|
|
2702
2736
|
t.bytes -= dropped.text.length;
|
|
2703
2737
|
}
|
|
2738
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2704
2739
|
}
|
|
2705
2740
|
function endTranscript(ticketId) {
|
|
2706
2741
|
const t = transcripts.get(ticketId);
|
|
2707
2742
|
if (t)
|
|
2708
2743
|
t.live = false;
|
|
2744
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2709
2745
|
}
|
|
2710
2746
|
function readTranscript(ticketId, after = -1) {
|
|
2711
2747
|
const t = transcripts.get(ticketId);
|
|
@@ -2720,6 +2756,7 @@ function readTranscript(ticketId, after = -1) {
|
|
|
2720
2756
|
}
|
|
2721
2757
|
var MAX_CHUNKS = 4000, MAX_BYTES = 1500000, transcripts;
|
|
2722
2758
|
var init_agent_stream = __esm(() => {
|
|
2759
|
+
init_bus();
|
|
2723
2760
|
transcripts = new Map;
|
|
2724
2761
|
});
|
|
2725
2762
|
|
|
@@ -4632,6 +4669,57 @@ var init_hooks = __esm(() => {
|
|
|
4632
4669
|
hooks_default = hooks;
|
|
4633
4670
|
});
|
|
4634
4671
|
|
|
4672
|
+
// src/routes/stream.ts
|
|
4673
|
+
import { Hono as Hono3 } from "hono";
|
|
4674
|
+
import { streamSSE } from "hono/streaming";
|
|
4675
|
+
function dedupeKey(s) {
|
|
4676
|
+
return s.kind === "transcript" ? `transcript:${s.ticketId}` : "event";
|
|
4677
|
+
}
|
|
4678
|
+
var stream, FLUSH_MS = 120, HEARTBEAT_MS = 25000, stream_default;
|
|
4679
|
+
var init_stream = __esm(() => {
|
|
4680
|
+
init_bus();
|
|
4681
|
+
stream = new Hono3;
|
|
4682
|
+
stream.get("/", (c) => streamSSE(c, async (sse) => {
|
|
4683
|
+
const pending = new Map;
|
|
4684
|
+
let done = false;
|
|
4685
|
+
const stop = () => {
|
|
4686
|
+
done = true;
|
|
4687
|
+
};
|
|
4688
|
+
const unsubscribe = subscribeLive((signal) => {
|
|
4689
|
+
pending.set(dedupeKey(signal), signal);
|
|
4690
|
+
});
|
|
4691
|
+
sse.onAbort(stop);
|
|
4692
|
+
c.req.raw.signal.addEventListener("abort", stop);
|
|
4693
|
+
try {
|
|
4694
|
+
await sse.writeSSE({ event: "ready", data: "{}" });
|
|
4695
|
+
let idleMs = 0;
|
|
4696
|
+
while (!done && !sse.aborted && !sse.closed) {
|
|
4697
|
+
await sse.sleep(FLUSH_MS);
|
|
4698
|
+
if (done || sse.aborted || sse.closed)
|
|
4699
|
+
break;
|
|
4700
|
+
if (pending.size === 0) {
|
|
4701
|
+
idleMs += FLUSH_MS;
|
|
4702
|
+
if (idleMs >= HEARTBEAT_MS) {
|
|
4703
|
+
idleMs = 0;
|
|
4704
|
+
await sse.writeSSE({ event: "ping", data: "{}" });
|
|
4705
|
+
}
|
|
4706
|
+
continue;
|
|
4707
|
+
}
|
|
4708
|
+
idleMs = 0;
|
|
4709
|
+
const batch = [...pending.values()];
|
|
4710
|
+
pending.clear();
|
|
4711
|
+
for (const signal of batch) {
|
|
4712
|
+
await sse.writeSSE({ event: "live", data: JSON.stringify(signal) });
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
} finally {
|
|
4716
|
+
unsubscribe();
|
|
4717
|
+
c.req.raw.signal.removeEventListener("abort", stop);
|
|
4718
|
+
}
|
|
4719
|
+
}));
|
|
4720
|
+
stream_default = stream;
|
|
4721
|
+
});
|
|
4722
|
+
|
|
4635
4723
|
// src/routes/web.ts
|
|
4636
4724
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4637
4725
|
import { readFile } from "fs/promises";
|
|
@@ -5124,8 +5212,6 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
5124
5212
|
kind: input.kind,
|
|
5125
5213
|
worktreePath
|
|
5126
5214
|
});
|
|
5127
|
-
if (input.kickoffLine)
|
|
5128
|
-
setKickoffOverride(session.id, input.kickoffLine);
|
|
5129
5215
|
let waypoint;
|
|
5130
5216
|
let resumeSessionId;
|
|
5131
5217
|
let resumeUnavailableFrom;
|
|
@@ -5158,11 +5244,24 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
5158
5244
|
resumeUnavailableFrom = "revisit";
|
|
5159
5245
|
}
|
|
5160
5246
|
}
|
|
5247
|
+
if (input.kind !== "waypoint" && input.kind !== "revisit") {
|
|
5248
|
+
resumeSessionId = mostRecentResumableSession(ctx, feature.id, input.kind)?.ccSessionId;
|
|
5249
|
+
}
|
|
5250
|
+
const kickoffLine = input.kickoffLine ?? (resumeSessionId ? resumeKickoffLine(input.kind) : undefined);
|
|
5251
|
+
if (kickoffLine)
|
|
5252
|
+
setKickoffOverride(session.id, kickoffLine);
|
|
5161
5253
|
emit(ctx, feature.id, {
|
|
5162
5254
|
type: "session.launching",
|
|
5163
5255
|
message: `launching ${input.kind} session`,
|
|
5164
5256
|
data: { sessionId: session.id, kind: input.kind, worktreePath, waypointId: waypoint?.id }
|
|
5165
5257
|
});
|
|
5258
|
+
if (resumeSessionId) {
|
|
5259
|
+
emit(ctx, feature.id, {
|
|
5260
|
+
type: "session.resumed",
|
|
5261
|
+
message: `resuming the previous ${input.kind} conversation`,
|
|
5262
|
+
data: { sessionId: session.id, kind: input.kind, resumeSessionId }
|
|
5263
|
+
});
|
|
5264
|
+
}
|
|
5166
5265
|
if (input.kind === "revisit" && resumeUnavailableFrom) {
|
|
5167
5266
|
emit(ctx, feature.id, {
|
|
5168
5267
|
type: "session.resume_unavailable",
|
|
@@ -6595,9 +6694,9 @@ __export(exports_src, {
|
|
|
6595
6694
|
buildApp: () => buildApp
|
|
6596
6695
|
});
|
|
6597
6696
|
import { trpcServer } from "@hono/trpc-server";
|
|
6598
|
-
import { Hono as
|
|
6697
|
+
import { Hono as Hono4 } from "hono";
|
|
6599
6698
|
function buildApp(ctx) {
|
|
6600
|
-
const app = new
|
|
6699
|
+
const app = new Hono4;
|
|
6601
6700
|
setRuntimeCtx(ctx);
|
|
6602
6701
|
app.get("/health", (c) => c.json({ ok: true }));
|
|
6603
6702
|
app.use("/api/trpc/*", async (c, next) => {
|
|
@@ -6611,6 +6710,7 @@ function buildApp(ctx) {
|
|
|
6611
6710
|
createContext: () => ctx
|
|
6612
6711
|
}));
|
|
6613
6712
|
app.route("/api/hooks", hooks_default);
|
|
6713
|
+
app.route("/api/stream", stream_default);
|
|
6614
6714
|
app.route("/mcp", server_default);
|
|
6615
6715
|
mountWebAppIfBuilt(app);
|
|
6616
6716
|
return app;
|
|
@@ -6663,6 +6763,7 @@ var init_src2 = __esm(() => {
|
|
|
6663
6763
|
init_registry();
|
|
6664
6764
|
init_ws();
|
|
6665
6765
|
init_hooks();
|
|
6766
|
+
init_stream();
|
|
6666
6767
|
init_web();
|
|
6667
6768
|
init_router();
|
|
6668
6769
|
init_reconcile_runs();
|
package/index.js
CHANGED
|
@@ -71,7 +71,7 @@ function resolveSandboxImage(config) {
|
|
|
71
71
|
function resolveModel(step, config, project, runOverride) {
|
|
72
72
|
return runOverride ?? config.stepModels[step] ?? project?.model ?? config.model;
|
|
73
73
|
}
|
|
74
|
-
var MODEL_STEPS, ModelStep, DEFAULT_SMOKE_MODEL = "claude-haiku-4-5
|
|
74
|
+
var MODEL_STEPS, ModelStep, DEFAULT_SMOKE_MODEL = "claude-haiku-4-5", foldLegacyModelConfig = (raw) => {
|
|
75
75
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
76
76
|
return raw;
|
|
77
77
|
const obj = { ...raw };
|
|
@@ -98,7 +98,7 @@ var init_config = __esm(() => {
|
|
|
98
98
|
ModelStep = z.enum(MODEL_STEPS);
|
|
99
99
|
RuncastleConfig = z.preprocess(foldLegacyModelConfig, z.object({
|
|
100
100
|
serverPort: z.number().default(4512),
|
|
101
|
-
model: z.string().default("claude-opus-
|
|
101
|
+
model: z.string().default("claude-opus-5"),
|
|
102
102
|
stepModels: z.partialRecord(ModelStep, z.string()).default({ smoke: DEFAULT_SMOKE_MODEL }),
|
|
103
103
|
sandbox: z.enum(["docker", "podman", "noSandbox"]).default("docker"),
|
|
104
104
|
mainBranch: z.string().default("main"),
|
|
@@ -1126,6 +1126,27 @@ var init_errors = __esm(() => {
|
|
|
1126
1126
|
};
|
|
1127
1127
|
});
|
|
1128
1128
|
|
|
1129
|
+
// src/services/bus.ts
|
|
1130
|
+
function subscribeLive(fn) {
|
|
1131
|
+
subscribers.add(fn);
|
|
1132
|
+
return () => {
|
|
1133
|
+
subscribers.delete(fn);
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
function publishLive(signal) {
|
|
1137
|
+
for (const fn of [...subscribers]) {
|
|
1138
|
+
try {
|
|
1139
|
+
fn(signal);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
console.error("live bus subscriber threw", err);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
var subscribers;
|
|
1146
|
+
var init_bus = __esm(() => {
|
|
1147
|
+
subscribers = new Set;
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1129
1150
|
// src/services/events.ts
|
|
1130
1151
|
import { and, asc, eq, gt } from "drizzle-orm";
|
|
1131
1152
|
function rowToEvent(row) {
|
|
@@ -1164,7 +1185,14 @@ function insertEvent(ctx, projectId, featureId, e) {
|
|
|
1164
1185
|
message: e.message,
|
|
1165
1186
|
data: e.data ?? null
|
|
1166
1187
|
}).returning().get();
|
|
1167
|
-
|
|
1188
|
+
const event = rowToEvent(row);
|
|
1189
|
+
publishLive({
|
|
1190
|
+
kind: "event",
|
|
1191
|
+
projectId: event.projectId,
|
|
1192
|
+
featureId: event.featureId,
|
|
1193
|
+
eventId: event.id
|
|
1194
|
+
});
|
|
1195
|
+
return event;
|
|
1168
1196
|
}
|
|
1169
1197
|
function listAfter(ctx, featureId, afterId = 0) {
|
|
1170
1198
|
const rows = ctx.db.select().from(events).where(and(eq(events.featureId, featureId), gt(events.id, afterId))).orderBy(asc(events.id)).all();
|
|
@@ -1177,6 +1205,7 @@ function listByProject(ctx, projectId, afterId = 0) {
|
|
|
1177
1205
|
var init_events = __esm(() => {
|
|
1178
1206
|
init_schema();
|
|
1179
1207
|
init_errors();
|
|
1208
|
+
init_bus();
|
|
1180
1209
|
});
|
|
1181
1210
|
|
|
1182
1211
|
// src/services/repo.ts
|
|
@@ -1515,6 +1544,9 @@ function markSessionLive(ctx, id, input = {}) {
|
|
|
1515
1544
|
function kickoffLineFor(kind, override) {
|
|
1516
1545
|
return override ?? KICKOFF_LINES[kind];
|
|
1517
1546
|
}
|
|
1547
|
+
function resumeKickoffLine(kind) {
|
|
1548
|
+
return RESUME_KICKOFF_PREFIX + KICKOFF_LINES[kind];
|
|
1549
|
+
}
|
|
1518
1550
|
function setKickoffOverride(sessionId, line) {
|
|
1519
1551
|
pendingKickoffOverrides.set(sessionId, line);
|
|
1520
1552
|
}
|
|
@@ -1563,15 +1595,15 @@ function markSessionEnded(ctx, id) {
|
|
|
1563
1595
|
ctx.db.update(sessions).set({ status: "ended" }).where(eq4(sessions.id, id)).run();
|
|
1564
1596
|
return getSessionRow(ctx, id);
|
|
1565
1597
|
}
|
|
1566
|
-
function mostRecentResumableSession(ctx, featureId) {
|
|
1567
|
-
const row = ctx.db.select().from(sessions).where(and4(eq4(sessions.featureId, featureId), eq4(sessions.status, "ended"), isNotNull(sessions.ccSessionId))).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1598
|
+
function mostRecentResumableSession(ctx, featureId, kind) {
|
|
1599
|
+
const row = ctx.db.select().from(sessions).where(and4(eq4(sessions.featureId, featureId), eq4(sessions.status, "ended"), isNotNull(sessions.ccSessionId), ...kind ? [eq4(sessions.kind, kind)] : [])).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1568
1600
|
return row ? rowToSession(row) : null;
|
|
1569
1601
|
}
|
|
1570
1602
|
function mostRecentLiveSession(ctx) {
|
|
1571
1603
|
const row = ctx.db.select().from(sessions).where(eq4(sessions.status, "live")).orderBy(desc2(sql2`rowid`)).limit(1).get();
|
|
1572
1604
|
return row ? rowToSession(row) : null;
|
|
1573
1605
|
}
|
|
1574
|
-
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, pendingKickoffOverrides;
|
|
1606
|
+
var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides;
|
|
1575
1607
|
var init_sessions = __esm(() => {
|
|
1576
1608
|
init_src();
|
|
1577
1609
|
init_schema();
|
|
@@ -1587,6 +1619,7 @@ var init_sessions = __esm(() => {
|
|
|
1587
1619
|
converge: CONVERGE_KICKOFF_LINE,
|
|
1588
1620
|
revisit: "Proceed with your task: invoke the /runcastle:revisit skill and work through what the " + "human brings up."
|
|
1589
1621
|
};
|
|
1622
|
+
RESUME_KICKOFF_PREFIX = "We are picking this conversation back up \u2014 runcastle restarted and closed the terminal, " + "but this conversation is intact. Do NOT start over: tell me in one or two lines where we " + "left off and what is next, then carry on. Your original instruction was: ";
|
|
1590
1623
|
pendingKickoffOverrides = new Map;
|
|
1591
1624
|
});
|
|
1592
1625
|
|
|
@@ -2685,6 +2718,7 @@ var init_skills_root = __esm(() => {
|
|
|
2685
2718
|
// src/services/agent-stream.ts
|
|
2686
2719
|
function beginTranscript(ticketId) {
|
|
2687
2720
|
transcripts.set(ticketId, { live: true, chunks: [], nextIndex: 0, bytes: 0 });
|
|
2721
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2688
2722
|
}
|
|
2689
2723
|
function appendTranscript(ticketId, chunk) {
|
|
2690
2724
|
const t = transcripts.get(ticketId);
|
|
@@ -2700,11 +2734,13 @@ function appendTranscript(ticketId, chunk) {
|
|
|
2700
2734
|
break;
|
|
2701
2735
|
t.bytes -= dropped.text.length;
|
|
2702
2736
|
}
|
|
2737
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2703
2738
|
}
|
|
2704
2739
|
function endTranscript(ticketId) {
|
|
2705
2740
|
const t = transcripts.get(ticketId);
|
|
2706
2741
|
if (t)
|
|
2707
2742
|
t.live = false;
|
|
2743
|
+
publishLive({ kind: "transcript", ticketId });
|
|
2708
2744
|
}
|
|
2709
2745
|
function readTranscript(ticketId, after = -1) {
|
|
2710
2746
|
const t = transcripts.get(ticketId);
|
|
@@ -2719,6 +2755,7 @@ function readTranscript(ticketId, after = -1) {
|
|
|
2719
2755
|
}
|
|
2720
2756
|
var MAX_CHUNKS = 4000, MAX_BYTES = 1500000, transcripts;
|
|
2721
2757
|
var init_agent_stream = __esm(() => {
|
|
2758
|
+
init_bus();
|
|
2722
2759
|
transcripts = new Map;
|
|
2723
2760
|
});
|
|
2724
2761
|
|
|
@@ -4631,6 +4668,57 @@ var init_hooks = __esm(() => {
|
|
|
4631
4668
|
hooks_default = hooks;
|
|
4632
4669
|
});
|
|
4633
4670
|
|
|
4671
|
+
// src/routes/stream.ts
|
|
4672
|
+
import { Hono as Hono3 } from "hono";
|
|
4673
|
+
import { streamSSE } from "hono/streaming";
|
|
4674
|
+
function dedupeKey(s) {
|
|
4675
|
+
return s.kind === "transcript" ? `transcript:${s.ticketId}` : "event";
|
|
4676
|
+
}
|
|
4677
|
+
var stream, FLUSH_MS = 120, HEARTBEAT_MS = 25000, stream_default;
|
|
4678
|
+
var init_stream = __esm(() => {
|
|
4679
|
+
init_bus();
|
|
4680
|
+
stream = new Hono3;
|
|
4681
|
+
stream.get("/", (c) => streamSSE(c, async (sse) => {
|
|
4682
|
+
const pending = new Map;
|
|
4683
|
+
let done = false;
|
|
4684
|
+
const stop = () => {
|
|
4685
|
+
done = true;
|
|
4686
|
+
};
|
|
4687
|
+
const unsubscribe = subscribeLive((signal) => {
|
|
4688
|
+
pending.set(dedupeKey(signal), signal);
|
|
4689
|
+
});
|
|
4690
|
+
sse.onAbort(stop);
|
|
4691
|
+
c.req.raw.signal.addEventListener("abort", stop);
|
|
4692
|
+
try {
|
|
4693
|
+
await sse.writeSSE({ event: "ready", data: "{}" });
|
|
4694
|
+
let idleMs = 0;
|
|
4695
|
+
while (!done && !sse.aborted && !sse.closed) {
|
|
4696
|
+
await sse.sleep(FLUSH_MS);
|
|
4697
|
+
if (done || sse.aborted || sse.closed)
|
|
4698
|
+
break;
|
|
4699
|
+
if (pending.size === 0) {
|
|
4700
|
+
idleMs += FLUSH_MS;
|
|
4701
|
+
if (idleMs >= HEARTBEAT_MS) {
|
|
4702
|
+
idleMs = 0;
|
|
4703
|
+
await sse.writeSSE({ event: "ping", data: "{}" });
|
|
4704
|
+
}
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
idleMs = 0;
|
|
4708
|
+
const batch = [...pending.values()];
|
|
4709
|
+
pending.clear();
|
|
4710
|
+
for (const signal of batch) {
|
|
4711
|
+
await sse.writeSSE({ event: "live", data: JSON.stringify(signal) });
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
} finally {
|
|
4715
|
+
unsubscribe();
|
|
4716
|
+
c.req.raw.signal.removeEventListener("abort", stop);
|
|
4717
|
+
}
|
|
4718
|
+
}));
|
|
4719
|
+
stream_default = stream;
|
|
4720
|
+
});
|
|
4721
|
+
|
|
4634
4722
|
// src/routes/web.ts
|
|
4635
4723
|
import { existsSync as existsSync12, statSync } from "fs";
|
|
4636
4724
|
import { readFile } from "fs/promises";
|
|
@@ -5123,8 +5211,6 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
5123
5211
|
kind: input.kind,
|
|
5124
5212
|
worktreePath
|
|
5125
5213
|
});
|
|
5126
|
-
if (input.kickoffLine)
|
|
5127
|
-
setKickoffOverride(session.id, input.kickoffLine);
|
|
5128
5214
|
let waypoint;
|
|
5129
5215
|
let resumeSessionId;
|
|
5130
5216
|
let resumeUnavailableFrom;
|
|
@@ -5157,11 +5243,24 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
5157
5243
|
resumeUnavailableFrom = "revisit";
|
|
5158
5244
|
}
|
|
5159
5245
|
}
|
|
5246
|
+
if (input.kind !== "waypoint" && input.kind !== "revisit") {
|
|
5247
|
+
resumeSessionId = mostRecentResumableSession(ctx, feature.id, input.kind)?.ccSessionId;
|
|
5248
|
+
}
|
|
5249
|
+
const kickoffLine = input.kickoffLine ?? (resumeSessionId ? resumeKickoffLine(input.kind) : undefined);
|
|
5250
|
+
if (kickoffLine)
|
|
5251
|
+
setKickoffOverride(session.id, kickoffLine);
|
|
5160
5252
|
emit(ctx, feature.id, {
|
|
5161
5253
|
type: "session.launching",
|
|
5162
5254
|
message: `launching ${input.kind} session`,
|
|
5163
5255
|
data: { sessionId: session.id, kind: input.kind, worktreePath, waypointId: waypoint?.id }
|
|
5164
5256
|
});
|
|
5257
|
+
if (resumeSessionId) {
|
|
5258
|
+
emit(ctx, feature.id, {
|
|
5259
|
+
type: "session.resumed",
|
|
5260
|
+
message: `resuming the previous ${input.kind} conversation`,
|
|
5261
|
+
data: { sessionId: session.id, kind: input.kind, resumeSessionId }
|
|
5262
|
+
});
|
|
5263
|
+
}
|
|
5165
5264
|
if (input.kind === "revisit" && resumeUnavailableFrom) {
|
|
5166
5265
|
emit(ctx, feature.id, {
|
|
5167
5266
|
type: "session.resume_unavailable",
|
|
@@ -6594,9 +6693,9 @@ __export(exports_src, {
|
|
|
6594
6693
|
buildApp: () => buildApp
|
|
6595
6694
|
});
|
|
6596
6695
|
import { trpcServer } from "@hono/trpc-server";
|
|
6597
|
-
import { Hono as
|
|
6696
|
+
import { Hono as Hono4 } from "hono";
|
|
6598
6697
|
function buildApp(ctx) {
|
|
6599
|
-
const app = new
|
|
6698
|
+
const app = new Hono4;
|
|
6600
6699
|
setRuntimeCtx(ctx);
|
|
6601
6700
|
app.get("/health", (c) => c.json({ ok: true }));
|
|
6602
6701
|
app.use("/api/trpc/*", async (c, next) => {
|
|
@@ -6610,6 +6709,7 @@ function buildApp(ctx) {
|
|
|
6610
6709
|
createContext: () => ctx
|
|
6611
6710
|
}));
|
|
6612
6711
|
app.route("/api/hooks", hooks_default);
|
|
6712
|
+
app.route("/api/stream", stream_default);
|
|
6613
6713
|
app.route("/mcp", server_default);
|
|
6614
6714
|
mountWebAppIfBuilt(app);
|
|
6615
6715
|
return app;
|
|
@@ -6662,6 +6762,7 @@ var init_src2 = __esm(() => {
|
|
|
6662
6762
|
init_registry();
|
|
6663
6763
|
init_ws();
|
|
6664
6764
|
init_hooks();
|
|
6765
|
+
init_stream();
|
|
6665
6766
|
init_web();
|
|
6666
6767
|
init_router();
|
|
6667
6768
|
init_reconcile_runs();
|