@sealant/mend 0.29.2 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-AIW6XFT5.js → chunk-J3TR4UAG.js} +486 -66
  3. package/dist/{dashboard-AREQGMNK.js → dashboard-3HNFSQZD.js} +511 -234
  4. package/dist/main.js +1839 -830
  5. package/man/mend-accounts.1 +1 -1
  6. package/man/mend-adopt.1 +1 -1
  7. package/man/mend-attach.1 +9 -3
  8. package/man/mend-codex.1 +13 -3
  9. package/man/mend-completions.1 +1 -1
  10. package/man/mend-connect.1 +1 -1
  11. package/man/mend-continue.1 +1 -1
  12. package/man/mend-doctor.1 +1 -1
  13. package/man/mend-dotfiles-repo.1 +38 -0
  14. package/man/mend-dotfiles-sync.1 +3 -3
  15. package/man/mend-dotfiles.1 +3 -3
  16. package/man/mend-env-cluster.1 +1 -1
  17. package/man/mend-env-load.1 +1 -1
  18. package/man/mend-env-show.1 +1 -1
  19. package/man/mend-folder-create.1 +1 -1
  20. package/man/mend-folder-list.1 +1 -1
  21. package/man/mend-folder-push.1 +1 -1
  22. package/man/mend-folder-rm.1 +1 -1
  23. package/man/mend-git-author.1 +24 -0
  24. package/man/mend-help.1 +1 -1
  25. package/man/mend-invite.1 +1 -1
  26. package/man/mend-keys-init.1 +1 -1
  27. package/man/mend-keys-mode.1 +1 -1
  28. package/man/mend-keys-share.1 +1 -1
  29. package/man/mend-keys-show.1 +1 -1
  30. package/man/mend-land.1 +48 -0
  31. package/man/mend-login.1 +1 -1
  32. package/man/mend-logout.1 +1 -1
  33. package/man/mend-man.1 +1 -1
  34. package/man/mend-members.1 +1 -1
  35. package/man/mend-operator-exposure.1 +1 -1
  36. package/man/mend-operator-gate.1 +1 -1
  37. package/man/mend-operator-grant-owner.1 +1 -1
  38. package/man/mend-operator-org-create.1 +1 -1
  39. package/man/mend-operator-org-invite-owner.1 +1 -1
  40. package/man/mend-operator-org-list.1 +1 -1
  41. package/man/mend-operator-org-rename.1 +1 -1
  42. package/man/mend-operator-reset-link.1 +1 -1
  43. package/man/mend-pair.1 +1 -1
  44. package/man/mend-projects.1 +1 -1
  45. package/man/mend-pull.1 +29 -0
  46. package/man/mend-refresh.1 +1 -1
  47. package/man/mend-rejoin.1 +7 -2
  48. package/man/mend-resume.1 +1 -1
  49. package/man/mend-run.1 +1 -1
  50. package/man/mend-server-logs.1 +1 -1
  51. package/man/mend-server-restart.1 +1 -1
  52. package/man/mend-server-setup.1 +1 -1
  53. package/man/mend-server-start.1 +1 -1
  54. package/man/mend-server-status.1 +1 -1
  55. package/man/mend-server-stop.1 +1 -1
  56. package/man/mend-server-upgrade.1 +1 -1
  57. package/man/mend-server.1 +1 -1
  58. package/man/mend-service-add.1 +1 -1
  59. package/man/mend-service-connect.1 +3 -1
  60. package/man/mend-service-init.1 +1 -1
  61. package/man/mend-service-list.1 +1 -1
  62. package/man/mend-service-logs.1 +1 -1
  63. package/man/mend-service-restart.1 +1 -1
  64. package/man/mend-service-run.1 +1 -1
  65. package/man/mend-service-stop.1 +1 -1
  66. package/man/mend-session-share.1 +1 -1
  67. package/man/mend-sessions.1 +1 -1
  68. package/man/mend-shell.1 +1 -1
  69. package/man/mend-skills-push.1 +1 -1
  70. package/man/mend-skills.1 +1 -1
  71. package/man/mend-snake.1 +13 -0
  72. package/man/mend-ssh-setup.1 +1 -1
  73. package/man/mend-ssh.1 +1 -1
  74. package/man/mend-stop.1 +8 -1
  75. package/man/mend-ui.1 +9 -3
  76. package/man/mend-uninstall.1 +1 -1
  77. package/man/mend-version.1 +1 -1
  78. package/man/mend-worktrees.1 +1 -1
  79. package/man/mend.1 +17 -2
  80. package/package.json +1 -1
@@ -4,64 +4,6 @@ var __export = (target, all3) => {
4
4
  __defProp(target, name, { get: all3[name], enumerable: true });
5
5
  };
6
6
 
