@nocobase/app-plugin-agents 0.1.0-beta.10 → 0.1.0-beta.11

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 (156) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +23 -1
  3. package/dist/client/locales/en-US.d.ts +1 -0
  4. package/dist/client/locales/en-US.d.ts.map +1 -1
  5. package/dist/client/locales/en-US.js +1 -0
  6. package/dist/client/locales/en-US.js.map +1 -1
  7. package/dist/client/locales/runtimes.en-US.d.ts +0 -1
  8. package/dist/client/locales/runtimes.en-US.d.ts.map +1 -1
  9. package/dist/client/locales/runtimes.en-US.js +0 -1
  10. package/dist/client/locales/runtimes.en-US.js.map +1 -1
  11. package/dist/client/locales/runtimes.zh-CN.d.ts.map +1 -1
  12. package/dist/client/locales/runtimes.zh-CN.js +0 -1
  13. package/dist/client/locales/runtimes.zh-CN.js.map +1 -1
  14. package/dist/client/locales/skills.en-US.d.ts +1 -0
  15. package/dist/client/locales/skills.en-US.d.ts.map +1 -1
  16. package/dist/client/locales/skills.en-US.js +2 -1
  17. package/dist/client/locales/skills.en-US.js.map +1 -1
  18. package/dist/client/locales/skills.zh-CN.d.ts.map +1 -1
  19. package/dist/client/locales/skills.zh-CN.js +2 -1
  20. package/dist/client/locales/skills.zh-CN.js.map +1 -1
  21. package/dist/client/locales/zh-CN.d.ts.map +1 -1
  22. package/dist/client/locales/zh-CN.js +1 -0
  23. package/dist/client/locales/zh-CN.js.map +1 -1
  24. package/dist/client/pages/runtimes/workspace-diagnostics.d.ts.map +1 -1
  25. package/dist/client/pages/runtimes/workspace-diagnostics.js +3 -5
  26. package/dist/client/pages/runtimes/workspace-diagnostics.js.map +1 -1
  27. package/dist/client/pages/skills/detail/index.d.ts.map +1 -1
  28. package/dist/client/pages/skills/detail/index.js +7 -10
  29. package/dist/client/pages/skills/detail/index.js.map +1 -1
  30. package/dist/client/pages/skills/index.d.ts.map +1 -1
  31. package/dist/client/pages/skills/index.js +2 -2
  32. package/dist/client/pages/skills/index.js.map +1 -1
  33. package/dist/database/migrations/.manifest.json +14 -2
  34. package/dist/database/migrations/202610020003_ag_create_runs.d.ts.map +1 -1
  35. package/dist/database/migrations/202610020003_ag_create_runs.js +0 -1
  36. package/dist/database/migrations/202610020003_ag_create_runs.js.map +1 -1
  37. package/dist/database/migrations/202610250001_ag_comment_runner_consent.d.ts +4 -0
  38. package/dist/database/migrations/202610250001_ag_comment_runner_consent.d.ts.map +1 -0
  39. package/dist/database/migrations/202610250001_ag_comment_runner_consent.js +41 -0
  40. package/dist/database/migrations/202610250001_ag_comment_runner_consent.js.map +1 -0
  41. package/dist/database/migrations/202610260001_ag_drop_directory_index.d.ts +4 -0
  42. package/dist/database/migrations/202610260001_ag_drop_directory_index.d.ts.map +1 -0
  43. package/dist/database/migrations/202610260001_ag_drop_directory_index.js +24 -0
  44. package/dist/database/migrations/202610260001_ag_drop_directory_index.js.map +1 -0
  45. package/dist/database/migrations/202610260001_ag_run_revision.d.ts +4 -0
  46. package/dist/database/migrations/202610260001_ag_run_revision.d.ts.map +1 -0
  47. package/dist/database/migrations/202610260001_ag_run_revision.js +19 -0
  48. package/dist/database/migrations/202610260001_ag_run_revision.js.map +1 -0
  49. package/dist/package.json +1 -1
  50. package/dist/server/composition.d.ts +2 -0
  51. package/dist/server/composition.d.ts.map +1 -1
  52. package/dist/server/composition.js +13 -1
  53. package/dist/server/composition.js.map +1 -1
  54. package/dist/server/core/agents/agent.store.d.ts +1 -1
  55. package/dist/server/core/agents/agent.store.d.ts.map +1 -1
  56. package/dist/server/core/agents/agent.store.js +3 -6
  57. package/dist/server/core/agents/agent.store.js.map +1 -1
  58. package/dist/server/core/brief/brief.js +1 -1
  59. package/dist/server/core/brief/brief.js.map +1 -1
  60. package/dist/server/core/runs/claim.d.ts +15 -3
  61. package/dist/server/core/runs/claim.d.ts.map +1 -1
  62. package/dist/server/core/runs/claim.js +257 -24
  63. package/dist/server/core/runs/claim.js.map +1 -1
  64. package/dist/server/core/runs/comment-consent.d.ts +24 -0
  65. package/dist/server/core/runs/comment-consent.d.ts.map +1 -0
  66. package/dist/server/core/runs/comment-consent.js +116 -0
  67. package/dist/server/core/runs/comment-consent.js.map +1 -0
  68. package/dist/server/core/runs/identity.d.ts +3 -0
  69. package/dist/server/core/runs/identity.d.ts.map +1 -1
  70. package/dist/server/core/runs/identity.js +38 -12
  71. package/dist/server/core/runs/identity.js.map +1 -1
  72. package/dist/server/core/runs/input-gate.d.ts.map +1 -1
  73. package/dist/server/core/runs/input-gate.js +4 -0
  74. package/dist/server/core/runs/input-gate.js.map +1 -1
  75. package/dist/server/core/runs/lifecycle.d.ts.map +1 -1
  76. package/dist/server/core/runs/lifecycle.js +24 -18
  77. package/dist/server/core/runs/lifecycle.js.map +1 -1
  78. package/dist/server/core/runs/ports.d.ts +8 -1
  79. package/dist/server/core/runs/ports.d.ts.map +1 -1
  80. package/dist/server/core/runs/ports.js.map +1 -1
  81. package/dist/server/core/runs/run-data-purge.d.ts +15 -0
  82. package/dist/server/core/runs/run-data-purge.d.ts.map +1 -0
  83. package/dist/server/core/runs/run-data-purge.js +82 -0
  84. package/dist/server/core/runs/run-data-purge.js.map +1 -0
  85. package/dist/server/core/runs/run-requests.d.ts +25 -0
  86. package/dist/server/core/runs/run-requests.d.ts.map +1 -1
  87. package/dist/server/core/runs/run-requests.js +131 -63
  88. package/dist/server/core/runs/run-requests.js.map +1 -1
  89. package/dist/server/core/runs/run.service.d.ts +12 -0
  90. package/dist/server/core/runs/run.service.d.ts.map +1 -1
  91. package/dist/server/core/runs/run.service.js +518 -33
  92. package/dist/server/core/runs/run.service.js.map +1 -1
  93. package/dist/server/core/runs/run.store.d.ts +5 -0
  94. package/dist/server/core/runs/run.store.d.ts.map +1 -1
  95. package/dist/server/core/runs/run.store.js +3 -0
  96. package/dist/server/core/runs/run.store.js.map +1 -1
  97. package/dist/server/core/runs/sweeper.d.ts +1 -0
  98. package/dist/server/core/runs/sweeper.d.ts.map +1 -1
  99. package/dist/server/core/runs/sweeper.js +1 -0
  100. package/dist/server/core/runs/sweeper.js.map +1 -1
  101. package/dist/server/core/runs/transitions.d.ts +1 -1
  102. package/dist/server/core/runs/transitions.d.ts.map +1 -1
  103. package/dist/server/core/runs/transitions.js +11 -3
  104. package/dist/server/core/runs/transitions.js.map +1 -1
  105. package/dist/server/core/skills/skill.service.d.ts +6 -2
  106. package/dist/server/core/skills/skill.service.d.ts.map +1 -1
  107. package/dist/server/core/skills/skill.service.js +43 -58
  108. package/dist/server/core/skills/skill.service.js.map +1 -1
  109. package/dist/server/core/skills/skill.store.d.ts +14 -0
  110. package/dist/server/core/skills/skill.store.d.ts.map +1 -1
  111. package/dist/server/core/skills/skill.store.js +30 -0
  112. package/dist/server/core/skills/skill.store.js.map +1 -1
  113. package/dist/server/kernel/scopes.d.ts.map +1 -1
  114. package/dist/server/kernel/scopes.js +3 -1
  115. package/dist/server/kernel/scopes.js.map +1 -1
  116. package/dist/server/providers/agents.d.ts +4 -0
  117. package/dist/server/providers/agents.d.ts.map +1 -1
  118. package/dist/server/providers/agents.js +3 -0
  119. package/dist/server/providers/agents.js.map +1 -1
  120. package/dist/server/providers/runners.d.ts.map +1 -1
  121. package/dist/server/providers/runners.js +23 -0
  122. package/dist/server/providers/runners.js.map +1 -1
  123. package/dist/server/routes/admin.d.ts.map +1 -1
  124. package/dist/server/routes/admin.js +18 -8
  125. package/dist/server/routes/admin.js.map +1 -1
  126. package/dist/server/routes/run-requests.js +3 -3
  127. package/dist/server/routes/run-requests.js.map +1 -1
  128. package/dist/server/routes/runners/runner.d.ts.map +1 -1
  129. package/dist/server/routes/runners/runner.js +2 -0
  130. package/dist/server/routes/runners/runner.js.map +1 -1
  131. package/dist/server/routes/schemas.d.ts.map +1 -1
  132. package/dist/server/routes/schemas.js +14 -2
  133. package/dist/server/routes/schemas.js.map +1 -1
  134. package/dist/server/runners/runner.service.d.ts +11 -1
  135. package/dist/server/runners/runner.service.d.ts.map +1 -1
  136. package/dist/server/runners/runner.service.js +19 -1
  137. package/dist/server/runners/runner.service.js.map +1 -1
  138. package/dist/server/tokens.d.ts +1 -0
  139. package/dist/server/tokens.d.ts.map +1 -1
  140. package/dist/server/tokens.js +1 -0
  141. package/dist/server/tokens.js.map +1 -1
  142. package/dist/shared/agents.d.ts +0 -2
  143. package/dist/shared/agents.d.ts.map +1 -1
  144. package/dist/shared/agents.js.map +1 -1
  145. package/dist/shared/runs.d.ts +10 -0
  146. package/dist/shared/runs.d.ts.map +1 -1
  147. package/dist/shared/runs.js.map +1 -1
  148. package/dist/shared/skills.d.ts +8 -4
  149. package/dist/shared/skills.d.ts.map +1 -1
  150. package/dist/shared/skills.js +7 -2
  151. package/dist/shared/skills.js.map +1 -1
  152. package/dist/shared/variables.js +1 -1
  153. package/dist/shared/variables.js.map +1 -1
  154. package/package.json +8 -8
  155. package/skills/nocobase-app-plugin-agents/references/extension-points.md +19 -19
  156. package/skills/nocobase-app-plugin-agents/references/variables-skills-usage.md +3 -1
