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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -2
  3. package/agent/superagent-router.md +7 -0
  4. package/agent/superagent-worker.md +10 -0
  5. package/dist/server.d.ts +237 -0
  6. package/dist/server.js +148 -0
  7. package/dist/src/abort.d.ts +31 -0
  8. package/dist/src/abort.js +171 -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 +27 -0
  12. package/dist/src/context.js +114 -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 +296 -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 +37 -0
  23. package/dist/src/protocol.d.ts +219 -0
  24. package/dist/src/protocol.js +95 -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 +78 -0
  28. package/dist/src/report.js +176 -0
  29. package/dist/src/router.d.ts +34 -0
  30. package/dist/src/router.js +234 -0
  31. package/dist/src/session-meta.d.ts +42 -0
  32. package/dist/src/session-meta.js +91 -0
  33. package/dist/src/sleep.d.ts +35 -0
  34. package/dist/src/sleep.js +58 -0
  35. package/dist/src/state.d.ts +126 -0
  36. package/dist/src/state.js +480 -0
  37. package/dist/src/ticket-tools.d.ts +45 -0
  38. package/dist/src/ticket-tools.js +148 -0
  39. package/dist/src/ticket-view.d.ts +62 -0
  40. package/dist/src/ticket-view.js +135 -0
  41. package/dist/src/ticket.d.ts +253 -0
  42. package/dist/src/ticket.js +717 -0
  43. package/dist/src/tools.d.ts +85 -0
  44. package/dist/src/tools.js +392 -0
  45. package/dist/src/watchdog.d.ts +23 -0
  46. package/dist/src/watchdog.js +94 -0
  47. package/dist/src/watcher.d.ts +17 -0
  48. package/dist/src/watcher.js +165 -0
  49. package/dist/tui.d.ts +3 -0
  50. package/dist/tui.js +1057 -0
  51. 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
@@ -0,0 +1,3 @@
1
+ import { Plugin } from "@opencode/plugin/tui";
2
+ declare const _default: Plugin.Definition;
3
+ export default _default;