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