@@ -29,6 +29,16 @@
29
29
  *
30
30
  * Portable by design: the database layer offers no `FOR UPDATE SKIP LOCKED` or advisory locks, and the guarded update
31
31
  * plus the agent-row write give the same guarantees on every dialect (SQLite serializes writers anyway).
32
+ *
33
+ * Polls are the hot path: every runner walks the same candidates, so a write per candidate is a write per candidate per
34
+ * runner per poll. Everything that rules a candidate out is read outside any transaction — identity, `fits()`, slots,
35
+ * concurrency and comment consent — and a transaction opens only for the run about to be claimed. The agent row is
36
+ * the only row a claim locks with a self-update, and only in that final transaction after `fits()` (or when a stale
37
+ * identity has to be repaired first, which current work never needs). PostgreSQL runs at READ COMMITTED, where each
38
+ * statement sees the latest committed rows, so no transaction needs no-op writes to refresh a snapshot; comment
39
+ * consent needs no chain lock, because confirmation is final and rejection or Run as me end the chain's queued runs,
40
+ * which the guarded update sees. A new write or lock here needs a measured reason (`tests/claim-concurrency.test.ts`
41
+ * counts them, and the lock waits on PostgreSQL).
32
42
 
33
43
  */
34
44
  import { type AgentTool, type RunApp, type RunnerFeature, type RunPayload, type RunSkill, type WorkspaceDir } from '@nocobase/agent-protocol';
