@open330/oac 2026.221.2 → 2026.222.2

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 (38) hide show
  1. package/README.md +170 -1
  2. package/dist/budget/index.js +1 -1
  3. package/dist/{chunk-EYUQMPVO.js → chunk-27FEE5KS.js} +86 -34
  4. package/dist/chunk-27FEE5KS.js.map +1 -0
  5. package/dist/{chunk-5GAUWC3L.js → chunk-ALBVUNUY.js} +1 -1
  6. package/dist/chunk-ALBVUNUY.js.map +1 -0
  7. package/dist/{chunk-VK33A5L4.js → chunk-ATVWSG75.js} +480 -232
  8. package/dist/chunk-ATVWSG75.js.map +1 -0
  9. package/dist/{chunk-7C7SC4TZ.js → chunk-I3TKNT4M.js} +9 -2
  10. package/dist/chunk-I3TKNT4M.js.map +1 -0
  11. package/dist/{chunk-6A37SKAJ.js → chunk-JDFAJP45.js} +1 -1
  12. package/dist/{chunk-6A37SKAJ.js.map → chunk-JDFAJP45.js.map} +1 -1
  13. package/dist/{chunk-OS3XDHOJ.js → chunk-UCYK4Z6O.js} +1 -1
  14. package/dist/chunk-UCYK4Z6O.js.map +1 -0
  15. package/dist/{chunk-OCCMKAJI.js → chunk-ZJBLRKCV.js} +3 -3
  16. package/dist/chunk-ZJBLRKCV.js.map +1 -0
  17. package/dist/cli/cli.js +7 -7
  18. package/dist/cli/index.js +7 -7
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/completion/index.d.ts +1 -1
  21. package/dist/completion/index.js +2 -2
  22. package/dist/completion/index.js.map +1 -1
  23. package/dist/{config-DequKoFA.d.ts → config-DnzZ7w92.d.ts} +60 -1
  24. package/dist/core/index.d.ts +1 -1
  25. package/dist/core/index.js +4 -2
  26. package/dist/dashboard/index.js +72 -23
  27. package/dist/dashboard/index.js.map +1 -1
  28. package/dist/discovery/index.d.ts +1 -1
  29. package/dist/discovery/index.js +2 -2
  30. package/dist/execution/index.js +3 -3
  31. package/dist/repo/index.js +1 -1
  32. package/package.json +13 -15
  33. package/dist/chunk-5GAUWC3L.js.map +0 -1
  34. package/dist/chunk-7C7SC4TZ.js.map +0 -1
  35. package/dist/chunk-EYUQMPVO.js.map +0 -1
  36. package/dist/chunk-OCCMKAJI.js.map +0 -1
  37. package/dist/chunk-OS3XDHOJ.js.map +0 -1
  38. package/dist/chunk-VK33A5L4.js.map +0 -1
