@vicfei/dsh-super-agent 0.0.0-stage → 0.5.1

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -2
  3. package/README.zh-CN.md +150 -0
  4. package/dist/assess.d.ts +13 -0
  5. package/dist/assess.js +64 -0
  6. package/dist/assess.js.map +1 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +215 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/clip.d.ts +3 -0
  11. package/dist/clip.js +42 -0
  12. package/dist/clip.js.map +1 -0
  13. package/dist/domain.d.ts +67 -0
  14. package/dist/domain.js +6 -0
  15. package/dist/domain.js.map +1 -0
  16. package/dist/engine.d.ts +94 -0
  17. package/dist/engine.js +370 -0
  18. package/dist/engine.js.map +1 -0
  19. package/dist/executor.d.ts +43 -0
  20. package/dist/executor.js +100 -0
  21. package/dist/executor.js.map +1 -0
  22. package/dist/gate.d.ts +34 -0
  23. package/dist/gate.js +66 -0
  24. package/dist/gate.js.map +1 -0
  25. package/dist/github/client.d.ts +63 -0
  26. package/dist/github/client.js +71 -0
  27. package/dist/github/client.js.map +1 -0
  28. package/dist/github/source.d.ts +33 -0
  29. package/dist/github/source.js +110 -0
  30. package/dist/github/source.js.map +1 -0
  31. package/dist/github/state.d.ts +15 -0
  32. package/dist/github/state.js +29 -0
  33. package/dist/github/state.js.map +1 -0
  34. package/dist/index.d.ts +13 -0
  35. package/dist/index.js +16 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/panel.d.ts +8 -0
  38. package/dist/panel.html +128 -0
  39. package/dist/panel.js +54 -0
  40. package/dist/panel.js.map +1 -0
  41. package/dist/runner.d.ts +9 -0
  42. package/dist/runner.js +23 -0
  43. package/dist/runner.js.map +1 -0
  44. package/dist/usage.d.ts +5 -0
  45. package/dist/usage.js +50 -0
  46. package/dist/usage.js.map +1 -0
  47. package/dist/watch.d.ts +19 -0
  48. package/dist/watch.js +53 -0
  49. package/dist/watch.js.map +1 -0
  50. package/dist/work.d.ts +27 -0
  51. package/dist/work.js +127 -0
  52. package/dist/work.js.map +1 -0
  53. package/docs/demo.gif +0 -0
  54. package/examples/executor.json +8 -0
  55. package/examples/policy.json +14 -0
  56. package/package.json +43 -4
