opencode-superagent 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +336 -2
- package/agent/superagent-router.md +7 -0
- package/agent/superagent-worker.md +10 -0
- package/dist/server.d.ts +237 -0
- package/dist/server.js +148 -0
- package/dist/src/abort.d.ts +31 -0
- package/dist/src/abort.js +171 -0
- package/dist/src/agents.d.ts +51 -0
- package/dist/src/agents.js +83 -0
- package/dist/src/context.d.ts +27 -0
- package/dist/src/context.js +114 -0
- package/dist/src/dirs.d.ts +45 -0
- package/dist/src/dirs.js +129 -0
- package/dist/src/dispatch.d.ts +38 -0
- package/dist/src/dispatch.js +296 -0
- package/dist/src/format.d.ts +3 -0
- package/dist/src/format.js +14 -0
- package/dist/src/guard.d.ts +4 -0
- package/dist/src/guard.js +9 -0
- package/dist/src/prompts.d.ts +1 -0
- package/dist/src/prompts.js +37 -0
- package/dist/src/protocol.d.ts +219 -0
- package/dist/src/protocol.js +95 -0
- package/dist/src/recall.d.ts +58 -0
- package/dist/src/recall.js +309 -0
- package/dist/src/report.d.ts +78 -0
- package/dist/src/report.js +176 -0
- package/dist/src/router.d.ts +34 -0
- package/dist/src/router.js +234 -0
- package/dist/src/session-meta.d.ts +42 -0
- package/dist/src/session-meta.js +91 -0
- package/dist/src/sleep.d.ts +35 -0
- package/dist/src/sleep.js +58 -0
- package/dist/src/state.d.ts +126 -0
- package/dist/src/state.js +480 -0
- package/dist/src/ticket-tools.d.ts +45 -0
- package/dist/src/ticket-tools.js +148 -0
- package/dist/src/ticket-view.d.ts +62 -0
- package/dist/src/ticket-view.js +135 -0
- package/dist/src/ticket.d.ts +253 -0
- package/dist/src/ticket.js +717 -0
- package/dist/src/tools.d.ts +85 -0
- package/dist/src/tools.js +392 -0
- package/dist/src/watchdog.d.ts +23 -0
- package/dist/src/watchdog.js +94 -0
- package/dist/src/watcher.d.ts +17 -0
- package/dist/src/watcher.js +165 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +1057 -0
- package/package.json +80 -4
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import { buildReport, lastAssistantText, reportNotice, reportSummary, saveReport } from "./report.js";
|
|
3
|
+
import { ensureRouter } from "./router.js";
|
|
4
|
+
import { saveWorker, store, toSessionID } from "./state.js";
|
|
5
|
+
import { closeTicketsForWorker } from "./ticket.js";
|
|
6
|
+
/**
|
|
7
|
+
* Capture one finished report: the complete text goes to `report:<sessionID>`
|
|
8
|
+
* first, then the worker record keeps a short summary for status lines and the
|
|
9
|
+
* router injection. The full text is written before anything else so a crash
|
|
10
|
+
* between the two writes can only lose the summary, never the evidence.
|
|
11
|
+
*/
|
|
12
|
+
export const captureReport = Effect.fn("superagent.captureReport")(function* (ctx, worker, text) {
|
|
13
|
+
const record = buildReport(worker.sessionID, text);
|
|
14
|
+
yield* saveReport(ctx, record);
|
|
15
|
+
worker.lastReport = reportSummary(record);
|
|
16
|
+
return record;
|
|
17
|
+
});
|
|
18
|
+
function errorDetail(data) {
|
|
19
|
+
if (typeof data !== "object" || data === null)
|
|
20
|
+
return undefined;
|
|
21
|
+
const error = data.error;
|
|
22
|
+
if (typeof error !== "object" || error === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
const typed = error;
|
|
25
|
+
const parts = [];
|
|
26
|
+
if (typeof typed.type === "string" && typed.type !== "")
|
|
27
|
+
parts.push(typed.type);
|
|
28
|
+
if (typeof typed.status === "number")
|
|
29
|
+
parts.push(`status ${typed.status}`);
|
|
30
|
+
if (typeof typed.message === "string") {
|
|
31
|
+
const message = typed.message.trim().slice(0, 500);
|
|
32
|
+
if (message !== "")
|
|
33
|
+
parts.push(message);
|
|
34
|
+
}
|
|
35
|
+
if (parts.length === 0)
|
|
36
|
+
return undefined;
|
|
37
|
+
return parts.join(" ");
|
|
38
|
+
}
|
|
39
|
+
function interruptDetail(data) {
|
|
40
|
+
if (typeof data !== "object" || data === null)
|
|
41
|
+
return undefined;
|
|
42
|
+
const reason = data.reason;
|
|
43
|
+
return typeof reason === "string" && reason !== "" ? `interrupted (${reason})` : "interrupted";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply a `session.idle` notification. Only a running worker may become idle:
|
|
47
|
+
* `done`, `failed` and `aborted` are terminal for the lifecycle, and an idle
|
|
48
|
+
* event must never resurrect or overwrite them.
|
|
49
|
+
*/
|
|
50
|
+
export const handleIdle = Effect.fn("superagent.workerIdle")(function* (ctx, sessionID) {
|
|
51
|
+
const worker = store.workers.get(sessionID);
|
|
52
|
+
if (worker === undefined || worker.state !== "running")
|
|
53
|
+
return;
|
|
54
|
+
worker.state = "idle";
|
|
55
|
+
yield* saveWorker(ctx, worker);
|
|
56
|
+
});
|
|
57
|
+
const handleEnded = Effect.fn("superagent.workerEnded")(function* (ctx, sessionID, state, detail) {
|
|
58
|
+
const sessions = ctx.session;
|
|
59
|
+
const worker = store.workers.get(sessionID);
|
|
60
|
+
if (worker === undefined)
|
|
61
|
+
return;
|
|
62
|
+
// A terminated worker keeps `aborted`; the abort tool already reported the
|
|
63
|
+
// outcome to whoever asked, so do not re-announce it as finished.
|
|
64
|
+
if (worker.state === "aborted")
|
|
65
|
+
return;
|
|
66
|
+
worker.state = state;
|
|
67
|
+
const messages = yield* sessions
|
|
68
|
+
.context({ sessionID: toSessionID(sessionID) })
|
|
69
|
+
.pipe(Effect.catch(() => Effect.succeed([])));
|
|
70
|
+
const report = lastAssistantText(messages);
|
|
71
|
+
// Full text first: `report:<sessionID>` is the read-back handle. The worker
|
|
72
|
+
// record only keeps a summary, which is what list/status lines show.
|
|
73
|
+
const captured = report === undefined ? undefined : yield* captureReport(ctx, worker, report);
|
|
74
|
+
yield* saveWorker(ctx, worker);
|
|
75
|
+
// A worker that reached a terminal state closes its in-progress ticket.
|
|
76
|
+
// Interruptions (`idle`) leave it open: the abort tool closes it explicitly
|
|
77
|
+
// and a plain interrupt may still be resumed with a follow-up.
|
|
78
|
+
if (state === "done" || state === "failed") {
|
|
79
|
+
yield* closeTicketsForWorker(ctx, {
|
|
80
|
+
sessionID,
|
|
81
|
+
outcome: state,
|
|
82
|
+
summary: worker.lastReport ?? report ?? detail ?? `${state} (worker ${sessionID})`,
|
|
83
|
+
}).pipe(Effect.catch((cause) => Effect.logWarning("superagent failed to close ticket for ended worker", { sessionID, cause })));
|
|
84
|
+
}
|
|
85
|
+
// Keep the thread subscriptions after the worker ends: follow-ups on the
|
|
86
|
+
// same thread resume this worker (prompting a finished session starts a new
|
|
87
|
+
// execution in it) instead of spawning a fresh session per message. A
|
|
88
|
+
// genuinely new task clears the affinity.
|
|
89
|
+
const info = yield* sessions.get({ sessionID: toSessionID(sessionID) }).pipe(Effect.map((session) => session), Effect.catch(() => Effect.succeed(undefined)));
|
|
90
|
+
const rawOutcome = info?.outcome;
|
|
91
|
+
const outcome = rawOutcome === "succeeded" || rawOutcome === "failed" || rawOutcome === "interrupted"
|
|
92
|
+
? rawOutcome
|
|
93
|
+
: state === "done"
|
|
94
|
+
? "succeeded"
|
|
95
|
+
: state === "failed"
|
|
96
|
+
? "failed"
|
|
97
|
+
: "interrupted";
|
|
98
|
+
const label = worker.title === undefined ? `Worker ${sessionID}` : `Worker ${sessionID} (${worker.title})`;
|
|
99
|
+
const text = report !== undefined && captured !== undefined
|
|
100
|
+
? [`${label} finished (${state}):`, reportSummary(captured), "---", reportNotice(captured)].join("\n")
|
|
101
|
+
: `${label} finished (${state}) with no report (outcome: ${outcome}).${detail === undefined ? "" : ` Cause: ${detail}.`} Suggest: 可换模型重派.`;
|
|
102
|
+
const routerID = yield* ensureRouter(ctx).pipe(Effect.catch((cause) => Effect.logWarning("superagent cannot report without a router", { cause }).pipe(Effect.as(undefined))));
|
|
103
|
+
if (routerID === undefined)
|
|
104
|
+
return;
|
|
105
|
+
yield* sessions
|
|
106
|
+
.synthetic({ sessionID: toSessionID(routerID), text, delivery: "steer" })
|
|
107
|
+
.pipe(Effect.catch((cause) => Effect.logWarning("superagent failed to report to router", { cause })));
|
|
108
|
+
});
|
|
109
|
+
export const registerWatcher = Effect.fn("superagent.registerWatcher")(function* (ctx) {
|
|
110
|
+
yield* ctx.event
|
|
111
|
+
.subscribe({
|
|
112
|
+
types: [
|
|
113
|
+
"session.execution.started",
|
|
114
|
+
"session.execution.succeeded",
|
|
115
|
+
"session.execution.failed",
|
|
116
|
+
"session.execution.interrupted",
|
|
117
|
+
"session.idle",
|
|
118
|
+
],
|
|
119
|
+
})
|
|
120
|
+
.pipe(Stream.runForEach((event) => Effect.gen(function* () {
|
|
121
|
+
const sessionID = event.data.sessionID;
|
|
122
|
+
switch (event.type) {
|
|
123
|
+
case "session.execution.started": {
|
|
124
|
+
if (store.routerID !== undefined && event.data.sessionID === store.routerID)
|
|
125
|
+
store.routerBusy = true;
|
|
126
|
+
const worker = store.workers.get(sessionID);
|
|
127
|
+
if (worker === undefined)
|
|
128
|
+
return;
|
|
129
|
+
worker.state = "running";
|
|
130
|
+
yield* saveWorker(ctx, worker);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
case "session.execution.succeeded": {
|
|
134
|
+
if (store.routerID !== undefined && event.data.sessionID === store.routerID) {
|
|
135
|
+
store.routerBusy = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
yield* handleEnded(ctx, sessionID, "done");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
case "session.execution.failed": {
|
|
142
|
+
if (store.routerID !== undefined && event.data.sessionID === store.routerID) {
|
|
143
|
+
store.routerBusy = false;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
yield* handleEnded(ctx, sessionID, "failed", errorDetail(event.data));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
case "session.execution.interrupted": {
|
|
150
|
+
if (store.routerID !== undefined && event.data.sessionID === store.routerID) {
|
|
151
|
+
store.routerBusy = false;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
yield* handleEnded(ctx, sessionID, "idle", interruptDetail(event.data));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
case "session.idle": {
|
|
158
|
+
yield* handleIdle(ctx, sessionID);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
default:
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}).pipe(Effect.catch((cause) => Effect.logWarning("superagent watcher failed", { cause })))), Effect.forkScoped);
|
|
165
|
+
});
|
package/dist/tui.d.ts
ADDED