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,35 @@
1
+ import type { Plugin } from "@opencode/plugin/effect";
2
+ import { Tool } from "@opencode/schema/tool";
3
+ import { Effect, Schema } from "effect";
4
+ /**
5
+ * Router-only `sleep` tool: a bounded, real pause after `dispatch` so the
6
+ * Worker gets execution time before the Router decides to act again. It is a
7
+ * timer, never a busy loop, so the Router session (and every other fiber in
8
+ * the process) stays responsive while it waits.
9
+ */
10
+ export declare const SLEEP_DEFAULT_SECONDS = 30;
11
+ export declare const SLEEP_MIN_SECONDS = 1;
12
+ export declare const SLEEP_MAX_SECONDS = 600;
13
+ export declare const SleepInput: Schema.Struct<{
14
+ readonly seconds: Schema.optionalKey<Schema.Number>;
15
+ }>;
16
+ export declare const SleepOutput: Schema.Struct<{
17
+ readonly slept: Schema.Number;
18
+ readonly text: Schema.String;
19
+ }>;
20
+ interface SleepInput {
21
+ readonly seconds?: number;
22
+ }
23
+ /** Performs the wait; tests inject a no-op so the default 30s never runs. */
24
+ export type Sleeper = (seconds: number) => Effect.Effect<void>;
25
+ /** The production sleeper: a platform timer, not a spin loop. */
26
+ export declare const effectSleeper: Sleeper;
27
+ export declare const runSleep: (input: SleepInput, caller: string, sleeper?: Sleeper | undefined) => Effect.Effect<{
28
+ output: {
29
+ slept: number;
30
+ text: string;
31
+ };
32
+ content: string;
33
+ }, Tool.Error, never>;
34
+ export declare const registerSleep: (ctx: Plugin.Context) => Effect.Effect<void, never, import("effect/Scope").Scope>;
35
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Tool } from "@opencode/schema/tool";
2
+ import { Duration, Effect, Schema } from "effect";
3
+ import { requireRouter } from "./guard.js";
4
+ import { specFor, toolPermission } from "./tools.js";
5
+ /**
6
+ * Router-only `sleep` tool: a bounded, real pause after `dispatch` so the
7
+ * Worker gets execution time before the Router decides to act again. It is a
8
+ * timer, never a busy loop, so the Router session (and every other fiber in
9
+ * the process) stays responsive while it waits.
10
+ */
11
+ export const SLEEP_DEFAULT_SECONDS = 30;
12
+ export const SLEEP_MIN_SECONDS = 1;
13
+ export const SLEEP_MAX_SECONDS = 600;
14
+ export const SleepInput = Schema.Struct({
15
+ seconds: Schema.optionalKey(Schema.Number),
16
+ });
17
+ export const SleepOutput = Schema.Struct({
18
+ slept: Schema.Number,
19
+ text: Schema.String,
20
+ });
21
+ /** The production sleeper: a platform timer, not a spin loop. */
22
+ export const effectSleeper = (seconds) => Effect.sleep(Duration.seconds(seconds));
23
+ export const runSleep = Effect.fn("superagent.sleep")(function* (input, caller, sleeper = effectSleeper) {
24
+ yield* requireRouter(caller, "sleep");
25
+ const seconds = input.seconds ?? SLEEP_DEFAULT_SECONDS;
26
+ // The registered schema already rejects non-numbers at the tool boundary;
27
+ // this re-check keeps direct/RPC-style callers from bypassing validation.
28
+ if (typeof seconds !== "number" || !Number.isFinite(seconds)) {
29
+ return yield* new Tool.Error({
30
+ message: `sleep seconds must be a number between ${SLEEP_MIN_SECONDS} and ${SLEEP_MAX_SECONDS} (got ${String(input.seconds)})`,
31
+ });
32
+ }
33
+ if (!Number.isInteger(seconds)) {
34
+ return yield* new Tool.Error({
35
+ message: `sleep seconds must be an integer between ${SLEEP_MIN_SECONDS} and ${SLEEP_MAX_SECONDS} (got ${seconds})`,
36
+ });
37
+ }
38
+ if (seconds < SLEEP_MIN_SECONDS || seconds > SLEEP_MAX_SECONDS) {
39
+ return yield* new Tool.Error({
40
+ message: `sleep seconds must be between ${SLEEP_MIN_SECONDS} and ${SLEEP_MAX_SECONDS} (got ${seconds})`,
41
+ });
42
+ }
43
+ yield* sleeper(seconds);
44
+ const text = `slept ${seconds}s`;
45
+ return { output: { slept: seconds, text }, content: text };
46
+ });
47
+ export const registerSleep = Effect.fn("superagent.registerSleep")(function* (ctx) {
48
+ yield* ctx.tool.transform((editor) => {
49
+ editor.add({
50
+ name: "sleep",
51
+ description: specFor("sleep").description,
52
+ input: SleepInput,
53
+ output: SleepOutput,
54
+ options: { permission: toolPermission(specFor("sleep")) },
55
+ execute: (input, context) => runSleep(input, context.sessionID),
56
+ });
57
+ });
58
+ });
@@ -0,0 +1,126 @@
1
+ import type { Plugin } from "@opencode/plugin/effect";
2
+ import type { Session } from "@opencode/schema/session";
3
+ import { Effect, type Schema } from "effect";
4
+ export type WorkerState = "running" | "idle" | "failed" | "done" | "aborted";
5
+ export interface WorkerRecord {
6
+ readonly sessionID: string;
7
+ readonly title?: string;
8
+ /**
9
+ * Primary thread this worker was dispatched for. Legacy records decode to
10
+ * `""` and `hydrate` backfills it from `thread.workers`, so every live record
11
+ * carries a forward link to its thread without losing the multi-thread
12
+ * `threads` list.
13
+ */
14
+ threadID: string;
15
+ /** Trigger that authorized the dispatch, when the record knows it. */
16
+ triggerID?: string;
17
+ readonly threads: Array<string>;
18
+ state: WorkerState;
19
+ readonly createdAt: number;
20
+ /** Last persisted write. Refreshed by `saveWorker`, so an aborted record keeps a traceable timestamp. */
21
+ updatedAt?: number;
22
+ lastReport?: string;
23
+ directory?: string;
24
+ }
25
+ export interface ThreadRecord {
26
+ readonly threadID: string;
27
+ readonly workers: Array<string>;
28
+ readonly updatedAt: number;
29
+ }
30
+ export interface TriggerInput {
31
+ readonly id?: string;
32
+ readonly source: string;
33
+ readonly threadID: string;
34
+ readonly author?: string;
35
+ readonly text: string;
36
+ readonly title?: string;
37
+ readonly createdAt?: number;
38
+ }
39
+ export interface PendingTrigger {
40
+ readonly trigger: TriggerInput;
41
+ readonly arrivedAt: number;
42
+ }
43
+ export type TriggerRoute = "router" | "worker";
44
+ export interface TriggerRecord {
45
+ readonly id: string;
46
+ readonly source: string;
47
+ readonly threadID: string;
48
+ readonly author?: string;
49
+ readonly text: string;
50
+ readonly title?: string;
51
+ readonly createdAt: number;
52
+ route?: TriggerRoute;
53
+ sessionID?: string;
54
+ /** Workers dispatched from this trigger (reverse of `WorkerRecord.triggerID`). */
55
+ workerIDs?: Array<string>;
56
+ }
57
+ export interface DirectTriggerInput {
58
+ readonly id: string;
59
+ readonly source: string;
60
+ readonly threadID: string;
61
+ readonly author?: string;
62
+ readonly text: string;
63
+ readonly createdAt: number;
64
+ }
65
+ export declare function triggerIdFor(input: TriggerInput): string;
66
+ /**
67
+ * Queue that transparently records trigger history. `server.ts` and
68
+ * `router.ts` keep using plain `push`/`splice`; interception here writes
69
+ * the memory map synchronously and persists to KV in the background, so
70
+ * no caller changes are needed.
71
+ */
72
+ export declare class PendingQueue extends Array<PendingTrigger> {
73
+ push(...items: Array<PendingTrigger>): number;
74
+ splice(start: number, deleteCount?: number, ...items: Array<PendingTrigger>): Array<PendingTrigger>;
75
+ }
76
+ /**
77
+ * Record a trigger delivered straight to a worker (it never passes the
78
+ * pending queue). The text comes from the admitted prompt, so it carries
79
+ * the same truncation as the worker-visible format; queued triggers keep
80
+ * their full text via `rememberQueued`.
81
+ */
82
+ export declare function upsertDirectTrigger(input: DirectTriggerInput, sessionID: string): TriggerRecord;
83
+ export interface Store {
84
+ routerID: string | undefined;
85
+ routerBusy: boolean;
86
+ readonly workers: Map<string, WorkerRecord>;
87
+ readonly threads: Map<string, Array<string>>;
88
+ readonly pending: Array<PendingTrigger>;
89
+ readonly triggers: Map<string, TriggerRecord>;
90
+ triggerTotal: number;
91
+ }
92
+ export declare const store: Store;
93
+ export declare const ROUTER_KEY = "router";
94
+ export declare const WORKER_PREFIX = "worker:";
95
+ export declare const THREAD_PREFIX = "thread:";
96
+ export declare const TRIGGER_PREFIX = "trigger:";
97
+ export declare const workerKey: (sessionID: string) => string;
98
+ export declare const threadKey: (threadID: string) => string;
99
+ export declare const triggerKey: (id: string) => string;
100
+ /** Brands are compile-time only, so a validated session id string converts freely. */
101
+ export declare const toSessionID: (id: string) => Session.ID;
102
+ /** A Session is running while `time.busy` is newer than the last completed execution's `time.idle`. */
103
+ export declare function isRunning(info: Session.Info): boolean;
104
+ export declare function decodeWorker(value: Schema.Json | undefined): WorkerRecord | undefined;
105
+ export declare function decodeThread(value: Schema.Json | undefined): ThreadRecord | undefined;
106
+ export declare function decodeTrigger(value: Schema.Json | undefined): TriggerRecord | undefined;
107
+ export declare function encodeWorker(record: WorkerRecord): Schema.Json;
108
+ export declare function encodeThread(record: ThreadRecord): Schema.Json;
109
+ export declare function encodeTrigger(record: TriggerRecord): Schema.Json;
110
+ /**
111
+ * Load KV state into the in-memory maps. Storage is the unique source of
112
+ * truth; every loaded session is validated with `session.get` and stale
113
+ * entries are cleaned from both memory and KV.
114
+ */
115
+ export declare const hydrate: (ctx: Plugin.Context) => Effect.Effect<void, never, never>;
116
+ /** Every worker write refreshes `updatedAt`, so an aborted record keeps a traceable timestamp. */
117
+ export declare const saveWorker: (ctx: Plugin.Context, record: WorkerRecord) => Effect.Effect<void, never, never>;
118
+ export declare const saveTrigger: (ctx: Plugin.Context, record: TriggerRecord) => Effect.Effect<void, never, never>;
119
+ /**
120
+ * Record the worker on the trigger that authorized it (`trigger.workerIDs`),
121
+ * completing the reverse edge of `WorkerRecord.triggerID`. Memory holds only
122
+ * the newest triggers, so an older trigger is read from KV once; a missing
123
+ * trigger is not invented. Repeating the call is a no-op.
124
+ */
125
+ export declare const linkTriggerToWorker: (ctx: Plugin.Context, triggerID: string, sessionID: string) => Effect.Effect<boolean, never, never>;
126
+ export declare const saveThread: (ctx: Plugin.Context, record: ThreadRecord) => Effect.Effect<void, never, never>;