@@ -42,7 +52,7 @@ import { type Clock } from '../../kernel/clock.js';
42
52
  import type { People } from '../../kernel/people.js';
43
53
  import type { IdSource } from '../../kernel/ids.js';
44
54
  import { type SecretMemory } from '../../kernel/redaction.js';
45
- import type { TxRunner } from '../../kernel/tx.js';
55
+ import type { Tx, TxRunner } from '../../kernel/tx.js';
46
56
  import type { SkillService, SkillTarget } from '../skills/index.js';
47
57
  import { type VariableService, type VariableTarget } from '../variables/index.js';
48
58
  import { type RepoAccessRegistry, type BriefSectionRegistry, type RunMountRegistry } from './extensions.js';
@@ -51,6 +61,8 @@ import { type AgentCli } from './policy.js';
51
61
  import { type RunRecord } from './run.store.js';
52
62
  import { type TransitionDeps } from './transitions.js';
53
63
  export interface ClaimService {
64
+ reconcileCommentConsent(unit: Tx, runId: string): Promise<void>;
65
+ reconcileCommentQueue(): Promise<void>;
54
66
  /** Structured ordered-model diagnostics; machine visibility is enforced by the caller. */
55
67
  selectionWait(runId: string): Promise<import('../../../shared/runs.js').ModelSelectionWait | null>;
56
68
  /** Read-only capacity and compatibility for a real subject and candidate agent, as the run's actor. */
@@ -120,8 +132,8 @@ export declare function payloadRequires(dirs: readonly WorkspaceDir[], skills: n
120
132
  export declare function payloadDirs(dirs: readonly SubjectDir[]): WorkspaceDir[];
121
133
  /** Where a run's variables come from, in merge order: the subject's scopes, its directories', the agent's. */
122
134
  export declare function variableTargets(agent: Pick<Agent, 'id'>, assembly: Pick<SubjectAssembly, 'scopes' | 'dirs'>): VariableTarget[];
123
- /** Where a run's skills come from, the agent's first so its own win a shared slug. */
124
- export declare function skillTargets(agent: Pick<Agent, 'id'>, assembly: Pick<SubjectAssembly, 'scopes' | 'dirs'>): SkillTarget[];
135
+ /** Team defaults, application scopes and directories, from lowest to highest priority. */
136
+ export declare function skillTargets(_agent: Pick<Agent, 'id'>, assembly: Pick<SubjectAssembly, 'scopes' | 'dirs'>): SkillTarget[];
125
137
  /** The brief of a run on a subject, as the claim renders it; the preview renders it the same way. */
126
138
  export interface RenderedBrief {
127
139
  readonly layers: BriefLayers;
@@ -1 +1 @@
1
- {"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../../../../server/core/runs/claim.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../../../../server/core/runs/claim.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { GLOBAL_SKILL_TARGET } from '../../../shared/skills.js';
1
2
  /**
2
3
  * Claiming: handing queued runs to a runner that fits them, within each agent's concurrency limit, one run per
3
4
  * (agent, subject, thread) at a time, through the runners' one long-poll (`routes/runners/runner.ts`), which hands
@@ -29,6 +30,16 @@
29
30
  *
30
31
  * Portable by design: the database layer offers no `FOR UPDATE SKIP LOCKED` or advisory locks, and the guarded update
31
32
  * plus the agent-row write give the same guarantees on every dialect (SQLite serializes writers anyway).
33
+ *
34
+ * Polls are the hot path: every runner walks the same candidates, so a write per candidate is a write per candidate per
35
+ * runner per poll. Everything that rules a candidate out is read outside any transaction — identity, `fits()`, slots,
36
+ * concurrency and comment consent — and a transaction opens only for the run about to be claimed. The agent row is
37
+ * the only row a claim locks with a self-update, and only in that final transaction after `fits()` (or when a stale
38
+ * identity has to be repaired first, which current work never needs). PostgreSQL runs at READ COMMITTED, where each
39
+ * statement sees the latest committed rows, so no transaction needs no-op writes to refresh a snapshot; comment
40
+ * consent needs no chain lock, because confirmation is final and rejection or Run as me end the chain's queued runs,
41
+ * which the guarded update sees. A new write or lock here needs a measured reason (`tests/claim-concurrency.test.ts`
42
+ * counts them, and the lock waits on PostgreSQL).
32
43
 
33
44
  */
34
45
  import { ANNOUNCED_FAILURES, isProtocolSupported, policyAllowsAgent, policyAllowsRepo, policyAllowsSubject, TIMINGS, } from '@nocobase/agent-protocol';
@@ -38,7 +49,7 @@ import { createHash } from 'node:crypto';
38
49
  import { later } from '../../kernel/clock.js';
39
50
  import { notFound } from '../../kernel/errors.js';
40
51
  import { runSecretsKey } from '../../kernel/redaction.js';
41
- import { cleanList, covers } from '../../kernel/values.js';
52
+ import { cleanList, covers, jsonObject } from '../../kernel/values.js';
42
53
  import { findAgent, lockAgentForClaim } from '../agents/index.js';
43
54
  import { joinBrief, renderBrief } from '../brief/index.js';
44
55
  import { cliPackageFor } from './cli-package.js';
@@ -48,10 +59,12 @@ import { mountsFor, prepareExtensions, repoAccessFor, sessionKeyOf, withSections
48
59
  import { MAX_CLAIM_FAILURES, toolPolicyFor } from './policy.js';
49
60
  import { mintRunToken } from './run-tokens.js';
50
61
  import { runsHeldByTool } from './runner-view.js';
51
- import { ACTIVE, countActive, eventsRepo, findRunRecord, markDelivered, pendingInputs, runsOfKey, runsRepo, sessionsRepo, toInput, toRun, } from './run.store.js';
62
+ import { ACTIVE, countActive, eventsRepo, findRunRecord, markDelivered, pendingInputs, runsOfKey, runsRepo, inputsRepo, sessionsRepo, toInput, toExecutions, toRun, } from './run.store.js';
52
63
  import { dropInputs } from './input-gate.js';
53
64
  import { finishRun } from './transitions.js';
54
- import { refreshQueuedIdentity } from './identity.js';
65
+ import { queuedIdentityCurrent, refreshQueuedIdentity } from './identity.js';
66
+ import { currentCommentOwner, commentConsent, commentFromOwner, withdrawCommentRequests, } from './comment-consent.js';
67
+ import { insertRunRequest, runRequestsRepo } from './run-requests.js';
55
68
  import { consumeReset, saveBrief } from './workspace.store.js';
56
69
  /** Queued runs looked at per claim, oldest first by priority. */
57
70
  const CANDIDATES = 50;
@@ -158,11 +171,11 @@ export function variableTargets(agent, assembly) {
158
171
  { scope: 'agent', scopeId: agent.id },
159
172
  ];
160
173
  }
161
- /** Where a run's skills come from, the agent's first so its own win a shared slug. */
162
- export function skillTargets(agent, assembly) {
174
+ /** Team defaults, application scopes and directories, from lowest to highest priority. */
175
+ export function skillTargets(_agent, assembly) {
163
176
  return [
164
- { scope: 'agent', scopeId: agent.id },
165
- ...assembly.scopes,
177
+ GLOBAL_SKILL_TARGET,
178
+ ...assembly.scopes.filter((target) => target.scope !== 'agent' && target.scope !== 'global'),
166
179
  ...assembly.dirs.flatMap((dir) => dir.scopeId ? [{ scope: 'workdir', scopeId: dir.scopeId }] : []),
167
180
  ];
168
181
  }
@@ -257,13 +270,14 @@ export function createClaimService(deps) {
257
270
  return 'restricted';
258
271
  return 'ready';
259
272
  }
260
- async function selectionFacts(conn, run, agent, requirePolicy = true) {
273
+ async function selectionFacts(conn, run, agent, requirePolicy = true, trust) {
261
274
  const policy = await deps.selectionPolicies?.forRun(conn, run);
262
275
  if ((requirePolicy && !policy) || !deps.runners)
263
276
  return null;
264
277
  const now = clock.now();
265
278
  const start = queueStart(run);
266
- const registered = await deps.runners.all(conn);
279
+ const registered = (await deps.runners.all(conn)).filter((runner) => !trust ||
280
+ (trust === 'team' ? runner.trust === 'team' : runner.trust !== 'team'));
267
281
  const binding = deps.subjects.get(run.subject.kind);
268
282
  if (!binding)
269
283
  throw new Error(`No context provider for ${run.subject.kind}.`);
@@ -536,7 +550,7 @@ export function createClaimService(deps) {
536
550
  const sessionNote = resume ? assembly.turn.resumed : assembly.turn.fresh;
537
551
  const turn = [
538
552
  brief.turn,
539
- ...(run.failureReason === 'runnerUpgrade'
553
+ ...(toExecutions(run.executionHistory).find((execution) => execution.attempt === Number(run.attempt) - 1)?.failureReason === 'runnerUpgrade'
540
554
  ? [
541
555
  'The previous attempt was interrupted by a runner upgrade. Continue from the current state.',
542
556
  ]
@@ -657,17 +671,175 @@ export function createClaimService(deps) {
657
671
  });
658
672
  });
659
673
  }
660
- async function admit(unit, run, agent, runner, inputs) {
661
- const rejection = await deps.subjects
662
- .get(run.subjectKind)
663
- ?.context.admit?.(unit.conn, {
674
+ /** What reconciling a comment run would change, read without writing: most runs need nothing. */
675
+ async function planCommentConsent(conn, runId) {
676
+ const none = { kind: 'none' };
677
+ const run = await findRunRecord(conn, runId);
678
+ if (!run?.commentRootRunId)
679
+ return none;
680
+ const root = await findRunRecord(conn, run.commentRootRunId);
681
+ if (!root)
682
+ return none;
683
+ if (run.status !== 'queued') {
684
+ const pending = await runRequestsRepo(conn).findMany({
685
+ filter: {
686
+ commentRootRunId: root.id,
687
+ commentRunId: run.id,
688
+ purpose: 'personalRunner',
689
+ status: 'pending',
690
+ },
691
+ limit: 1,
692
+ });
693
+ return pending.length > 0 ? { kind: 'withdraw', rootId: root.id } : none;
694
+ }
695
+ if (!(await queuedIdentityCurrent(conn, deps, run)))
696
+ return { kind: 'refresh', run };
697
+ if (!(await currentCommentOwner(conn, deps.subjects, root)))
698
+ return {
699
+ kind: 'cancel',
700
+ run,
701
+ detail: 'The comment belongs to an earlier owner assignment.',
702
+ };
703
+ if (root.commentExecution === 'requester' ||
704
+ run.actorUserId !== root.ownerUserId)
705
+ return none;
706
+ const { decision } = await commentConsent(conn, root);
707
+ if (decision === 'confirmed' || decision === 'pending')
708
+ return none;
709
+ if (await commentFromOwner(conn, root))
710
+ return none;
711
+ if (decision === 'rejected')
712
+ return {
713
+ kind: 'cancel',
714
+ run,
715
+ detail: 'The comment personal runner authorization is no longer valid.',
716
+ };
717
+ const agent = await findAgent(conn, run.agentId);
718
+ if (!agent)
719
+ return none;
720
+ const team = await selectionFacts(conn, toRun(run), agent, false, 'team');
721
+ if (!team ||
722
+ team.entries.some((e) => e.compatibleCount > 0 ||
723
+ e.reason === 'unknown' ||
724
+ e.blockers?.includes('unknown')))
725
+ return none;
726
+ const personal = await selectionFacts(conn, toRun(run), agent, false, 'personal');
727
+ if (!personal?.entries.some((e) => e.compatibleCount > 0))
728
+ return none;
729
+ const sourceInputs = await inputsRepo(conn).findMany({
730
+ filter: { runId: root.id },
731
+ sort: (sort) => [sort.field('createdAt').asc(), sort.field('id').asc()],
732
+ });
733
+ const input = sourceInputs.find((input) => input.type === 'comment' &&
734
+ jsonObject(input.payload).commentSourceSnapshot === true) ?? sourceInputs.find((input) => input.type === 'comment');
735
+ return input ? { kind: 'request', run, root, input } : none;
736
+ }
737
+ /**
738
+ * Brings a comment run's consent up to date. It takes no lock of its own: callers that may create a request hold
739
+ * the run's agent row (enqueue, retry and the sweeper do), which serializes the duplicate check in insertRunRequest
740
+ * with every other enqueue and claim of that agent. Everything else is a guarded update.
741
+ */
742
+ async function reconcileCommentConsent(unit, runId) {
743
+ let plan = await planCommentConsent(unit.conn, runId);
744
+ if (plan.kind === 'refresh') {
745
+ if (!(await refreshQueuedIdentity(unit, deps, plan.run)))
746
+ return;
747
+ plan = await planCommentConsent(unit.conn, runId);
748
+ }
749
+ switch (plan.kind) {
750
+ case 'withdraw':
751
+ await withdrawCommentRequests(unit, plan.rootId, clock.now().toISOString(), runId);
752
+ return;
753
+ case 'cancel':
754
+ await finishRun(unit, deps, plan.run, {
755
+ status: 'cancelled',
756
+ reason: null,
757
+ detail: plan.detail,
758
+ });
759
+ return;
760
+ case 'request': {
761
+ const { run, root, input } = plan;
762
+ await insertRunRequest(unit, deps, {
763
+ purpose: 'personalRunner',
764
+ commentRootRunId: root.id,
765
+ commentRunId: run.id,
766
+ commentOwnerKey: root.commentOwnerKey ?? null,
767
+ agentId: run.agentId,
768
+ subject: toRun(run).subject,
769
+ threadScope: run.threadScope,
770
+ // The plan only asks for runs acting as the chain's owner.
771
+ responsibleUserId: run.actorUserId,
772
+ requestedByUserId: root.requestedByUserId ?? root.actorUserId,
773
+ ownerUserId: run.actorUserId,
774
+ priority: Number(run.priority),
775
+ requires: toRun(run).requires,
776
+ fireAt: run.availableAt,
777
+ maxAttempts: Number(run.maxAttempts),
778
+ input: toInput(input),
779
+ });
780
+ return;
781
+ }
782
+ default:
783
+ return;
784
+ }
785
+ }
786
+ /**
787
+ * Whether this runner may take this candidate under comment consent, read outside any transaction. Immutable facts
788
+ * settle most candidates without a query: no comment chain, work run as the commenter, or work not run as the
789
+ * chain's owner. The rest read the chain's requests once. A decision never needs a lock: confirmation is final,
790
+ * and rejection or Run as me end the chain's queued runs, which the claim's guarded `status = 'queued'` update sees.
791
+ */
792
+ async function commentConsentFor(conn, runner, run) {
793
+ const allowed = { allowed: true, confirmedByUserId: null };
794
+ const refused = { allowed: false, confirmedByUserId: null };
795
+ if (!run.commentRootRunId || run.commentExecution === 'requester')
796
+ return allowed;
797
+ const root = await findRunRecord(conn, run.commentRootRunId);
798
+ if (!root || root.commentRootRunId !== root.id)
799
+ return refused;
800
+ if (root.commentExecution === 'requester' ||
801
+ run.actorUserId !== root.ownerUserId)
802
+ return allowed;
803
+ const { decision, confirmation } = await commentConsent(conn, root);
804
+ if (decision === 'rejected')
805
+ return refused;
806
+ if (decision === 'confirmed')
807
+ return {
808
+ allowed: true,
809
+ confirmedByUserId: confirmation?.settledById ?? null,
810
+ };
811
+ if (runner.trust === 'team')
812
+ return allowed;
813
+ return (await commentFromOwner(conn, root)) ? allowed : refused;
814
+ }
815
+ /** Withdraws the request a team claim made obsolete, writing only when one is pending. */
816
+ async function withdrawIfPending(rootId, runId) {
817
+ try {
818
+ const pending = await runRequestsRepo(tx.read()).findMany({
819
+ filter: {
820
+ commentRootRunId: rootId,
821
+ commentRunId: runId,
822
+ purpose: 'personalRunner',
823
+ status: 'pending',
824
+ },
825
+ limit: 1,
826
+ });
827
+ if (pending.length > 0)
828
+ await tx.run((unit) => withdrawCommentRequests(unit, rootId, clock.now().toISOString(), runId));
829
+ }
830
+ catch (error) {
831
+ deps.onClaimFailure?.(runId, error);
832
+ }
833
+ }
834
+ async function admit(unit, run, agent, runner, inputs, prepared) {
835
+ const rejection = await deps.subjects.get(run.subjectKind)?.context.admit?.(unit.conn, {
664
836
  run: toRun(run),
665
837
  agent,
666
838
  runner,
667
839
  inputs,
668
840
  cli: deps.cli.name,
669
841
  appName: deps.app.name,
670
- });
842
+ }, prepared);
671
843
  if (!rejection)
672
844
  return true;
673
845
  await finishRun(unit, deps, run, {
@@ -678,8 +850,17 @@ export function createClaimService(deps) {
678
850
  return false;
679
851
  }
680
852
  async function attempt(runner, agent, candidate, reported) {
681
- // Repair pre-upgrade queued work before even checking personal runner ownership.
682
- if (deps.subjects.get(candidate.subjectKind)?.actsAs) {
853
+ const conn = tx.read();
854
+ // Rules that do not depend on who the run acts as come first, and settle the rest of the pass: an agent at its
855
+ // limit is checked once per poll, not once per queued run.
856
+ if ((await deps.slotsUsed(conn, runner.id)) >= runner.slots)
857
+ return { kind: 'full', scope: 'runner' };
858
+ if ((await countActive(conn, 'agentId', agent.id)) >= agent.maxConcurrentRuns)
859
+ return { kind: 'full', scope: 'agent' };
860
+ // Repair pre-upgrade queued work before even checking personal runner ownership. Reading first keeps a poll over
861
+ // current work free of transactions and row locks; only work whose identity changed is written.
862
+ if (deps.subjects.get(candidate.subjectKind)?.actsAs &&
863
+ !(await queuedIdentityCurrent(conn, deps, candidate))) {
683
864
  const current = await tx.run(async (unit) => {
684
865
  await lockAgentForClaim(unit.conn, agent.id, clock.now().toISOString());
685
866
  const run = await findRunRecord(unit.conn, candidate.id);
@@ -692,13 +873,12 @@ export function createClaimService(deps) {
692
873
  candidate = current;
693
874
  }
694
875
  // Avoid assembling or asking authorization when a cheap claim rule already rules this work out.
695
- const conn = tx.read();
696
876
  if (!fits(runner, agent, candidate) ||
697
- (await deps.slotsUsed(conn, runner.id)) >= runner.slots ||
698
- (await countActive(conn, 'agentId', agent.id)) >=
699
- agent.maxConcurrentRuns ||
700
877
  (await runsOfKey(conn, candidate, ACTIVE)).length > 0)
701
878
  return { kind: 'skipped' };
879
+ const consent = await commentConsentFor(conn, runner, candidate);
880
+ if (!consent.allowed)
881
+ return { kind: 'skipped' };
702
882
  // Before the transaction: on SQLite it holds the only connection, so the providers read what else they need now.
703
883
  const prepared = new Map(await prepareExtensions(toRun(candidate), deps.sections, deps.mounts, (error) => deps.onClaimFailure?.(candidate.id, error), deps.repoAccess, { onDemand: asksCredentialsOnDemand(runner) }));
704
884
  const subjectContext = deps.subjects.get(candidate.subjectKind)?.context;
@@ -753,6 +933,8 @@ export function createClaimService(deps) {
753
933
  ]),
754
934
  values: {
755
935
  status: 'dispatched',
936
+ failureReason: null,
937
+ failureDetail: null,
756
938
  runnerId: runner.id,
757
939
  // Brief providers observe this reservation inside the claim transaction.
758
940
  // Replace a previous attempt's tool before computing the roster.
@@ -762,16 +944,21 @@ export function createClaimService(deps) {
762
944
  lastActivityAt: nowText,
763
945
  availableAt: null,
764
946
  updatedAt: nowText,
947
+ ...(consent.confirmedByUserId
948
+ ? { confirmedByUserId: consent.confirmedByUserId }
949
+ : {}),
765
950
  },
766
951
  });
767
952
  if (claimed.updatedCount !== 1)
768
953
  return null;
769
954
  const run = (await findRunRecord(conn, candidate.id));
770
- const pending = await pendingInputs(conn, run.id);
955
+ const pending = (await pendingInputs(conn, run.id)).filter((input) => !run.commentRootRunId ||
956
+ input.type !== 'comment' ||
957
+ jsonObject(input.payload).commentRootRunId === run.commentRootRunId);
771
958
  let assembled;
772
959
  let selectionSnapshot;
773
960
  try {
774
- if (!(await admit(unit, run, agent, runner, pending.map(toInput))))
961
+ if (!(await admit(unit, run, agent, runner, pending.map(toInput), prepared.get('subject'))))
775
962
  return null;
776
963
  // Admission must precede preflight assembly: a missing or obsolete
777
964
  // subject is cancelled normally, rather than counted as setup failure.
@@ -865,6 +1052,11 @@ export function createClaimService(deps) {
865
1052
  await discardRepoAccess(candidate, prepared);
866
1053
  return { kind: 'skipped' };
867
1054
  }
1055
+ // After the claim commits, never inside it: decisions settle the request before touching runs, so a claim that
1056
+ // held its run while waiting for the request could deadlock with them. A request left pending by a crash here is
1057
+ // withdrawn by the next reconciliation, and confirming it finds the run no longer waiting.
1058
+ if (candidate.commentRootRunId && runner.trust === 'team')
1059
+ await withdrawIfPending(candidate.commentRootRunId, candidate.id);
868
1060
  return { kind: 'claimed', payload: taken };
869
1061
  }
870
1062
  async function discardRepoAccess(run, prepared) {
@@ -897,6 +1089,7 @@ export function createClaimService(deps) {
897
1089
  filter: { id: runId },
898
1090
  values: {
899
1091
  claimFailures: failures,
1092
+ failureReason: 'setupFailed',
900
1093
  failureDetail: error.message.slice(0, 10_000),
901
1094
  },
902
1095
  });
@@ -927,8 +1120,9 @@ export function createClaimService(deps) {
927
1120
  const conn = tx.read();
928
1121
  const queued = await candidates();
929
1122
  const agents = new Map();
1123
+ const full = new Set();
930
1124
  for (const candidate of queued) {
931
- if (seen.has(candidate.id))
1125
+ if (seen.has(candidate.id) || full.has(candidate.agentId))
932
1126
  continue;
933
1127
  seen.add(candidate.id);
934
1128
  if (!agents.has(candidate.agentId))
@@ -945,6 +1139,13 @@ export function createClaimService(deps) {
945
1139
  const result = await attempt(runner, agent, candidate, reported);
946
1140
  if (result.kind === 'claimed')
947
1141
  return result.payload;
1142
+ if (result.kind === 'full') {
1143
+ if (result.scope === 'runner')
1144
+ return null;
1145
+ // Seen candidates are passed over for good in this claim; unvisited ones stay eligible for the next pass.
1146
+ seen.delete(candidate.id);
1147
+ full.add(candidate.agentId);
1148
+ }
948
1149
  }
949
1150
  catch (error) {
950
1151
  // Rolled back: the run stays queued for a runner it fits.
@@ -958,6 +1159,38 @@ export function createClaimService(deps) {
958
1159
  return null;
959
1160
  }
960
1161
  return {
1162
+ reconcileCommentConsent,
1163
+ async reconcileCommentQueue() {
1164
+ const conn = tx.read();
1165
+ const queued = await runsRepo(conn).findMany({
1166
+ filter: (f) => f.and([
1167
+ f.string('status').eq('queued'),
1168
+ f.string('commentRootRunId').notEmpty(),
1169
+ ]),
1170
+ sort: (s) => [s.field('createdAt').asc()],
1171
+ limit: 100,
1172
+ });
1173
+ const pending = await runRequestsRepo(conn).findMany({
1174
+ filter: { purpose: 'personalRunner', status: 'pending' },
1175
+ limit: 100,
1176
+ });
1177
+ for (const id of new Set([
1178
+ ...queued.map((run) => run.id),
1179
+ ...pending
1180
+ .map((request) => request.commentRunId)
1181
+ .filter((id) => Boolean(id)),
1182
+ ])) {
1183
+ // Read first: a sweep over settled chains opens no transaction at all.
1184
+ const plan = await planCommentConsent(conn, id);
1185
+ if (plan.kind === 'none')
1186
+ continue;
1187
+ await tx.run(async (unit) => {
1188
+ if (plan.kind === 'request' || plan.kind === 'refresh')
1189
+ await lockAgentForClaim(unit.conn, plan.run.agentId, clock.now().toISOString());
1190
+ await reconcileCommentConsent(unit, id);
1191
+ });
1192
+ }
1193
+ },
961
1194
  diagnose: (conn, run, agent) => selectionFacts(conn, run, agent, false),
962
1195
  async selectionWait(runId) {
963
1196
  const conn = tx.read();