@sealant/mend 0.29.2 → 0.30.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 (79) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-AIW6XFT5.js → chunk-BMFB2UN7.js} +410 -64
  3. package/dist/{dashboard-AREQGMNK.js → dashboard-OF5UXII2.js} +452 -228
  4. package/dist/main.js +1688 -825
  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-help.1 +1 -1
  24. package/man/mend-invite.1 +1 -1
  25. package/man/mend-keys-init.1 +1 -1
  26. package/man/mend-keys-mode.1 +1 -1
  27. package/man/mend-keys-share.1 +1 -1
  28. package/man/mend-keys-show.1 +1 -1
  29. package/man/mend-land.1 +41 -0
  30. package/man/mend-login.1 +1 -1
  31. package/man/mend-logout.1 +1 -1
  32. package/man/mend-man.1 +1 -1
  33. package/man/mend-members.1 +1 -1
  34. package/man/mend-operator-exposure.1 +1 -1
  35. package/man/mend-operator-gate.1 +1 -1
  36. package/man/mend-operator-grant-owner.1 +1 -1
  37. package/man/mend-operator-org-create.1 +1 -1
  38. package/man/mend-operator-org-invite-owner.1 +1 -1
  39. package/man/mend-operator-org-list.1 +1 -1
  40. package/man/mend-operator-org-rename.1 +1 -1
  41. package/man/mend-operator-reset-link.1 +1 -1
  42. package/man/mend-pair.1 +1 -1
  43. package/man/mend-projects.1 +1 -1
  44. package/man/mend-pull.1 +29 -0
  45. package/man/mend-refresh.1 +1 -1
  46. package/man/mend-rejoin.1 +7 -2
  47. package/man/mend-resume.1 +1 -1
  48. package/man/mend-run.1 +1 -1
  49. package/man/mend-server-logs.1 +1 -1
  50. package/man/mend-server-restart.1 +1 -1
  51. package/man/mend-server-setup.1 +1 -1
  52. package/man/mend-server-start.1 +1 -1
  53. package/man/mend-server-status.1 +1 -1
  54. package/man/mend-server-stop.1 +1 -1
  55. package/man/mend-server-upgrade.1 +1 -1
  56. package/man/mend-server.1 +1 -1
  57. package/man/mend-service-add.1 +1 -1
  58. package/man/mend-service-connect.1 +3 -1
  59. package/man/mend-service-init.1 +1 -1
  60. package/man/mend-service-list.1 +1 -1
  61. package/man/mend-service-logs.1 +1 -1
  62. package/man/mend-service-restart.1 +1 -1
  63. package/man/mend-service-run.1 +1 -1
  64. package/man/mend-service-stop.1 +1 -1
  65. package/man/mend-session-share.1 +1 -1
  66. package/man/mend-sessions.1 +1 -1
  67. package/man/mend-shell.1 +1 -1
  68. package/man/mend-skills-push.1 +1 -1
  69. package/man/mend-skills.1 +1 -1
  70. package/man/mend-snake.1 +13 -0
  71. package/man/mend-ssh-setup.1 +1 -1
  72. package/man/mend-ssh.1 +1 -1
  73. package/man/mend-stop.1 +1 -1
  74. package/man/mend-ui.1 +8 -2
  75. package/man/mend-uninstall.1 +1 -1
  76. package/man/mend-version.1 +1 -1
  77. package/man/mend-worktrees.1 +1 -1
  78. package/man/mend.1 +14 -2
  79. package/package.json +1 -1
package/README.md CHANGED
@@ -137,6 +137,9 @@ mend resume [session-id] [--with h] rejoin a settled session (state restored;
137
137
  mend rejoin [session-id] [--harness h] attach if live, otherwise resume; newest live wins
138
138
  mend sessions [--all] [--project p] [--json]
139
139
  mend status active sessions (alias of mend sessions)
140
+ mend land <session> [--branch b] [--no-pr] [--title t]
141
+ push the change to origin, open or update its pull request
142
+ mend pull <session> [--force] fetch the change into this clone as mend/<name>
140
143
  mend pair [--url <base url>] pair a phone or a second machine: QR + code + URL
141
144
  mend doctor read-only checklist of this machine's setup
142
145
  mend skills [--project [p]] your skill library on the server (or a project's)
@@ -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,22 @@ 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"
26790
26821
  ]);