@@ -0,0 +1,43 @@
1
+ export interface ExecutorConfig {
2
+ /** Shell command (sh -c). Env: DSH_DIR, DSH_PROMPT_FILE, DSH_TICKET, DSH_THREAD, DSH_REPO, DSH_AUTHOR. */
3
+ command: string;
4
+ /** Local clone to prepare worktrees from. */
5
+ repoDir: string;
6
+ /** Worktrees live here (suggested .state/work). */
7
+ workRoot: string;
8
+ /** Prepare command template with {dir} and {branch}; default uses git worktree add. */
9
+ prepare?: string;
10
+ /** Background cap (docs/003: at most 30 minutes). */
11
+ timeoutMs: number;
12
+ /** Worker slots per run. */
13
+ maxWorkers: number;
14
+ /** Substrings that refuse execution — "fail when invoked by name". */
15
+ blockedCommands: string[];
16
+ }
17
+ export interface ExecutorResult {
18
+ exitCode: number | null;
19
+ timedOut: boolean;
20
+ output: string;
21
+ worktree: string;
22
+ /** Commit the worktree branch was created from — the base for PR delivery diffing. */
23
+ baseSha: string;
24
+ }
25
+ export declare class Executor {
26
+ readonly cfg: ExecutorConfig;
27
+ constructor(cfg: ExecutorConfig);
28
+ /** Throws on blocked command, prepare failure, or unusable config — the caller records failure. */
29
+ run(ticket: {
30
+ id: number;
31
+ threadId: string;
32
+ }, trigger: {
33
+ body: string;
34
+ author: string;
35
+ }, repo: string): Promise<ExecutorResult>;
36
+ }
37
+ export declare function renderPrompt(ticket: {
38
+ id: number;
39
+ threadId: string;
40
+ }, trigger: {
41
+ body: string;
42
+ author: string;
43
+ }, repo: string): string;
@@ -0,0 +1,100 @@
1
+ // Executor — a ticket's work runs in a subprocess in its own git worktree, following
2
+ // dsh-superagent-prototype docs/003: per-ticket Executor directory (git worktree), prepare once,
3
+ // background cap enforced by timeout+kill, blocked commands fail by name.
4
+ // Any CLI counts as the worker: the command string sees only env vars, never credentials.
5
+ import { spawn } from "node:child_process";
6
+ import { mkdirSync, writeFileSync } from "node:fs";
7
+ import { join, resolve } from "node:path";
8
+ const OUTPUT_CAP = 64 * 1024;
9
+ function sh(cmd, opts) {
10
+ return new Promise((resolve) => {
11
+ // detached ⇒ own process group, so a timeout can kill the whole tree — killing only the
12
+ // shell leaves grandchildren holding the stdio pipes (and the promise pending).
13
+ const child = spawn("/bin/sh", ["-c", cmd], { cwd: opts.cwd, env: { ...process.env, ...opts.env }, detached: true });
14
+ let out = "";
15
+ const onData = (d) => {
16
+ if (out.length < OUTPUT_CAP)
17
+ out += d.toString("utf8");
18
+ };
19
+ child.stdout.on("data", onData);
20
+ child.stderr.on("data", onData);
21
+ const timer = setTimeout(() => {
22
+ try {
23
+ if (child.pid !== undefined)
24
+ process.kill(-child.pid, "SIGKILL");
25
+ }
26
+ catch { /* already gone */ }
27
+ }, opts.timeoutMs);
28
+ child.on("close", (code) => {
29
+ clearTimeout(timer);
30
+ resolve({ code, output: out });
31
+ });
32
+ child.on("error", (err) => {
33
+ clearTimeout(timer);
34
+ resolve({ code: null, output: `${out}\n${err.message}` });
35
+ });
36
+ });
37
+ }
38
+ export class Executor {
39
+ cfg;
40
+ constructor(cfg) {
41
+ this.cfg = cfg;
42
+ }
43
+ /** Throws on blocked command, prepare failure, or unusable config — the caller records failure. */
44
+ async run(ticket, trigger, repo) {
45
+ const blocked = this.cfg.blockedCommands.find((b) => this.cfg.command.includes(b));
46
+ if (blocked)
47
+ throw new Error(`executor command blocked by name: ${blocked}`);
48
+ // Absolute paths: the child runs with cwd = worktree, so a relative DSH_DIR/repoDir would
49
+ // resolve against the wrong directory.
50
+ const dir = resolve(join(this.cfg.workRoot, `ticket-${ticket.id}`));
51
+ const branch = `dsh/ticket-${ticket.id}`;
52
+ const repoDir = resolve(this.cfg.repoDir);
53
+ mkdirSync(dir, { recursive: true });
54
+ // -B makes re-running a resumed ticket idempotent (branch reset instead of "already exists").
55
+ const prepare = this.cfg.prepare ?? `git worktree add -B {branch} {dir}`;
56
+ const prep = await sh(prepare.replaceAll("{dir}", JSON.stringify(dir)).replaceAll("{branch}", branch), {
57
+ cwd: repoDir,
58
+ timeoutMs: 10 * 60 * 1000, // prepare gets ten minutes (docs/003)
59
+ });
60
+ if (prep.code !== 0)
61
+ throw new Error(`prepare failed (exit ${prep.code}): ${prep.output.trim().slice(0, 300)}`);
62
+ const promptFile = join(dir, "DSH_PROMPT.md");
63
+ writeFileSync(promptFile, renderPrompt(ticket, trigger, repo));
64
+ const baseSha = (await sh(`git -C ${JSON.stringify(dir)} rev-parse HEAD`, { timeoutMs: 10_000 })).output.trim();
65
+ const run = await sh(this.cfg.command, {
66
+ cwd: dir,
67
+ timeoutMs: this.cfg.timeoutMs,
68
+ env: {
69
+ DSH_DIR: dir,
70
+ DSH_PROMPT_FILE: promptFile,
71
+ DSH_TICKET: String(ticket.id),
72
+ DSH_THREAD: ticket.threadId,
73
+ DSH_REPO: repo,
74
+ DSH_AUTHOR: trigger.author,
75
+ },
76
+ });
77
+ const timedOut = run.code === null;
78
+ return { exitCode: run.code, timedOut, output: run.output, worktree: dir, baseSha };
79
+ }
80
+ }
81
+ export function renderPrompt(ticket, trigger, repo) {
82
+ return [
83
+ `# Ticket #${ticket.id}`,
84
+ "",
85
+ `Author: @${trigger.author}`,
86
+ `Thread: ${ticket.threadId}`,
87
+ `Repository: ${repo}`,
88
+ "",
89
+ "## Request",
90
+ "",
91
+ trigger.body.trim() || "(no body)",
92
+ "",
93
+ "## Rules",
94
+ "",
95
+ "- Do the requested work inside this directory (a git worktree of the repository).",
96
+ "- Your stdout is posted to the thread as the report — end it with a concise summary (≤150 words).",
97
+ "- If the request is beyond what you can do here, say so plainly in the summary instead of improvising.",
98
+ ].join("\n");
99
+ }
100
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
package/dist/gate.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { Policy, Strength, Trigger } from "./domain.js";
2
+ export type GateResult = {
3
+ strength: Strength;
4
+ basis: string;
5
+ } | {
6
+ strength: null;
7
+ basis: string;
8
+ };
9
+ /** A listed bot can produce an inferred candidate, never an explicit request. */
10
+ export declare function evaluateGate(policy: Policy, trigger: Trigger): GateResult;
11
+ export type Assessment = "positive" | "negative";
12
+ export type Resolution = {
13
+ action: "accept";
14
+ strength: Strength;
15
+ basis: string;
16
+ }
17
+ /** observe mode: recorded, nothing accepted. */
18
+ | {
19
+ action: "shadow";
20
+ strength: "inferred";
21
+ basis: string;
22
+ wouldAccept: true;
23
+ } | {
24
+ action: "reject";
25
+ basis: string;
26
+ };
27
+ /**
28
+ * Resolve a gated Trigger under the Policy's accept mode.
29
+ * Invariants (docs/002):
30
+ * - explicit requests work in every mode;
31
+ * - nothing inferred becomes a candidate while mode is off;
32
+ * - accepting an inferred candidate always requires an assessment.
33
+ */
34
+ export declare function resolveCandidate(policy: Policy, gate: GateResult, assessment: Assessment | undefined): Resolution;
package/dist/gate.js ADDED
@@ -0,0 +1,66 @@
1
+ // Host-enforced admission gate — adapted from dsh-superagent-prototype docs/002 (MIT, DeepSeek).
2
+ // The gate is deterministic (Fast Path): sender + scope + addressing checked against the Policy's
3
+ // admission table. LLM assessment (Slow Path) only ever sees candidates that survive here, and
4
+ // only for inferred strength — natural language can never turn an ignore into an admit.
5
+ const lc = (s) => s.toLowerCase();
6
+ /** Missing scope rows fail closed: every cell ignore. */
7
+ function rowOf(policy, trigger) {
8
+ const closed = { scope: trigger.scope, listedNotAddressing: "ignore", unlistedAddressing: "ignore", unlistedNotAddressing: "ignore" };
9
+ return policy.table.find((r) => r.scope === trigger.scope) ?? closed;
10
+ }
11
+ /** A listed bot can produce an inferred candidate, never an explicit request. */
12
+ export function evaluateGate(policy, trigger) {
13
+ const row = rowOf(policy, trigger);
14
+ const listed = policy.allowedList.map(lc).includes(lc(trigger.author.login));
15
+ const knownBot = trigger.author.isBot || policy.botList.map(lc).includes(lc(trigger.author.login));
16
+ if (knownBot) {
17
+ // Bots are checked against the bot list, not the allowed list; ceiling is "inferred".
18
+ if (trigger.addressing)
19
+ return { strength: "inferred", basis: `bot ${trigger.author.login} addressing agent` };
20
+ const cell = row.unlistedNotAddressing; // bots never count as "listed" senders
21
+ return cell === "inferred"
22
+ ? { strength: "inferred", basis: `bot ${trigger.author.login} in receive-all scope` }
23
+ : { strength: null, basis: `bot ${trigger.author.login} not admitted by cell` };
24
+ }
25
+ if (listed) {
26
+ if (trigger.addressing)
27
+ return { strength: "explicit", basis: `allowed ${trigger.author.login} addressing agent` };
28
+ return row.listedNotAddressing === "inferred"
29
+ ? { strength: "inferred", basis: `allowed ${trigger.author.login} not addressing` }
30
+ : { strength: null, basis: `allowed ${trigger.author.login} ignored by cell` };
31
+ }
32
+ const cell = trigger.addressing ? row.unlistedAddressing : row.unlistedNotAddressing;
33
+ return cell === "inferred"
34
+ ? { strength: "inferred", basis: `unlisted ${trigger.author.login} admitted by cell` }
35
+ : { strength: null, basis: `unlisted ${trigger.author.login} not admitted by cell` };
36
+ }
37
+ /**
38
+ * Resolve a gated Trigger under the Policy's accept mode.
39
+ * Invariants (docs/002):
40
+ * - explicit requests work in every mode;
41
+ * - nothing inferred becomes a candidate while mode is off;
42
+ * - accepting an inferred candidate always requires an assessment.
43
+ */
44
+ export function resolveCandidate(policy, gate, assessment) {
45
+ if (gate.strength === null)
46
+ return { action: "reject", basis: gate.basis };
47
+ if (gate.strength === "explicit") {
48
+ // Explicit admission never waits on an assessment or a mode.
49
+ return { action: "accept", strength: "explicit", basis: gate.basis };
50
+ }
51
+ if (assessment === undefined) {
52
+ throw new Error("inferred candidate cannot be resolved without an assessment (direct accept refused)");
53
+ }
54
+ if (assessment === "negative") {
55
+ return { action: "reject", basis: `inferred candidate assessed negative (${gate.basis})` };
56
+ }
57
+ switch (policy.acceptMode) {
58
+ case "off":
59
+ return { action: "reject", basis: "inferred strength has no effect while acceptMode is off" };
60
+ case "observe":
61
+ return { action: "shadow", strength: "inferred", basis: gate.basis, wouldAccept: true };
62
+ case "auto":
63
+ return { action: "accept", strength: "inferred", basis: gate.basis };
64
+ }
65
+ }
66
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ export interface GhActor {
2
+ login: string;
3
+ type: string;
4
+ }
5
+ interface GhItemBase {
6
+ id: number;
7
+ user: GhActor | null;
8
+ body: string | null;
9
+ created_at: string;
10
+ }
11
+ export interface GhIssue extends GhItemBase {
12
+ number: number;
13
+ title: string | null;
14
+ /** Present ⇒ this entry is a PR that leaked into the issues feed. */
15
+ pull_request?: unknown;
16
+ }
17
+ export interface GhIssueComment extends GhItemBase {
18
+ /** api url of the parent issue (issues or PR conversation). */
19
+ issue_url: string;
20
+ }
21
+ export interface GhPull extends GhItemBase {
22
+ number: number;
23
+ title: string | null;
24
+ }
25
+ export interface GhReviewComment extends GhItemBase {
26
+ /** api url of the parent pull. */
27
+ pull_request_url: string;
28
+ in_reply_to_id?: number;
29
+ }
30
+ export declare class GitHubClient {
31
+ private readonly fetchImpl;
32
+ private readonly apiBase;
33
+ constructor(opts?: {
34
+ token?: string;
35
+ fetchImpl?: typeof fetch;
36
+ apiBase?: string;
37
+ });
38
+ private readonly token;
39
+ private request;
40
+ /** Newest-first single page. Filtering by read position happens in the Source. */
41
+ private list;
42
+ listIssues(repo: string): Promise<GhIssue[]>;
43
+ listIssueComments(repo: string): Promise<GhIssueComment[]>;
44
+ listPulls(repo: string): Promise<GhPull[]>;
45
+ listReviewComments(repo: string): Promise<GhReviewComment[]>;
46
+ /** Delivery: write into the originating thread. */
47
+ postIssueComment(repo: string, issueNumber: number, body: string): Promise<{
48
+ id: number;
49
+ }>;
50
+ private defaultBranchCache;
51
+ getDefaultBranch(repo: string): Promise<string>;
52
+ /** Delivery: turn a pushed ticket branch into a pull request. */
53
+ createPullRequest(repo: string, pr: {
54
+ title: string;
55
+ head: string;
56
+ base: string;
57
+ body: string;
58
+ }): Promise<{
59
+ number: number;
60
+ html_url: string;
61
+ }>;
62
+ }
63
+ export {};
@@ -0,0 +1,71 @@
1
+ // GitHub REST client — thin, injectable fetch, credentials never persisted.
2
+ // v0 uses a PAT and polling; a GitHub App is a later Source-kind config, not a code path.
3
+ export class GitHubClient {
4
+ fetchImpl;
5
+ apiBase;
6
+ constructor(opts = {}) {
7
+ this.fetchImpl = opts.fetchImpl ?? fetch;
8
+ this.apiBase = opts.apiBase ?? "https://api.github.com";
9
+ this.token = opts.token ?? "";
10
+ }
11
+ token;
12
+ async request(method, path, body) {
13
+ const res = await this.fetchImpl(`${this.apiBase}${path}`, {
14
+ method,
15
+ headers: {
16
+ accept: "application/vnd.github+json",
17
+ "x-github-api-version": "2022-11-28",
18
+ ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
19
+ ...(body !== undefined ? { "content-type": "application/json" } : {}),
20
+ },
21
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
22
+ });
23
+ if (!res.ok) {
24
+ const text = await res.text().catch(() => "");
25
+ // The three failures a fresh install hits; the raw body alone is cryptic (M5-5).
26
+ const hint = res.status === 401
27
+ ? " — GITHUB_TOKEN was rejected: check its value / expiry"
28
+ : res.status === 403 && /rate limit/i.test(text)
29
+ ? " — rate limited: set GITHUB_TOKEN to raise the limit"
30
+ : res.status === 403
31
+ ? " — forbidden: the token may lack access to this repo"
32
+ : "";
33
+ throw new Error(`GitHub API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}${hint}`);
34
+ }
35
+ return (await res.json());
36
+ }
37
+ /** Newest-first single page. Filtering by read position happens in the Source. */
38
+ list(repo, path) {
39
+ return this.request("GET", `/repos/${repo}${path}?state=all&sort=created&direction=desc&per_page=50`);
40
+ }
41
+ listIssues(repo) {
42
+ return this.list(repo, "/issues");
43
+ }
44
+ listIssueComments(repo) {
45
+ return this.list(repo, "/issues/comments");
46
+ }
47
+ listPulls(repo) {
48
+ return this.list(repo, "/pulls");
49
+ }
50
+ listReviewComments(repo) {
51
+ return this.list(repo, "/pulls/comments");
52
+ }
53
+ /** Delivery: write into the originating thread. */
54
+ postIssueComment(repo, issueNumber, body) {
55
+ return this.request("POST", `/repos/${repo}/issues/${issueNumber}/comments`, { body });
56
+ }
57
+ defaultBranchCache = new Map();
58
+ async getDefaultBranch(repo) {
59
+ const cached = this.defaultBranchCache.get(repo);
60
+ if (cached)
61
+ return cached;
62
+ const data = await this.request("GET", `/repos/${repo}`);
63
+ this.defaultBranchCache.set(repo, data.default_branch);
64
+ return data.default_branch;
65
+ }
66
+ /** Delivery: turn a pushed ticket branch into a pull request. */
67
+ createPullRequest(repo, pr) {
68
+ return this.request("POST", `/repos/${repo}/pulls`, pr);
69
+ }
70
+ }
71
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { Trigger } from "../domain.js";
2
+ import { GitHubClient } from "./client.js";
3
+ import type { SourceState, StateStore } from "./state.js";
4
+ export interface GitHubSourceOptions {
5
+ /** owner/repo */
6
+ repo: string;
7
+ /** The agent's own login — addressing is an @mention of it (case-insensitive). */
8
+ agentLogin: string;
9
+ client: GitHubClient;
10
+ /** Required only for poll(); collect() is stateless. */
11
+ state?: StateStore;
12
+ }
13
+ /** True for App accounts and `[bot]` logins — bots cap at inferred strength in the gate. */
14
+ export declare function isBotActor(login: string, type: string): boolean;
15
+ export declare class GitHubSource {
16
+ private readonly opts;
17
+ constructor(opts: GitHubSourceOptions);
18
+ /**
19
+ * Pure collection: compute triggers newer than `from` and the next state, persisting nothing.
20
+ * The Engine ingests triggers + positions in one transaction (exactly-once); poll() is the
21
+ * file-backed convenience wrapper kept for standalone use. `exclude` drops the agent's own
22
+ * posted comments (docs/001: a Source "filters its own messages").
23
+ */
24
+ collect(from: SourceState, opts?: {
25
+ exclude?: ReadonlySet<string>;
26
+ }): Promise<{
27
+ triggers: Trigger[];
28
+ next: SourceState;
29
+ }>;
30
+ /** File-backed wrapper: collect + persist positions (at-most-once across a crash). */
31
+ poll(): Promise<Trigger[]>;
32
+ private toTrigger;
33
+ }
@@ -0,0 +1,110 @@
1
+ // GitHub Source — turns repo events into domain Triggers (docs/001: "A Source produces Triggers").
2
+ // Four feeds polled newest-first; an item is new iff its id exceeds the feed's read position.
3
+ // PRs leak into the issues listing (same id space) and are skipped there — the pulls feed owns them.
4
+ const SCOPE_OF_FEED = {
5
+ issue: "issue",
6
+ issue_comment: "issue_comment",
7
+ pr: "pr",
8
+ review_comment: "review_comment",
9
+ };
10
+ /** True for App accounts and `[bot]` logins — bots cap at inferred strength in the gate. */
11
+ export function isBotActor(login, type) {
12
+ return type === "Bot" || login.endsWith("[bot]");
13
+ }
14
+ function issueNumberFromUrl(url) {
15
+ const m = url.match(/\/(?:issues|pulls)\/(\d+)$/);
16
+ return m ? m[1] : "unknown";
17
+ }
18
+ export class GitHubSource {
19
+ opts;
20
+ constructor(opts) {
21
+ this.opts = opts;
22
+ }
23
+ /**
24
+ * Pure collection: compute triggers newer than `from` and the next state, persisting nothing.
25
+ * The Engine ingests triggers + positions in one transaction (exactly-once); poll() is the
26
+ * file-backed convenience wrapper kept for standalone use. `exclude` drops the agent's own
27
+ * posted comments (docs/001: a Source "filters its own messages").
28
+ */
29
+ async collect(from, opts = {}) {
30
+ const state = { positions: { ...from.positions } };
31
+ const out = [];
32
+ const wanted = (id) => !opts.exclude?.has(id);
33
+ // Each feed compares against its position AT COLLECT START (frozen) and advances once at the
34
+ // end — comparing against a mid-loop maximum would swallow older-but-new items (newest-first).
35
+ const issues = await this.opts.client.listIssues(this.opts.repo);
36
+ let start = state.positions.issue;
37
+ let maxSeen = start;
38
+ for (const it of issues) {
39
+ if (it.id <= start)
40
+ break;
41
+ if (it.pull_request !== undefined)
42
+ continue; // pulls feed owns PRs
43
+ if (wanted(`github:issue:${it.id}`))
44
+ out.push(this.toTrigger("issue", it.id, it.user, it.body, it.created_at, `github:${this.opts.repo}/issues/${it.number}`, SCOPE_OF_FEED.issue));
45
+ maxSeen = Math.max(maxSeen, it.id);
46
+ }
47
+ state.positions.issue = maxSeen;
48
+ const comments = await this.opts.client.listIssueComments(this.opts.repo);
49
+ start = state.positions.issue_comment;
50
+ maxSeen = start;
51
+ for (const it of comments) {
52
+ if (it.id <= start)
53
+ break;
54
+ const n = issueNumberFromUrl(it.issue_url);
55
+ if (wanted(`github:issue_comment:${it.id}`))
56
+ out.push(this.toTrigger("issue_comment", it.id, it.user, it.body, it.created_at, `github:${this.opts.repo}/issues/${n}`, SCOPE_OF_FEED.issue_comment));
57
+ maxSeen = Math.max(maxSeen, it.id);
58
+ }
59
+ state.positions.issue_comment = maxSeen;
60
+ const pulls = await this.opts.client.listPulls(this.opts.repo);
61
+ start = state.positions.pr;
62
+ maxSeen = start;
63
+ for (const it of pulls) {
64
+ if (it.id <= start)
65
+ break;
66
+ if (wanted(`github:pr:${it.id}`))
67
+ out.push(this.toTrigger("pr", it.id, it.user, it.body, it.created_at, `github:${this.opts.repo}/pulls/${it.number}`, SCOPE_OF_FEED.pr));
68
+ maxSeen = Math.max(maxSeen, it.id);
69
+ }
70
+ state.positions.pr = maxSeen;
71
+ const reviews = await this.opts.client.listReviewComments(this.opts.repo);
72
+ start = state.positions.review_comment;
73
+ maxSeen = start;
74
+ for (const it of reviews) {
75
+ if (it.id <= start)
76
+ break;
77
+ const n = issueNumberFromUrl(it.pull_request_url);
78
+ if (wanted(`github:review_comment:${it.id}`))
79
+ out.push(this.toTrigger("review_comment", it.id, it.user, it.body, it.created_at, `github:${this.opts.repo}/pulls/${n}`, SCOPE_OF_FEED.review_comment));
80
+ maxSeen = Math.max(maxSeen, it.id);
81
+ }
82
+ state.positions.review_comment = maxSeen;
83
+ return { triggers: out, next: state };
84
+ }
85
+ /** File-backed wrapper: collect + persist positions (at-most-once across a crash). */
86
+ async poll() {
87
+ if (!this.opts.state)
88
+ throw new Error("poll() requires a StateStore; use collect() otherwise");
89
+ const from = this.opts.state.load();
90
+ const { triggers, next } = await this.collect(from);
91
+ this.opts.state.save(next); // persist before handing out — see state.ts note
92
+ return triggers;
93
+ }
94
+ toTrigger(feed, id, user, body, ts, threadId, scope) {
95
+ const login = user?.login ?? "ghost";
96
+ const mention = `@${this.opts.agentLogin.toLowerCase()}`;
97
+ const addressing = (body ?? "").toLowerCase().includes(mention);
98
+ return {
99
+ id: `github:${feed}:${id}`,
100
+ source: "github",
101
+ scope,
102
+ threadId,
103
+ author: { login, isBot: user ? isBotActor(login, user.type) : true },
104
+ addressing,
105
+ body: body ?? "",
106
+ ts,
107
+ };
108
+ }
109
+ }
110
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/github/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export type Feed = "issue" | "issue_comment" | "pr" | "review_comment";
2
+ export interface SourceState {
3
+ /** Max item id already taken per feed; 0 = never polled. Item ids are increasing ints. */
4
+ positions: Record<Feed, number>;
5
+ }
6
+ export interface StateStore {
7
+ load(): SourceState;
8
+ save(state: SourceState): void;
9
+ }
10
+ export declare class FileSourceState implements StateStore {
11
+ private readonly file;
12
+ constructor(file: string);
13
+ load(): SourceState;
14
+ save(state: SourceState): void;
15
+ }
@@ -0,0 +1,29 @@
1
+ // Read position per feed — docs/001: "A Source holds its own read position (a restart must not
2
+ // replay events)". File-backed for milestone 1; milestone 2's Engine moves it into SQLite in the
3
+ // same transaction as the triggers it produced (exactly-once). Until then: positions are persisted
4
+ // before triggers are handed out (at-most-once across a crash, never replay).
5
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ export class FileSourceState {
8
+ file;
9
+ constructor(file) {
10
+ this.file = file;
11
+ }
12
+ load() {
13
+ const zeros = { issue: 0, issue_comment: 0, pr: 0, review_comment: 0 };
14
+ try {
15
+ const raw = JSON.parse(readFileSync(this.file, "utf8"));
16
+ return { positions: { ...zeros, ...raw.positions } };
17
+ }
18
+ catch {
19
+ return { positions: zeros };
20
+ }
21
+ }
22
+ save(state) {
23
+ const tmp = join(dirname(this.file), `.${Math.random().toString(36).slice(2)}.tmp`);
24
+ mkdirSync(dirname(this.file), { recursive: true });
25
+ writeFileSync(tmp, JSON.stringify(state, null, 2));
26
+ renameSync(tmp, this.file); // atomic on POSIX
27
+ }
28
+ }
29
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/github/state.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,iGAAiG;AACjG,mGAAmG;AACnG,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAc1C,MAAM,OAAO,eAAe;IACT,IAAI,CAAS;IAE9B,YAAY,IAAY;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI;QACF,MAAM,KAAK,GAAyB,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QAC7F,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;YACvE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAkB;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACpF,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB;IAChD,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ export * from "./domain.js";
2
+ export { evaluateGate, resolveCandidate, type GateResult, type Resolution, type Assessment } from "./gate.js";
3
+ export { Engine, type IngestItem, type TicketView, type DecisionView, type IngestReport } from "./engine.js";
4
+ export { GitHubClient } from "./github/client.js";
5
+ export { GitHubSource, isBotActor } from "./github/source.js";
6
+ export { FileSourceState, type StateStore, type SourceState } from "./github/state.js";
7
+ export { Executor, renderPrompt, type ExecutorConfig, type ExecutorResult } from "./executor.js";
8
+ export { runWork, deliverPullRequest, type WorkReport } from "./work.js";
9
+ export { runWatch, type WatchOptions } from "./watch.js";
10
+ export { classify, formatOutcome, type PollOutcome } from "./runner.js";
11
+ export { llmAssessor, stubAssessor, type Assessor } from "./assess.js";
12
+ export { clipReply, wordCount } from "./clip.js";
13
+ export { startPanel } from "./panel.js";
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // Public API — everything importable for programmatic use.
2
+ // The CLI (`dist/cli.js`, bin `dsh-super-agent`) covers the operational surface.
3
+ export * from "./domain.js";
4
+ export { evaluateGate, resolveCandidate } from "./gate.js";
5
+ export { Engine } from "./engine.js";
6
+ export { GitHubClient } from "./github/client.js";
7
+ export { GitHubSource, isBotActor } from "./github/source.js";
8
+ export { FileSourceState } from "./github/state.js";
9
+ export { Executor, renderPrompt } from "./executor.js";
10
+ export { runWork, deliverPullRequest } from "./work.js";
11
+ export { runWatch } from "./watch.js";
12
+ export { classify, formatOutcome } from "./runner.js";
13
+ export { llmAssessor, stubAssessor } from "./assess.js";
14
+ export { clipReply, wordCount } from "./clip.js";
15
+ export { startPanel } from "./panel.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,iFAAiF;AAEjF,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAqD,MAAM,WAAW,CAAC;AAC9G,OAAO,EAAE,MAAM,EAA0E,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAqC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAA4C,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAmB,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAqB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAoB,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}