@brainervirus/workit-pi 1.1.1 → 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(),
@@ -12476,7 +12507,13 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
12476
12507
  payload,
12477
12508
  ...payload && typeof payload === "object" && !Array.isArray(payload) && "resolved" in payload && payload.resolved && typeof payload.resolved === "object" && Array.isArray(payload.resolved.steps) ? { steps: payload.resolved.steps } : {}
12478
12509
  });
12479
- 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).";
12510
+ var planReservationLength = (operation, descriptorPayload) => {
12511
+ if (operation !== "git.commit")
12512
+ return null;
12513
+ const payload = descriptorPayload ?? {};
12514
+ return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
12515
+ };
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).";
12480
12517
  var externalActionRef = (host, actor, descriptor) => ({
12481
12518
  kind: "host",
12482
12519
  host,
@@ -12490,12 +12527,12 @@ var externalActionState = (store, host, actor, operation) => {
12490
12527
  if (!workspace.data)
12491
12528
  return failure("not_found", "workspace not found");
12492
12529
  const candidates = listed.data.flatMap((task) => {
12493
- 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)
12494
12531
  return [];
12495
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 }));
12496
12533
  });
12497
12534
  if (candidates.length !== 1)
12498
- 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");
12499
12536
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
12500
12537
  };
12501
12538
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -12575,8 +12612,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
12575
12612
  const matches = listed.data.flatMap((task) => {
12576
12613
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12577
12614
  return [];
12578
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12579
- return [];
12580
12615
  return task.decisions.flatMap((entry) => {
12581
12616
  const decision = entry.data;
12582
12617
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12618,8 +12653,6 @@ var approvedChainStep = (store, host, actor, query) => {
12618
12653
  const matches = listed.data.flatMap((task) => {
12619
12654
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12620
12655
  return [];
12621
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12622
- return [];
12623
12656
  return task.decisions.flatMap((entry) => {
12624
12657
  const decision = entry.data;
12625
12658
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12776,7 +12809,7 @@ var branchSetupIntent = (descriptor) => {
12776
12809
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
12777
12810
  const current = branchSetupIntent(operation);
12778
12811
  if (!current)
12779
- 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");
12780
12813
  const listed = store.listTasks();
12781
12814
  const workspace = store.readWorkspace();
12782
12815
  if (!listed.ok || !workspace.ok)
@@ -12784,7 +12817,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12784
12817
  if (!workspace.data)
12785
12818
  return failure("not_found", "workspace not found");
12786
12819
  const candidates = listed.data.flatMap((task) => {
12787
- 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)
12788
12821
  return [];
12789
12822
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
12790
12823
  });
@@ -12793,7 +12826,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12793
12826
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
12794
12827
  });
12795
12828
  if (matches.length !== 1)
12796
- 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");
12797
12830
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
12798
12831
  };
12799
12832
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -13350,6 +13383,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
13350
13383
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
13351
13384
  state: expected.state,
13352
13385
  session: expected.session,
13386
+ report: expected.report ?? null,
13353
13387
  provenance: result.data,
13354
13388
  ...binding
13355
13389
  });
@@ -13360,7 +13394,7 @@ var takeWorker = (token, binding, expected) => {
13360
13394
  verifiedWorkers.delete(token);
13361
13395
  if (!value)
13362
13396
  return null;
13363
- 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))
13364
13398
  return null;
13365
13399
  return value;
13366
13400
  };
@@ -13401,6 +13435,8 @@ var applyWorkerLifecycle = (input) => {
13401
13435
  return failure("invalid_transition", "worker already has an observed session");
13402
13436
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
13403
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");
13404
13440
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
13405
13441
  return failure("invalid_input", "running worker observations require a session");
13406
13442
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -13417,12 +13453,13 @@ var applyWorkerLifecycle = (input) => {
13417
13453
  data: {
13418
13454
  ...entry.data,
13419
13455
  state: terminalCancel ? "cancelling" : input.state,
13420
- session: input.session
13456
+ session: input.session,
13457
+ report: entry.data.report ?? input.report ?? null
13421
13458
  }
13422
13459
  };
13423
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;
13424
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;
13425
- 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)
13426
13463
  return success(task.data.revision, workspace.data.revision, entry);
13427
13464
  const changed = input.store.mutateTaskAndWorkspace({
13428
13465
  taskId: task.data.id,
@@ -13840,14 +13877,30 @@ class WorkitCore {
13840
13877
  const listed = this.store.listTasks();
13841
13878
  if (!listed.ok)
13842
13879
  return listed;
13843
- const summaries = [];
13844
- for (const task of listed.data) {
13845
- const summary = this.summary(task);
13846
- if (!summary.ok)
13847
- return summary;
13848
- summaries.push(summary.data);
13849
- }
13850
- 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);
13851
13904
  }
13852
13905
  case "inspect": {
13853
13906
  const task = this.store.readTask(input.taskId);
@@ -13856,7 +13909,7 @@ class WorkitCore {
13856
13909
  const helper = this.helperTaskGuard(task.data, true);
13857
13910
  if (!helper.ok)
13858
13911
  return helper;
13859
- if (input.view === "summary")
13912
+ if ((input.view ?? "summary") === "summary")
13860
13913
  return this.summary(task.data);
13861
13914
  return this.view(task.data);
13862
13915
  }
@@ -14200,7 +14253,7 @@ class WorkitCore {
14200
14253
  const evaluations = evaluateEvidence(task.data, current.data);
14201
14254
  const verified = evidence.some((entry) => {
14202
14255
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
14203
- return findingVerificationPasses(finding.data.candidateId, {
14256
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
14204
14257
  kind: entry.data.kind,
14205
14258
  candidateId: entry.data.candidateId,
14206
14259
  status: evaluation?.status ?? "missing"
@@ -14280,7 +14333,18 @@ class WorkitCore {
14280
14333
  if (task.data.status !== "active")
14281
14334
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
14282
14335
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
14283
- 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
+ });
14284
14348
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
14285
14349
  return failure("invalid_input", "worker assignment references an unknown decision");
14286
14350
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -14487,7 +14551,15 @@ class WorkitCore {
14487
14551
  ...item,
14488
14552
  recordedAt: mutation.now,
14489
14553
  provenance: attested.data,
14490
- 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
+ }
14491
14563
  } : item)
14492
14564
  });
14493
14565
  }
@@ -14563,6 +14635,7 @@ class WorkitCore {
14563
14635
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
14564
14636
  state: "stopped",
14565
14637
  session: null,
14638
+ report: null,
14566
14639
  provenance: attested.data,
14567
14640
  owner: this.authorityOwner,
14568
14641
  store: this.store,
@@ -14870,10 +14943,12 @@ class WorkitCore {
14870
14943
  return workspace;
14871
14944
  if (!workspace.data)
14872
14945
  return failure("not_found", "workspace not found");
14873
- 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());
14874
14948
  if (!current.ok)
14875
14949
  return current;
14876
- 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);
14877
14952
  return success(task.revision, workspace.data.revision, {
14878
14953
  id: task.id,
14879
14954
  revision: task.revision,
@@ -14887,7 +14962,7 @@ class WorkitCore {
14887
14962
  progress: task.progress,
14888
14963
  policy: task.policy,
14889
14964
  requirements,
14890
- writer: workspace.data.writer
14965
+ writer: evaluationWorkspace.writer
14891
14966
  });
14892
14967
  }
14893
14968
  view(task) {
@@ -14896,13 +14971,15 @@ class WorkitCore {
14896
14971
  return workspace;
14897
14972
  if (!workspace.data)
14898
14973
  return failure("not_found", "workspace not found");
14899
- 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());
14900
14976
  if (!current.ok)
14901
14977
  return current;
14902
- 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);
14903
14980
  return success(task.revision, workspace.data.revision, {
14904
14981
  task,
14905
- workspace: workspace.data,
14982
+ workspace: evaluationWorkspace,
14906
14983
  capabilities: this.context.capabilities,
14907
14984
  requirements,
14908
14985
  evidence: evaluateEvidence(task, current.data)
@@ -14993,8 +15070,6 @@ class WorkitCore {
14993
15070
  return helper;
14994
15071
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
14995
15072
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
14996
- if (status === "active" && input.authorityRefs.length === 0)
14997
- return failure("permission_denied", "resuming a task requires authority references");
14998
15073
  const workspace = this.store.readWorkspace();
14999
15074
  if (!workspace.ok)
15000
15075
  return workspace;
@@ -15024,7 +15099,7 @@ class WorkitCore {
15024
15099
  return failure("needs_input", "imported task requires fresh destination evidence");
15025
15100
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
15026
15101
  return failure("recovery_required", "imported task requires resume reconciliation");
15027
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
15102
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
15028
15103
  return failure("permission_denied", "imported resume requires native destination approval");
15029
15104
  resumeCandidate = current.data.candidate;
15030
15105
  }
@@ -15154,7 +15229,7 @@ class WorkitCore {
15154
15229
  task: (value, context) => success(context.revision, null, {
15155
15230
  ...value,
15156
15231
  status: "closed",
15157
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
15232
+ candidates: taskForView.candidates,
15158
15233
  closure: {
15159
15234
  outcome: closure.data.outcome,
15160
15235
  at: context.now,
@@ -17463,7 +17538,7 @@ var readExternalContext = async (root, payload) => {
17463
17538
  }
17464
17539
  }
17465
17540
  };
17466
- 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";
17467
17542
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17468
17543
  const store = new TaskStore(root);
17469
17544
  const listed = store.listTasks();
@@ -17474,7 +17549,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17474
17549
  return workspace;
17475
17550
  if (!workspace.data)
17476
17551
  return failure("not_found", "workspace not found");
17477
- 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));
17478
17553
  if (candidates.length !== 1)
17479
17554
  return failure("permission_denied", "local external action requires exactly one active task");
17480
17555
  const allowed = assertProductWriteAllowed({
@@ -17484,7 +17559,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
17484
17559
  paths,
17485
17560
  store
17486
17561
  });
17487
- return allowed.ok ? success(null, null, null) : allowed;
17562
+ return allowed;
17488
17563
  };
17489
17564
  var safePath = (root, value) => {
17490
17565
  try {
@@ -17496,13 +17571,20 @@ var safePath = (root, value) => {
17496
17571
  var branchCreationBaseline = (root, target, base) => {
17497
17572
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
17498
17573
  if (exists)
17499
- 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
+ };
17500
17580
  const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
17501
17581
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
17582
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
17502
17583
  return {
17503
17584
  base_branch: base,
17504
17585
  target_exists: false,
17505
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
17586
+ remote_base: remoteBase,
17587
+ target_head: remoteBase
17506
17588
  };
17507
17589
  };
17508
17590
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -17613,7 +17695,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
17613
17695
  var approvedResolvedExternalAction = (content) => {
17614
17696
  try {
17615
17697
  const value = JSON.parse(content);
17616
- 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")
17617
17699
  return failure("invalid_input", "approved action is not a reconciliable external action");
17618
17700
  if (!value.payload || typeof value.payload !== "object")
17619
17701
  return failure("invalid_input", "approved action payload is incomplete");
@@ -17624,18 +17706,24 @@ var approvedResolvedExternalAction = (content) => {
17624
17706
  if (!request.ok || !resolved || typeof resolved !== "object")
17625
17707
  return failure("invalid_input", "approved hosting target is incomplete");
17626
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");
17627
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"))
17628
17716
  return failure("invalid_input", "approved hosting target is incomplete");
17629
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"))
17630
17718
  return failure("invalid_input", "approved hosting target is incomplete");
17631
17719
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
17632
17720
  return failure("invalid_input", "approved changelog target is incomplete");
17633
- 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)))
17634
17722
  return failure("invalid_input", "approved YouTrack target is incomplete");
17635
17723
  return success(null, null, {
17636
17724
  request: request.data,
17637
17725
  descriptorPayload: value.payload,
17638
- marker: String(target.marker)
17726
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
17639
17727
  });
17640
17728
  } catch {
17641
17729
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -17661,12 +17749,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
17661
17749
  outcome: "unknown"
17662
17750
  });
17663
17751
  var readHostingAction = async (root, resolved, actionRef) => {
17664
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
17665
- 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);
17666
17755
  try {
17667
17756
  const cfg = vcsConfig("load", root);
17668
17757
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
17669
- return unknownHostingEvidence("hosting.pull_request");
17758
+ return unknownHostingEvidence(operation);
17670
17759
  const remote = pushRemote(root);
17671
17760
  const repo = remote ? remoteRepo(remote) : null;
17672
17761
  const approved = resolved.descriptorPayload;
@@ -17685,7 +17774,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17685
17774
  }
17686
17775
  })();
17687
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)
17688
- return unknownHostingEvidence("hosting.pull_request");
17777
+ return unknownHostingEvidence(operation);
17689
17778
  const token = fs12.readFileSync(cfg.tokenPath, "utf8").trim();
17690
17779
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
17691
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`;
@@ -17693,14 +17782,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
17693
17782
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
17694
17783
  });
17695
17784
  if (!response.ok)
17696
- return unknownHostingEvidence("hosting.pull_request");
17785
+ return unknownHostingEvidence(operation);
17697
17786
  const values = await response.json();
17698
17787
  if (!Array.isArray(values) || values.length >= 100)
17699
- return unknownHostingEvidence("hosting.pull_request");
17788
+ return unknownHostingEvidence(operation);
17700
17789
  const records = [];
17701
17790
  for (const item of values) {
17702
17791
  if (!item || typeof item !== "object" || Array.isArray(item))
17703
- return unknownHostingEvidence("hosting.pull_request");
17792
+ return unknownHostingEvidence(operation);
17704
17793
  const record = item;
17705
17794
  const body = record.body ?? record.description;
17706
17795
  const base = record.base;
@@ -17712,7 +17801,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17712
17801
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
17713
17802
  const idValue = record.number ?? record.iid ?? record.id;
17714
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")
17715
- return unknownHostingEvidence("hosting.pull_request");
17804
+ return unknownHostingEvidence(operation);
17716
17805
  records.push(record);
17717
17806
  }
17718
17807
  const matches = records.filter((item) => {
@@ -17722,15 +17811,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
17722
17811
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
17723
17812
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
17724
17813
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
17725
- 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;
17726
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);
17727
17823
  if (matches.length !== 1)
17728
- return unknownHostingEvidence("hosting.pull_request");
17824
+ return unknownHostingEvidence(operation);
17729
17825
  const item = matches[0];
17730
17826
  const id = item.number ?? item.iid ?? item.id;
17731
17827
  if (typeof id !== "string" && typeof id !== "number")
17732
- return unknownHostingEvidence("hosting.pull_request");
17828
+ return unknownHostingEvidence(operation);
17733
17829
  const evidenceDigest = sha256({
17830
+ operation,
17734
17831
  provider: cfg.provider,
17735
17832
  id,
17736
17833
  marker: resolved.marker,
@@ -17751,7 +17848,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
17751
17848
  data: { provider: cfg.provider, id }
17752
17849
  });
17753
17850
  } catch {
17754
- return unknownHostingEvidence("hosting.pull_request");
17851
+ return unknownHostingEvidence(operation);
17755
17852
  }
17756
17853
  };
17757
17854
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -17841,7 +17938,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
17841
17938
  return unknownHostingEvidence(operation);
17842
17939
  }
17843
17940
  };
17844
- 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);
17845
18005
  var youTrackBase = () => {
17846
18006
  try {
17847
18007
  const config = JSON.parse(fs12.readFileSync(youTrackConfigPath(), "utf8"));
@@ -17899,6 +18059,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
17899
18059
  return failure("storage_error", "current Git commit could not be resolved");
17900
18060
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
17901
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
+ }
17902
18072
  const normalized = {
17903
18073
  operation: request.operation,
17904
18074
  payload: {
@@ -17913,7 +18083,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
17913
18083
  request: normalized,
17914
18084
  descriptorPayload: {
17915
18085
  ...normalized.payload,
17916
- resolved: { head, dirty, ...creation }
18086
+ resolved: { head, dirty, ...creation, ...stashTarget }
17917
18087
  }
17918
18088
  });
17919
18089
  }
@@ -18065,7 +18235,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
18065
18235
  outcome: "not_started",
18066
18236
  fields: [{ path: "source_branch", reason: "protected branch" }]
18067
18237
  });
18068
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
18238
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
18069
18239
  const remote = pushRemote(root);
18070
18240
  const identity = remote ? credentialFreeRemote(remote) : null;
18071
18241
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -18353,7 +18523,17 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
18353
18523
  const workText = fresh.data.descriptorPayload.resolved?.workText;
18354
18524
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
18355
18525
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
18356
- 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;
18357
18537
  };
18358
18538
 
18359
18539
  // packages/workit-pi/src/tools.ts
@@ -18620,6 +18800,7 @@ var registerWorkitTools = (pi, options = {}) => {
18620
18800
  "hosting.merge",
18621
18801
  "changelog.apply"
18622
18802
  ].includes(resolved.data.request.operation);
18803
+ let writerTaskId = null;
18623
18804
  if (localOperation) {
18624
18805
  const writer = assertLocalExternalActionWriter(ctx.cwd, { host: "pi", actor });
18625
18806
  if (!writer.ok)
@@ -18628,6 +18809,7 @@ var registerWorkitTools = (pi, options = {}) => {
18628
18809
  operation: resolved.data.request.operation,
18629
18810
  guidance: "Acquire checkout writer ownership first (writer.acquire) and retry the action."
18630
18811
  }));
18812
+ writerTaskId = writer.data.taskId;
18631
18813
  }
18632
18814
  const descriptor = externalActionDescriptor(resolved.data.request.operation, resolved.data.descriptorPayload);
18633
18815
  const priorRequest = priorExternalAction(store, "pi", actor, resolved.data.request.operation, resolved.data.request.payload);
@@ -18665,7 +18847,7 @@ var registerWorkitTools = (pi, options = {}) => {
18665
18847
  }
18666
18848
  return output(failure("external_outcome_unknown", "previous external action outcome is unknown"));
18667
18849
  }
18668
- 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")
18669
18851
  return output(failure("permission_denied", "external action was already settled"));
18670
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);
18671
18853
  if (!drift.ok)
@@ -18680,6 +18862,12 @@ var registerWorkitTools = (pi, options = {}) => {
18680
18862
  return output(result);
18681
18863
  }
18682
18864
  if (standingAutoApplies(store, "pi", actor, descriptor)) {
18865
+ const planCount = planReservationLength(resolved.data.request.operation, resolved.data.descriptorPayload);
18866
+ if (planCount !== null) {
18867
+ const bound = standingAutoBinding(core, store, "pi", actor, descriptor);
18868
+ if (bound)
18869
+ return output(success(null, null, { plan_commits: planCount }));
18870
+ }
18683
18871
  const result = await nativeExternalActionRunner(ctx.cwd, actor, core)(descriptor, (step) => executeResolvedExternalAction(resolved.data, ctx.cwd, step, { host: "pi", actor }));
18684
18872
  return output(result);
18685
18873
  }
@@ -18690,7 +18878,7 @@ var registerWorkitTools = (pi, options = {}) => {
18690
18878
  const workspace = store.readWorkspace();
18691
18879
  if (!listed.ok || !workspace.ok || !workspace.data)
18692
18880
  return output(failure("storage_error", "external action state is unavailable"));
18693
- 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));
18694
18882
  if (candidates.length !== 1)
18695
18883
  return output(failure("permission_denied", "external action requires exactly one active Pi task"));
18696
18884
  const task = candidates[0];
@@ -18722,9 +18910,9 @@ var registerWorkitTools = (pi, options = {}) => {
18722
18910
  if (!decision.ok)
18723
18911
  return output(decision);
18724
18912
  }
18725
- const planSteps = resolved.data.descriptorPayload.plan_steps;
18726
- if (resolved.data.request.operation === "git.commit" && Array.isArray(planSteps))
18727
- return output(success(null, null, { plan_commits: planSteps.length }));
18913
+ const planCount = planReservationLength(resolved.data.request.operation, resolved.data.descriptorPayload);
18914
+ if (planCount !== null)
18915
+ return output(success(null, null, { plan_commits: planCount }));
18728
18916
  const result = await nativeExternalActionRunner(ctx.cwd, actor, core)(descriptor, (step) => executeResolvedExternalAction(resolved.data, ctx.cwd, step, { host: "pi", actor }));
18729
18917
  return output(result);
18730
18918
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "1.1.1",
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",