package/README.md CHANGED
@@ -17,12 +17,22 @@
17
17
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
18
18
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-3178C6?logo=typescript&logoColor=white)](https://typescriptlang.org/)
19
19
 
20
- [Getting Started](#-getting-started) · [How It Works](#-how-it-works) · [Commands](#commands) · [Configuration](#configuration) · [Architecture](#architecture) · [Contributing](#contributing)
20
+ [Getting Started](#-getting-started) · [How It Works](#-how-it-works) · [Commands](#commands) · [Configuration](#configuration) · [Concurrency & Multi-User Safety](#concurrency--multi-user-safety) · [For Maintainers](#for-maintainers-target-repos) · [Architecture](#architecture) · [Contributing](#contributing)
21
21
 
22
22
  </div>
23
23
 
24
24
  ---
25
25
 
26
+ <div><img src="https://quickstart-for-agents.vercel.app/api/header.svg?theme=opencode&logo=OAC&title=Use+leftover+AI+tokens+to+auto-contribute+to+open+source+repos&font=inter" width="100%" /></div>
27
+
28
+ ```
29
+ You are an AI agent working on Open Agent Contribution (OAC), a CLI tool that
30
+ uses spare AI agent tokens to automatically scan repos, discover tasks, and
31
+ submit pull requests via Claude Code or Codex.
32
+ Clone https://github.com/Open330/open-agent-contribution and help improve
33
+ scanners, execution pipelines, or add new agent provider adapters.
34
+ ```
35
+
26
36
  ## The Problem
27
37
 
28
38
  You pay for AI agent tokens every month. Claude Code, Codex, OpenCode — they all come with token budgets. But most days, you don't use them all. Those leftover tokens? **Wasted.**
@@ -276,6 +286,165 @@ export default defineConfig({
276
286
 
277
287
  ---
278
288
 
289
+ ## Concurrency & Multi-User Safety
290
+
291
+ When multiple OAC instances run against the same repository simultaneously (e.g., several team members running `oac run` at the same time), there is a risk of duplicate PRs targeting the same issue. OAC prevents this with a **2-layer guard system** that checks for existing OAC pull requests at two critical points in the pipeline.
292
+
293
+ ```
294
+ Instance A Instance B
295
+ ───────── ─────────
296
+ oac run oac run
297
+ │ │
298
+ ▼ ▼
299
+ ┌──────────────┐ ┌──────────────┐
300
+ │ Layer 1 │ │ Layer 1 │
301
+ │ Discovery │ ◄── Both scan ──►│ Discovery │
302
+ │ PR check │ GitHub PRs │ PR check │
303
+ └──────┬───────┘ └──────┬───────┘
304
+ │ │
305
+ Issue #42 not Issue #42 not
306
+ claimed → keep claimed → keep
307
+ │ │
308
+ ▼ ▼
309
+ (analyze, plan, (analyze, plan,
310
+ execute...) execute...)
311
+ │ │
312
+ ▼ ▼
313
+ ┌──────────────┐ ┌──────────────┐
314
+ │ Layer 3 │ │ Layer 3 │
315
+ │ Pre-PR │ │ Pre-PR │
316
+ │ guard │ │ guard │
317
+ └──────┬───────┘ └──────┬───────┘
318
+ │ │
319
+ No OAC PR yet Instance A's PR
320
+ → create PR ✔ now exists → skip ✘
321
+ ```
322
+
323
+ ### Layer 1: Discovery-Time Filtering
324
+
325
+ During task discovery, the GitHub Issues scanner fetches all open PRs whose title starts with `[OAC]` and extracts the issue numbers they reference (via `Fixes #N`, `Closes #N`, or `Resolves #N` in the PR body). Any issue that already has a matching OAC PR is filtered out of the task list entirely — the agent never even attempts work on it.
326
+
327
+ - **When:** Runs at the start of every `oac run`, during the scan phase
328
+ - **Effect:** Issues with existing OAC PRs are excluded from the task list
329
+ - **Failure mode:** Fail-open — if the GitHub API is unreachable, no issues are filtered out and the pipeline continues normally
330
+
331
+ ### Layer 3: Pre-PR Guard
332
+
333
+ Even after Layer 1, a race condition is possible: two instances might discover the same issue before either has created a PR. Layer 3 closes this gap by performing a second check immediately before pushing the branch and creating the PR. If another OAC PR for the same issue now exists, the PR creation is skipped.
334
+
335
+ - **When:** Runs after code execution and diff validation, just before `git push` and PR creation
336
+ - **Effect:** Skips PR creation if a duplicate OAC PR is detected, avoiding wasted pushes
337
+ - **Failure mode:** Fail-open — if the check fails, the PR is created anyway (better to create a possible duplicate than to silently discard completed work)
338
+
339
+ ### How OAC PRs Are Identified
340
+
341
+ Both layers use the same detection logic:
342
+ 1. Fetch up to 100 most recently updated open PRs from the target repository
343
+ 2. Filter to PRs whose title starts with **`[OAC]`**
344
+ 3. Scan the PR body for **`Fixes #N`**, **`Closes #N`**, or **`Resolves #N`**
345
+ 4. Match the extracted issue number against the current task's linked issue
346
+
347
+ ### Best Practices for Teams
348
+
349
+ - **No configuration needed.** The guards are always active — there is nothing to enable or disable.
350
+ - **Stagger start times slightly** (even 30 seconds apart) to give Layer 1 the best chance of catching duplicates before any work begins.
351
+ - **Use a shared config** (`oac.config.ts`) with the same `issueLabels` filter so all instances target the same pool of issues and the guards can detect overlaps.
352
+ - **Check `oac log`** after runs to see if any tasks were skipped due to duplicate detection.
353
+ - **Don't worry about edge cases.** Both layers are fail-open by design — in the worst case, a duplicate PR is created, which is easy to close manually. No work is ever silently lost.
354
+
355
+ ---
356
+
357
+ ## For Maintainers (Target Repos)
358
+
359
+ If you are the repository owner receiving OAC contributions, treat contribution rules as
360
+ **repository-owned policy** (in the target repo), not contributor-local config.
361
+
362
+ ### Ownership Model
363
+
364
+ - **Target repo owns scope and rules**: keep allowed areas, constraints, and acceptance criteria in the target repository.
365
+ - **Contributors own runtime choices**: provider, token budget, and local execution environment stay in each contributor's `oac.config.ts`.
366
+ - **Why this split works**: maintainers can evolve policy in git history, reviewers can audit intent, and contributors cannot silently bypass project rules.
367
+
368
+ ### Recommended Structure (in the target repo)
369
+
370
+ ```text
371
+ .context/
372
+ plans/
373
+ README.md # contribution policy and workflow
374
+ ISSUE-123.md # task-specific plan (one issue = one plan)
375
+ ISSUE-456.md
376
+ ```
377
+
378
+ ### Plan Template (`.context/plans/ISSUE-123.md`)
379
+
380
+ ```markdown
381
+ # ISSUE-123 - Improve contribution intake
382
+
383
+ ## Scope
384
+ - Allowed paths: `src/discovery/**`, `README.md`
385
+ - Forbidden paths: `package.json`, `.github/workflows/**`
386
+
387
+ ## Must
388
+ - Keep backward compatibility for existing config keys
389
+ - Add/adjust tests for changed behavior
390
+ - Keep PR title format: `[OAC] ...`
391
+
392
+ ## Must Not
393
+ - No breaking CLI flag changes
394
+ - No unrelated refactors
395
+
396
+ ## Acceptance Criteria
397
+ - `pnpm lint`, `pnpm typecheck`, `pnpm test` all pass
398
+ - PR body links this issue and summarizes user impact
399
+ - Reviewer can validate behavior with one command sequence
400
+
401
+ ## Notes for Agent
402
+ - Prefer minimal, surgical changes
403
+ - If ambiguous, choose the safest non-breaking path
404
+ ```
405
+
406
+ ### Add a Maintainer Section to Your Target Repo README
407
+
408
+ Use this snippet in repos that want to receive OAC contributions:
409
+
410
+ ````markdown
411
+ ## AI Contribution Policy (OAC)
412
+
413
+ This repository accepts contributions generated by Open Agent Contribution (OAC).
414
+
415
+ - Before running OAC, read `.context/plans/README.md` and the relevant `ISSUE-*.md` plan.
416
+ - Work outside allowed paths will be rejected in review.
417
+ - PRs must include issue linkage and pass lint/typecheck/tests.
418
+
419
+ Recommended command:
420
+
421
+ ```bash
422
+ oac run --repo <owner/repo>
423
+ ```
424
+ ````
425
+
426
+ ### Systematic Intake Workflow
427
+
428
+ 1. **Maintainer prepares issues**
429
+ - Create actionable issues and add labels such as `oac-ready`, `documentation`, `good-first-issue`.
430
+ - Add or update `.context/plans/ISSUE-<number>.md` for each issue you want agents to pick up.
431
+ 2. **Contributor scopes discovery**
432
+ - In contributor `oac.config.ts`, set `discovery.issueLabels` to maintainer labels (for example, `"oac-ready"`).
433
+ 3. **OAC executes with duplicate guards**
434
+ - Layer 1 + Layer 3 prevent most duplicate PRs across concurrent contributors.
435
+ 4. **Maintainer reviews against plan**
436
+ - Check diff vs `Scope`, `Must`, `Must Not`, and acceptance criteria in the issue plan document.
437
+
438
+ ### Current Behavior Note
439
+
440
+ Today, OAC does not hard-fail when `.context/plans/*` is missing. The recommended production pattern is:
441
+
442
+ - maintain plan documents in the target repo,
443
+ - require issue/PR linkage,
444
+ - and enforce policy at review or CI level.
445
+
446
+ ---
447
+
279
448
  ## Architecture
280
449
 
281
450
  ```
@@ -8,7 +8,7 @@ import {
8
8
  estimateLocChanges,
9
9
  estimateTokens,
10
10
  resetCounters
11
- } from "../chunk-5GAUWC3L.js";
11
+ } from "../chunk-ALBVUNUY.js";
12
12
  export {
13
13
  ClaudeTokenCounter,
14
14
  CodexTokenCounter,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  OacError,
3
3
  executionError
4
- } from "./chunk-7C7SC4TZ.js";
4
+ } from "./chunk-I3TKNT4M.js";
5
5
  import {
6
6
  isRecord
7
- } from "./chunk-6A37SKAJ.js";
7
+ } from "./chunk-JDFAJP45.js";
8
8
 
9
9
  // src/execution/agents/claude-code.adapter.ts
10
10
  import { createInterface } from "readline";
@@ -126,11 +126,18 @@ function patchTokenState(state, patch) {
126
126
  };
127
127
  }
128
128
  function parseTokenPatchFromPayload(payload) {
129
- const usage = isRecord(payload.usage) ? payload.usage : void 0;
129
+ const message = isRecord(payload.message) ? payload.message : void 0;
130
+ const usage = isRecord(payload.usage) ? payload.usage : isRecord(message?.usage) ? message.usage : void 0;
131
+ const baseInput = readNumber(
132
+ payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
133
+ );
134
+ const cacheRead = readNumber(usage?.cache_read_input_tokens ?? usage?.cacheReadInputTokens);
135
+ const cacheCreate = readNumber(
136
+ usage?.cache_creation_input_tokens ?? usage?.cacheCreationInputTokens
137
+ );
138
+ const effectiveInput = baseInput !== void 0 ? (baseInput ?? 0) + (cacheRead ?? 0) + (cacheCreate ?? 0) : void 0;
130
139
  return {
131
- inputTokens: readNumber(
132
- payload.inputTokens ?? payload.input_tokens ?? payload.promptTokens ?? payload.prompt_tokens ?? usage?.inputTokens ?? usage?.input_tokens ?? usage?.promptTokens ?? usage?.prompt_tokens
133
- ),
140
+ inputTokens: effectiveInput,
134
141
  outputTokens: readNumber(
135
142
  payload.outputTokens ?? payload.output_tokens ?? payload.completionTokens ?? payload.completion_tokens ?? usage?.outputTokens ?? usage?.output_tokens ?? usage?.completionTokens ?? usage?.completion_tokens
136
143
  ),
@@ -313,7 +320,7 @@ var ClaudeCodeAdapter = class {
313
320
  runningExecutions = /* @__PURE__ */ new Map();
314
321
  async checkAvailability() {
315
322
  try {
316
- const result = await execa("claude", ["--version"], { reject: false });
323
+ const result = await execa("claude", ["--version"], { reject: false, stdin: "ignore" });
317
324
  const version = result.stdout.trim().split("\n")[0];
318
325
  if (result.exitCode === 0) {
319
326
  return {
@@ -353,14 +360,25 @@ var ClaudeCodeAdapter = class {
353
360
  OAC_TOKEN_BUDGET: `${params.tokenBudget}`,
354
361
  OAC_ALLOW_COMMITS: `${params.allowCommits}`
355
362
  };
356
- const subprocess = execa("claude", ["-p", params.prompt], {
357
- cwd: params.workingDirectory,
358
- env: processEnv,
359
- extendEnv: false,
360
- reject: false,
361
- timeout: params.timeoutMs,
362
- stdin: "ignore"
363
- });
363
+ const subprocess = execa(
364
+ "claude",
365
+ [
366
+ "-p",
367
+ "--dangerously-skip-permissions",
368
+ "--verbose",
369
+ "--output-format",
370
+ "stream-json",
371
+ params.prompt
372
+ ],
373
+ {
374
+ cwd: params.workingDirectory,
375
+ env: processEnv,
376
+ extendEnv: false,
377
+ reject: false,
378
+ timeout: params.timeoutMs,
379
+ stdin: "ignore"
380
+ }
381
+ );
364
382
  this.runningExecutions.set(params.executionId, subprocess);
365
383
  const consumeStream = async (stream, streamName) => {
366
384
  if (!stream) {
@@ -1065,10 +1083,14 @@ function normalizeUnknownError3(error, executionId) {
1065
1083
  });
1066
1084
  }
1067
1085
  if (/rate.limit|429|too many requests|throttl/i.test(message)) {
1068
- return executionError("AGENT_RATE_LIMITED", `OpenCode execution rate-limited for ${executionId}`, {
1069
- context: { executionId, message },
1070
- cause: error
1071
- });
1086
+ return executionError(
1087
+ "AGENT_RATE_LIMITED",
1088
+ `OpenCode execution rate-limited for ${executionId}`,
1089
+ {
1090
+ context: { executionId, message },
1091
+ cause: error
1092
+ }
1093
+ );
1072
1094
  }
1073
1095
  if (/network|ECONN|ENOTFOUND|EAI_AGAIN/i.test(message)) {
1074
1096
  return new OacError(
@@ -1159,17 +1181,13 @@ var OpenCodeAdapter = class {
1159
1181
  OAC_TOKEN_BUDGET: `${params.tokenBudget}`,
1160
1182
  OAC_ALLOW_COMMITS: `${params.allowCommits}`
1161
1183
  };
1162
- const subprocess = execa3(
1163
- "opencode",
1164
- ["run", "--format", "json", params.prompt],
1165
- {
1166
- cwd: params.workingDirectory,
1167
- env: processEnv,
1168
- reject: false,
1169
- timeout: params.timeoutMs,
1170
- stdin: "ignore"
1171
- }
1172
- );
1184
+ const subprocess = execa3("opencode", ["run", "--format", "json", params.prompt], {
1185
+ cwd: params.workingDirectory,
1186
+ env: processEnv,
1187
+ reject: false,
1188
+ timeout: params.timeoutMs,
1189
+ stdin: "ignore"
1190
+ });
1173
1191
  this.runningExecutions.set(params.executionId, subprocess);
1174
1192
  const processStdoutLine = (line) => {
1175
1193
  const payload = parseJsonPayload3(line);
@@ -1304,9 +1322,7 @@ var OpenCodeAdapter = class {
1304
1322
  var AdapterRegistry = class {
1305
1323
  factories = /* @__PURE__ */ new Map();
1306
1324
  /** Well-known aliases (e.g. legacy IDs) that map to canonical provider IDs. */
1307
- aliases = /* @__PURE__ */ new Map([
1308
- ["codex-cli", "codex"]
1309
- ]);
1325
+ aliases = /* @__PURE__ */ new Map([["codex-cli", "codex"]]);
1310
1326
  /** Register a new adapter factory. Replaces any previous factory for the same ID. */
1311
1327
  register(id, factory) {
1312
1328
  this.factories.set(id, factory);
@@ -1466,8 +1482,27 @@ function readPositiveNumber(value) {
1466
1482
  function readMetadataNumber(task, key) {
1467
1483
  return readPositiveNumber(task.metadata[key]);
1468
1484
  }
1485
+ function isRecord2(value) {
1486
+ return typeof value === "object" && value !== null;
1487
+ }
1488
+ function readContextAck(task) {
1489
+ const raw = task.metadata.contextAck;
1490
+ if (!isRecord2(raw)) {
1491
+ return void 0;
1492
+ }
1493
+ const files = Array.isArray(raw.files) ? raw.files.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
1494
+ const summary = Array.isArray(raw.summary) ? raw.summary.filter(
1495
+ (item) => typeof item === "string" && item.trim().length > 0
1496
+ ) : [];
1497
+ const digest = typeof raw.digest === "string" && raw.digest.trim().length > 0 ? raw.digest : void 0;
1498
+ if (files.length === 0) {
1499
+ return void 0;
1500
+ }
1501
+ return { files, summary, digest };
1502
+ }
1469
1503
  function buildTaskPrompt(task) {
1470
1504
  const fileList = task.targetFiles.length > 0 ? task.targetFiles.join("\n") : "(none provided)";
1505
+ const contextAck = readContextAck(task);
1471
1506
  const lines = [
1472
1507
  "You are implementing a scoped repository contribution task.",
1473
1508
  `Task ID: ${task.id}`,
@@ -1495,6 +1530,23 @@ function buildTaskPrompt(task) {
1495
1530
  "",
1496
1531
  "Apply minimal, safe changes and ensure the repository remains buildable."
1497
1532
  );
1533
+ if (contextAck) {
1534
+ lines.push(
1535
+ "",
1536
+ "Repository contribution policy (MUST FOLLOW):",
1537
+ ...contextAck.files.map((file) => `- ${file}`)
1538
+ );
1539
+ if (contextAck.summary.length > 0) {
1540
+ lines.push("", "Policy summary:", ...contextAck.summary.map((item) => `- ${item}`));
1541
+ }
1542
+ if (contextAck.digest) {
1543
+ lines.push("", `Context digest: ${contextAck.digest}`);
1544
+ }
1545
+ lines.push(
1546
+ "",
1547
+ "Treat these policy files as authoritative. Stay within scope and satisfy all Must/Must Not constraints."
1548
+ );
1549
+ }
1498
1550
  return lines.filter((l) => l !== void 0).join("\n");
1499
1551
  }
1500
1552
  function stageFromEvent(event) {
@@ -1877,4 +1929,4 @@ export {
1877
1929
  isTransientError,
1878
1930
  ExecutionEngine
1879
1931
  };
1880
- //# sourceMappingURL=chunk-EYUQMPVO.js.map
1932
+ //# sourceMappingURL=chunk-27FEE5KS.js.map