26791
26822
  var AuditData = Schema_exports.Record(
26792
26823
  Schema_exports.String,
@@ -26798,7 +26829,10 @@ var AuditEvent = class extends Schema_exports.Class("AuditEvent")({
26798
26829
  /** The account that acted. */
26799
26830
  actorUserId: Schema_exports.String,
26800
26831
  action: AuditAction,
26801
- /** What the action touched: `member`, `invitation`, `project`, `session`, `folder` or `reference`. */
26832
+ /**
26833
+ * What the action touched: `member`, `invitation`, `project`, `session`, `folder`, `reference`,
26834
+ * `slack` (the organization's Slack app) or `change` (a landing).
26835
+ */
26802
26836
  subjectType: Schema_exports.String,
26803
26837
  subjectId: Schema_exports.String,
26804
26838
  data: AuditData,
@@ -26806,6 +26840,142 @@ var AuditEvent = class extends Schema_exports.Class("AuditEvent")({
26806
26840
  }) {
26807
26841
  };
26808
26842
 
26843
+ // ../../packages/domain/src/workbench/landing.ts
26844
+ var LandingTrigger = Schema_exports.Literals(["manual", "automatic"]);
26845
+ var LandingOutcome = Schema_exports.Literals(["pushed", "pull-request", "refused", "failed"]);
26846
+ var PullRequestState = Schema_exports.Literals(["open", "closed", "merged"]);
26847
+ var LandedPullRequest = Schema_exports.Struct({
26848
+ number: Schema_exports.Int,
26849
+ url: Schema_exports.String,
26850
+ state: PullRequestState,
26851
+ observedAt: Timestamp
26852
+ });
26853
+ var ChangeLanding = class extends Schema_exports.Class("ChangeLanding")({
26854
+ id: ChangeLandingId,
26855
+ changeId: ChangeId,
26856
+ /** The session that landed; null once that session is deleted. */
26857
+ sessionId: Schema_exports.NullOr(SessionId),
26858
+ projectId: ProjectId,
26859
+ /** The landed checkpoint; null when it was since removed, or never taken. */
26860
+ checkpointId: Schema_exports.NullOr(CheckpointId),
26861
+ checkpointRef: Schema_exports.NullOr(Schema_exports.String),
26862
+ checkpointSha: Schema_exports.NullOr(Sha),
26863
+ /** The commit Mend wrote for work the agent left uncommitted; null when it wrote none. */
26864
+ commitSha: Schema_exports.NullOr(Sha),
26865
+ /** The branch on origin, `mend/<name>` unless the owner named another. */
26866
+ remoteBranch: Schema_exports.String,
26867
+ /** What origin's branch was moved to; null when nothing was pushed. */
26868
+ pushedSha: Schema_exports.NullOr(Sha),
26869
+ trigger: LandingTrigger,
26870
+ pullRequest: Schema_exports.NullOr(LandedPullRequest),
26871
+ outcome: LandingOutcome,
26872
+ /** The remote's or `gh`'s own words for a refusal or a failure; null otherwise. */
26873
+ message: Schema_exports.NullOr(Schema_exports.String),
26874
+ /** The change's owner (`changeOwnerOf`), whose key pushed and who speaks on GitHub. */
26875
+ userId: Schema_exports.String,
26876
+ createdAt: Timestamp
26877
+ }) {
26878
+ };
26879
+ var RequestIntent = Schema_exports.Literals(["change", "question"]);
26880
+ var RequestIntentSource = Schema_exports.Literals(["read", "option", "unread"]);
26881
+ var RequestIntentReading = Schema_exports.Union([
26882
+ Schema_exports.Struct({ intent: RequestIntent, source: Schema_exports.Literals(["read", "option"]) }),
26883
+ Schema_exports.Struct({ intent: Schema_exports.Null, source: Schema_exports.Literals(["unread"]) })
26884
+ ]);
26885
+ var LANDING_GUARD = [
26886
+ "--- How this work is published ---",
26887
+ "Mend publishes the changes this session makes: it pushes the branch and opens or updates the pull request.",
26888
+ "- If the request is a question, answer it and change no files.",
26889
+ "- Change code only when the request asks for a change.",
26890
+ "- Never push and never open a pull request. Committing is fine.",
26891
+ "--- End of how this work is published ---"
26892
+ ].join("\n");
26893
+ var NotLandedReason = Schema_exports.Literals([
26894
+ /** The request read as a question. */
26895
+ "question",
26896
+ /** The request said `autopr=false`. */
26897
+ "option",
26898
+ /** Automatic landing is off for the session. */
26899
+ "off",
26900
+ /** Someone other than the owner sent the turn under shared control. */
26901
+ "not-owner"
26902
+ ]);
26903
+ var TurnLanding = Schema_exports.Literals([
26904
+ "attempted",
26905
+ "question",
26906
+ "option",
26907
+ "off",
26908
+ "not-owner",
26909
+ "skipped"
26910
+ ]);
26911
+ var LandingFact = Schema_exports.Union([
26912
+ /** A landing moved origin's branch to this sha. */
26913
+ Schema_exports.TaggedStruct("pushed", { branch: Schema_exports.String, sha: Sha }),
26914
+ /** The landing's pull request, as `gh` last reported it. */
26915
+ Schema_exports.TaggedStruct("pull-request", {
26916
+ number: Schema_exports.Int,
26917
+ state: PullRequestState,
26918
+ observedAt: Timestamp
26919
+ }),
26920
+ /** A fetch found commits on origin's branch that Mend's branch does not have. */
26921
+ Schema_exports.TaggedStruct("origin-moved", { branch: Schema_exports.String, commits: Schema_exports.Int }),
26922
+ /** The worktree moved past the landed checkpoint. */
26923
+ Schema_exports.TaggedStruct("changed-since-landing", { files: Schema_exports.Int }),
26924
+ /** The latest landing's push was refused, in the remote's words. */
26925
+ Schema_exports.TaggedStruct("refused", { branch: Schema_exports.String, message: Schema_exports.String }),
26926
+ /** The latest landing failed before it pushed. */
26927
+ Schema_exports.TaggedStruct("failed", { message: Schema_exports.String }),
26928
+ /** The latest landing pushed, and its pull request step failed. */
26929
+ Schema_exports.TaggedStruct("pull-request-failed", { message: Schema_exports.String }),
26930
+ /** A push the agent made itself, through the workspace's git transport. Not a landing. */
26931
+ Schema_exports.TaggedStruct("agent-push", { ref: Schema_exports.String, sha: Schema_exports.NullOr(Sha) }),
26932
+ /** A completed turn left changes that did not land. */
26933
+ Schema_exports.TaggedStruct("not-landed", { reason: NotLandedReason }),
26934
+ /** The request's intent could not be read, so it was treated as a change. */
26935
+ Schema_exports.TaggedStruct("intent-not-read", {})
26936
+ ]);
26937
+ var decodeWireFacts = Schema_exports.decodeUnknownSync(Schema_exports.toCodecJson(Schema_exports.Array(LandingFact)));
26938
+ var landingFactsFromWire = (wire) => decodeWireFacts(wire);
26939
+ var shortSha = (sha) => sha.slice(0, 7);
26940
+ var plural = (count, one, many) => `${count} ${count === 1 ? one : many}`;
26941
+ var observedAgo = (at, now2) => {
26942
+ const seconds2 = Math.max(0, Math.floor((now2.getTime() - at.getTime()) / 1e3));
26943
+ if (seconds2 < 60) return `${seconds2} s ago`;
26944
+ if (seconds2 < 3600) return `${Math.floor(seconds2 / 60)} min ago`;
26945
+ if (seconds2 < 86400) return `${Math.floor(seconds2 / 3600)} h ago`;
26946
+ return `${Math.floor(seconds2 / 86400)} d ago`;
26947
+ };
26948
+ var NOT_LANDED = {
26949
+ question: "the request read as a question",
26950
+ option: "the request said autopr=false",
26951
+ off: "automatic landing is off",
26952
+ "not-owner": "the turn was not sent by the owner"
26953
+ };
26954
+ var landingFactLine = (fact, now2) => {
26955
+ switch (fact._tag) {
26956
+ case "pushed":
26957
+ return `pushed \xB7 ${fact.branch} \xB7 ${shortSha(fact.sha)} \xB7 observed`;
26958
+ case "pull-request":
26959
+ return `pull request #${fact.number} \xB7 ${fact.state} \xB7 observed ${observedAgo(fact.observedAt, now2)}`;
26960
+ case "origin-moved":
26961
+ return `origin has moved \xB7 ${fact.branch} has ${plural(fact.commits, "commit", "commits")} Mend has not seen`;
26962
+ case "changed-since-landing":
26963
+ return `changed since landing \xB7 ${plural(fact.files, "file", "files")}`;
26964
+ case "refused":
26965
+ return `push refused \xB7 ${fact.branch} \xB7 ${fact.message}`;
26966
+ case "failed":
26967
+ return `landing failed \xB7 ${fact.message}`;
26968
+ case "pull-request-failed":
26969
+ return `pull request step failed \xB7 ${fact.message}`;
26970
+ case "agent-push":
26971
+ return fact.sha === null ? `deleted by the agent \xB7 ${fact.ref}` : `pushed by the agent \xB7 ${fact.ref} \xB7 ${shortSha(fact.sha)}`;
26972
+ case "not-landed":
26973
+ return `changes not landed \xB7 ${NOT_LANDED[fact.reason]}`;
26974
+ case "intent-not-read":
26975
+ return "intent not read";
26976
+ }
26977
+ };
26978
+
26809
26979
  // ../../packages/domain/src/workbench/agent-protocol.ts
26810
26980
  var AgentLaunchMode = Schema_exports.Literals(["pty", "protocol"]);
26811
26981
  var AgentTurnStatus = Schema_exports.Literals([
@@ -26836,6 +27006,23 @@ var AgentTurn = class extends Schema_exports.Class("AgentTurn")({
26836
27006
  providerTurnId: Schema_exports.NullOr(Schema_exports.String),
26837
27007
  error: Schema_exports.NullOr(Schema_exports.String),
26838
27008
  usage: Schema_exports.NullOr(AgentTurnUsage),
27009
+ /**
27010
+ * What the request asked for (docs/adr/0007-landing.md, "Questions do not open pull requests"):
27011
+ * null until Mend reads it, and null with source `unread` when it could not.
27012
+ */
27013
+ intent: Schema_exports.NullOr(RequestIntent).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27014
+ intentSource: Schema_exports.NullOr(RequestIntentSource).pipe(
27015
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
27016
+ ),
27017
+ /**
27018
+ * What Mend decided about landing once the turn ended ("When a completed turn lands"): null
27019
+ * until it decides, or for a turn that ended before automatic landing existed.
27020
+ */
27021
+ landing: Schema_exports.NullOr(TurnLanding).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27022
+ /** The automatic landing this turn started, when it `attempted` one and the row remains. */
27023
+ landingId: Schema_exports.NullOr(ChangeLandingId).pipe(
27024
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
27025
+ ),
26839
27026
  createdAt: Timestamp,
26840
27027
  startedAt: Schema_exports.NullOr(Timestamp),
26841
27028
  endedAt: Schema_exports.NullOr(Timestamp)
@@ -27186,6 +27373,10 @@ var SessionReferenceMount = class extends Schema_exports.Class(
27186
27373
  };
27187
27374
 
27188
27375
  // ../../packages/domain/src/workbench/session.ts
27376
+ var SessionDotfilesNotApplied = Schema_exports.Struct({
27377
+ source: Schema_exports.Literals(["repository", "snapshot"]),
27378
+ reason: Schema_exports.String
27379
+ });
27189
27380
  var SessionDotfiles = Schema_exports.Struct({
27190
27381
  repository: Schema_exports.NullOr(
27191
27382
  Schema_exports.Struct({
@@ -27193,7 +27384,11 @@ var SessionDotfiles = Schema_exports.Struct({
27193
27384
  ref: Schema_exports.NullOr(Schema_exports.String)
27194
27385
  })
27195
27386
  ),
27196
- snapshotSha: Schema_exports.NullOr(Schema_exports.String)
27387
+ snapshotSha: Schema_exports.NullOr(Schema_exports.String),
27388
+ /** Rows stamped before this field decode as nothing left out. */
27389
+ notApplied: Schema_exports.Array(SessionDotfilesNotApplied).pipe(
27390
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed([]))
27391
+ )
27197
27392
  });
27198
27393
  var SessionStatus = Schema_exports.Literals([
27199
27394
  "starting",
@@ -27204,6 +27399,7 @@ var SessionStatus = Schema_exports.Literals([
27204
27399
  "failed",
27205
27400
  "stopped"
27206
27401
  ]);
27402
+ var SessionOrigin = Schema_exports.Literals(["mend", "slack"]);
27207
27403
  var NativeIngestCursor = Schema_exports.Struct({
27208
27404
  providerSessionId: Schema_exports.String,
27209
27405
  lastEntryUuid: Schema_exports.NullOr(Schema_exports.String),
@@ -27256,6 +27452,14 @@ var Session = class extends Schema_exports.Class("Session")({
27256
27452
  dotfiles: Schema_exports.NullOr(SessionDotfiles),
27257
27453
  /** Who provisioned the session — whose dotfiles apply. Null for pre-column rows. */
27258
27454
  ownerUserId: Schema_exports.NullOr(Schema_exports.String),
27455
+ /** Where it was started from (docs/adr/0006-slack.md). */
27456
+ origin: SessionOrigin.pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed("mend"))),
27457
+ /**
27458
+ * The session's own automatic-landing override (docs/adr/0007-landing.md), set at start: the
27459
+ * composer's or `mend`'s `--land` / `--no-land`, or a Slack request's `autopr=`. Null follows the
27460
+ * project and Settings.
27461
+ */
27462
+ autoLand: Schema_exports.NullOr(Schema_exports.Boolean).pipe(Schema_exports.withConstructorDefault(Effect_exports.succeed(null))),
27259
27463
  /**
27260
27464
  * Shared control (docs/adr/0003-organizations-and-tenancy.md): when set, anyone who can see the
27261
27465
  * session may steer it, still on the owner's credentials. Who turned it on, and when.
@@ -27825,6 +28029,73 @@ var SkillWithFiles = class extends Schema_exports.Class("SkillWithFiles")({
27825
28029
  var SKILL_MAX_FILE_BYTES = 512 * 1024;
27826
28030
  var SKILL_MAX_BYTES = 2 * 1024 * 1024;
27827
28031
 
28032
+ // ../../packages/domain/src/workbench/slack.ts
28033
+ var SlackInstallSettings = Schema_exports.Struct({
28034
+ defaultHarness: Schema_exports.String,
28035
+ /** The plan, closing messages and questions, as well as status, reactions and links. */
28036
+ showAgentMessages: Schema_exports.Boolean,
28037
+ /** The diff of each changed file, as well as the file list and line counts. */
28038
+ showDiffs: Schema_exports.Boolean,
28039
+ /** Whether the two settings above also apply in Slack Connect channels. */
28040
+ externalChannels: Schema_exports.Boolean,
28041
+ /**
28042
+ * "Land automatically" (docs/adr/0007-landing.md): a session started from Slack pushes its
28043
+ * branch and opens its pull request when a turn completes. On unless an owner turned it off;
28044
+ * a request's `autopr=` and the project's own `off` win over it.
28045
+ */
28046
+ landAutomatically: Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(true)))
28047
+ });
28048
+ var SlackProjectSource = Schema_exports.Literals([
28049
+ "message",
28050
+ "thread-session",
28051
+ "thread-link",
28052
+ "thread-inference",
28053
+ "channel-default",
28054
+ "personal-default",
28055
+ "picked"
28056
+ ]);
28057
+ var SlackSessionState = Schema_exports.Literals([
28058
+ "starting",
28059
+ "running",
28060
+ "waiting",
28061
+ "completed",
28062
+ "failed",
28063
+ "stopped"
28064
+ ]);
28065
+ var SlackPendingFile = Schema_exports.Struct({
28066
+ id: Schema_exports.String,
28067
+ name: Schema_exports.NullOr(Schema_exports.String),
28068
+ mimetype: Schema_exports.NullOr(Schema_exports.String),
28069
+ urlPrivate: Schema_exports.NullOr(Schema_exports.String),
28070
+ size: Schema_exports.NullOr(Schema_exports.Number)
28071
+ });
28072
+ var SlackPendingMention = Schema_exports.Struct({
28073
+ channelId: Schema_exports.String,
28074
+ /** The mention itself: the message Mend reacts to. */
28075
+ messageTs: Schema_exports.String,
28076
+ /** The thread it was written in; null when the mention is the thread's first message. */
28077
+ threadTs: Schema_exports.NullOr(Schema_exports.String),
28078
+ text: Schema_exports.String,
28079
+ /**
28080
+ * Whether the channel is a Slack Connect channel, as the event said. Absent on codes minted
28081
+ * before Mend recorded it, which the reporter treats as external.
28082
+ */
28083
+ external: Schema_exports.optional(Schema_exports.Boolean),
28084
+ /** The files on the mention itself. Absent on codes minted before Mend recorded them. */
28085
+ files: Schema_exports.optional(Schema_exports.Array(SlackPendingFile))
28086
+ });
28087
+ var SlackLinkedMentionJob = Schema_exports.Struct({
28088
+ organizationId: OrganizationId,
28089
+ teamId: Schema_exports.String,
28090
+ /** The mention's author in Slack. */
28091
+ slackUserId: Schema_exports.String,
28092
+ /** The Mend account they linked, the session's owner. */
28093
+ userId: Schema_exports.String,
28094
+ request: SlackPendingMention,
28095
+ /** ISO time of the link, so the runner can leave a stale request alone. */
28096
+ linkedAt: Schema_exports.String
28097
+ });
28098
+
27828
28099
  // ../../packages/domain/src/workbench/tour.ts
27829
28100
  var TourStop = class extends Schema_exports.Class("TourStop")({
27830
28101
  title: Schema_exports.String,
@@ -27907,6 +28178,8 @@ var LAUNCH_ERROR = {
27907
28178
  fast: false,
27908
28179
  detach: false,
27909
28180
  foreground: false,
28181
+ noTunnel: false,
28182
+ autoLand: null,
27910
28183
  custom: []
27911
28184
  };
27912
28185
  var parseLaunchArgs = (args2) => {
@@ -27924,6 +28197,9 @@ var parseLaunchArgs = (args2) => {
27924
28197
  let fast = false;
27925
28198
  let detach = false;
27926
28199
  let foreground = false;
28200
+ let noTunnel = false;
28201
+ let land = false;
28202
+ let noLand = false;
27927
28203
  for (let index2 = 0; index2 < flagArgs.length; index2 += 1) {
27928
28204
  const arg = flagArgs[index2] ?? "";
27929
28205
  if (arg === "--ask") {
@@ -27942,6 +28218,18 @@ var parseLaunchArgs = (args2) => {
27942
28218
  foreground = true;
27943
28219
  continue;
27944
28220
  }
28221
+ if (arg === "--no-tunnel") {
28222
+ noTunnel = true;
28223
+ continue;
28224
+ }
28225
+ if (arg === "--land") {
28226
+ land = true;
28227
+ continue;
28228
+ }
28229
+ if (arg === "--no-land") {
28230
+ noLand = true;
28231
+ continue;
28232
+ }
27945
28233
  if (arg === "--project" || arg === "--model" || arg === "--effort" || arg === "--base" || arg === "--name" || arg === "--worktree") {
27946
28234
  const value3 = flagArgs[index2 + 1];
27947
28235
  if (value3 === void 0) return { ...LAUNCH_ERROR, error: `${arg} needs a value` };
@@ -27974,6 +28262,9 @@ var parseLaunchArgs = (args2) => {
27974
28262
  if (detach && foreground) {
27975
28263
  return { ...LAUNCH_ERROR, error: "--detach and --foreground contradict \u2014 pick one" };
27976
28264
  }
28265
+ if (land && noLand) {
28266
+ return { ...LAUNCH_ERROR, error: "--land and --no-land contradict \u2014 pick one" };
28267
+ }
27977
28268
  if (workName !== null && joinWorktree !== null) {
27978
28269
  return {
27979
28270
  ...LAUNCH_ERROR,
@@ -27992,6 +28283,8 @@ var parseLaunchArgs = (args2) => {
27992
28283
  fast,
27993
28284
  detach,
27994
28285
  foreground,
28286
+ noTunnel,
28287
+ autoLand: land ? true : noLand ? false : null,
27995
28288
  custom,
27996
28289
  error: null
27997
28290
  };
@@ -28105,12 +28398,62 @@ var trackBracketedPaste = (output, current) => {
28105
28398
  };
28106
28399
  var pasteBytes = (text, bracketed) => Buffer.from(bracketed ? `\x1B[200~${text}\x1B[201~` : text, "utf8");
28107
28400
 
28401
+ // src/workbench-events.ts
28402
+ var createSseParser = () => {
28403
+ let buffer = "";
28404
+ return {
28405
+ push: (chunk) => {
28406
+ buffer += chunk;
28407
+ const payloads = [];
28408
+ for (; ; ) {
28409
+ const boundary = buffer.indexOf("\n\n");
28410
+ if (boundary === -1) break;
28411
+ const frame = buffer.slice(0, boundary);
28412
+ buffer = buffer.slice(boundary + 2);
28413
+ const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, "")).join("\n");
28414
+ if (data !== "") payloads.push(data);
28415
+ }
28416
+ return payloads;
28417
+ }
28418
+ };
28419
+ };
28420
+ var eventFamilies = (payload) => {
28421
+ let parsed;
28422
+ try {
28423
+ parsed = JSON.parse(payload);
28424
+ } catch {
28425
+ return [];
28426
+ }
28427
+ if (typeof parsed !== "object" || parsed === null || !("type" in parsed)) return [];
28428
+ switch (parsed.type) {
28429
+ case "project":
28430
+ case "worktree":
28431
+ case "session-process":
28432
+ return ["workbench"];
28433
+ // Settlement refreshes the final transcript immediately. Conversation
28434
+ // items can arrive many times per second; the live preview's bounded
28435
+ // polling reads those instead of repeatedly fetching the whole record.
28436
+ case "session":
28437
+ return ["workbench", "review", "transcript"];
28438
+ case "agent-conversation":
28439
+ return ["workbench", "review"];
28440
+ // A comment moves the review AND the dashboard's open-comment counts.
28441
+ case "session-change":
28442
+ case "review-comment":
28443
+ return ["workbench", "review"];
28444
+ default:
28445
+ return [];
28446
+ }
28447
+ };
28448
+
28108
28449
  export {
28450
+ repositoryCloneUrlIssue,
28451
+ RepositoryCloneUrl,
28452
+ landingFactsFromWire,
28453
+ landingFactLine,
28109
28454
  narrowCredential,
28110
28455
  claudeGrantFacts,
28111
28456
  sameGrant,
28112
- repositoryCloneUrlIssue,
28113
- RepositoryCloneUrl,
28114
28457
  HARNESS_COMMANDS,
28115
28458
  pendingId,
28116
28459
  isPendingId,
@@ -28120,6 +28463,7 @@ export {
28120
28463
  LIVE_STATUSES,
28121
28464
  agentIsLive,
28122
28465
  agentOutcome,
28466
+ normalizeRemoteUrl,
28123
28467
  matchProjectByCwd,
28124
28468
  cwdFacts,
28125
28469
  gitCurrentBranch,
@@ -28128,5 +28472,7 @@ export {
28128
28472
  normalizeProjectName,
28129
28473
  isPasteChunk,
28130
28474
  trackBracketedPaste,
28131
- pasteBytes
28475
+ pasteBytes,
28476
+ createSseParser,
28477
+ eventFamilies
28132
28478
  };