@brainervirus/workit-opencode 1.2.0 → 1.2.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 (2) hide show
  1. package/dist/plugin.js +393 -187
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -7965,6 +7965,7 @@ var workerSchema = object({
7965
7965
  assignment: assignmentSchema,
7966
7966
  state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7967
7967
  session: refSchema.nullable(),
7968
+ coordinator: refSchema.optional(),
7968
7969
  report: workerReportSchema.nullable()
7969
7970
  }).strict();
7970
7971
  var ownerSchema = object({
@@ -8076,6 +8077,7 @@ var taskSummarySchema = object({
8076
8077
  requirements: array(requirementEvaluationSchema),
8077
8078
  writer: workspaceRecordSchema.shape.writer
8078
8079
  }).strict();
8080
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
8079
8081
  var exportBundleSchema = object({
8080
8082
  schemaVersion: literal(1),
8081
8083
  exportedAt: utc,
@@ -8097,11 +8099,15 @@ var taskOperations = {
8097
8099
  expectedWorkspaceRevision: revision.nullable().optional(),
8098
8100
  intent: intentSchema
8099
8101
  }),
8100
- list: operation({ action: literal("list") }),
8102
+ list: operation({
8103
+ action: literal("list"),
8104
+ status: _enum(["open", "closed", "all"]).optional(),
8105
+ limit: number2().int().min(1).max(50).optional()
8106
+ }),
8101
8107
  inspect: operation({
8102
8108
  action: literal("inspect"),
8103
8109
  ...taskId,
8104
- view: _enum(["summary", "full"])
8110
+ view: _enum(["summary", "full"]).optional()
8105
8111
  }),
8106
8112
  revise: operation({
8107
8113
  action: literal("revise"),
@@ -8129,7 +8135,7 @@ var taskOperations = {
8129
8135
  ...taskId,
8130
8136
  ...revisions,
8131
8137
  expectedWorkspaceRevision: revision.optional(),
8132
- authorityRefs: array(refSchema)
8138
+ authorityRefs: array(refSchema).optional()
8133
8139
  }),
8134
8140
  close: operation({
8135
8141
  action: literal("close"),
@@ -9962,6 +9968,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
9962
9968
  }) && !value.split(/[\\/]/).includes("..");
9963
9969
  var callerValue = (value) => ({ host: value.host, actor: value.actor });
9964
9970
  var workerIdOf = (value) => value.workerId ?? null;
9971
+ var currentWriterOwnsTask = (task, workspace, caller) => {
9972
+ const owner = workspace.writer?.owner;
9973
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
9974
+ };
9965
9975
  function assertProductWriteAllowed(input) {
9966
9976
  if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9967
9977
  return failure("invalid_input", "invalid product write path");
@@ -11144,9 +11154,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
11144
11154
  var verifyStandingApproval = (root, task, caller, binding) => {
11145
11155
  if (task.status !== "active")
11146
11156
  return failure("invalid_transition", "only active tasks can authorize actions");
11147
- const session = task.intent?.provenance?.session;
11148
- if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
11149
- return failure("permission_denied", "standing approval requires the lead session");
11157
+ const workspace = new TaskStore(root).readWorkspace();
11158
+ if (!workspace.ok)
11159
+ return workspace;
11160
+ if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
11161
+ return failure("permission_denied", "standing approval requires the current writer session");
11150
11162
  const standing = binding.standing;
11151
11163
  if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
11152
11164
  return failure("invalid_input", "standing approval needs a workspace and class");
@@ -11194,14 +11206,14 @@ var standingAutoApplies = (store, host, actor, operation) => {
11194
11206
  const workspace = store.readWorkspace();
11195
11207
  if (!listed.ok || !workspace.ok || !workspace.data)
11196
11208
  return null;
11197
- const matches = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data.id && task.intent?.provenance?.session?.kind === "host" && task.intent.provenance.session.host === host && task.intent.provenance.session.handle === actor);
11198
- if (matches.length !== 1)
11209
+ const task = listed.data.find((task) => currentWriterOwnsTask(task, workspace.data, { host, actor }));
11210
+ if (!task)
11199
11211
  return null;
11200
11212
  const resolved = resolveAutoApproval(store.root);
11201
11213
  if (resolved.status !== "on")
11202
11214
  return null;
11203
11215
  return {
11204
- task: matches[0],
11216
+ task,
11205
11217
  workspaceId: workspace.data.id,
11206
11218
  workspaceRevision: workspace.data.revision,
11207
11219
  cls,
@@ -11460,6 +11472,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
11460
11472
  };
11461
11473
  var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
11462
11474
  var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
11475
+ var approvalAuthorityMatches = (store, task, approval, current) => {
11476
+ if (approval.kind !== "host_observed" || approval.receipts.length === 0)
11477
+ return false;
11478
+ if (provenanceMatches(approval, current))
11479
+ return true;
11480
+ const session = current.kind === "host_observed" ? current.session : null;
11481
+ if (!session || session.kind !== "host")
11482
+ return false;
11483
+ const workspace = store.readWorkspace();
11484
+ return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
11485
+ host: session.host,
11486
+ actor: session.handle
11487
+ });
11488
+ };
11463
11489
  var validateAction = (store, task, workspaceId, input, authority) => {
11464
11490
  if (task.status !== "active")
11465
11491
  return failure("invalid_transition", "only active tasks can authorize actions");
@@ -11485,7 +11511,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
11485
11511
  return failure("permission_denied", "native reservation authority does not match decision");
11486
11512
  if (decision.response !== "approved")
11487
11513
  return failure("permission_denied", "decision was rejected");
11488
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
11514
+ if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
11489
11515
  return failure("permission_denied", "action approval lacks native receipt assurance");
11490
11516
  const receipts = entry.provenance.receipts;
11491
11517
  const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
@@ -11652,7 +11678,7 @@ function settleAction(input) {
11652
11678
  return failure("not_found", "decision not found");
11653
11679
  if (!authorityDecisionMatches(authority, entry.data))
11654
11680
  return failure("permission_denied", "native settlement authority does not match decision");
11655
- if (!provenanceMatches(entry.provenance, authority.provenance))
11681
+ if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11656
11682
  return failure("permission_denied", "native settlement caller does not match approval");
11657
11683
  if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
11658
11684
  return failure("permission_denied", "decision binding is invalid");
@@ -11751,7 +11777,7 @@ function reconcileAction(input) {
11751
11777
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
11752
11778
  if (!entry || entry.data.purpose !== "action")
11753
11779
  return failure("not_found", "decision not found");
11754
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
11780
+ if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11755
11781
  return failure("permission_denied", "native reconciliation caller does not match approval");
11756
11782
  if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
11757
11783
  return failure("permission_denied", "reconciliation decision binding is invalid");
@@ -12088,7 +12114,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
12088
12114
  candidateId: evidence.candidateId ?? fallback
12089
12115
  };
12090
12116
  };
12091
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
12117
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
12092
12118
  var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
12093
12119
  var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
12094
12120
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
@@ -12558,6 +12584,11 @@ function shouldDenyShellRoute(dir, command) {
12558
12584
  }
12559
12585
  // packages/workit-core/src/core/external-action.ts
12560
12586
  import { execFileSync as execFileSync3 } from "node:child_process";
12587
+ var chainStepSchema = union([
12588
+ string2().min(1),
12589
+ object({ branch: string2().min(1) }).strict(),
12590
+ object({ pr: literal(true) }).strict()
12591
+ ]);
12561
12592
  var externalActionSchema = discriminatedUnion("operation", [
12562
12593
  object({
12563
12594
  operation: literal("git.branch_setup"),
@@ -12572,7 +12603,7 @@ var externalActionSchema = discriminatedUnion("operation", [
12572
12603
  operation: literal("git.commit"),
12573
12604
  payload: object({
12574
12605
  message: string2().min(1).optional(),
12575
- plan_steps: array(string2().min(1)).optional(),
12606
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
12576
12607
  plan_branch: string2().min(1).optional()
12577
12608
  }).strict()
12578
12609
  }).strict(),
@@ -12676,7 +12707,7 @@ var planReservationLength = (operation, descriptorPayload) => {
12676
12707
  const payload = descriptorPayload ?? {};
12677
12708
  return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
12678
12709
  };
12679
- var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch(required unless reapply_stash; the working branch to create or switch to),stash?}; git.commit {message}; git.push {branch?}; hosting.pull_request {title,body?,draft?,target_branch?,babysit?}; hosting.merge {target_branch?,source_branch?}; youtrack.update {issueId,markdown,minutes?}; youtrack.time {issueId,minutes,text?,dateMs?}; youtrack.meeting {issueId,minutes,text}; changelog.apply {entries?,path?,normalize_only?}; context.read {kind,range?,issueId?,issueUrl?,issueRef?,mode?,specPath?,planPath?}. context.read is read-only and needs no approval; all other operations require native approval (CLI uses a TTY; caller-unattested MCP cannot mutate).";
12710
+ var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch(required unless reapply_stash; the working branch to create or switch to),stash?}; git.commit {message?; plan_steps?: (string | {branch:string} | {pr:true})[]; plan_branch?}; git.push {branch?}; hosting.pull_request {title,body?,draft?,target_branch?,babysit?}; hosting.merge {target_branch?,source_branch?}; youtrack.update {issueId,markdown,minutes?}; youtrack.time {issueId,minutes,text?,dateMs?}; youtrack.meeting {issueId,minutes,text}; changelog.apply {entries?,path?,normalize_only?}; context.read {kind: git|pr|youtrack|github_issue|gitlab_issue|changelog|release|affected,range?,issueId?,issueUrl?,issueRef?,mode?,specPath?,planPath?}. context.read is read-only and needs no approval; all other operations require native approval (CLI uses a TTY; caller-unattested MCP cannot mutate).";
12680
12711
  var externalActionRef = (host, actor, descriptor) => ({
12681
12712
  kind: "host",
12682
12713
  host,
@@ -12690,12 +12721,12 @@ var externalActionState = (store, host, actor, operation) => {
12690
12721
  if (!workspace.data)
12691
12722
  return failure("not_found", "workspace not found");
12692
12723
  const candidates = listed.data.flatMap((task) => {
12693
- if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12724
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12694
12725
  return [];
12695
12726
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null && entry.data.binding.approvedContent === operation).map((entry) => ({ task, entry }));
12696
12727
  });
12697
12728
  if (candidates.length !== 1)
12698
- return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
12729
+ return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12699
12730
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
12700
12731
  };
12701
12732
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -12775,8 +12806,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
12775
12806
  const matches = listed.data.flatMap((task) => {
12776
12807
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12777
12808
  return [];
12778
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12779
- return [];
12780
12809
  return task.decisions.flatMap((entry) => {
12781
12810
  const decision = entry.data;
12782
12811
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12818,8 +12847,6 @@ var approvedChainStep = (store, host, actor, query) => {
12818
12847
  const matches = listed.data.flatMap((task) => {
12819
12848
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12820
12849
  return [];
12821
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12822
- return [];
12823
12850
  return task.decisions.flatMap((entry) => {
12824
12851
  const decision = entry.data;
12825
12852
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12976,7 +13003,7 @@ var branchSetupIntent = (descriptor) => {
12976
13003
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
12977
13004
  const current = branchSetupIntent(operation);
12978
13005
  if (!current)
12979
- return failure("permission_denied", "no approved action is bound to this session");
13006
+ return failure("permission_denied", "no approved action is bound to the active task");
12980
13007
  const listed = store.listTasks();
12981
13008
  const workspace = store.readWorkspace();
12982
13009
  if (!listed.ok || !workspace.ok)
@@ -12984,7 +13011,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12984
13011
  if (!workspace.data)
12985
13012
  return failure("not_found", "workspace not found");
12986
13013
  const candidates = listed.data.flatMap((task) => {
12987
- if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
13014
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12988
13015
  return [];
12989
13016
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
12990
13017
  });
@@ -12993,7 +13020,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12993
13020
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
12994
13021
  });
12995
13022
  if (matches.length !== 1)
12996
- return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
13023
+ return failure("permission_denied", matches.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12997
13024
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
12998
13025
  };
12999
13026
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -13550,6 +13577,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
13550
13577
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
13551
13578
  state: expected.state,
13552
13579
  session: expected.session,
13580
+ report: expected.report ?? null,
13553
13581
  provenance: result.data,
13554
13582
  ...binding
13555
13583
  });
@@ -13560,7 +13588,7 @@ var takeWorker = (token, binding, expected) => {
13560
13588
  verifiedWorkers.delete(token);
13561
13589
  if (!value)
13562
13590
  return null;
13563
- if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session))
13591
+ if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session) || !sameValue(value.report, expected.report ?? null))
13564
13592
  return null;
13565
13593
  return value;
13566
13594
  };
@@ -13601,6 +13629,8 @@ var applyWorkerLifecycle = (input) => {
13601
13629
  return failure("invalid_transition", "worker already has an observed session");
13602
13630
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
13603
13631
  return failure("invalid_transition", "worker launch was not claimed");
13632
+ if (input.report && input.state !== "stopped")
13633
+ return failure("invalid_input", "worker reports require a stopped worker");
13604
13634
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
13605
13635
  return failure("invalid_input", "running worker observations require a session");
13606
13636
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -13617,12 +13647,13 @@ var applyWorkerLifecycle = (input) => {
13617
13647
  data: {
13618
13648
  ...entry.data,
13619
13649
  state: terminalCancel ? "cancelling" : input.state,
13620
- session: input.session
13650
+ session: input.session,
13651
+ report: entry.data.report ?? input.report ?? null
13621
13652
  }
13622
13653
  };
13623
13654
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13624
13655
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13625
- if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
13656
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
13626
13657
  return success(task.data.revision, workspace.data.revision, entry);
13627
13658
  const changed = input.store.mutateTaskAndWorkspace({
13628
13659
  taskId: task.data.id,
@@ -14040,14 +14071,30 @@ class WorkitCore {
14040
14071
  const listed = this.store.listTasks();
14041
14072
  if (!listed.ok)
14042
14073
  return listed;
14043
- const summaries = [];
14044
- for (const task of listed.data) {
14045
- const summary = this.summary(task);
14046
- if (!summary.ok)
14047
- return summary;
14048
- summaries.push(summary.data);
14049
- }
14050
- return success(null, null, summaries);
14074
+ const status = input.status ?? "open";
14075
+ const limit = input.limit ?? 20;
14076
+ const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
14077
+ if (selected.length === 0)
14078
+ return success(null, null, []);
14079
+ const workspace = this.store.readWorkspace();
14080
+ if (!workspace.ok)
14081
+ return workspace;
14082
+ if (!workspace.data)
14083
+ return failure("not_found", "workspace not found");
14084
+ const tasks = selected.map((task) => ({
14085
+ id: task.id,
14086
+ revision: task.revision,
14087
+ workspaceRevision: workspace.data.revision,
14088
+ createdAt: task.createdAt,
14089
+ updatedAt: task.updatedAt,
14090
+ runtime: task.runtime ?? null,
14091
+ objective: task.intent.data.objective,
14092
+ status: task.status,
14093
+ closure: task.closure,
14094
+ progress: task.progress,
14095
+ writer: task.status === "closed" ? null : workspace.data.writer
14096
+ }));
14097
+ return success(null, null, tasks);
14051
14098
  }
14052
14099
  case "inspect": {
14053
14100
  const task = this.store.readTask(input.taskId);
@@ -14056,7 +14103,7 @@ class WorkitCore {
14056
14103
  const helper = this.helperTaskGuard(task.data, true);
14057
14104
  if (!helper.ok)
14058
14105
  return helper;
14059
- if (input.view === "summary")
14106
+ if ((input.view ?? "summary") === "summary")
14060
14107
  return this.summary(task.data);
14061
14108
  return this.view(task.data);
14062
14109
  }
@@ -14400,7 +14447,7 @@ class WorkitCore {
14400
14447
  const evaluations = evaluateEvidence(task.data, current.data);
14401
14448
  const verified = evidence.some((entry) => {
14402
14449
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
14403
- return findingVerificationPasses(finding.data.candidateId, {
14450
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
14404
14451
  kind: entry.data.kind,
14405
14452
  candidateId: entry.data.candidateId,
14406
14453
  status: evaluation?.status ?? "missing"
@@ -14480,7 +14527,18 @@ class WorkitCore {
14480
14527
  if (task.data.status !== "active")
14481
14528
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
14482
14529
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
14483
- return failure("permission_denied", "worker assignment is outside task scope");
14530
+ return failure("permission_denied", "worker assignment is outside task scope", {
14531
+ fields: [
14532
+ {
14533
+ path: "assignment.scope",
14534
+ reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
14535
+ },
14536
+ {
14537
+ path: "task.intent.scope",
14538
+ reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
14539
+ }
14540
+ ]
14541
+ });
14484
14542
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
14485
14543
  return failure("invalid_input", "worker assignment references an unknown decision");
14486
14544
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -14687,7 +14745,15 @@ class WorkitCore {
14687
14745
  ...item,
14688
14746
  recordedAt: mutation.now,
14689
14747
  provenance: attested.data,
14690
- data: { ...item.data, state: "dispatching" }
14748
+ data: {
14749
+ ...item.data,
14750
+ state: "dispatching",
14751
+ coordinator: {
14752
+ kind: "host",
14753
+ host: this.context.caller.host,
14754
+ handle: this.context.caller.actor
14755
+ }
14756
+ }
14691
14757
  } : item)
14692
14758
  });
14693
14759
  }
@@ -14763,6 +14829,7 @@ class WorkitCore {
14763
14829
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
14764
14830
  state: "stopped",
14765
14831
  session: null,
14832
+ report: null,
14766
14833
  provenance: attested.data,
14767
14834
  owner: this.authorityOwner,
14768
14835
  store: this.store,
@@ -15070,10 +15137,12 @@ class WorkitCore {
15070
15137
  return workspace;
15071
15138
  if (!workspace.data)
15072
15139
  return failure("not_found", "workspace not found");
15073
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
15140
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
15141
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
15074
15142
  if (!current.ok)
15075
15143
  return current;
15076
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
15144
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
15145
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15077
15146
  return success(task.revision, workspace.data.revision, {
15078
15147
  id: task.id,
15079
15148
  revision: task.revision,
@@ -15087,7 +15156,7 @@ class WorkitCore {
15087
15156
  progress: task.progress,
15088
15157
  policy: task.policy,
15089
15158
  requirements,
15090
- writer: workspace.data.writer
15159
+ writer: evaluationWorkspace.writer
15091
15160
  });
15092
15161
  }
15093
15162
  view(task) {
@@ -15096,13 +15165,15 @@ class WorkitCore {
15096
15165
  return workspace;
15097
15166
  if (!workspace.data)
15098
15167
  return failure("not_found", "workspace not found");
15099
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
15168
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
15169
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
15100
15170
  if (!current.ok)
15101
15171
  return current;
15102
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
15172
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
15173
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15103
15174
  return success(task.revision, workspace.data.revision, {
15104
15175
  task,
15105
- workspace: workspace.data,
15176
+ workspace: evaluationWorkspace,
15106
15177
  capabilities: this.context.capabilities,
15107
15178
  requirements,
15108
15179
  evidence: evaluateEvidence(task, current.data)
@@ -15193,8 +15264,6 @@ class WorkitCore {
15193
15264
  return helper;
15194
15265
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
15195
15266
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
15196
- if (status === "active" && input.authorityRefs.length === 0)
15197
- return failure("permission_denied", "resuming a task requires authority references");
15198
15267
  const workspace = this.store.readWorkspace();
15199
15268
  if (!workspace.ok)
15200
15269
  return workspace;
@@ -15224,7 +15293,7 @@ class WorkitCore {
15224
15293
  return failure("needs_input", "imported task requires fresh destination evidence");
15225
15294
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
15226
15295
  return failure("recovery_required", "imported task requires resume reconciliation");
15227
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
15296
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
15228
15297
  return failure("permission_denied", "imported resume requires native destination approval");
15229
15298
  resumeCandidate = current.data.candidate;
15230
15299
  }
@@ -15354,7 +15423,7 @@ class WorkitCore {
15354
15423
  task: (value, context) => success(context.revision, null, {
15355
15424
  ...value,
15356
15425
  status: "closed",
15357
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
15426
+ candidates: taskForView.candidates,
15358
15427
  closure: {
15359
15428
  outcome: closure.data.outcome,
15360
15429
  at: context.now,
@@ -15731,31 +15800,25 @@ var packageRoot2 = () => {
15731
15800
  var assetsRoot = () => path13.join(packageRoot2(), "assets");
15732
15801
 
15733
15802
  // packages/workit-opencode/src/stale-sources.ts
15734
- import { existsSync as existsSync8 } from "node:fs";
15803
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
15735
15804
  import path14 from "node:path";
15736
15805
  import { fileURLToPath as fileURLToPath3 } from "node:url";
15737
- var packageFiles = [
15738
- "src/index.ts",
15739
- "src/plugin.ts",
15740
- "src/v1/server.ts",
15741
- "src/v2/plugin.ts",
15742
- "src/shared/tools.ts",
15743
- "src/tools/workit.ts"
15744
- ].map((file) => path14.join(packageRoot2(), file));
15745
- var coreDir = path14.join(packageRoot2(), "..", "workit-core", "src", "core");
15806
+ var typescriptFiles = (directory) => {
15807
+ if (!existsSync8(directory))
15808
+ return [];
15809
+ return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
15810
+ const file = path14.join(directory, entry.name);
15811
+ return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
15812
+ });
15813
+ };
15746
15814
  var pluginSourceFiles = [
15747
- fileURLToPath3(import.meta.url),
15748
- ...packageFiles,
15749
- ...[
15750
- "task-contract.ts",
15751
- "task-engine.ts",
15752
- "task-evaluation.ts",
15753
- "task-store.ts",
15754
- "workers.ts",
15755
- "authority.ts",
15756
- "methods.ts"
15757
- ].map((file) => path14.join(coreDir, file))
15758
- ].filter((file) => existsSync8(file));
15815
+ ...new Set([
15816
+ fileURLToPath3(import.meta.url),
15817
+ ...typescriptFiles(path14.join(packageRoot2(), "src")),
15818
+ path14.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
15819
+ ...typescriptFiles(path14.join(packageRoot2(), "..", "workit-core", "src", "core"))
15820
+ ])
15821
+ ].filter((file) => existsSync8(file)).sort();
15759
15822
 
15760
15823
  // packages/workit-opencode/src/tools/repo.ts
15761
15824
  import { realpathSync as realpathSync9 } from "node:fs";
@@ -28471,7 +28534,7 @@ function parseVerifyOutput(stdout) {
28471
28534
 
28472
28535
  // packages/workit-core/src/core/repo-context.ts
28473
28536
  import { spawnSync as spawnSync4 } from "node:child_process";
28474
- import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5 } from "node:fs";
28537
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync6 } from "node:fs";
28475
28538
  import path16 from "node:path";
28476
28539
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
28477
28540
  try {
@@ -28586,7 +28649,7 @@ var printSection = (title) => `
28586
28649
  `;
28587
28650
  var probeCaseInsensitive = (dir, wanted) => {
28588
28651
  try {
28589
- for (const entry of readdirSync5(dir)) {
28652
+ for (const entry of readdirSync6(dir)) {
28590
28653
  if (entry.toLowerCase() === wanted.toLowerCase())
28591
28654
  return entry;
28592
28655
  }
@@ -28655,7 +28718,7 @@ var documentationFiles = (cwd) => {
28655
28718
  return;
28656
28719
  let entries;
28657
28720
  try {
28658
- entries = readdirSync5(dir, { withFileTypes: true });
28721
+ entries = readdirSync6(dir, { withFileTypes: true });
28659
28722
  } catch {
28660
28723
  return;
28661
28724
  }
@@ -29488,7 +29551,7 @@ import {
29488
29551
  mkdirSync as mkdirSync5,
29489
29552
  mkdtempSync,
29490
29553
  readFileSync as readFileSync12,
29491
- readdirSync as readdirSync6,
29554
+ readdirSync as readdirSync7,
29492
29555
  realpathSync as realpathSync8,
29493
29556
  renameSync as renameSync2,
29494
29557
  rmSync,
@@ -31754,7 +31817,7 @@ var readExternalContext = async (root, payload) => {
31754
31817
  }
31755
31818
  }
31756
31819
  };
31757
- var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "changelog.apply";
31820
+ var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "hosting.merge" || operation === "changelog.apply";
31758
31821
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31759
31822
  const store = new TaskStore(root);
31760
31823
  const listed = store.listTasks();
@@ -31765,7 +31828,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31765
31828
  return workspace;
31766
31829
  if (!workspace.data)
31767
31830
  return failure("not_found", "workspace not found");
31768
- const candidates = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === caller.host && task.intent.provenance.session.handle === caller.actor);
31831
+ const candidates = listed.data.filter((task) => currentWriterOwnsTask(task, workspace.data, caller));
31769
31832
  if (candidates.length !== 1)
31770
31833
  return failure("permission_denied", "local external action requires exactly one active task");
31771
31834
  const allowed = assertProductWriteAllowed({
@@ -31775,7 +31838,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31775
31838
  paths,
31776
31839
  store
31777
31840
  });
31778
- return allowed.ok ? success(null, null, null) : allowed;
31841
+ return allowed;
31779
31842
  };
31780
31843
  var safePath = (root, value) => {
31781
31844
  try {
@@ -31787,13 +31850,20 @@ var safePath = (root, value) => {
31787
31850
  var branchCreationBaseline = (root, target, base) => {
31788
31851
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
31789
31852
  if (exists)
31790
- return { base_branch: base, target_exists: true, remote_base: null };
31853
+ return {
31854
+ base_branch: base,
31855
+ target_exists: true,
31856
+ remote_base: null,
31857
+ target_head: gitValue(root, ["rev-parse", `refs/heads/${target}`])
31858
+ };
31791
31859
  const remote = run4(root, ["ls-remote", "origin", `refs/heads/${base}`]);
31792
31860
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
31861
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
31793
31862
  return {
31794
31863
  base_branch: base,
31795
31864
  target_exists: false,
31796
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
31865
+ remote_base: remoteBase,
31866
+ target_head: remoteBase
31797
31867
  };
31798
31868
  };
31799
31869
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -31904,7 +31974,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
31904
31974
  var approvedResolvedExternalAction = (content) => {
31905
31975
  try {
31906
31976
  const value = JSON.parse(content);
31907
- if (value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && value.operation !== "youtrack.update" && value.operation !== "youtrack.time" && value.operation !== "youtrack.meeting")
31977
+ if (value.operation !== "git.branch_setup" && value.operation !== "git.commit" && value.operation !== "git.push" && value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && value.operation !== "youtrack.update" && value.operation !== "youtrack.time" && value.operation !== "youtrack.meeting")
31908
31978
  return failure("invalid_input", "approved action is not a reconciliable external action");
31909
31979
  if (!value.payload || typeof value.payload !== "object")
31910
31980
  return failure("invalid_input", "approved action payload is incomplete");
@@ -31915,18 +31985,24 @@ var approvedResolvedExternalAction = (content) => {
31915
31985
  if (!request.ok || !resolved || typeof resolved !== "object")
31916
31986
  return failure("invalid_input", "approved hosting target is incomplete");
31917
31987
  const target = resolved;
31988
+ if (value.operation === "git.commit" && (typeof target.head !== "string" || typeof target.staged !== "string" || typeof payload.message !== "string"))
31989
+ return failure("invalid_input", "approved Git commit target is incomplete");
31990
+ if (value.operation === "git.push" && (typeof target.branch !== "string" || typeof target.commit !== "string" || typeof target.remote !== "string"))
31991
+ return failure("invalid_input", "approved Git push target is incomplete");
31992
+ if (value.operation === "git.branch_setup" && (typeof target.head !== "string" || (payload.action === "reapply_stash" ? typeof target.stash_commit !== "string" : typeof payload.target_branch !== "string" || typeof target.target_head !== "string")))
31993
+ return failure("invalid_input", "approved Git branch target is incomplete");
31918
31994
  if (value.operation === "hosting.pull_request" && (typeof target.marker !== "string" || typeof target.source_branch !== "string" || typeof target.source_commit !== "string" || typeof target.remote !== "string" || typeof payload.target_branch !== "string"))
31919
31995
  return failure("invalid_input", "approved hosting target is incomplete");
31920
31996
  if (value.operation === "hosting.merge" && (typeof target.marker !== "string" || typeof target.source_branch !== "string" || typeof target.source_commit !== "string" || typeof target.remote !== "string" || typeof payload.target_branch !== "string"))
31921
31997
  return failure("invalid_input", "approved hosting target is incomplete");
31922
31998
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
31923
31999
  return failure("invalid_input", "approved changelog target is incomplete");
31924
- if (value.operation !== "hosting.pull_request" && value.operation !== "changelog.apply" && (typeof target.marker !== "string" || typeof target.baseUrl !== "string" || typeof target.issueId !== "string" || !Array.isArray(target.steps)))
32000
+ if (value.operation !== "git.branch_setup" && value.operation !== "git.commit" && value.operation !== "git.push" && value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && (typeof target.marker !== "string" || typeof target.baseUrl !== "string" || typeof target.issueId !== "string" || !Array.isArray(target.steps)))
31925
32001
  return failure("invalid_input", "approved YouTrack target is incomplete");
31926
32002
  return success(null, null, {
31927
32003
  request: request.data,
31928
32004
  descriptorPayload: value.payload,
31929
- marker: String(target.marker)
32005
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
31930
32006
  });
31931
32007
  } catch {
31932
32008
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -31952,12 +32028,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
31952
32028
  outcome: "unknown"
31953
32029
  });
31954
32030
  var readHostingAction = async (root, resolved, actionRef) => {
31955
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
31956
- return unknownHostingEvidence("hosting.pull_request");
32031
+ const operation = resolved.request.operation;
32032
+ if (operation !== "hosting.pull_request" && operation !== "hosting.merge" || !resolved.marker)
32033
+ return unknownHostingEvidence(operation);
31957
32034
  try {
31958
32035
  const cfg = vcsConfig("load", root);
31959
32036
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
31960
- return unknownHostingEvidence("hosting.pull_request");
32037
+ return unknownHostingEvidence(operation);
31961
32038
  const remote = pushRemote(root);
31962
32039
  const repo = remote ? remoteRepo(remote) : null;
31963
32040
  const approved = resolved.descriptorPayload;
@@ -31976,7 +32053,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
31976
32053
  }
31977
32054
  })();
31978
32055
  if (!repo || typeof branch !== "string" || typeof target !== "string" || typeof sourceCommit !== "string" || typeof approvedRemote !== "string" || !currentIdentity || currentIdentity !== approvedIdentity || repo !== remoteRepo(approvedRemote) || remoteProvider2(approvedRemote) !== null && remoteProvider2(approvedRemote) !== cfg.provider || remoteParts(approvedRemote)?.host !== apiHost)
31979
- return unknownHostingEvidence("hosting.pull_request");
32056
+ return unknownHostingEvidence(operation);
31980
32057
  const token = fs13.readFileSync(cfg.tokenPath, "utf8").trim();
31981
32058
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
31982
32059
  const url = cfg.provider === "github" ? `https://api.github.com/repos/${repo}/pulls?state=all&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(target)}&per_page=100&page=1` : `${cfg.gitlab?.apiUrl ?? "https://gitlab.com/api/v4"}/projects/${encodeURIComponent(repo)}/merge_requests?state=all&source_branch=${encodeURIComponent(branch)}&target_branch=${encodeURIComponent(target)}&per_page=100&page=1`;
@@ -31984,14 +32061,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
31984
32061
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
31985
32062
  });
31986
32063
  if (!response.ok)
31987
- return unknownHostingEvidence("hosting.pull_request");
32064
+ return unknownHostingEvidence(operation);
31988
32065
  const values = await response.json();
31989
32066
  if (!Array.isArray(values) || values.length >= 100)
31990
- return unknownHostingEvidence("hosting.pull_request");
32067
+ return unknownHostingEvidence(operation);
31991
32068
  const records = [];
31992
32069
  for (const item of values) {
31993
32070
  if (!item || typeof item !== "object" || Array.isArray(item))
31994
- return unknownHostingEvidence("hosting.pull_request");
32071
+ return unknownHostingEvidence(operation);
31995
32072
  const record = item;
31996
32073
  const body = record.body ?? record.description;
31997
32074
  const base = record.base;
@@ -32003,7 +32080,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
32003
32080
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
32004
32081
  const idValue = record.number ?? record.iid ?? record.id;
32005
32082
  if (body !== undefined && body !== null && typeof body !== "string" || (cfg.provider === "github" ? !githubShape : !gitlabShape) || typeof targetValue !== "string" || typeof sourceValue !== "string" || typeof shaValue !== "string" || typeof idValue !== "string" && typeof idValue !== "number")
32006
- return unknownHostingEvidence("hosting.pull_request");
32083
+ return unknownHostingEvidence(operation);
32007
32084
  records.push(record);
32008
32085
  }
32009
32086
  const matches = records.filter((item) => {
@@ -32013,15 +32090,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
32013
32090
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
32014
32091
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
32015
32092
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
32016
- return body.includes(resolved.marker) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
32093
+ const merged = cfg.provider === "github" ? typeof item.merged_at === "string" : item.state === "merged" || typeof item.merged_at === "string";
32094
+ return (operation === "hosting.merge" ? merged : body.includes(resolved.marker)) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
32017
32095
  });
32096
+ if (operation === "hosting.merge" && records.some((item) => {
32097
+ const base = item.base;
32098
+ const head = item.head;
32099
+ return (cfg.provider === "github" ? base?.ref : item.target_branch) === target && (cfg.provider === "github" ? head?.ref : item.source_branch) === branch && (cfg.provider === "github" ? typeof item.merged_at !== "string" : item.state !== "merged" && typeof item.merged_at !== "string");
32100
+ }))
32101
+ return unknownHostingEvidence(operation);
32018
32102
  if (matches.length !== 1)
32019
- return unknownHostingEvidence("hosting.pull_request");
32103
+ return unknownHostingEvidence(operation);
32020
32104
  const item = matches[0];
32021
32105
  const id = item.number ?? item.iid ?? item.id;
32022
32106
  if (typeof id !== "string" && typeof id !== "number")
32023
- return unknownHostingEvidence("hosting.pull_request");
32107
+ return unknownHostingEvidence(operation);
32024
32108
  const evidenceDigest = sha256({
32109
+ operation,
32025
32110
  provider: cfg.provider,
32026
32111
  id,
32027
32112
  marker: resolved.marker,
@@ -32042,7 +32127,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
32042
32127
  data: { provider: cfg.provider, id }
32043
32128
  });
32044
32129
  } catch {
32045
- return unknownHostingEvidence("hosting.pull_request");
32130
+ return unknownHostingEvidence(operation);
32046
32131
  }
32047
32132
  };
32048
32133
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -32132,7 +32217,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
32132
32217
  return unknownHostingEvidence(operation);
32133
32218
  }
32134
32219
  };
32135
- var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "hosting.pull_request" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
32220
+ var readLocalAction = async (root, resolved, actionRef) => {
32221
+ const operation = resolved.request.operation;
32222
+ const target = resolved.descriptorPayload.resolved;
32223
+ if (!target)
32224
+ return unknownHostingEvidence(operation);
32225
+ let observed = null;
32226
+ if (operation === "git.commit") {
32227
+ const commit = gitValue(root, ["rev-parse", "HEAD"]);
32228
+ const parent = commit ? gitValue(root, ["rev-parse", `${commit}^`]) : null;
32229
+ const message = commit ? gitValue(root, ["show", "-s", "--format=%B", commit]) : null;
32230
+ const diff = commit && parent ? gitRaw(root, ["diff", "--raw", "-z", "--no-ext-diff", "--no-textconv", parent, commit]) : null;
32231
+ if (commit && parent === target.head && message === resolved.request.payload.message && diff !== null && sha256(diff) === target.staged)
32232
+ observed = { commit, parent, message, staged: target.staged };
32233
+ } else if (operation === "git.push") {
32234
+ const branch = target.branch;
32235
+ const commit = target.commit;
32236
+ const approvedRemote = target.remote;
32237
+ const currentRemote = pushRemote(root);
32238
+ const currentIdentity = currentRemote ? credentialFreeRemote(currentRemote) : null;
32239
+ if (typeof branch === "string" && typeof commit === "string" && typeof approvedRemote === "string" && currentIdentity === approvedRemote) {
32240
+ const remote = run4(root, ["ls-remote", currentRemote, `refs/heads/${branch}`]);
32241
+ const remoteCommit = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : null;
32242
+ if (remoteCommit === commit)
32243
+ observed = { branch, commit, remote: approvedRemote };
32244
+ }
32245
+ } else if (operation === "git.branch_setup") {
32246
+ const targetBranch = resolved.request.payload.target_branch;
32247
+ const sdd = resolved.request.payload.sdd_dir;
32248
+ if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
32249
+ try {
32250
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd, "manifest.json"), "utf8"));
32251
+ const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
32252
+ if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
32253
+ `).includes(target.stash_commit))
32254
+ observed = { stash_commit: target.stash_commit };
32255
+ } catch {}
32256
+ } else if (typeof targetBranch === "string" && typeof sdd === "string" && gitValue(root, ["branch", "--show-current"]) === targetBranch && typeof target.target_head === "string" && gitValue(root, ["rev-parse", "HEAD"]) === target.target_head) {
32257
+ try {
32258
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd, "manifest.json"), "utf8"));
32259
+ if (manifest.branch === targetBranch)
32260
+ observed = { branch: targetBranch };
32261
+ } catch {}
32262
+ }
32263
+ } else if (operation === "changelog.apply") {
32264
+ const afterDigest = target.afterDigest;
32265
+ const preview = changelogApplyPreview({ ...resolved.request.payload, workspace_root: root });
32266
+ if (!preview.error && typeof afterDigest === "string" && preview.beforeDigest === afterDigest)
32267
+ observed = { target: target.target, afterDigest };
32268
+ }
32269
+ if (!observed)
32270
+ return unknownHostingEvidence(operation);
32271
+ const evidenceDigest = sha256({ operation, observed });
32272
+ return success(null, null, {
32273
+ outcome: "succeeded",
32274
+ evidenceDigest,
32275
+ observation: {
32276
+ kind: "provider_read",
32277
+ actionRef,
32278
+ outcome: "succeeded",
32279
+ evidenceDigest
32280
+ }
32281
+ });
32282
+ };
32283
+ var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "git.branch_setup" || resolved.request.operation === "git.commit" || resolved.request.operation === "git.push" || resolved.request.operation === "changelog.apply" ? readLocalAction(root, resolved, actionRef) : resolved.request.operation === "hosting.pull_request" || resolved.request.operation === "hosting.merge" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
32136
32284
  var youTrackBase = () => {
32137
32285
  try {
32138
32286
  const config = JSON.parse(fs13.readFileSync(youTrackConfigPath(), "utf8"));
@@ -32190,6 +32338,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32190
32338
  return failure("storage_error", "current Git commit could not be resolved");
32191
32339
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
32192
32340
  const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
32341
+ let stashTarget;
32342
+ if (action === "reapply_stash") {
32343
+ try {
32344
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd_dir, "manifest.json"), "utf8"));
32345
+ const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
32346
+ const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
32347
+ if (stash_ref && stash_commit)
32348
+ stashTarget = { stash_ref, stash_commit };
32349
+ } catch {}
32350
+ }
32193
32351
  const normalized = {
32194
32352
  operation: request.operation,
32195
32353
  payload: {
@@ -32204,7 +32362,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32204
32362
  request: normalized,
32205
32363
  descriptorPayload: {
32206
32364
  ...normalized.payload,
32207
- resolved: { head, dirty, ...creation }
32365
+ resolved: { head, dirty, ...creation, ...stashTarget }
32208
32366
  }
32209
32367
  });
32210
32368
  }
@@ -32356,7 +32514,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32356
32514
  outcome: "not_started",
32357
32515
  fields: [{ path: "source_branch", reason: "protected branch" }]
32358
32516
  });
32359
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
32517
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
32360
32518
  const remote = pushRemote(root);
32361
32519
  const identity = remote ? credentialFreeRemote(remote) : null;
32362
32520
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -32644,7 +32802,17 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
32644
32802
  const workText = fresh.data.descriptorPayload.resolved?.workText;
32645
32803
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
32646
32804
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
32647
- return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32805
+ const result = await executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32806
+ if (!result.ok && result.code === "external_outcome_unknown" && caller) {
32807
+ const observed = await readLocalAction(root, fresh.data, {
32808
+ kind: "host",
32809
+ host: caller.host,
32810
+ handle: `local-recovery:${fresh.data.request.operation}`
32811
+ });
32812
+ if (observed.ok)
32813
+ return success(null, null, { recovered: true });
32814
+ }
32815
+ return result;
32648
32816
  };
32649
32817
 
32650
32818
  // packages/workit-opencode/src/shared/session.ts
@@ -32702,6 +32870,7 @@ var purposeForQuestion = (question) => {
32702
32870
  class NativeReceiptStore {
32703
32871
  #receipts = new Map;
32704
32872
  #observations = new WeakSet;
32873
+ #reservations = new WeakMap;
32705
32874
  #pending = new Map;
32706
32875
  #now;
32707
32876
  constructor(options = {}) {
@@ -32774,7 +32943,7 @@ class NativeReceiptStore {
32774
32943
  const args = input.args;
32775
32944
  this.mint(input.sessionID, input.callID, args?.questions, answers);
32776
32945
  }
32777
- consume(sessionID, purpose, expected = {}) {
32946
+ reserve(sessionID, purpose, expected = {}) {
32778
32947
  const queue = this.#receipts.get(sessionID) ?? [];
32779
32948
  for (let i = queue.length - 1;i >= 0; i -= 1) {
32780
32949
  const receipt = queue[i];
@@ -32782,11 +32951,9 @@ class NativeReceiptStore {
32782
32951
  continue;
32783
32952
  if (expected.callID !== undefined && receipt.callID !== expected.callID || expected.selectedLabel !== undefined && normalizeReceiptLabel(receipt.selectedLabel) !== normalizeReceiptLabel(expected.selectedLabel) || expected.selectedDescription !== undefined && receipt.selectedDescription !== expected.selectedDescription || expected.decisionPurpose !== undefined && receipt.decisionPurpose !== expected.decisionPurpose || expected.contentDigest !== undefined && receipt.contentDigest !== expected.contentDigest || expected.question !== undefined && receipt.question !== expected.question)
32784
32953
  continue;
32785
- queue.splice(i, 1);
32786
- if (!queue.length)
32787
- this.#receipts.delete(sessionID);
32788
32954
  const observation = { receipt };
32789
32955
  this.#observations.add(observation);
32956
+ this.#reservations.set(observation, receipt);
32790
32957
  return { ok: true, receipt, observation };
32791
32958
  }
32792
32959
  return {
@@ -32812,6 +32979,26 @@ class NativeReceiptStore {
32812
32979
  })()
32813
32980
  };
32814
32981
  }
32982
+ commit(observation) {
32983
+ const receipt = this.#reservations.get(observation);
32984
+ this.#reservations.delete(observation);
32985
+ if (!receipt)
32986
+ return false;
32987
+ const queue = this.#receipts.get(receipt.sessionID) ?? [];
32988
+ const index = queue.indexOf(receipt);
32989
+ if (index < 0)
32990
+ return false;
32991
+ queue.splice(index, 1);
32992
+ if (!queue.length)
32993
+ this.#receipts.delete(receipt.sessionID);
32994
+ return true;
32995
+ }
32996
+ consume(sessionID, purpose, expected = {}) {
32997
+ const reserved = this.reserve(sessionID, purpose, expected);
32998
+ if (reserved.ok)
32999
+ this.commit(reserved.observation);
33000
+ return reserved;
33001
+ }
32815
33002
  verify(observation, sessionID, purpose) {
32816
33003
  if (typeof observation !== "object" || observation === null || !this.#observations.has(observation))
32817
33004
  return null;
@@ -33054,6 +33241,10 @@ var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedEx
33054
33241
  settleObservation: (outcome, revisions) => nativeExternalActionObservation(actor, actionRef, outcome, actionRef.kind === "host" ? actionRef.handle : "external-action", revisions)
33055
33242
  };
33056
33243
  });
33244
+ var workerCoordinatorFor = (task, worker) => {
33245
+ const coordinator = worker.data.coordinator ?? task.intent.provenance.session;
33246
+ return coordinator?.kind === "host" && coordinator.host === "opencode" ? coordinator.handle : null;
33247
+ };
33057
33248
  var workerIdFor = (store, actor, current, directChildren) => {
33058
33249
  const parentID = sessionParent(current);
33059
33250
  if (!parentID)
@@ -33064,10 +33255,9 @@ var workerIdFor = (store, actor, current, directChildren) => {
33064
33255
  const matches = tasks.data.flatMap((task) => {
33065
33256
  if (task.status !== "active")
33066
33257
  return [];
33067
- const coordinator = task.intent.provenance.session;
33068
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode" || coordinator.handle !== parentID || directChildren.get(actor) !== parentID)
33258
+ if (directChildren.get(actor) !== parentID)
33069
33259
  return [];
33070
- return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor);
33260
+ return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor && workerCoordinatorFor(task, worker) === parentID);
33071
33261
  });
33072
33262
  return matches.length === 1 ? matches[0].id : null;
33073
33263
  };
@@ -33127,10 +33317,11 @@ var createWorkitTools = ({
33127
33317
  };
33128
33318
  if (decision.response === "approved")
33129
33319
  expectation.selectedDescription = decision.binding.approvedContent;
33130
- const observed = receipts.consume(context.sessionID, "decision", expectation);
33320
+ const observed = receipts.reserve(context.sessionID, "decision", expectation);
33131
33321
  if (!observed.ok)
33132
33322
  return output2(failure("permission_denied", observed.error));
33133
33323
  let recordInput = parsed.data;
33324
+ let proposalToCommit;
33134
33325
  if (decision.purpose === "action" && decision.response === "approved") {
33135
33326
  const queue = actionProposals.get(context.sessionID) ?? [];
33136
33327
  const textMatches = queue.filter((pending) => pending.presented === decision.binding.presented && pending.approvedText === decision.binding.approvedContent);
@@ -33142,7 +33333,7 @@ var createWorkitTools = ({
33142
33333
  const fresh = resolveExternalActionRequest(context.directory, pending.request);
33143
33334
  const freshDescriptor = fresh.ok ? externalActionDescriptor(pending.request.operation, fresh.data.descriptorPayload) : null;
33144
33335
  if (freshDescriptor === pending.descriptor) {
33145
- actionProposals.set(context.sessionID, queue.filter((candidate) => candidate !== pending));
33336
+ proposalToCommit = pending;
33146
33337
  recordInput = {
33147
33338
  ...recordInput,
33148
33339
  binding: {
@@ -33161,6 +33352,13 @@ var createWorkitTools = ({
33161
33352
  }
33162
33353
  }
33163
33354
  result = core.observeDecision(recordInput, observed.observation);
33355
+ if (result.ok) {
33356
+ receipts.commit(observed.observation);
33357
+ if (proposalToCommit) {
33358
+ const queue = actionProposals.get(context.sessionID) ?? [];
33359
+ actionProposals.set(context.sessionID, queue.filter((candidate) => candidate !== proposalToCommit));
33360
+ }
33361
+ }
33164
33362
  } else {
33165
33363
  const run = core[family];
33166
33364
  result = run.call(core, parsed.data);
@@ -33216,7 +33414,7 @@ var createWorkitTools = ({
33216
33414
  return output2(failure("permission_denied", "child sessions cannot run external actions"));
33217
33415
  const store = new TaskStore(context.directory);
33218
33416
  const prior = priorExternalAction(store, "opencode", context.sessionID, resolved.data.request.operation, resolved.data.request.payload);
33219
- if (prior.ok && prior.data.entry.data.consumption !== null && prior.data.entry.data.consumption.state !== "uncertain")
33417
+ if (prior.ok && prior.data.entry.data.consumption !== null && prior.data.entry.data.consumption.state !== "uncertain" && resolved.data.request.operation !== "git.commit" && resolved.data.request.operation !== "git.push")
33220
33418
  return output2(failure("permission_denied", "external action was already settled"));
33221
33419
  const reconciliationTokens = new WeakSet;
33222
33420
  const core = new WorkitCore(store, {
@@ -33519,10 +33717,10 @@ var plugin = async ({ client, directory }) => {
33519
33717
  const listed = store.listTasks();
33520
33718
  if (!workspace.ok || !workspace.data || !listed.ok)
33521
33719
  return "unmanaged";
33522
- const owned = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator);
33523
- if (owned.length === 0)
33720
+ const managed = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && (task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator || task.workers.some((entry) => entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator) || workspace.data.writer?.owner.taskId === task.id && workspace.data.writer.owner.session.host === "opencode" && workspace.data.writer.owner.session.handle === coordinator));
33721
+ if (managed.length === 0)
33524
33722
  return "unmanaged";
33525
- const eligible = owned.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
33723
+ const eligible = managed.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
33526
33724
  const next = oldestOfSingleTask(eligible);
33527
33725
  if (!next)
33528
33726
  return "unbound";
@@ -33606,7 +33804,7 @@ var plugin = async ({ client, directory }) => {
33606
33804
  dispatches.delete(coordinator);
33607
33805
  return committed.ok;
33608
33806
  };
33609
- const observeLifecycle = async (sessionID, parentID, state, binding, initial = false, eventInfo) => {
33807
+ const observeLifecycle = async (sessionID, parentID, state, binding, eventInfo, report) => {
33610
33808
  const store = new TaskStore(directory);
33611
33809
  const listed = store.listTasks();
33612
33810
  const workspace = store.readWorkspace();
@@ -33616,7 +33814,7 @@ var plugin = async ({ client, directory }) => {
33616
33814
  }
33617
33815
  const persisted = listed.data.flatMap((task) => task.status === "active" ? task.workers.filter((entry) => entry.id === binding?.workerId || entry.data.session?.kind === "host" && entry.data.session.handle === sessionID).map((entry) => ({ task, entry })) : []);
33618
33816
  const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
33619
- if (binding && !initial && matches.length !== 1) {
33817
+ if (binding && matches.length !== 1) {
33620
33818
  droppedLifecycle(sessionID, "bound child worker handle is ambiguous");
33621
33819
  return;
33622
33820
  }
@@ -33625,7 +33823,7 @@ var plugin = async ({ client, directory }) => {
33625
33823
  droppedLifecycle(sessionID, binding ? "bound worker is missing from persisted state" : "worker session is ambiguous");
33626
33824
  return;
33627
33825
  }
33628
- if (!initial && selected.entry.data.session?.kind !== "host") {
33826
+ if (selected.entry.data.session?.kind !== "host") {
33629
33827
  droppedLifecycle(sessionID, "selected worker has no bound host session");
33630
33828
  return;
33631
33829
  }
@@ -33633,25 +33831,26 @@ var plugin = async ({ client, directory }) => {
33633
33831
  droppedLifecycle(sessionID, "selected worker provenance is not host-observed");
33634
33832
  return;
33635
33833
  }
33636
- if (!binding) {
33637
- const coordinator = selected.task.intent.provenance.session;
33638
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode") {
33639
- droppedLifecycle(sessionID, "coordinator session is not validated");
33834
+ const observed = eventInfo ?? await sessionData2(client, sessionID);
33835
+ if (binding && eventInfo) {
33836
+ if (!deletedSessionEnd(directory, sessionID, parentID, observed)) {
33837
+ droppedLifecycle(sessionID, "deleted session payload contradicts the binding");
33640
33838
  return;
33641
33839
  }
33642
- parentID = coordinator.handle;
33840
+ } else if (!trustedSession(directory, sessionID, observed)) {
33841
+ droppedLifecycle(sessionID, "live session observation is not trusted");
33842
+ return;
33643
33843
  }
33644
- if (!initial) {
33645
- const observed = eventInfo ?? await sessionData2(client, sessionID);
33646
- if (binding && eventInfo) {
33647
- if (!deletedSessionEnd(directory, sessionID, parentID, observed)) {
33648
- droppedLifecycle(sessionID, "deleted session payload contradicts the binding");
33649
- return;
33650
- }
33651
- } else if (!trustedSession(directory, sessionID, observed) || observed.parentID !== parentID) {
33652
- droppedLifecycle(sessionID, "live session observation is not trusted");
33844
+ if (!binding) {
33845
+ const coordinator = workerCoordinatorFor(selected.task, selected.entry);
33846
+ if (!coordinator || observed.parentID !== coordinator) {
33847
+ droppedLifecycle(sessionID, "live session parent contradicts persisted coordinator");
33653
33848
  return;
33654
33849
  }
33850
+ parentID = coordinator;
33851
+ } else if (!eventInfo && observed.parentID !== parentID) {
33852
+ droppedLifecycle(sessionID, "live session parent contradicts the binding");
33853
+ return;
33655
33854
  }
33656
33855
  directChildren.set(sessionID, parentID);
33657
33856
  const core = new WorkitCore(store, {
@@ -33669,6 +33868,7 @@ var plugin = async ({ client, directory }) => {
33669
33868
  expectedWorkspaceRevision: workspace.data.revision,
33670
33869
  state,
33671
33870
  session: { kind: "host", host: "opencode", handle: sessionID },
33871
+ ...report ? { report } : {},
33672
33872
  observation: { event: state, sessionID }
33673
33873
  });
33674
33874
  if (!result.ok) {
@@ -33684,25 +33884,9 @@ var plugin = async ({ client, directory }) => {
33684
33884
  const bindCreatedSession = async (info) => {
33685
33885
  if (typeof info.id !== "string" || typeof info.parentID !== "string" || !sameWorkspace(directory, info.directory))
33686
33886
  return;
33687
- const live = dispatches.get(info.parentID);
33688
- if (live)
33689
- live.generation.childCreated = true;
33690
- const store = new TaskStore(directory);
33691
- const workspace = store.readWorkspace();
33692
- const listed = store.listTasks();
33693
- if (!workspace.ok || !workspace.data || !listed.ok)
33694
- return;
33695
- const candidates = listed.data.flatMap((task) => task.status === "active" && task.workspaceId === workspace.data?.id ? task.workers.filter((entry) => (entry.data.state === "assigned" || entry.data.state === "dispatching") && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === info.parentID).map((entry) => ({ task, entry })) : []);
33696
- const next = oldestOfSingleTask(candidates);
33697
- if (!next)
33698
- return;
33699
33887
  directChildren.set(info.id, info.parentID);
33700
- if (live && live.taskId === next.task.id && live.workerId === next.entry.id && commitDispatchStart(info.parentID, info.id))
33701
- return;
33702
- await observeLifecycle(info.id, info.parentID, "running", {
33703
- taskId: next.task.id,
33704
- workerId: next.entry.id
33705
- }, true);
33888
+ if (dispatches.has(info.parentID))
33889
+ commitDispatchStart(info.parentID, info.id);
33706
33890
  };
33707
33891
  return {
33708
33892
  tool: tools,
@@ -33724,7 +33908,7 @@ var plugin = async ({ client, directory }) => {
33724
33908
  return;
33725
33909
  const binding = lifecycleBindings.get(sessionID);
33726
33910
  const state = event.type === "session.status" ? event.properties.status.type === "busy" ? "running" : event.properties.status.type === "idle" ? "stopped" : "unknown" : event.type === "session.idle" ? "stopped" : event.type === "session.deleted" ? "stopped" : "unknown";
33727
- await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding, false, event.type === "session.deleted" ? properties.info : undefined);
33911
+ await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding, event.type === "session.deleted" ? properties.info : undefined);
33728
33912
  },
33729
33913
  "tool.execute.after": async (input, output) => {
33730
33914
  observeQuestion(receipts, input, output);
@@ -33750,7 +33934,12 @@ var plugin = async ({ client, directory }) => {
33750
33934
  if (completedChild) {
33751
33935
  const completionBinding = lifecycleBindings.get(completedChild);
33752
33936
  if (completionBinding)
33753
- await observeLifecycle(completedChild, input.sessionID, "stopped", completionBinding, false, undefined);
33937
+ await observeLifecycle(completedChild, input.sessionID, "stopped", completionBinding, undefined, {
33938
+ outcome: "completed",
33939
+ summary: outputText.match(/<task_result>([\s\S]*?)<\/task_result>/)?.[1]?.trim() || outputText.trim() || "Native worker completed.",
33940
+ evidenceIds: [],
33941
+ findingIds: []
33942
+ });
33754
33943
  } else if (!resolved && /(?:cancel|interrupt|unknown|uncertain)/.test(state))
33755
33944
  unresolvedTaskLaunches.add(input.sessionID);
33756
33945
  }
@@ -33992,6 +34181,7 @@ var createV2Lifecycle = (deps) => {
33992
34181
  return null;
33993
34182
  return session;
33994
34183
  };
34184
+ const managedByCoordinator = (task, workspace, coordinator) => task.status === "active" && task.workspaceId === workspace.id && (task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator || task.workers.some((entry) => entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator) || workspace.writer?.owner.taskId === task.id && workspace.writer.owner.session.host === "opencode" && workspace.writer.owner.session.handle === coordinator);
33995
34185
  const ownsActiveTask = (coordinator) => {
33996
34186
  try {
33997
34187
  const store = new TaskStore(root);
@@ -33999,7 +34189,7 @@ var createV2Lifecycle = (deps) => {
33999
34189
  const listed = store.listTasks();
34000
34190
  if (!workspace.ok || !workspace.data || !listed.ok)
34001
34191
  return false;
34002
- return listed.data.some((task) => task.status === "active" && task.workspaceId === workspace.data?.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator);
34192
+ return listed.data.some((task) => managedByCoordinator(task, workspace.data, coordinator));
34003
34193
  } catch {
34004
34194
  return false;
34005
34195
  }
@@ -34027,10 +34217,10 @@ var createV2Lifecycle = (deps) => {
34027
34217
  const listed = store.listTasks();
34028
34218
  if (!workspace.ok || !workspace.data || !listed.ok)
34029
34219
  return "unmanaged";
34030
- const owned = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator);
34031
- if (owned.length === 0)
34220
+ const managed = listed.data.filter((task) => managedByCoordinator(task, workspace.data, coordinator));
34221
+ if (managed.length === 0)
34032
34222
  return "unmanaged";
34033
- const eligible = owned.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
34223
+ const eligible = managed.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
34034
34224
  const next = oldestOfSingleTask(eligible);
34035
34225
  if (!next)
34036
34226
  return "unbound";
@@ -34098,7 +34288,7 @@ var createV2Lifecycle = (deps) => {
34098
34288
  });
34099
34289
  return true;
34100
34290
  };
34101
- const observeLifecycle = async (sessionID, parentID, state, binding, initial = false, deleted = false) => {
34291
+ const observeLifecycle = async (sessionID, parentID, state, binding, deleted = false, report) => {
34102
34292
  const store = new TaskStore(root);
34103
34293
  const listed = store.listTasks();
34104
34294
  const workspace = store.readWorkspace();
@@ -34106,24 +34296,28 @@ var createV2Lifecycle = (deps) => {
34106
34296
  return;
34107
34297
  const persisted = listed.data.flatMap((task) => task.status === "active" ? task.workers.filter((entry) => entry.id === binding?.workerId || entry.data.session?.kind === "host" && entry.data.session.handle === sessionID).map((entry) => ({ task, entry })) : []);
34108
34298
  const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
34109
- if (binding && !initial && matches.length !== 1)
34299
+ if (binding && matches.length !== 1)
34110
34300
  return;
34111
34301
  const selected = binding ? persisted.find(({ task, entry }) => task.id === binding.taskId && entry.id === binding.workerId) : matches.length === 1 ? matches[0] : undefined;
34112
34302
  if (!selected)
34113
34303
  return;
34114
- if (!initial && selected.entry.data.session?.kind !== "host")
34304
+ if (selected.entry.data.session?.kind !== "host")
34115
34305
  return;
34116
34306
  if (selected.entry.provenance.session?.kind !== "host")
34117
34307
  return;
34118
- if (!binding) {
34119
- const coordinator = selected.task.intent.provenance.session;
34120
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode")
34121
- return;
34122
- parentID = coordinator.handle;
34123
- }
34124
- if (!initial && !deleted) {
34308
+ if (!binding && deleted)
34309
+ return;
34310
+ if (!deleted) {
34125
34311
  const observed = await readSession(sessionID);
34126
- if (!observed || !sameWorkspace(root, observed.directory) || parentOf(observed) !== parentID)
34312
+ const observedParent = parentOf(observed);
34313
+ if (!observed || !sameWorkspace(root, observed.directory) || !observedParent)
34314
+ return;
34315
+ if (!binding) {
34316
+ const coordinator = workerCoordinatorFor(selected.task, selected.entry);
34317
+ if (!coordinator || observedParent !== coordinator)
34318
+ return;
34319
+ parentID = coordinator;
34320
+ } else if (observedParent !== parentID)
34127
34321
  return;
34128
34322
  }
34129
34323
  directChildren.set(sessionID, parentID);
@@ -34142,6 +34336,7 @@ var createV2Lifecycle = (deps) => {
34142
34336
  expectedWorkspaceRevision: workspace.data.revision,
34143
34337
  state,
34144
34338
  session: { kind: "host", host: "opencode", handle: sessionID },
34339
+ ...report ? { report } : {},
34145
34340
  observation: { event: state, sessionID }
34146
34341
  });
34147
34342
  if (!result.ok)
@@ -34188,6 +34383,10 @@ var createV2Lifecycle = (deps) => {
34188
34383
  const child = childID ? await readSession(childID) : null;
34189
34384
  if (!child || parentOf(child) !== coordinator)
34190
34385
  throw new Error("delegation_lineage_denied: a continuation sessionID must name an existing direct child of the coordinator");
34386
+ const listed = new TaskStore(root).listTasks();
34387
+ const bound = listed.ok ? listed.data.filter((task) => task.workers.some((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.handle === childID && workerCoordinatorFor(task, worker) === coordinator)).length : 0;
34388
+ if (bound !== 1 && ownsActiveTask(coordinator))
34389
+ throw new Error("delegation_lineage_denied: a continuation sessionID must name a running Workit worker");
34191
34390
  return;
34192
34391
  }
34193
34392
  if (!ownsActiveTask(coordinator)) {
@@ -34248,7 +34447,12 @@ var createV2Lifecycle = (deps) => {
34248
34447
  if (completedChild) {
34249
34448
  const binding = lifecycleBindings.get(completedChild);
34250
34449
  if (binding)
34251
- await observeLifecycle(completedChild, coordinator, "stopped", binding);
34450
+ await observeLifecycle(completedChild, coordinator, "stopped", binding, false, {
34451
+ outcome: "completed",
34452
+ summary: output.trim() || "Native worker completed.",
34453
+ evidenceIds: [],
34454
+ findingIds: []
34455
+ });
34252
34456
  }
34253
34457
  } else {
34254
34458
  const message = String(event.error?.message ?? event.error ?? "").toLowerCase();
@@ -34266,24 +34470,12 @@ var createV2Lifecycle = (deps) => {
34266
34470
  const bindCreatedSession = async (data) => {
34267
34471
  if (!sameWorkspace(root, data.directory))
34268
34472
  return;
34473
+ directChildren.set(data.sessionID, data.parentID);
34269
34474
  const live = dispatches.get(data.parentID);
34270
34475
  if (live) {
34271
- live.generation.childCreated = true;
34272
34476
  live.childID = data.sessionID;
34477
+ commitDispatchStart(data.parentID, data.sessionID);
34273
34478
  }
34274
- const store = new TaskStore(root);
34275
- const workspace = store.readWorkspace();
34276
- const listed = store.listTasks();
34277
- if (!workspace.ok || !workspace.data || !listed.ok)
34278
- return;
34279
- const candidates = listed.data.flatMap((task) => task.status === "active" && task.workspaceId === workspace.data?.id ? task.workers.filter((entry) => (entry.data.state === "assigned" || entry.data.state === "dispatching") && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === data.parentID).map((entry) => ({ task, entry })) : []);
34280
- const next = oldestOfSingleTask(candidates);
34281
- if (!next)
34282
- return;
34283
- directChildren.set(data.sessionID, data.parentID);
34284
- if (live && live.taskId === next.task.id && live.workerId === next.entry.id && commitDispatchStart(data.parentID, data.sessionID))
34285
- return;
34286
- await observeLifecycle(data.sessionID, data.parentID, "running", { taskId: next.task.id, workerId: next.entry.id }, true);
34287
34479
  };
34288
34480
  const handleEvent = async (event) => {
34289
34481
  const type = event.type;
@@ -34310,7 +34502,7 @@ var createV2Lifecycle = (deps) => {
34310
34502
  const directory = event.location?.directory;
34311
34503
  if (typeof directory === "string" && !sameWorkspace(root, directory))
34312
34504
  return;
34313
- await observeLifecycle(sessionID, binding?.parentID ?? "", "stopped", binding, false, true);
34505
+ await observeLifecycle(sessionID, binding?.parentID ?? "", "stopped", binding, true);
34314
34506
  return;
34315
34507
  }
34316
34508
  const state = type === "session.execution.started" ? "running" : "stopped";
@@ -34391,13 +34583,13 @@ var normalizeQuestionAnswers = (answers) => {
34391
34583
  }
34392
34584
  if (first && typeof first === "object") {
34393
34585
  const values = Object.values(first);
34394
- if (values.length > 0 && values.every((entry) => singleValue(entry) !== null))
34586
+ if (values.length === 1 && singleValue(values[0]) !== null)
34395
34587
  return [[singleValue(values[0])]];
34396
34588
  }
34397
34589
  return answers;
34398
34590
  }
34399
34591
  const values = Object.values(answers);
34400
- if (values.length > 0 && values.every((entry) => singleValue(entry) !== null))
34592
+ if (values.length === 1 && singleValue(values[0]) !== null)
34401
34593
  return [[singleValue(values[0])]];
34402
34594
  return answers;
34403
34595
  };
@@ -34528,10 +34720,9 @@ var workerIdFor2 = (store, actor, parentID, directChildren) => {
34528
34720
  const matches = tasks.data.flatMap((task) => {
34529
34721
  if (task.status !== "active")
34530
34722
  return [];
34531
- const coordinator = task.intent.provenance.session;
34532
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode" || coordinator.handle !== parentID || directChildren.get(actor) !== parentID)
34723
+ if (directChildren.get(actor) !== parentID)
34533
34724
  return [];
34534
- return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor);
34725
+ return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor && workerCoordinatorFor(task, worker) === parentID);
34535
34726
  });
34536
34727
  return matches.length === 1 ? matches[0].id : null;
34537
34728
  };
@@ -34543,7 +34734,7 @@ var recordDecision = (core, receipts, request, sessionID) => {
34543
34734
  const budgetIssue = workitBindingQuestionIssue([content]);
34544
34735
  if (budgetIssue)
34545
34736
  return failure("invalid_input", budgetIssue);
34546
- const observed = receipts.consume(sessionID, "decision", {
34737
+ const observed = receipts.reserve(sessionID, "decision", {
34547
34738
  selectedLabel: decision.response,
34548
34739
  decisionPurpose: decision.purpose,
34549
34740
  contentDigest: sha256(canonicalJson(content)),
@@ -34554,10 +34745,15 @@ var recordDecision = (core, receipts, request, sessionID) => {
34554
34745
  return failure("permission_denied", observed.error);
34555
34746
  if (decision.purpose === "action" && decision.response === "approved" && !isSelfAuthorizingActionContent(decision.binding.approvedContent))
34556
34747
  return failure("invalid_input", "no matching action proposal; resolve the action through the action tool first");
34557
- return core.observeDecision(request, observed.observation);
34748
+ const result = core.observeDecision(request, observed.observation);
34749
+ if (result.ok)
34750
+ receipts.commit(observed.observation);
34751
+ return result;
34558
34752
  };
34559
34753
  var setup = async (ctx) => {
34560
34754
  const root = ctx.location.directory;
34755
+ const sourceMarker = markSourcesLoaded(pluginSourceFiles);
34756
+ let staleSourcesWarned = false;
34561
34757
  const receipts = new NativeReceiptStore;
34562
34758
  const lifecycle = createV2Lifecycle({
34563
34759
  root,
@@ -34664,6 +34860,16 @@ var setup = async (ctx) => {
34664
34860
  await ctx.session.hook("context", async (event) => {
34665
34861
  const session = await sessionFacts(ctx, String(event.sessionID));
34666
34862
  injectAgentContext(root, session, lifecycle.directChildren, event.system);
34863
+ if (!staleSourcesWarned) {
34864
+ const changed = changedSourcesSinceLoad(sourceMarker);
34865
+ if (changed.length > 0) {
34866
+ staleSourcesWarned = true;
34867
+ event.system.push({
34868
+ type: "text",
34869
+ text: `<workit-warning>Workit sources changed after plugin load (${changed.map((file) => file.split(/[\\/]/).at(-1)).join(", ")}); restart the session for latest behavior.</workit-warning>`
34870
+ });
34871
+ }
34872
+ }
34667
34873
  });
34668
34874
  await ctx.session.hook("compaction", (event) => {
34669
34875
  injectCompactionContext(root, String(event.sessionID), event.system);