7
- // ../../packages/domain/src/workbench/connected-credential.ts
8
- var CLAUDE_GRANT_SECTION = "claudeAiOauth";
9
- var narrowCredential = (provider, secret) => {
10
- if (provider !== "claude") return { kind: "unchanged", secret };
11
- const parsed = parseDocument(secret);
12
- if (parsed === null) return { kind: "unchanged", secret };
13
- const grant = parsed[CLAUDE_GRANT_SECTION];
14
- if (grant === void 0) return { kind: "unchanged", secret };
15
- const dropped = Object.keys(parsed).filter((key) => key !== CLAUDE_GRANT_SECTION).toSorted();
16
- return {
17
- kind: "narrowed",
18
- // Two spaces and a trailing newline: the shape Claude Code writes, so a round trip through
19
- // Mend leaves a file a person can still read.
20
- secret: `${JSON.stringify({ [CLAUDE_GRANT_SECTION]: grant }, null, 2)}
21
- `,
22
- dropped
23
- };
24
- };
25
- var parseDocument = (secret) => {
26
- const trimmed = secret.trim();
27
- if (!trimmed.startsWith("{")) return null;
28
- try {
29
- const value3 = JSON.parse(trimmed);
30
- return typeof value3 === "object" && value3 !== null && !Array.isArray(value3) ? value3 : null;
31
- } catch {
32
- return null;
33
- }
34
- };
35
- var claudeGrantFacts = (secret) => {
36
- const parsed = parseDocument(secret);
37
- const grant = parsed?.[CLAUDE_GRANT_SECTION];
38
- if (typeof grant !== "object" || grant === null) return null;
39
- const record3 = { ...grant };
40
- const refreshToken = record3["refreshToken"];
41
- return {
42
- accessExpiresAt: asDate(record3["expiresAt"]),
43
- refreshExpiresAt: asDate(record3["refreshTokenExpiresAt"]),
44
- subscriptionType: typeof record3["subscriptionType"] === "string" ? record3["subscriptionType"] : null,
45
- hasRefreshToken: typeof refreshToken === "string" && refreshToken !== ""
46
- };
47
- };
48
- var asDate = (value3) => {
49
- if (typeof value3 !== "number" || !Number.isFinite(value3) || value3 <= 0) return null;
50
- const date2 = new Date(value3);
51
- return Number.isNaN(date2.getTime()) ? null : date2;
52
- };
53
- var sameGrant = (left, right) => {
54
- const token = (secret) => {
55
- const grant = parseDocument(secret)?.[CLAUDE_GRANT_SECTION];
56
- if (typeof grant !== "object" || grant === null) return null;
57
- const value3 = grant["refreshToken"];
58
- return typeof value3 === "string" && value3 !== "" ? value3 : null;
59
- };
60
- const a = token(left);
61
- const b = token(right);
62
- return a !== null && b !== null && a === b;
63
- };
64
-
65
7
  // ../../node_modules/.pnpm/effect@4.0.0-beta.93/node_modules/effect/dist/Pipeable.js
66
8
  var pipeArguments = (self, args2) => {
67
9
  switch (args2.length) {
@@ -26499,10 +26441,12 @@ var InvitationId = Schema_exports.String.pipe(Schema_exports.brand("InvitationId
26499
26441
  var FolderId = Schema_exports.String.pipe(Schema_exports.brand("FolderId"));
26500
26442
  var AuditEventId = Schema_exports.String.pipe(Schema_exports.brand("AuditEventId"));
26501
26443
  var SessionControlEventId = Schema_exports.String.pipe(Schema_exports.brand("SessionControlEventId"));
26444
+ var ChangeLandingId = Schema_exports.String.pipe(Schema_exports.brand("ChangeLandingId"));
26502
26445
 
26503
26446
  // ../../packages/domain/src/settings.ts
26504
26447
  var PrMode = Schema_exports.Literals(["draft-immediately", "pr-on-approval"]);
26505
26448
  var onByDefault = Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(true)));
26449
+ var offByDefault = Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(false)));
26506
26450
  var WorkspaceImageOs = Schema_exports.Literals(["fedora", "arch", "nix", "ubuntu"]);
