@brainervirus/workit-pi 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.
package/dist/worker.js CHANGED
@@ -7602,6 +7602,7 @@ var workerSchema = object({
7602
7602
  assignment: assignmentSchema,
7603
7603
  state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7604
7604
  session: refSchema.nullable(),
7605
+ coordinator: refSchema.optional(),
7605
7606
  report: workerReportSchema.nullable()
7606
7607
  }).strict();
7607
7608
  var ownerSchema = object({
@@ -7713,6 +7714,7 @@ var taskSummarySchema = object({
7713
7714
  requirements: array(requirementEvaluationSchema),
7714
7715
  writer: workspaceRecordSchema.shape.writer
7715
7716
  }).strict();
7717
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
7716
7718
  var exportBundleSchema = object({
7717
7719
  schemaVersion: literal(1),
7718
7720
  exportedAt: utc,
@@ -7734,11 +7736,15 @@ var taskOperations = {
7734
7736
  expectedWorkspaceRevision: revision.nullable().optional(),
7735
7737
  intent: intentSchema
7736
7738
  }),
7737
- list: operation({ action: literal("list") }),
7739
+ list: operation({
7740
+ action: literal("list"),
7741
+ status: _enum(["open", "closed", "all"]).optional(),
7742
+ limit: number2().int().min(1).max(50).optional()
7743
+ }),
7738
7744
  inspect: operation({
7739
7745
  action: literal("inspect"),
7740
7746
  ...taskId,
7741
- view: _enum(["summary", "full"])
7747
+ view: _enum(["summary", "full"]).optional()
7742
7748
  }),
7743
7749
  revise: operation({
7744
7750
  action: literal("revise"),
@@ -7766,7 +7772,7 @@ var taskOperations = {
7766
7772
  ...taskId,
7767
7773
  ...revisions,
7768
7774
  expectedWorkspaceRevision: revision.optional(),
7769
- authorityRefs: array(refSchema)
7775
+ authorityRefs: array(refSchema).optional()
7770
7776
  }),
7771
7777
  close: operation({
7772
7778
  action: literal("close"),
@@ -8022,6 +8028,11 @@ var DEFAULTS = {
8022
8028
  // packages/workit-core/src/core/authority.ts
8023
8029
  var verifiedAuthorities = new WeakMap;
8024
8030
  // packages/workit-core/src/core/external-action.ts
8031
+ var chainStepSchema = union([
8032
+ string2().min(1),
8033
+ object({ branch: string2().min(1) }).strict(),
8034
+ object({ pr: literal(true) }).strict()
8035
+ ]);
8025
8036
  var externalActionSchema = discriminatedUnion("operation", [
8026
8037
  object({
8027
8038
  operation: literal("git.branch_setup"),
@@ -8036,7 +8047,7 @@ var externalActionSchema = discriminatedUnion("operation", [
8036
8047
  operation: literal("git.commit"),
8037
8048
  payload: object({
8038
8049
  message: string2().min(1).optional(),
8039
- plan_steps: array(string2().min(1)).optional(),
8050
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
8040
8051
  plan_branch: string2().min(1).optional()
8041
8052
  }).strict()
8042
8053
  }).strict(),
package/dist/workit.js CHANGED
@@ -7949,6 +7949,7 @@ var workerSchema = object({
7949
7949
  assignment: assignmentSchema,
7950
7950
  state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7951
7951
  session: refSchema.nullable(),
7952
+ coordinator: refSchema.optional(),
7952
7953
  report: workerReportSchema.nullable()
7953
7954
  }).strict();
7954
7955
  var ownerSchema = object({
@@ -8060,6 +8061,7 @@ var taskSummarySchema = object({
8060
8061
  requirements: array(requirementEvaluationSchema),
8061
8062
  writer: workspaceRecordSchema.shape.writer
8062
8063
  }).strict();
8064
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
8063
8065
  var exportBundleSchema = object({
8064
8066
  schemaVersion: literal(1),
8065
8067
  exportedAt: utc,
@@ -8081,11 +8083,15 @@ var taskOperations = {
8081
8083
  expectedWorkspaceRevision: revision.nullable().optional(),
8082
8084
  intent: intentSchema
8083
8085
  }),
8084
- list: operation({ action: literal("list") }),
8086
+ list: operation({
8087
+ action: literal("list"),
8088
+ status: _enum(["open", "closed", "all"]).optional(),
8089
+ limit: number2().int().min(1).max(50).optional()
8090
+ }),
8085
8091
  inspect: operation({
8086
8092
  action: literal("inspect"),
8087
8093
  ...taskId,
8088
- view: _enum(["summary", "full"])
8094
+ view: _enum(["summary", "full"]).optional()
8089
8095
  }),
8090
8096
  revise: operation({
8091
8097
  action: literal("revise"),
@@ -8113,7 +8119,7 @@ var taskOperations = {
8113
8119
  ...taskId,
8114
8120
  ...revisions,
8115
8121
  expectedWorkspaceRevision: revision.optional(),
8116
- authorityRefs: array(refSchema)
8122
+ authorityRefs: array(refSchema).optional()
8117
8123
  }),
8118
8124
  close: operation({
8119
8125
  action: literal("close"),
@@ -9946,6 +9952,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
9946
9952
  }) && !value.split(/[\\/]/).includes("..");
9947
9953
  var callerValue = (value) => ({ host: value.host, actor: value.actor });
9948
9954
  var workerIdOf = (value) => value.workerId ?? null;
9955
+ var currentWriterOwnsTask = (task, workspace, caller) => {
9956
+ const owner = workspace.writer?.owner;
9957
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
9958
+ };
9949
9959
  function assertProductWriteAllowed(input) {
9950
9960
  if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9951
9961
  return failure("invalid_input", "invalid product write path");
@@ -10950,9 +10960,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
10950
10960
  var verifyStandingApproval = (root, task, caller, binding) => {
10951
10961
  if (task.status !== "active")
10952
10962
  return failure("invalid_transition", "only active tasks can authorize actions");
10953
- const session = task.intent?.provenance?.session;
10954
- if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
10955
- return failure("permission_denied", "standing approval requires the lead session");
10963
+ const workspace = new TaskStore(root).readWorkspace();
10964
+ if (!workspace.ok)
10965
+ return workspace;
10966
+ if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
10967
+ return failure("permission_denied", "standing approval requires the current writer session");
10956
10968
  const standing = binding.standing;
10957
10969
  if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
10958
10970
  return failure("invalid_input", "standing approval needs a workspace and class");
@@ -11000,14 +11012,14 @@ var standingAutoApplies = (store, host, actor, operation) => {
11000
11012
  const workspace = store.readWorkspace();
11001
11013
  if (!listed.ok || !workspace.ok || !workspace.data)
11002
11014
  return null;
11003
- 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);
11004
- if (matches.length !== 1)
11015
+ const task = listed.data.find((task) => currentWriterOwnsTask(task, workspace.data, { host, actor }));
11016
+ if (!task)
11005
11017
  return null;
11006
11018
  const resolved = resolveAutoApproval(store.root);
11007
11019
  if (resolved.status !== "on")
11008
11020
  return null;
11009
11021
  return {
11010
- task: matches[0],
11022
+ task,
11011
11023
  workspaceId: workspace.data.id,
11012
11024
  workspaceRevision: workspace.data.revision,
11013
11025
  cls,
@@ -11266,6 +11278,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
11266
11278
  };
11267
11279
  var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
11268
11280
  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);
11281
+ var approvalAuthorityMatches = (store, task, approval, current) => {
11282
+ if (approval.kind !== "host_observed" || approval.receipts.length === 0)
11283
+ return false;
11284
+ if (provenanceMatches(approval, current))
11285
+ return true;
11286
+ const session = current.kind === "host_observed" ? current.session : null;
11287
+ if (!session || session.kind !== "host")
11288
+ return false;
11289
+ const workspace = store.readWorkspace();
11290
+ return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
11291
+ host: session.host,
11292
+ actor: session.handle
11293
+ });
11294
+ };
11269
11295
  var validateAction = (store, task, workspaceId, input, authority) => {
11270
11296
  if (task.status !== "active")
11271
11297
  return failure("invalid_transition", "only active tasks can authorize actions");
@@ -11291,7 +11317,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
11291
11317
  return failure("permission_denied", "native reservation authority does not match decision");
11292
11318
  if (decision.response !== "approved")
11293
11319
  return failure("permission_denied", "decision was rejected");
11294
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
11320
+ if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
11295
11321
  return failure("permission_denied", "action approval lacks native receipt assurance");
11296
11322
  const receipts = entry.provenance.receipts;
11297
11323
  const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
@@ -11458,7 +11484,7 @@ function settleAction(input) {
11458
11484
  return failure("not_found", "decision not found");
11459
11485
  if (!authorityDecisionMatches(authority, entry.data))
11460
11486
  return failure("permission_denied", "native settlement authority does not match decision");
11461
- if (!provenanceMatches(entry.provenance, authority.provenance))
11487
+ if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11462
11488
  return failure("permission_denied", "native settlement caller does not match approval");
11463
11489
  if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
11464
11490
  return failure("permission_denied", "decision binding is invalid");
@@ -11557,7 +11583,7 @@ function reconcileAction(input) {
11557
11583
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
11558
11584
  if (!entry || entry.data.purpose !== "action")
11559
11585
  return failure("not_found", "decision not found");
11560
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
11586
+ if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11561
11587
  return failure("permission_denied", "native reconciliation caller does not match approval");
11562
11588
  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)
11563
11589
  return failure("permission_denied", "reconciliation decision binding is invalid");
@@ -11894,7 +11920,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
11894
11920
  candidateId: evidence.candidateId ?? fallback
11895
11921
  };
11896
11922
  };
11897
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
11923
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
11898
11924
  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")));
11899
11925
  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 }));
11900
11926
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
@@ -12364,6 +12390,11 @@ function shouldDenyShellRoute(dir, command) {
12364
12390
  }
12365
12391
  // packages/workit-core/src/core/external-action.ts
12366
12392
  import { execFileSync as execFileSync3 } from "node:child_process";
12393
+ var chainStepSchema = union([
12394
+ string2().min(1),
12395
+ object({ branch: string2().min(1) }).strict(),
12396
+ object({ pr: literal(true) }).strict()
12397
+ ]);
12367
12398
  var externalActionSchema = discriminatedUnion("operation", [
12368
12399
  object({
12369
12400
  operation: literal("git.branch_setup"),
@@ -12378,7 +12409,7 @@ var externalActionSchema = discriminatedUnion("operation", [
12378
12409
  operation: literal("git.commit"),
12379
12410
  payload: object({
12380
12411
  message: string2().min(1).optional(),
12381
- plan_steps: array(string2().min(1)).optional(),
12412
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
12382
12413
  plan_branch: string2().min(1).optional()
12383
12414
  }).strict()
12384
12415
  }).strict(),
@@ -12482,7 +12513,7 @@ var planReservationLength = (operation, descriptorPayload) => {
12482
12513
  const payload = descriptorPayload ?? {};
12483
12514
  return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
12484
12515
  };
12485
- 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).";
12516
+ 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).";
12486
12517
  var externalActionRef = (host, actor, descriptor) => ({
12487
12518
  kind: "host",
12488
12519
  host,
@@ -12496,12 +12527,12 @@ var externalActionState = (store, host, actor, operation) => {
12496
12527
  if (!workspace.data)
12497
12528
  return failure("not_found", "workspace not found");
12498
12529
  const candidates = listed.data.flatMap((task) => {
12499
- 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)
12530
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12500
12531
  return [];
12501
12532
  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 }));
12502
12533
  });
12503
12534
  if (candidates.length !== 1)
12504
- return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
12535
+ return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12505
12536
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
12506
12537
  };
12507
12538
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -12581,8 +12612,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
12581
12612
  const matches = listed.data.flatMap((task) => {
12582
12613
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12583
12614
  return [];
12584
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12585
- return [];
12586
12615
  return task.decisions.flatMap((entry) => {
12587
12616
  const decision = entry.data;
12588
12617
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12624,8 +12653,6 @@ var approvedChainStep = (store, host, actor, query) => {
12624
12653
  const matches = listed.data.flatMap((task) => {
12625
12654
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12626
12655
  return [];
12627
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12628
- return [];
12629
12656
  return task.decisions.flatMap((entry) => {
12630
12657
  const decision = entry.data;
12631
12658
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12782,7 +12809,7 @@ var branchSetupIntent = (descriptor) => {
12782
12809
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
12783
12810
  const current = branchSetupIntent(operation);
12784
12811
  if (!current)
12785
- return failure("permission_denied", "no approved action is bound to this session");
12812
+ return failure("permission_denied", "no approved action is bound to the active task");
12786
12813
  const listed = store.listTasks();
12787
12814
  const workspace = store.readWorkspace();
12788
12815
  if (!listed.ok || !workspace.ok)
@@ -12790,7 +12817,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12790
12817
  if (!workspace.data)
12791
12818
  return failure("not_found", "workspace not found");
12792
12819
  const candidates = listed.data.flatMap((task) => {
12793
- 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)
12820
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12794
12821
  return [];
12795
12822
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
12796
12823
  });
@@ -12799,7 +12826,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12799
12826
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
12800
12827
  });
12801
12828
  if (matches.length !== 1)
12802
- return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
12829
+ return failure("permission_denied", matches.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12803
12830
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
12804
12831
  };
12805
12832
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -13356,6 +13383,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
13356
13383
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
13357
13384
  state: expected.state,
13358
13385
  session: expected.session,
13386
+ report: expected.report ?? null,
13359
13387
  provenance: result.data,
13360
13388
  ...binding
13361
13389
  });
@@ -13366,7 +13394,7 @@ var takeWorker = (token, binding, expected) => {
13366
13394
  verifiedWorkers.delete(token);
13367
13395
  if (!value)
13368
13396
  return null;
13369
- 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))
13397
+ 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))
13370
13398
  return null;
13371
13399
  return value;
13372
13400
  };
@@ -13407,6 +13435,8 @@ var applyWorkerLifecycle = (input) => {
13407
13435
  return failure("invalid_transition", "worker already has an observed session");
13408
13436
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
13409
13437
  return failure("invalid_transition", "worker launch was not claimed");
13438
+ if (input.report && input.state !== "stopped")
13439
+ return failure("invalid_input", "worker reports require a stopped worker");
13410
13440
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
13411
13441
  return failure("invalid_input", "running worker observations require a session");
13412
13442
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -13423,12 +13453,13 @@ var applyWorkerLifecycle = (input) => {
13423
13453
  data: {
13424
13454
  ...entry.data,
13425
13455
  state: terminalCancel ? "cancelling" : input.state,
13426
- session: input.session
13456
+ session: input.session,
13457
+ report: entry.data.report ?? input.report ?? null
13427
13458
  }
13428
13459
  };
13429
13460
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13430
13461
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13431
- if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
13462
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
13432
13463
  return success(task.data.revision, workspace.data.revision, entry);
13433
13464
  const changed = input.store.mutateTaskAndWorkspace({
13434
13465
  taskId: task.data.id,
@@ -13846,14 +13877,30 @@ class WorkitCore {
13846
13877
  const listed = this.store.listTasks();
13847
13878
  if (!listed.ok)
13848
13879
  return listed;
13849
- const summaries = [];
13850
- for (const task of listed.data) {
13851
- const summary = this.summary(task);
13852
- if (!summary.ok)
13853
- return summary;
13854
- summaries.push(summary.data);
13855
- }
13856
- return success(null, null, summaries);
13880
+ const status = input.status ?? "open";
13881
+ const limit = input.limit ?? 20;
13882
+ 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);
13883
+ if (selected.length === 0)
13884
+ return success(null, null, []);
13885
+ const workspace = this.store.readWorkspace();
13886
+ if (!workspace.ok)
13887
+ return workspace;
13888
+ if (!workspace.data)
13889
+ return failure("not_found", "workspace not found");
13890
+ const tasks = selected.map((task) => ({
13891
+ id: task.id,
13892
+ revision: task.revision,
13893
+ workspaceRevision: workspace.data.revision,
13894
+ createdAt: task.createdAt,
13895
+ updatedAt: task.updatedAt,
13896
+ runtime: task.runtime ?? null,
13897
+ objective: task.intent.data.objective,
13898
+ status: task.status,
13899
+ closure: task.closure,
13900
+ progress: task.progress,
13901
+ writer: task.status === "closed" ? null : workspace.data.writer
13902
+ }));
13903
+ return success(null, null, tasks);
13857
13904
  }
13858
13905
  case "inspect": {
13859
13906
  const task = this.store.readTask(input.taskId);
@@ -13862,7 +13909,7 @@ class WorkitCore {
13862
13909
  const helper = this.helperTaskGuard(task.data, true);
13863
13910
  if (!helper.ok)
13864
13911
  return helper;
13865
- if (input.view === "summary")
13912
+ if ((input.view ?? "summary") === "summary")
13866
13913
  return this.summary(task.data);
13867
13914
  return this.view(task.data);
13868
13915
  }
@@ -14206,7 +14253,7 @@ class WorkitCore {
14206
14253
  const evaluations = evaluateEvidence(task.data, current.data);
14207
14254
  const verified = evidence.some((entry) => {
14208
14255
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
14209
- return findingVerificationPasses(finding.data.candidateId, {
14256
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
14210
14257
  kind: entry.data.kind,
14211
14258
  candidateId: entry.data.candidateId,
14212
14259
  status: evaluation?.status ?? "missing"
@@ -14286,7 +14333,18 @@ class WorkitCore {
14286
14333
  if (task.data.status !== "active")
14287
14334
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
14288
14335
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
14289
- return failure("permission_denied", "worker assignment is outside task scope");
14336
+ return failure("permission_denied", "worker assignment is outside task scope", {
14337
+ fields: [
14338
+ {
14339
+ path: "assignment.scope",
14340
+ reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
14341
+ },
14342
+ {
14343
+ path: "task.intent.scope",
14344
+ reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
14345
+ }
14346
+ ]
14347
+ });
14290
14348
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
14291
14349
  return failure("invalid_input", "worker assignment references an unknown decision");
14292
14350
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -14493,7 +14551,15 @@ class WorkitCore {
14493
14551
  ...item,
14494
14552
  recordedAt: mutation.now,
14495
14553
  provenance: attested.data,
14496
- data: { ...item.data, state: "dispatching" }
14554
+ data: {
14555
+ ...item.data,
14556
+ state: "dispatching",
14557
+ coordinator: {
14558
+ kind: "host",
14559
+ host: this.context.caller.host,
14560
+ handle: this.context.caller.actor
14561
+ }
14562
+ }
14497
14563
  } : item)
14498
14564
  });
14499
14565
  }
@@ -14569,6 +14635,7 @@ class WorkitCore {
14569
14635
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
14570
14636
  state: "stopped",
14571
14637
  session: null,
14638
+ report: null,
14572
14639
  provenance: attested.data,
14573
14640
  owner: this.authorityOwner,
14574
14641
  store: this.store,
@@ -14876,10 +14943,12 @@ class WorkitCore {
14876
14943
  return workspace;
14877
14944
  if (!workspace.data)
14878
14945
  return failure("not_found", "workspace not found");
14879
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
14946
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
14947
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
14880
14948
  if (!current.ok)
14881
14949
  return current;
14882
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
14950
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14951
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
14883
14952
  return success(task.revision, workspace.data.revision, {
14884
14953
  id: task.id,
14885
14954
  revision: task.revision,
@@ -14893,7 +14962,7 @@ class WorkitCore {
14893
14962
  progress: task.progress,
14894
14963
  policy: task.policy,
14895
14964
  requirements,
14896
- writer: workspace.data.writer
14965
+ writer: evaluationWorkspace.writer
14897
14966
  });
14898
14967
  }
14899
14968
  view(task) {
@@ -14902,13 +14971,15 @@ class WorkitCore {
14902
14971
  return workspace;
14903
14972
  if (!workspace.data)
14904
14973
  return failure("not_found", "workspace not found");
14905
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
14974
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
14975
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
14906
14976
  if (!current.ok)
14907
14977
  return current;
14908
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
14978
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14979
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
14909
14980
  return success(task.revision, workspace.data.revision, {
14910
14981
  task,
14911
- workspace: workspace.data,
14982
+ workspace: evaluationWorkspace,
14912
14983
  capabilities: this.context.capabilities,
14913
14984
  requirements,
14914
14985
  evidence: evaluateEvidence(task, current.data)
@@ -14999,8 +15070,6 @@ class WorkitCore {
14999
15070
  return helper;
15000
15071
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
15001
15072
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
15002
- if (status === "active" && input.authorityRefs.length === 0)
15003
- return failure("permission_denied", "resuming a task requires authority references");
15004
15073
  const workspace = this.store.readWorkspace();
15005
15074
  if (!workspace.ok)
15006
15075
  return workspace;
@@ -15030,7 +15099,7 @@ class WorkitCore {
15030
15099
  return failure("needs_input", "imported task requires fresh destination evidence");
15031
15100
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
15032
15101
  return failure("recovery_required", "imported task requires resume reconciliation");
15033
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
15102
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
15034
15103
  return failure("permission_denied", "imported resume requires native destination approval");
15035
15104
  resumeCandidate = current.data.candidate;
15036
15105
  }
@@ -15160,7 +15229,7 @@ class WorkitCore {
15160
15229
  task: (value, context) => success(context.revision, null, {
15161
15230
  ...value,
15162
15231
  status: "closed",
15163
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
15232
+ candidates: taskForView.candidates,
15164
15233
  closure: {
15165
15234
  outcome: closure.data.outcome,
15166
15235
  at: context.now,
@@ -17469,7 +17538,7 @@ var readExternalContext = async (root, payload) => {
17469
17538
  }
17470
17539
  }
17471
17540
  };
17472
- var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "changelog.apply";
17541
+ var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "hosting.merge" || operation === "changelog.apply";
17473
17542
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17474
17543
  const store = new TaskStore(root);
17475
17544
  const listed = store.listTasks();
@@ -17480,7 +17549,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17480
17549
  return workspace;
17481
17550
  if (!workspace.data)
17482
17551
  return failure("not_found", "workspace not found");
17483
- 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);
17552
+ const candidates = listed.data.filter((task) => currentWriterOwnsTask(task, workspace.data, caller));
17484
17553
  if (candidates.length !== 1)
17485
17554
  return failure("permission_denied", "local external action requires exactly one active task");
17486
17555
  const allowed = assertProductWriteAllowed({
@@ -17490,7 +17559,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17490
17559
  paths,
17491
17560
  store
17492
17561
  });
17493
- return allowed.ok ? success(null, null, null) : allowed;
17562
+ return allowed;
17494
17563
  };
17495
17564
  var safePath = (root, value) => {
17496
17565
  try {
@@ -17502,13 +17571,20 @@ var safePath = (root, value) => {
17502
17571
  var branchCreationBaseline = (root, target, base) => {
17503
17572
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
17504
17573
  if (exists)
17505
- return { base_branch: base, target_exists: true, remote_base: null };
17574
+ return {
17575
+ base_branch: base,
17576
+ target_exists: true,
17577
+ remote_base: null,
17578
+ target_head: gitValue(root, ["rev-parse", `refs/heads/${target}`])
17579
+ };
17506
17580
  const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
17507
17581
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
17582
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
17508
17583
  return {
17509
17584
  base_branch: base,
17510
17585
  target_exists: false,
17511
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
17586
+ remote_base: remoteBase,
17587
+ target_head: remoteBase
17512
17588
  };
17513
17589
  };
17514
17590
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -17619,7 +17695,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
17619
17695
  var approvedResolvedExternalAction = (content) => {
17620
17696
  try {
17621
17697
  const value = JSON.parse(content);
17622
- 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")
17698
+ 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")
17623
17699
  return failure("invalid_input", "approved action is not a reconciliable external action");
17624
17700
  if (!value.payload || typeof value.payload !== "object")
17625
17701
  return failure("invalid_input", "approved action payload is incomplete");
@@ -17630,18 +17706,24 @@ var approvedResolvedExternalAction = (content) => {
17630
17706
  if (!request.ok || !resolved || typeof resolved !== "object")
17631
17707
  return failure("invalid_input", "approved hosting target is incomplete");
17632
17708
  const target = resolved;
17709
+ if (value.operation === "git.commit" && (typeof target.head !== "string" || typeof target.staged !== "string" || typeof payload.message !== "string"))
17710
+ return failure("invalid_input", "approved Git commit target is incomplete");
17711
+ if (value.operation === "git.push" && (typeof target.branch !== "string" || typeof target.commit !== "string" || typeof target.remote !== "string"))
17712
+ return failure("invalid_input", "approved Git push target is incomplete");
17713
+ 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")))
17714
+ return failure("invalid_input", "approved Git branch target is incomplete");
17633
17715
  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"))
17634
17716
  return failure("invalid_input", "approved hosting target is incomplete");
17635
17717
  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"))
17636
17718
  return failure("invalid_input", "approved hosting target is incomplete");
17637
17719
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
17638
17720
  return failure("invalid_input", "approved changelog target is incomplete");
17639
- 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)))
17721
+ 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)))
17640
17722
  return failure("invalid_input", "approved YouTrack target is incomplete");
17641
17723
  return success(null, null, {
17642
17724
  request: request.data,
17643
17725
  descriptorPayload: value.payload,
17644
- marker: String(target.marker)
17726
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
17645
17727
  });
17646
17728
  } catch {
17647
17729
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -17667,12 +17749,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
17667
17749
  outcome: "unknown"
17668
17750
  });
17669
17751
  var readHostingAction = async (root, resolved, actionRef) => {
17670
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
17671
- return unknownHostingEvidence("hosting.pull_request");
17752
+ const operation = resolved.request.operation;
17753
+ if (operation !== "hosting.pull_request" && operation !== "hosting.merge" || !resolved.marker)
17754
+ return unknownHostingEvidence(operation);
17672
17755
  try {
17673
17756
  const cfg = vcsConfig("load", root);
17674
17757
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
17675
- return unknownHostingEvidence("hosting.pull_request");
17758
+ return unknownHostingEvidence(operation);
17676
17759
  const remote = pushRemote(root);
17677
17760
  const repo = remote ? remoteRepo(remote) : null;
17678
17761
  const approved = resolved.descriptorPayload;
@@ -17691,7 +17774,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17691
17774
  }
17692
17775
  })();
17693
17776
  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)
17694
- return unknownHostingEvidence("hosting.pull_request");
17777
+ return unknownHostingEvidence(operation);
17695
17778
  const token = fs12.readFileSync(cfg.tokenPath, "utf8").trim();
17696
17779
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
17697
17780
  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`;
@@ -17699,14 +17782,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
17699
17782
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
17700
17783
  });
17701
17784
  if (!response.ok)
17702
- return unknownHostingEvidence("hosting.pull_request");
17785
+ return unknownHostingEvidence(operation);
17703
17786
  const values = await response.json();
17704
17787
  if (!Array.isArray(values) || values.length >= 100)
17705
- return unknownHostingEvidence("hosting.pull_request");
17788
+ return unknownHostingEvidence(operation);
17706
17789
  const records = [];
17707
17790
  for (const item of values) {
17708
17791
  if (!item || typeof item !== "object" || Array.isArray(item))
17709
- return unknownHostingEvidence("hosting.pull_request");
17792
+ return unknownHostingEvidence(operation);
17710
17793
  const record = item;
17711
17794
  const body = record.body ?? record.description;
17712
17795
  const base = record.base;
@@ -17718,7 +17801,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17718
17801
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
17719
17802
  const idValue = record.number ?? record.iid ?? record.id;
17720
17803
  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")
17721
- return unknownHostingEvidence("hosting.pull_request");
17804
+ return unknownHostingEvidence(operation);
17722
17805
  records.push(record);
17723
17806
  }
17724
17807
  const matches = records.filter((item) => {
@@ -17728,15 +17811,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
17728
17811
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
17729
17812
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
17730
17813
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
17731
- return body.includes(resolved.marker) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
17814
+ const merged = cfg.provider === "github" ? typeof item.merged_at === "string" : item.state === "merged" || typeof item.merged_at === "string";
17815
+ return (operation === "hosting.merge" ? merged : body.includes(resolved.marker)) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
17732
17816
  });
17817
+ if (operation === "hosting.merge" && records.some((item) => {
17818
+ const base = item.base;
17819
+ const head = item.head;
17820
+ 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");
17821
+ }))
17822
+ return unknownHostingEvidence(operation);
17733
17823
  if (matches.length !== 1)
17734
- return unknownHostingEvidence("hosting.pull_request");
17824
+ return unknownHostingEvidence(operation);
17735
17825
  const item = matches[0];
17736
17826
  const id = item.number ?? item.iid ?? item.id;
17737
17827
  if (typeof id !== "string" && typeof id !== "number")
17738
- return unknownHostingEvidence("hosting.pull_request");
17828
+ return unknownHostingEvidence(operation);
17739
17829
  const evidenceDigest = sha256({
17830
+ operation,
17740
17831
  provider: cfg.provider,
17741
17832
  id,
17742
17833
  marker: resolved.marker,
@@ -17757,7 +17848,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17757
17848
  data: { provider: cfg.provider, id }
17758
17849
  });
17759
17850
  } catch {
17760
- return unknownHostingEvidence("hosting.pull_request");
17851
+ return unknownHostingEvidence(operation);
17761
17852
  }
17762
17853
  };
17763
17854
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -17847,7 +17938,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
17847
17938
  return unknownHostingEvidence(operation);
17848
17939
  }
17849
17940
  };
17850
- var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "hosting.pull_request" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
17941
+ var readLocalAction = async (root, resolved, actionRef) => {
17942
+ const operation = resolved.request.operation;
17943
+ const target = resolved.descriptorPayload.resolved;
17944
+ if (!target)
17945
+ return unknownHostingEvidence(operation);
17946
+ let observed = null;
17947
+ if (operation === "git.commit") {
17948
+ const commit = gitValue(root, ["rev-parse", "HEAD"]);
17949
+ const parent = commit ? gitValue(root, ["rev-parse", `${commit}^`]) : null;
17950
+ const message = commit ? gitValue(root, ["show", "-s", "--format=%B", commit]) : null;
17951
+ const diff = commit && parent ? gitRaw(root, ["diff", "--raw", "-z", "--no-ext-diff", "--no-textconv", parent, commit]) : null;
17952
+ if (commit && parent === target.head && message === resolved.request.payload.message && diff !== null && sha256(diff) === target.staged)
17953
+ observed = { commit, parent, message, staged: target.staged };
17954
+ } else if (operation === "git.push") {
17955
+ const branch = target.branch;
17956
+ const commit = target.commit;
17957
+ const approvedRemote = target.remote;
17958
+ const currentRemote = pushRemote(root);
17959
+ const currentIdentity = currentRemote ? credentialFreeRemote(currentRemote) : null;
17960
+ if (typeof branch === "string" && typeof commit === "string" && typeof approvedRemote === "string" && currentIdentity === approvedRemote) {
17961
+ const remote = run3(root, ["ls-remote", currentRemote, `refs/heads/${branch}`]);
17962
+ const remoteCommit = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : null;
17963
+ if (remoteCommit === commit)
17964
+ observed = { branch, commit, remote: approvedRemote };
17965
+ }
17966
+ } else if (operation === "git.branch_setup") {
17967
+ const targetBranch = resolved.request.payload.target_branch;
17968
+ const sdd = resolved.request.payload.sdd_dir;
17969
+ if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
17970
+ try {
17971
+ const manifest = JSON.parse(fs12.readFileSync(path16.join(sdd, "manifest.json"), "utf8"));
17972
+ const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
17973
+ if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
17974
+ `).includes(target.stash_commit))
17975
+ observed = { stash_commit: target.stash_commit };
17976
+ } catch {}
17977
+ } 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) {
17978
+ try {
17979
+ const manifest = JSON.parse(fs12.readFileSync(path16.join(sdd, "manifest.json"), "utf8"));
17980
+ if (manifest.branch === targetBranch)
17981
+ observed = { branch: targetBranch };
17982
+ } catch {}
17983
+ }
17984
+ } else if (operation === "changelog.apply") {
17985
+ const afterDigest = target.afterDigest;
17986
+ const preview = changelogApplyPreview({ ...resolved.request.payload, workspace_root: root });
17987
+ if (!preview.error && typeof afterDigest === "string" && preview.beforeDigest === afterDigest)
17988
+ observed = { target: target.target, afterDigest };
17989
+ }
17990
+ if (!observed)
17991
+ return unknownHostingEvidence(operation);
17992
+ const evidenceDigest = sha256({ operation, observed });
17993
+ return success(null, null, {
17994
+ outcome: "succeeded",
17995
+ evidenceDigest,
17996
+ observation: {
17997
+ kind: "provider_read",
17998
+ actionRef,
17999
+ outcome: "succeeded",
18000
+ evidenceDigest
18001
+ }
18002
+ });
18003
+ };
18004
+ 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);
17851
18005
  var youTrackBase = () => {
17852
18006
  try {
17853
18007
  const config = JSON.parse(fs12.readFileSync(youTrackConfigPath(), "utf8"));
@@ -17905,6 +18059,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
17905
18059
  return failure("storage_error", "current Git commit could not be resolved");
17906
18060
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
17907
18061
  const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
18062
+ let stashTarget;
18063
+ if (action === "reapply_stash") {
18064
+ try {
18065
+ const manifest = JSON.parse(fs12.readFileSync(path16.join(sdd_dir, "manifest.json"), "utf8"));
18066
+ const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
18067
+ const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
18068
+ if (stash_ref && stash_commit)
18069
+ stashTarget = { stash_ref, stash_commit };
18070
+ } catch {}
18071
+ }
17908
18072
  const normalized = {
17909
18073
  operation: request.operation,
17910
18074
  payload: {
@@ -17919,7 +18083,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
17919
18083
  request: normalized,
17920
18084
  descriptorPayload: {
17921
18085
  ...normalized.payload,
17922
- resolved: { head, dirty, ...creation }
18086
+ resolved: { head, dirty, ...creation, ...stashTarget }
17923
18087
  }
17924
18088
  });
17925
18089
  }
@@ -18071,7 +18235,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
18071
18235
  outcome: "not_started",
18072
18236
  fields: [{ path: "source_branch", reason: "protected branch" }]
18073
18237
  });
18074
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
18238
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
18075
18239
  const remote = pushRemote(root);
18076
18240
  const identity = remote ? credentialFreeRemote(remote) : null;
18077
18241
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -18359,7 +18523,17 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
18359
18523
  const workText = fresh.data.descriptorPayload.resolved?.workText;
18360
18524
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
18361
18525
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
18362
- return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
18526
+ const result = await executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
18527
+ if (!result.ok && result.code === "external_outcome_unknown" && caller) {
18528
+ const observed = await readLocalAction(root, fresh.data, {
18529
+ kind: "host",
18530
+ host: caller.host,
18531
+ handle: `local-recovery:${fresh.data.request.operation}`
18532
+ });
18533
+ if (observed.ok)
18534
+ return success(null, null, { recovered: true });
18535
+ }
18536
+ return result;
18363
18537
  };
18364
18538
 
18365
18539
  // packages/workit-pi/src/tools.ts
@@ -18626,6 +18800,7 @@ var registerWorkitTools = (pi, options = {}) => {
18626
18800
  "hosting.merge",
18627
18801
  "changelog.apply"
18628
18802
  ].includes(resolved.data.request.operation);
18803
+ let writerTaskId = null;
18629
18804
  if (localOperation) {
18630
18805
  const writer = assertLocalExternalActionWriter(ctx.cwd, { host: "pi", actor });
18631
18806
  if (!writer.ok)
@@ -18634,6 +18809,7 @@ var registerWorkitTools = (pi, options = {}) => {
18634
18809
  operation: resolved.data.request.operation,
18635
18810
  guidance: "Acquire checkout writer ownership first (writer.acquire) and retry the action."
18636
18811
  }));
18812
+ writerTaskId = writer.data.taskId;
18637
18813
  }
18638
18814
  const descriptor = externalActionDescriptor(resolved.data.request.operation, resolved.data.descriptorPayload);
18639
18815
  const priorRequest = priorExternalAction(store, "pi", actor, resolved.data.request.operation, resolved.data.request.payload);
@@ -18671,7 +18847,7 @@ var registerWorkitTools = (pi, options = {}) => {
18671
18847
  }
18672
18848
  return output(failure("external_outcome_unknown", "previous external action outcome is unknown"));
18673
18849
  }
18674
- if (priorRequest.ok && priorRequest.data.entry.data.consumption !== null)
18850
+ if (priorRequest.ok && priorRequest.data.entry.data.consumption !== null && resolved.data.request.operation !== "git.commit" && resolved.data.request.operation !== "git.push")
18675
18851
  return output(failure("permission_denied", "external action was already settled"));
18676
18852
  const drift = resolved.data.request.operation === "git.branch_setup" ? priorResolvedDrift(store, "pi", actor, resolved.data.request.operation, resolved.data.request.payload, resolved.data.descriptorPayload.resolved) : success(null, null, null);
18677
18853
  if (!drift.ok)
@@ -18702,7 +18878,7 @@ var registerWorkitTools = (pi, options = {}) => {
18702
18878
  const workspace = store.readWorkspace();
18703
18879
  if (!listed.ok || !workspace.ok || !workspace.data)
18704
18880
  return output(failure("storage_error", "external action state is unavailable"));
18705
- 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 === "pi" && task.intent.provenance.session.handle === actor);
18881
+ const candidates = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data.id && (writerTaskId === null || task.id === writerTaskId));
18706
18882
  if (candidates.length !== 1)
18707
18883
  return output(failure("permission_denied", "external action requires exactly one active Pi task"));
18708
18884
  const task = candidates[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Workit native extension for Pi — tools, method skills, and session continuity",
5
5
  "keywords": [
6
6
  "agentic-coding",