26507
26451
  var DEFAULT_WORKSPACE_PACKAGES = [
26508
26452
  "pnpm",
@@ -26598,6 +26542,12 @@ var MendSettings = class extends Schema_exports.Class("MendSettings")({
26598
26542
  autoSuggest: onByDefault,
26599
26543
  /** Name the session from its first prompt — a label appears in lists while it still runs. */
26600
26544
  autoName: onByDefault,
26545
+ /**
26546
+ * Land the change when a turn completes (docs/adr/0007-landing.md, "Automatic landing"): push
26547
+ * the branch and open or update its pull request. Off by default for sessions people run
26548
+ * themselves; Slack sessions follow the Slack app's own setting instead.
26549
+ */
26550
+ autoLand: offByDefault,
26601
26551
  /** Base operating system and additional tools baked into every new session workspace. */
26602
26552
  workspaceImage: workspaceImageWithDefault,
26603
26553
  /**
@@ -26615,6 +26565,7 @@ var defaultSettings = new MendSettings({
26615
26565
  autoTour: true,
26616
26566
  autoSuggest: true,
26617
26567
  autoName: true,
26568
+ autoLand: false,
26618
26569
  workspaceImage: defaultWorkspaceImage,
26619
26570
  backgroundSessions: true
26620
26571
  });
@@ -26726,6 +26677,13 @@ var Project = class extends Schema_exports.Class("Project")({
26726
26677
  autoSuggest: AutomationChoice,
26727
26678
  /** Override of the Settings default: name the session from its first prompt. */
26728
26679
  autoName: AutomationChoice,
26680
+ /**
26681
+ * Override of the Settings default: land the change when a turn completes (docs/adr/0007). `off`
26682
+ * also stops sessions started from Slack.
26683
+ */
26684
+ autoLand: AutomationChoice.pipe(
26685
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed("inherit"))
26686
+ ),
26729
26687
  /**
26730
26688
  * Override of the Settings default: sessions keep running when every client
26731
26689
  * disconnects. Resolved by the launching CLI (flag → project → settings) —
@@ -26767,6 +26725,64 @@ var Project = class extends Schema_exports.Class("Project")({
26767
26725
  }) {
26768
26726
  };
26769
26727
 
26728
+ // ../../packages/domain/src/workbench/connected-credential.ts
26729
+ var CLAUDE_GRANT_SECTION = "claudeAiOauth";
26730
+ var narrowCredential = (provider, secret) => {
26731
+ if (provider !== "claude") return { kind: "unchanged", secret };
26732
+ const parsed = parseDocument(secret);
26733
+ if (parsed === null) return { kind: "unchanged", secret };
26734
+ const grant = parsed[CLAUDE_GRANT_SECTION];
26735
+ if (grant === void 0) return { kind: "unchanged", secret };
26736
+ const dropped = Object.keys(parsed).filter((key) => key !== CLAUDE_GRANT_SECTION).toSorted();
26737
+ return {
26738
+ kind: "narrowed",
26739
+ // Two spaces and a trailing newline: the shape Claude Code writes, so a round trip through
26740
+ // Mend leaves a file a person can still read.
26741
+ secret: `${JSON.stringify({ [CLAUDE_GRANT_SECTION]: grant }, null, 2)}
26742
+ `,
26743
+ dropped
26744
+ };
26745
+ };
26746
+ var parseDocument = (secret) => {
26747
+ const trimmed = secret.trim();
26748
+ if (!trimmed.startsWith("{")) return null;
26749
+ try {
26750
+ const value3 = JSON.parse(trimmed);
26751
+ return typeof value3 === "object" && value3 !== null && !Array.isArray(value3) ? value3 : null;
26752
+ } catch {
26753
+ return null;
26754
+ }
26755
+ };
26756
+ var claudeGrantFacts = (secret) => {
26757
+ const parsed = parseDocument(secret);
26758
+ const grant = parsed?.[CLAUDE_GRANT_SECTION];
26759
+ if (typeof grant !== "object" || grant === null) return null;
26760
+ const record3 = { ...grant };
26761
+ const refreshToken = record3["refreshToken"];
26762
+ return {
26763
+ accessExpiresAt: asDate(record3["expiresAt"]),
26764
+ refreshExpiresAt: asDate(record3["refreshTokenExpiresAt"]),
26765
+ subscriptionType: typeof record3["subscriptionType"] === "string" ? record3["subscriptionType"] : null,
26766
+ hasRefreshToken: typeof refreshToken === "string" && refreshToken !== ""
26767
+ };
26768
+ };
26769
+ var asDate = (value3) => {
26770
+ if (typeof value3 !== "number" || !Number.isFinite(value3) || value3 <= 0) return null;
26771
+ const date2 = new Date(value3);
26772
+ return Number.isNaN(date2.getTime()) ? null : date2;
26773
+ };
26774
+ var sameGrant = (left, right) => {
26775
+ const token = (secret) => {
26776
+ const grant = parseDocument(secret)?.[CLAUDE_GRANT_SECTION];
26777
+ if (typeof grant !== "object" || grant === null) return null;
26778
+ const value3 = grant["refreshToken"];
26779
+ return typeof value3 === "string" && value3 !== "" ? value3 : null;
26780
+ };
26781
+ const a = token(left);
26782
+ const b = token(right);
26783
+ return a !== null && b !== null && a === b;
26784
+ };
26785
+
26770
26786
  // ../../packages/domain/src/workbench/audit.ts
26771
26787
  var AuditAction = Schema_exports.Literals([
26772
26788
  "invitation.created",
@@ -26786,7 +26802,24 @@ var AuditAction = Schema_exports.Literals([
26786
26802
  "organization.created",
26787
26803
  "organization.renamed",
26788
26804
  "recovery.owner_granted",
26789
- "recovery.password_reset_issued"
26805
+ "recovery.password_reset_issued",
26806
+ // docs/adr/0006-slack.md, "Audit".
26807
+ "slack.installed",
26808
+ "slack.replaced",
26809
+ "slack.removed",
26810
+ "slack.settings_changed",
26811
+ "slack.link_created",
26812
+ "slack.link_removed",
26813
+ "slack.session_started",
26814
+ "slack.channel_default_set",
26815
+ "slack.channel_default_cleared",
26816
+ // docs/adr/0007-landing.md, "What Mend records and shows": the owner's credentials pushed, and
26817
+ // `gh` spoke as them.
26818
+ "change.landed",
26819
+ "change.pull_request_refreshed",
26820
+ "change.bundle_downloaded",
26821
+ // A pull request opened outside Mend was read with the owner's `gh` and recorded.
26822
+ "change.pull_request_adopted"
26790
26823
  ]);
26791
26824
  var AuditData = Schema_exports.Record(
26792
26825
  Schema_exports.String,
@@ -26798,7 +26831,10 @@ var AuditEvent = class extends Schema_exports.Class("AuditEvent")({
26798
26831
  /** The account that acted. */
26799
26832
  actorUserId: Schema_exports.String,
26800
26833
  action: AuditAction,
26801
- /** What the action touched: `member`, `invitation`, `project`, `session`, `folder` or `reference`. */
26834
+ /**
26835
+ * What the action touched: `member`, `invitation`, `project`, `session`, `folder`, `reference`,
26836
+ * `slack` (the organization's Slack app) or `change` (a landing).
26837
+ */
26802
26838
  subjectType: Schema_exports.String,
26803
26839
  subjectId: Schema_exports.String,
26804
26840
  data: AuditData,
@@ -26806,6 +26842,165 @@ var AuditEvent = class extends Schema_exports.Class("AuditEvent")({
26806
26842
  }) {
26807
26843
  };
26808
26844
 
26845
+ // ../../packages/domain/src/workbench/landing.ts
26846
+ var LandingTrigger = Schema_exports.Literals(["manual", "automatic", "adopted"]);
26847
+ var LandingOutcome = Schema_exports.Literals([
26848
+ "pushed",
26849
+ "pull-request",
26850
+ "refused",
26851
+ "failed",
26852
+ "adopted"
26853
+ ]);
26854
+ var PullRequestState = Schema_exports.Literals(["open", "closed", "merged"]);
26855
+ var LandedPullRequest = Schema_exports.Struct({
26856
+ number: Schema_exports.Int,
26857
+ url: Schema_exports.String,
26858
+ state: PullRequestState,
26859
+ observedAt: Timestamp
26860
+ });
26861
+ var ChangeLanding = class extends Schema_exports.Class("ChangeLanding")({
26862
+ id: ChangeLandingId,
26863
+ changeId: ChangeId,
26864
+ /** The session that landed; null once that session is deleted. */
26865
+ sessionId: Schema_exports.NullOr(SessionId),
26866
+ projectId: ProjectId,
26867
+ /** The landed checkpoint; null when it was since removed, or never taken. */
26868
+ checkpointId: Schema_exports.NullOr(CheckpointId),
26869
+ checkpointRef: Schema_exports.NullOr(Schema_exports.String),
26870
+ checkpointSha: Schema_exports.NullOr(Sha),
26871
+ /** The commit Mend wrote for work the agent left uncommitted; null when it wrote none. */
26872
+ commitSha: Schema_exports.NullOr(Sha),
26873
+ /** The branch on origin, `mend/<name>` unless the owner named another. */
26874
+ remoteBranch: Schema_exports.String,
26875
+ /** What origin's branch was moved to; null when nothing was pushed. */
26876
+ pushedSha: Schema_exports.NullOr(Sha),
26877
+ trigger: LandingTrigger,
26878
+ pullRequest: Schema_exports.NullOr(LandedPullRequest),
26879
+ /**
26880
+ * The pull request's head is in another repository (a fork): Mend pushes to origin only, so it
26881
+ * never updates such a pull request. Older servers omit it.
26882
+ */
26883
+ pullRequestCrossRepository: Schema_exports.Boolean.pipe(
26884
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(false)),
26885
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(false))
26886
+ ),
26887
+ /** Who owns the repository the pull request's head is in, as `gh` reported it; null unknown. */
26888
+ pullRequestHeadOwner: Schema_exports.NullOr(Schema_exports.String).pipe(
26889
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)),
26890
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
26891
+ ),
26892
+ outcome: LandingOutcome,
26893
+ /** The remote's or `gh`'s own words for a refusal or a failure; null otherwise. */
26894
+ message: Schema_exports.NullOr(Schema_exports.String),
26895
+ /** The change's owner (`changeOwnerOf`), whose key pushed and who speaks on GitHub. */
26896
+ userId: Schema_exports.String,
26897
+ createdAt: Timestamp
26898
+ }) {
26899
+ };
26900
+ var RequestIntent = Schema_exports.Literals(["change", "question"]);
26901
+ var RequestIntentSource = Schema_exports.Literals(["read", "option", "unread"]);
26902
+ var RequestIntentReading = Schema_exports.Union([
26903
+ Schema_exports.Struct({ intent: RequestIntent, source: Schema_exports.Literals(["read", "option"]) }),
26904
+ Schema_exports.Struct({ intent: Schema_exports.Null, source: Schema_exports.Literals(["unread"]) })
26905
+ ]);
26906
+ var LANDING_GUARD = [
26907
+ "--- How this work is published ---",
26908
+ "Mend publishes the changes this session makes: it pushes the branch and opens or updates the pull request.",
26909
+ "- If the request is a question, answer it and change no files.",
26910
+ "- Change code only when the request asks for a change.",
26911
+ "- Never push and never open a pull request. Committing is fine.",
26912
+ "--- End of how this work is published ---"
26913
+ ].join("\n");
26914
+ var NotLandedReason = Schema_exports.Literals([
26915
+ /** The request read as a question. */
26916
+ "question",
26917
+ /** The request said `autopr=false`. */
26918
+ "option",
26919
+ /** Automatic landing is off for the session. */
26920
+ "off",
26921
+ /** Someone other than the owner sent the turn under shared control. */
26922
+ "not-owner"
26923
+ ]);
26924
+ var TurnLanding = Schema_exports.Literals([
26925
+ "attempted",
26926
+ "question",
26927
+ "option",
26928
+ "off",
26929
+ "not-owner",
26930
+ "skipped"
26931
+ ]);
26932
+ var LandingFact = Schema_exports.Union([
26933
+ /** A landing moved origin's branch to this sha. */
26934
+ Schema_exports.TaggedStruct("pushed", { branch: Schema_exports.String, sha: Sha }),
26935
+ /** The landing's pull request, as `gh` last reported it. */
26936
+ Schema_exports.TaggedStruct("pull-request", {
26937
+ number: Schema_exports.Int,
26938
+ state: PullRequestState,
26939
+ observedAt: Timestamp,
26940
+ /** Opened outside Mend and adopted. Older servers omit it. */
26941
+ outside: Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(false))),
26942
+ /** The fork's owner when its head is in another repository; null for origin's own. */
26943
+ fork: Schema_exports.NullOr(Schema_exports.String).pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)))
26944
+ }),
26945
+ /** A fetch found commits on origin's branch that Mend's branch does not have. */
26946
+ Schema_exports.TaggedStruct("origin-moved", { branch: Schema_exports.String, commits: Schema_exports.Int }),
26947
+ /** The worktree moved past the landed checkpoint. */
26948
+ Schema_exports.TaggedStruct("changed-since-landing", { files: Schema_exports.Int }),
26949
+ /** The latest landing's push was refused, in the remote's words. */
26950
+ Schema_exports.TaggedStruct("refused", { branch: Schema_exports.String, message: Schema_exports.String }),
26951
+ /** The latest landing failed before it pushed. */
26952
+ Schema_exports.TaggedStruct("failed", { message: Schema_exports.String }),
26953
+ /** The latest landing pushed, and its pull request step failed. */
26954
+ Schema_exports.TaggedStruct("pull-request-failed", { message: Schema_exports.String }),
26955
+ /** A push the agent made itself, through the workspace's git transport. Not a landing. */
26956
+ Schema_exports.TaggedStruct("agent-push", { ref: Schema_exports.String, sha: Schema_exports.NullOr(Sha) }),
26957
+ /** A completed turn left changes that did not land. */
26958
+ Schema_exports.TaggedStruct("not-landed", { reason: NotLandedReason }),
26959
+ /** The request's intent could not be read, so it was treated as a change. */
26960
+ Schema_exports.TaggedStruct("intent-not-read", {})
26961
+ ]);
26962
+ var decodeWireFacts = Schema_exports.decodeUnknownSync(Schema_exports.toCodecJson(Schema_exports.Array(LandingFact)));
26963
+ var landingFactsFromWire = (wire) => decodeWireFacts(wire);
26964
+ var shortSha = (sha) => sha.slice(0, 7);
26965
+ var plural = (count, one, many) => `${count} ${count === 1 ? one : many}`;
26966
+ var observedAgo = (at, now2) => {
26967
+ const seconds2 = Math.max(0, Math.floor((now2.getTime() - at.getTime()) / 1e3));
26968
+ if (seconds2 < 60) return `${seconds2} s ago`;
26969
+ if (seconds2 < 3600) return `${Math.floor(seconds2 / 60)} min ago`;
26970
+ if (seconds2 < 86400) return `${Math.floor(seconds2 / 3600)} h ago`;
26971
+ return `${Math.floor(seconds2 / 86400)} d ago`;
26972
+ };
26973
+ var NOT_LANDED = {
26974
+ question: "the request read as a question",
26975
+ option: "the request said autopr=false",
26976
+ off: "automatic landing is off",
26977
+ "not-owner": "the turn was not sent by the owner"
26978
+ };
26979
+ var landingFactLine = (fact, now2) => {
26980
+ switch (fact._tag) {
26981
+ case "pushed":
26982
+ return `pushed \xB7 ${fact.branch} \xB7 ${shortSha(fact.sha)} \xB7 observed`;
26983
+ case "pull-request":
26984
+ return `pull request #${fact.number} \xB7 ${fact.state} \xB7 observed ${observedAgo(fact.observedAt, now2)}` + (fact.outside ? " \xB7 opened outside Mend" : "") + (fact.fork === null ? "" : ` \xB7 from ${fact.fork === "" ? "a fork" : `${fact.fork}'s fork`}`);
26985
+ case "origin-moved":
26986
+ return `origin has moved \xB7 ${fact.branch} has ${plural(fact.commits, "commit", "commits")} Mend has not seen`;
26987
+ case "changed-since-landing":
26988
+ return `changed since landing \xB7 ${plural(fact.files, "file", "files")}`;
26989
+ case "refused":
26990
+ return `push refused \xB7 ${fact.branch} \xB7 ${fact.message}`;
26991
+ case "failed":
26992
+ return `landing failed \xB7 ${fact.message}`;
26993
+ case "pull-request-failed":
26994
+ return `pull request step failed \xB7 ${fact.message}`;
26995
+ case "agent-push":
26996
+ return fact.sha === null ? `deleted by the agent \xB7 ${fact.ref}` : `pushed by the agent \xB7 ${fact.ref} \xB7 ${shortSha(fact.sha)}`;
26997
+ case "not-landed":
26998
+ return `changes not landed \xB7 ${NOT_LANDED[fact.reason]}`;
26999
+ case "intent-not-read":
27000
+ return "intent not read";
27001
+ }
27002
+ };
27003
+
26809
27004
  // ../../packages/domain/src/workbench/agent-protocol.ts
26810
27005
  var AgentLaunchMode = Schema_exports.Literals(["pty", "protocol"]);
26811
27006
  var AgentTurnStatus = Schema_exports.Literals([
@@ -26836,6 +27031,23 @@ var AgentTurn = class extends Schema_exports.Class("AgentTurn")({
26836
27031
  providerTurnId: Schema_exports.NullOr(Schema_exports.String),
26837
27032
  error: Schema_exports.NullOr(Schema_exports.String),
26838
27033
  usage: Schema_exports.NullOr(AgentTurnUsage),
27034
+ /**
27035
+ * What the request asked for (docs/adr/0007-landing.md, "Questions do not open pull requests"):
27036
+ * null until Mend reads it, and null with source `unread` when it could not.
27037
+ */
27038
+ intent: Schema_exports.NullOr(RequestIntent).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27039
+ intentSource: Schema_exports.NullOr(RequestIntentSource).pipe(
27040
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
27041
+ ),
27042
+ /**
27043
+ * What Mend decided about landing once the turn ended ("When a completed turn lands"): null
27044
+ * until it decides, or for a turn that ended before automatic landing existed.
27045
+ */
27046
+ landing: Schema_exports.NullOr(TurnLanding).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27047
+ /** The automatic landing this turn started, when it `attempted` one and the row remains. */
27048
+ landingId: Schema_exports.NullOr(ChangeLandingId).pipe(
27049
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
27050
+ ),
26839
27051
  createdAt: Timestamp,
26840
27052
  startedAt: Schema_exports.NullOr(Timestamp),
26841
27053
  endedAt: Schema_exports.NullOr(Timestamp)
@@ -27071,6 +27283,37 @@ var FolderFile = class extends Schema_exports.Class("FolderFile")({
27071
27283
  var FOLDER_MAX_FILE_BYTES = 1024 * 1024;
27072
27284
  var FOLDER_MAX_REQUEST_BYTES = 4 * 1024 * 1024;
27073
27285
 
27286
+ // ../../packages/domain/src/workbench/git-author.ts
27287
+ var GitAuthorSource = Schema_exports.Literals(["setting", "account"]);
27288
+ var GitAuthor = class extends Schema_exports.Class("GitAuthor")({
27289
+ name: Schema_exports.String,
27290
+ email: Schema_exports.String
27291
+ }) {
27292
+ };
27293
+ var ResolvedGitAuthor = class extends Schema_exports.Class("ResolvedGitAuthor")({
27294
+ name: Schema_exports.String,
27295
+ email: Schema_exports.String,
27296
+ source: GitAuthorSource
27297
+ }) {
27298
+ };
27299
+ var GIT_AUTHOR_MAX_LENGTH = 200;
27300
+ var normalizeGitAuthor = (author) => new GitAuthor({ name: author.name.trim(), email: author.email.trim() });
27301
+ var gitAuthorIssue = (author) => {
27302
+ const { name, email } = normalizeGitAuthor(author);
27303
+ if (name === "") return "the name is empty";
27304
+ if (email === "") return "the email is empty";
27305
+ if (name.length > GIT_AUTHOR_MAX_LENGTH) {
27306
+ return `the name is longer than ${GIT_AUTHOR_MAX_LENGTH} characters`;
27307
+ }
27308
+ if (email.length > GIT_AUTHOR_MAX_LENGTH) {
27309
+ return `the email is longer than ${GIT_AUTHOR_MAX_LENGTH} characters`;
27310
+ }
27311
+ if (/[<>\r\n]/.test(name)) return "the name contains <, > or a line break";
27312
+ if (!/^[^\s<>@]+@[^\s<>@]+$/.test(email))
27313
+ return "the email is not an address like you@example.com";
27314
+ return null;
27315
+ };
27316
+
27074
27317
  // ../../packages/domain/src/workbench/review-slice.ts
27075
27318
  var DiffDigest = Schema_exports.String.pipe(
27076
27319
  Schema_exports.check(Schema_exports.isPattern(/^[0-9a-f]{64}$/)),
@@ -27186,6 +27429,10 @@ var SessionReferenceMount = class extends Schema_exports.Class(
27186
27429
  };
27187
27430
 
27188
27431
  // ../../packages/domain/src/workbench/session.ts
27432
+ var SessionDotfilesNotApplied = Schema_exports.Struct({
27433
+ source: Schema_exports.Literals(["repository", "snapshot"]),
27434
+ reason: Schema_exports.String
27435
+ });
27189
27436
  var SessionDotfiles = Schema_exports.Struct({
27190
27437
  repository: Schema_exports.NullOr(
27191
27438
  Schema_exports.Struct({
@@ -27193,7 +27440,11 @@ var SessionDotfiles = Schema_exports.Struct({
27193
27440
  ref: Schema_exports.NullOr(Schema_exports.String)
27194
27441
  })
27195
27442
  ),
27196
- snapshotSha: Schema_exports.NullOr(Schema_exports.String)
27443
+ snapshotSha: Schema_exports.NullOr(Schema_exports.String),
27444
+ /** Rows stamped before this field decode as nothing left out. */
27445
+ notApplied: Schema_exports.Array(SessionDotfilesNotApplied).pipe(
27446
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed([]))
27447
+ )
27197
27448
  });
27198
27449
  var SessionStatus = Schema_exports.Literals([
27199
27450
  "starting",
@@ -27204,6 +27455,7 @@ var SessionStatus = Schema_exports.Literals([
27204
27455
  "failed",
27205
27456
  "stopped"
27206
27457
  ]);
27458
+ var SessionOrigin = Schema_exports.Literals(["mend", "slack"]);
27207
27459
  var NativeIngestCursor = Schema_exports.Struct({
27208
27460
  providerSessionId: Schema_exports.String,
27209
27461
  lastEntryUuid: Schema_exports.NullOr(Schema_exports.String),
@@ -27256,6 +27508,14 @@ var Session = class extends Schema_exports.Class("Session")({
27256
27508
  dotfiles: Schema_exports.NullOr(SessionDotfiles),
27257
27509
  /** Who provisioned the session — whose dotfiles apply. Null for pre-column rows. */
27258
27510
  ownerUserId: Schema_exports.NullOr(Schema_exports.String),
27511
+ /** Where it was started from (docs/adr/0006-slack.md). */
27512
+ origin: SessionOrigin.pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed("mend"))),
27513
+ /**
27514
+ * The session's own automatic-landing override (docs/adr/0007-landing.md), set at start: the
27515
+ * composer's or `mend`'s `--land` / `--no-land`, or a Slack request's `autopr=`. Null follows the
27516
+ * project and Settings.
27517
+ */
27518
+ autoLand: Schema_exports.NullOr(Schema_exports.Boolean).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27259
27519
  /**
27260
27520
  * Shared control (docs/adr/0003-organizations-and-tenancy.md): when set, anyone who can see the
27261
27521
  * session may steer it, still on the owner's credentials. Who turned it on, and when.
@@ -27359,13 +27619,14 @@ var ProjectLink = class extends Schema_exports.Class("ProjectLink")({
27359
27619
 
27360
27620
  // ../../packages/domain/src/workbench/pass.ts
27361
27621
  var PassKind = Schema_exports.Literals(["tour", "read", "suggest"]);
27362
- var PassStatus = Schema_exports.Literals(["running", "completed", "failed"]);
27622
+ var PassStatus = Schema_exports.Literals(["queued", "running", "completed", "failed"]);
27363
27623
  var ChangePass = class extends Schema_exports.Class("ChangePass")({
27364
27624
  changeId: ChangeId,
27365
27625
  kind: PassKind,
27366
27626
  status: PassStatus,
27367
27627
  detail: Schema_exports.NullOr(Schema_exports.String),
27368
27628
  findings: Schema_exports.NullOr(Schema_exports.Int),
27629
+ /** When it began running; for a `queued` row, when it was queued. */
27369
27630
  startedAt: Timestamp,
27370
27631
  finishedAt: Schema_exports.NullOr(Timestamp)
27371
27632
  }) {
@@ -27395,7 +27656,7 @@ var ProjectClusterBindingsSnapshot = class extends Schema_exports.Class(
27395
27656
  }) {
27396
27657
  };
27397
27658
 
27398
- // ../../node_modules/.pnpm/@sealant+api-contracts@0.37.0_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts/dist/workspace-environment.js
27659
+ // ../../node_modules/.pnpm/@sealant+api-contracts@0.37.1_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts/dist/workspace-environment.js
27399
27660
  var RESERVED_NAMES = new Map([
27400
27661
  // Identity/process roots the daemon and shells own.
27401
27662
  ...[
@@ -27730,12 +27991,20 @@ var ServiceView = class extends Schema_exports.Class("ServiceView")({
27730
27991
  }) {
27731
27992
  };
27732
27993
 
27994
+ // ../../packages/domain/src/workbench/session-fold.ts
27995
+ var servicesHoldLine = (input) => {
27996
+ if (input.agentLive || input.liveServices <= 0) return null;
27997
+ const hold = input.liveServices === 1 ? "1 service keeps the workspace up" : `${input.liveServices} services keep the workspace up`;
27998
+ return input.agentOutcome === null ? hold : `agent ${input.agentOutcome} \xB7 ${hold}`;
27999
+ };
28000
+
27733
28001
  // ../../packages/domain/src/workbench/session-control.ts
27734
28002
  var SessionControlKind = Schema_exports.Literals([
27735
28003
  "interrupt",
27736
28004
  "terminal-attach",
27737
28005
  "shell-open",
27738
28006
  "stop",
28007
+ "services-stop",
27739
28008
  "shared-control-on",
27740
28009
  "shared-control-off"
27741
28010
  ]);
@@ -27825,6 +28094,73 @@ var SkillWithFiles = class extends Schema_exports.Class("SkillWithFiles")({
27825
28094
  var SKILL_MAX_FILE_BYTES = 512 * 1024;
27826
28095
  var SKILL_MAX_BYTES = 2 * 1024 * 1024;
27827
28096
 
28097
+ // ../../packages/domain/src/workbench/slack.ts
28098
+ var SlackInstallSettings = Schema_exports.Struct({
28099
+ defaultHarness: Schema_exports.String,
28100
+ /** The plan, closing messages and questions, as well as status, reactions and links. */
28101
+ showAgentMessages: Schema_exports.Boolean,
28102
+ /** The diff of each changed file, as well as the file list and line counts. */
28103
+ showDiffs: Schema_exports.Boolean,
28104
+ /** Whether the two settings above also apply in Slack Connect channels. */
28105
+ externalChannels: Schema_exports.Boolean,
28106
+ /**
28107
+ * "Land automatically" (docs/adr/0007-landing.md): a session started from Slack pushes its
28108
+ * branch and opens its pull request when a turn completes. On unless an owner turned it off;
28109
+ * a request's `autopr=` and the project's own `off` win over it.
28110
+ */
28111
+ landAutomatically: Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(true)))
28112
+ });
28113
+ var SlackProjectSource = Schema_exports.Literals([
28114
+ "message",
28115
+ "thread-session",
28116
+ "thread-link",
28117
+ "thread-inference",
28118
+ "channel-default",
28119
+ "personal-default",
28120
+ "picked"
28121
+ ]);
28122
+ var SlackSessionState = Schema_exports.Literals([
28123
+ "starting",
28124
+ "running",
28125
+ "waiting",
28126
+ "completed",
28127
+ "failed",
28128
+ "stopped"
28129
+ ]);
28130
+ var SlackPendingFile = Schema_exports.Struct({
28131
+ id: Schema_exports.String,
28132
+ name: Schema_exports.NullOr(Schema_exports.String),
28133
+ mimetype: Schema_exports.NullOr(Schema_exports.String),
28134
+ urlPrivate: Schema_exports.NullOr(Schema_exports.String),
28135
+ size: Schema_exports.NullOr(Schema_exports.Number)
28136
+ });
28137
+ var SlackPendingMention = Schema_exports.Struct({
28138
+ channelId: Schema_exports.String,
28139
+ /** The mention itself: the message Mend reacts to. */
28140
+ messageTs: Schema_exports.String,
28141
+ /** The thread it was written in; null when the mention is the thread's first message. */
28142
+ threadTs: Schema_exports.NullOr(Schema_exports.String),
28143
+ text: Schema_exports.String,
28144
+ /**
28145
+ * Whether the channel is a Slack Connect channel, as the event said. Absent on codes minted
28146
+ * before Mend recorded it, which the reporter treats as external.
28147
+ */
28148
+ external: Schema_exports.optional(Schema_exports.Boolean),
28149
+ /** The files on the mention itself. Absent on codes minted before Mend recorded them. */
28150
+ files: Schema_exports.optional(Schema_exports.Array(SlackPendingFile))
28151
+ });
28152
+ var SlackLinkedMentionJob = Schema_exports.Struct({
28153
+ organizationId: OrganizationId,
28154
+ teamId: Schema_exports.String,
28155
+ /** The mention's author in Slack. */
28156
+ slackUserId: Schema_exports.String,
28157
+ /** The Mend account they linked, the session's owner. */
28158
+ userId: Schema_exports.String,
28159
+ request: SlackPendingMention,
28160
+ /** ISO time of the link, so the runner can leave a stale request alone. */
28161
+ linkedAt: Schema_exports.String
28162
+ });
28163
+
27828
28164
  // ../../packages/domain/src/workbench/tour.ts
27829
28165
  var TourStop = class extends Schema_exports.Class("TourStop")({
27830
28166
  title: Schema_exports.String,
@@ -27907,6 +28243,8 @@ var LAUNCH_ERROR = {
27907
28243
  fast: false,
27908
28244
  detach: false,
27909
28245
  foreground: false,
28246
+ noTunnel: false,
28247
+ autoLand: null,
27910
28248
  custom: []
27911
28249
  };
27912
28250
  var parseLaunchArgs = (args2) => {
@@ -27924,6 +28262,9 @@ var parseLaunchArgs = (args2) => {
27924
28262
  let fast = false;
27925
28263
  let detach = false;
27926
28264
  let foreground = false;
28265
+ let noTunnel = false;
28266
+ let land = false;
28267
+ let noLand = false;
27927
28268
  for (let index2 = 0; index2 < flagArgs.length; index2 += 1) {
27928
28269
  const arg = flagArgs[index2] ?? "";
27929
28270
  if (arg === "--ask") {
@@ -27942,6 +28283,18 @@ var parseLaunchArgs = (args2) => {
27942
28283
  foreground = true;
27943
28284
  continue;
27944
28285
  }
28286
+ if (arg === "--no-tunnel") {
28287
+ noTunnel = true;
28288
+ continue;
28289
+ }
28290
+ if (arg === "--land") {
28291
+ land = true;
28292
+ continue;
28293
+ }
28294
+ if (arg === "--no-land") {
28295
+ noLand = true;
28296
+ continue;
28297
+ }
27945
28298
  if (arg === "--project" || arg === "--model" || arg === "--effort" || arg === "--base" || arg === "--name" || arg === "--worktree") {
27946
28299
  const value3 = flagArgs[index2 + 1];
27947
28300
  if (value3 === void 0) return { ...LAUNCH_ERROR, error: `${arg} needs a value` };
@@ -27974,6 +28327,9 @@ var parseLaunchArgs = (args2) => {
27974
28327
  if (detach && foreground) {
27975
28328
  return { ...LAUNCH_ERROR, error: "--detach and --foreground contradict \u2014 pick one" };
27976
28329
  }
28330
+ if (land && noLand) {
28331
+ return { ...LAUNCH_ERROR, error: "--land and --no-land contradict \u2014 pick one" };
28332
+ }
27977
28333
  if (workName !== null && joinWorktree !== null) {
27978
28334
  return {
27979
28335
  ...LAUNCH_ERROR,
@@ -27992,6 +28348,8 @@ var parseLaunchArgs = (args2) => {
27992
28348
  fast,
27993
28349
  detach,
27994
28350
  foreground,
28351
+ noTunnel,
28352
+ autoLand: land ? true : noLand ? false : null,
27995
28353
  custom,
27996
28354
  error: null
27997
28355
  };
@@ -28022,6 +28380,11 @@ var agentOutcome = (currentAgent) => {
28022
28380
  if (currentAgent.harness === "shell") return "completed";
28023
28381
  return currentAgent.exitCode === null || currentAgent.exitCode === 0 ? "completed" : "failed";
28024
28382
  };
28383
+ var servicesHoldOf = (session, currentAgent, liveServices) => servicesHoldLine({
28384
+ agentLive: agentIsLive(session, currentAgent),
28385
+ agentOutcome: agentOutcome(currentAgent),
28386
+ liveServices
28387
+ });
28025
28388
  var normalizeRemoteUrl = (raw) => {
28026
28389
  if (raw === null) return null;
28027
28390
  const trimmed = raw.trim();
@@ -28105,12 +28468,65 @@ var trackBracketedPaste = (output, current) => {
28105
28468
  };
28106
28469
  var pasteBytes = (text, bracketed) => Buffer.from(bracketed ? `\x1B[200~${text}\x1B[201~` : text, "utf8");
28107
28470
 
28471
+ // src/workbench-events.ts
28472
+ var createSseParser = () => {
28473
+ let buffer = "";
28474
+ return {
28475
+ push: (chunk) => {
28476
+ buffer += chunk;
28477
+ const payloads = [];
28478
+ for (; ; ) {
28479
+ const boundary = buffer.indexOf("\n\n");
28480
+ if (boundary === -1) break;
28481
+ const frame = buffer.slice(0, boundary);
28482
+ buffer = buffer.slice(boundary + 2);
28483
+ const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, "")).join("\n");
28484
+ if (data !== "") payloads.push(data);
28485
+ }
28486
+ return payloads;
28487
+ }
28488
+ };
28489
+ };
28490
+ var eventFamilies = (payload) => {
28491
+ let parsed;
28492
+ try {
28493
+ parsed = JSON.parse(payload);
28494
+ } catch {
28495
+ return [];
28496
+ }
28497
+ if (typeof parsed !== "object" || parsed === null || !("type" in parsed)) return [];
28498
+ switch (parsed.type) {
28499
+ case "project":
28500
+ case "worktree":
28501
+ case "session-process":
28502
+ return ["workbench"];
28503
+ // Settlement refreshes the final transcript immediately. Conversation
28504
+ // items can arrive many times per second; the live preview's bounded
28505
+ // polling reads those instead of repeatedly fetching the whole record.
28506
+ case "session":
28507
+ return ["workbench", "review", "transcript"];
28508
+ case "agent-conversation":
28509
+ return ["workbench", "review"];
28510
+ // A comment moves the review AND the dashboard's open-comment counts.
28511
+ case "session-change":
28512
+ case "review-comment":
28513
+ return ["workbench", "review"];
28514
+ default:
28515
+ return [];
28516
+ }
28517
+ };
28518
+
28108
28519
  export {
28520
+ repositoryCloneUrlIssue,
28521
+ RepositoryCloneUrl,
28522
+ landingFactsFromWire,
28523
+ landingFactLine,
28109
28524
  narrowCredential,
28110
28525
  claudeGrantFacts,
28111
28526
  sameGrant,
28112
- repositoryCloneUrlIssue,
28113
- RepositoryCloneUrl,
28527
+ normalizeGitAuthor,
28528
+ gitAuthorIssue,
28529
+ servicesHoldLine,
28114
28530
  HARNESS_COMMANDS,
28115
28531
  pendingId,
28116
28532
  isPendingId,
@@ -28120,6 +28536,8 @@ export {
28120
28536
  LIVE_STATUSES,
28121
28537
  agentIsLive,
28122
28538
  agentOutcome,
28539
+ servicesHoldOf,
28540
+ normalizeRemoteUrl,
28123
28541
  matchProjectByCwd,
28124
28542
  cwdFacts,
28125
28543
  gitCurrentBranch,
@@ -28128,5 +28546,7 @@ export {
28128
28546
  normalizeProjectName,
28129
28547
  isPasteChunk,
28130
28548
  trackBracketedPaste,
28131
- pasteBytes
28549
+ pasteBytes,
28550
+ createSseParser,
28551
+ eventFamilies
28132
28552
  };