@overdeck/contracts 0.61.0 → 0.62.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/index.cjs CHANGED
@@ -41,7 +41,8 @@ const BackendPane = effect.Schema.Struct({
41
41
  "test",
42
42
  "uat",
43
43
  "strike",
44
- "plan"
44
+ "plan",
45
+ "conversation"
45
46
  ]),
46
47
  harness: effect.Schema.String,
47
48
  model: effect.Schema.String,
@@ -172,9 +173,17 @@ const MEMBERSHIP_UNAVAILABLE_REASONS = [
172
173
  "repo_unavailable",
173
174
  "default_branch_unresolved",
174
175
  "forge_unavailable",
176
+ "forge_transient",
175
177
  "tracker_unconfigured",
176
178
  "gather_failed"
177
179
  ];
180
+ /**
181
+ * PAN-3527 — the membership snapshot has not been gathered yet (a freshly
182
+ * restarted server warming its cache). Sent with HTTP 503 and a `Retry-After`
183
+ * header. Unlike `PipelineMembershipUnavailableBody` (a settled answer about
184
+ * the project), this is a temporary state: callers retry it.
185
+ */
186
+ const PIPELINE_MEMBERSHIP_LOADING_CODE = "snapshot_loading";
178
187
  const AgentId = effect.Schema.String;
179
188
  const SequenceNumber = effect.Schema.Number;
180
189
  const AgentStatus = effect.Schema.Literals([
@@ -353,6 +362,12 @@ const AgentSnapshot = effect.Schema.Struct({
353
362
  phase: effect.Schema.optional(effect.Schema.String),
354
363
  workType: effect.Schema.optional(effect.Schema.String),
355
364
  roleRunHead: effect.Schema.optional(effect.Schema.String),
365
+ /** The agent has a live pane on its terminal backend (Herdr or tmux). */
366
+ hasLivePane: effect.Schema.optional(effect.Schema.Boolean),
367
+ /**
368
+ * @deprecated Misnamed alias of `hasLivePane`: true for a live pane on either
369
+ * backend, not only a tmux session. Still populated for compatibility (#4105).
370
+ */
356
371
  hasLiveTmuxSession: effect.Schema.optional(effect.Schema.Boolean),
357
372
  stoppedByUser: effect.Schema.optional(effect.Schema.Boolean),
358
373
  paused: effect.Schema.optional(effect.Schema.Boolean),
@@ -467,6 +482,39 @@ const ProjectCiSnapshot = effect.Schema.Struct({
467
482
  /** Newest `observedAt` folded into this record. */
468
483
  updatedAt: effect.Schema.String
469
484
  });
485
+ /**
486
+ * Where an in-flight dashboard self-deploy (`pan reload`) stands.
487
+ *
488
+ * `building` the reload holds the restart lock and is building from origin/main;
489
+ * `awaiting-approval` the build is ready and the reload waits on the restart gate;
490
+ * `restarting` the operator approved and the old dashboard is being replaced;
491
+ * `failed` the last reload recorded a failure (visible for a short window).
492
+ */
493
+ const ProjectDeployPhase = effect.Schema.Literals([
494
+ "building",
495
+ "awaiting-approval",
496
+ "restarting",
497
+ "failed"
498
+ ]);
499
+ /**
500
+ * One project's deploy, derived on the server from live runtime files: the
501
+ * restart lock, the restart gate, the restart-status journal and the reload
502
+ * process's own stdout. Nothing here is stored; a server restart re-derives it.
503
+ */
504
+ const ProjectDeploySnapshot = effect.Schema.Struct({
505
+ projectKey: effect.Schema.String,
506
+ trigger: effect.Schema.String,
507
+ phase: ProjectDeployPhase,
508
+ /** The deploying process, while it is alive. */
509
+ pid: effect.Schema.optional(effect.Schema.Number),
510
+ /** When the server first observed this deploy (for `failed`, when it failed). */
511
+ startedAt: effect.Schema.String,
512
+ error: effect.Schema.optional(effect.Schema.String),
513
+ /** The reload's output file, when its stdout is a regular file. */
514
+ logPath: effect.Schema.optional(effect.Schema.String),
515
+ /** Last lines of `logPath`. */
516
+ logTail: effect.Schema.optional(effect.Schema.Array(effect.Schema.String))
517
+ });
470
518
  /** What kind of process asked for the voluntary dashboard restart. */
471
519
  const RestartGateKind = effect.Schema.Literals([
472
520
  "deploy",
@@ -536,6 +584,8 @@ const DashboardSnapshot = effect.Schema.Struct({
536
584
  embedProgressBySessionId: effect.Schema.optional(effect.Schema.Record(effect.Schema.String, EmbedProgressSnapshot)),
537
585
  ciByProjectKey: effect.Schema.optional(effect.Schema.Record(effect.Schema.String, ProjectCiSnapshot)),
538
586
  restartGate: effect.Schema.optional(RestartGateSnapshot),
587
+ /** PAN-3751 — in-flight deploys keyed by project key. */
588
+ deployByProjectKey: effect.Schema.optional(effect.Schema.Record(effect.Schema.String, ProjectDeploySnapshot)),
539
589
  timestamp: effect.Schema.String
540
590
  });
541
591
  const ResourceStats = effect.Schema.Struct({ containers: effect.Schema.Array(effect.Schema.Struct({
@@ -790,6 +840,95 @@ const ScanResult = effect.Schema.Struct({
790
840
  durationMs: effect.Schema.Number
791
841
  });
792
842
  //#endregion
843
+ //#region src/pull-request-links.ts
844
+ const PullRequestLinkSource = effect.Schema.Literals([
845
+ "manual",
846
+ "agent",
847
+ "created",
848
+ "branch"
849
+ ]);
850
+ const PullRequestKey = effect.Schema.Struct({
851
+ host: effect.Schema.String,
852
+ repository: effect.Schema.String,
853
+ number: effect.Schema.Number
854
+ });
855
+ const PullRequestState = effect.Schema.Literals([
856
+ "open",
857
+ "merged",
858
+ "closed"
859
+ ]);
860
+ const PullRequestSnapshot = effect.Schema.Struct({
861
+ state: PullRequestState,
862
+ isDraft: effect.Schema.Boolean,
863
+ title: effect.Schema.String,
864
+ headBranch: effect.Schema.NullOr(effect.Schema.String),
865
+ baseBranch: effect.Schema.NullOr(effect.Schema.String),
866
+ reviewState: PrReviewState,
867
+ checks: PrChecksState,
868
+ mergeable: effect.Schema.NullOr(effect.Schema.Boolean),
869
+ additions: effect.Schema.NullOr(effect.Schema.Number),
870
+ deletions: effect.Schema.NullOr(effect.Schema.Number),
871
+ changedFiles: effect.Schema.NullOr(effect.Schema.Number),
872
+ author: effect.Schema.NullOr(effect.Schema.String),
873
+ /** Forge timestamp, ISO. */
874
+ updatedAt: effect.Schema.NullOr(effect.Schema.String),
875
+ mergedAt: effect.Schema.NullOr(effect.Schema.String),
876
+ closedAt: effect.Schema.NullOr(effect.Schema.String),
877
+ /** When Overdeck last read the PR, ISO. */
878
+ syncedAt: effect.Schema.String
879
+ });
880
+ const PullRequestLink = effect.Schema.Struct({
881
+ ...PullRequestKey.fields,
882
+ url: effect.Schema.String,
883
+ source: PullRequestLinkSource,
884
+ linkedAt: effect.Schema.String,
885
+ dismissedAt: effect.Schema.NullOr(effect.Schema.String),
886
+ snapshot: effect.Schema.NullOr(PullRequestSnapshot)
887
+ });
888
+ const ConversationPullRequests = effect.Schema.Struct({
889
+ links: effect.Schema.Array(PullRequestLink),
890
+ effective: effect.Schema.NullOr(PullRequestLink)
891
+ });
892
+ /** One conversation linked to a PR — the reverse index (PR → conversations). */
893
+ const PullRequestLinkedConversation = effect.Schema.Struct({
894
+ /** The /conv/<id> number. */
895
+ id: effect.Schema.Number,
896
+ name: effect.Schema.String,
897
+ title: effect.Schema.NullOr(effect.Schema.String),
898
+ source: PullRequestLinkSource,
899
+ linkedAt: effect.Schema.String
900
+ });
901
+ /** One live link across all conversations, for the Pull requests list. */
902
+ const PullRequestLinkListing = effect.Schema.Struct({
903
+ ...PullRequestLink.fields,
904
+ conversationId: effect.Schema.Number,
905
+ conversationName: effect.Schema.String,
906
+ conversationTitle: effect.Schema.NullOr(effect.Schema.String),
907
+ /** The conversation's effective project, or null when none resolves. */
908
+ projectKey: effect.Schema.NullOr(effect.Schema.String)
909
+ });
910
+ function stateRank(link) {
911
+ if (link.snapshot === null) return 1;
912
+ if (link.snapshot.state === "open") return 0;
913
+ if (link.snapshot.state === "merged") return 2;
914
+ return 3;
915
+ }
916
+ function recency(link) {
917
+ return Date.parse(link.snapshot?.updatedAt ?? link.linkedAt) || 0;
918
+ }
919
+ /**
920
+ * The one PR shown on a conversation: explicit links before branch links,
921
+ * open before merged before closed, most recently updated among equals.
922
+ * Dismissed links never count.
923
+ */
924
+ function resolveEffectivePullRequest(links) {
925
+ const live = links.filter((link) => link.dismissedAt === null);
926
+ const explicit = live.filter((link) => link.source !== "branch");
927
+ const pool = explicit.length > 0 ? explicit : live;
928
+ if (pool.length === 0) return null;
929
+ return [...pool].sort((a, b) => stateRank(a) - stateRank(b) || recency(b) - recency(a))[0] ?? null;
930
+ }
931
+ //#endregion
793
932
  //#region src/memory.ts
794
933
  const MemoryAgentRole = effect.Schema.Union([Role, effect.Schema.Literal("conversation")]);
795
934
  const MemoryIdentity = effect.Schema.Struct({
@@ -1047,6 +1186,9 @@ const AgentHealthSnapshot = effect.Schema.Struct({
1047
1186
  kind: effect.Schema.optional(AgentHealthKind),
1048
1187
  status: AgentHealthStatus,
1049
1188
  lifecycle: effect.Schema.optional(SpecialistLifecycle),
1189
+ /** The agent has a live pane on the selected terminal backend (Herdr or tmux). */
1190
+ hasLivePane: effect.Schema.optional(effect.Schema.Boolean),
1191
+ /** @deprecated Alias of `hasLivePane` kept for existing readers; true on either backend, not only tmux. */
1050
1192
  tmuxActive: effect.Schema.optional(effect.Schema.Boolean),
1051
1193
  memoryBytes: effect.Schema.optional(effect.Schema.Number),
1052
1194
  memoryGb: effect.Schema.optional(effect.Schema.Number),
@@ -1314,6 +1456,20 @@ const RestartGateChangedEvent = effect.Schema.Struct({
1314
1456
  timestamp: effect.Schema.String,
1315
1457
  payload: RestartGateSnapshot
1316
1458
  });
1459
+ /**
1460
+ * The set of in-flight project deploys changed (PAN-3751).
1461
+ *
1462
+ * The payload is the COMPLETE projection keyed by project key, so the reducer
1463
+ * is a plain replace. The server derives it from runtime files (restart lock,
1464
+ * restart gate, restart-status journal) and emits it in-memory only, like
1465
+ * `restart_gate.changed`: it is never persisted and never replayed at boot.
1466
+ */
1467
+ const ProjectDeployChangedEvent = effect.Schema.Struct({
1468
+ type: effect.Schema.Literal("project.deploy_changed"),
1469
+ sequence: SequenceNumber,
1470
+ timestamp: effect.Schema.String,
1471
+ payload: effect.Schema.Struct({ deploys: effect.Schema.Record(effect.Schema.String, ProjectDeploySnapshot) })
1472
+ });
1317
1473
  /** Replaces socket.io `agents:changed` (event: 'started') */
1318
1474
  const AgentStartedEvent = effect.Schema.Struct({
1319
1475
  type: effect.Schema.Literal("agent.started"),
@@ -1403,6 +1559,9 @@ const AgentStatusChangedEvent = effect.Schema.Struct({
1403
1559
  workspaceId: effect.Schema.optional(effect.Schema.String),
1404
1560
  status: AgentStatus,
1405
1561
  previousStatus: effect.Schema.optional(AgentStatus),
1562
+ /** The agent has a live pane on its terminal backend (Herdr or tmux). */
1563
+ hasLivePane: effect.Schema.optional(effect.Schema.Boolean),
1564
+ /** @deprecated Alias of `hasLivePane`, kept for events written before it existed (#4105). */
1406
1565
  hasLiveTmuxSession: effect.Schema.optional(effect.Schema.Boolean),
1407
1566
  stoppedByUser: effect.Schema.optional(effect.Schema.Boolean),
1408
1567
  stoppedByPause: effect.Schema.optional(effect.Schema.Boolean),
@@ -2469,6 +2628,17 @@ const ConversationTitleChangedEvent = effect.Schema.Struct({
2469
2628
  titleSource: effect.Schema.String
2470
2629
  })
2471
2630
  });
2631
+ /** Emitted (in-memory only) when a conversation's pull-request links or their
2632
+ * snapshots change (PAN-3822), so the list and open panel refresh the badge. */
2633
+ const ConversationPullRequestsChangedEvent = effect.Schema.Struct({
2634
+ type: effect.Schema.Literal("conversation.pull_requests_changed"),
2635
+ sequence: SequenceNumber,
2636
+ timestamp: effect.Schema.String,
2637
+ payload: effect.Schema.Struct({
2638
+ conversationName: effect.Schema.String,
2639
+ effective: effect.Schema.NullOr(PullRequestLink)
2640
+ })
2641
+ });
2472
2642
  /** Emitted (in-memory only) when a PermissionRequest hook fires or resolves for a conversation. */
2473
2643
  const ConversationPermissionChangedEvent = effect.Schema.Struct({
2474
2644
  type: effect.Schema.Literal("conversation.permission_changed"),
@@ -2603,6 +2773,7 @@ const DomainEvent = effect.Schema.Union([
2603
2773
  ProjectCiSuiteObservedEvent,
2604
2774
  ProjectCiHeadObservedEvent,
2605
2775
  RestartGateChangedEvent,
2776
+ ProjectDeployChangedEvent,
2606
2777
  AgentCreatedEvent,
2607
2778
  AgentEnrichmentChangedEvent,
2608
2779
  AgentStartedEvent,
@@ -2693,6 +2864,7 @@ const DomainEvent = effect.Schema.Union([
2693
2864
  ConversationCreatedEvent,
2694
2865
  ConversationMovedEvent,
2695
2866
  ConversationTitleChangedEvent,
2867
+ ConversationPullRequestsChangedEvent,
2696
2868
  ConversationPermissionChangedEvent,
2697
2869
  ScanStartedEvent,
2698
2870
  ScanProgressEvent,
@@ -3506,6 +3678,7 @@ const INITIAL_READ_MODEL_STATE = {
3506
3678
  embedProgressBySessionId: {},
3507
3679
  ciByProjectKey: {},
3508
3680
  restartGate: null,
3681
+ deployByProjectKey: {},
3509
3682
  agentIdBySessionId: {},
3510
3683
  dashboardLifecycle: {
3511
3684
  active: false,
@@ -3636,14 +3809,29 @@ function syncSnapshot(state, snapshot) {
3636
3809
  embedProgressBySessionId: snapshot.embedProgressBySessionId ?? {},
3637
3810
  ciByProjectKey: snapshot.ciByProjectKey ?? state.ciByProjectKey,
3638
3811
  restartGate: snapshot.restartGate ?? state.restartGate,
3812
+ deployByProjectKey: snapshot.deployByProjectKey ?? state.deployByProjectKey,
3639
3813
  agentIdBySessionId
3640
3814
  };
3641
3815
  }
3816
+ /**
3817
+ * #4105 — `hasLivePane` and its deprecated alias `hasLiveTmuxSession` carry one
3818
+ * fact. An agent snapshot from a stored event may carry only the old name; fill
3819
+ * whichever one is missing so readers of either name agree.
3820
+ */
3821
+ function withPaneLivenessAlias(agent) {
3822
+ const pane = agent.hasLivePane ?? agent.hasLiveTmuxSession;
3823
+ if (pane === void 0 || agent.hasLivePane === pane && agent.hasLiveTmuxSession === pane) return agent;
3824
+ return {
3825
+ ...agent,
3826
+ hasLivePane: pane,
3827
+ hasLiveTmuxSession: pane
3828
+ };
3829
+ }
3642
3830
  function applyEvent(state, event) {
3643
3831
  switch (event.type) {
3644
3832
  case "agent.created": {
3645
3833
  const existing = state.agentsById[event.payload.agentId];
3646
- const agent = event.payload.agent;
3834
+ const agent = withPaneLivenessAlias(event.payload.agent);
3647
3835
  const nextAgentIdBySessionId = agent.sessionId ? {
3648
3836
  ...state.agentIdBySessionId,
3649
3837
  [agent.sessionId]: agent.id
@@ -3662,7 +3850,7 @@ function applyEvent(state, event) {
3662
3850
  };
3663
3851
  }
3664
3852
  case "agent.started": {
3665
- const agent = event.payload.agent;
3853
+ const agent = withPaneLivenessAlias(event.payload.agent);
3666
3854
  const nextAgentIdBySessionId = agent.sessionId ? {
3667
3855
  ...state.agentIdBySessionId,
3668
3856
  [agent.sessionId]: agent.id
@@ -3712,6 +3900,7 @@ function applyEvent(state, event) {
3712
3900
  [event.payload.agentId]: {
3713
3901
  ...stoppedAgent,
3714
3902
  status: "stopped",
3903
+ hasLivePane: false,
3715
3904
  hasLiveTmuxSession: false,
3716
3905
  lastActivity: event.timestamp
3717
3906
  }
@@ -3773,6 +3962,7 @@ function applyEvent(state, event) {
3773
3962
  status: event.payload.status
3774
3963
  };
3775
3964
  for (const field of [
3965
+ "hasLivePane",
3776
3966
  "hasLiveTmuxSession",
3777
3967
  "stoppedByUser",
3778
3968
  "stoppedByPause",
@@ -3817,7 +4007,15 @@ function applyEvent(state, event) {
3817
4007
  if (value === null || value === void 0) delete base[field];
3818
4008
  else base[field] = value;
3819
4009
  }
3820
- const sessionAlive = event.payload.hasLiveTmuxSession === true;
4010
+ const payloadPane = event.payload.hasLivePane ?? event.payload.hasLiveTmuxSession;
4011
+ if ("hasLivePane" in event.payload || "hasLiveTmuxSession" in event.payload) if (payloadPane === null || payloadPane === void 0) {
4012
+ delete base["hasLivePane"];
4013
+ delete base["hasLiveTmuxSession"];
4014
+ } else {
4015
+ base["hasLivePane"] = payloadPane;
4016
+ base["hasLiveTmuxSession"] = payloadPane;
4017
+ }
4018
+ const sessionAlive = payloadPane === true;
3821
4019
  if (event.payload.status !== "running" && event.payload.status !== "starting" && !sessionAlive) {
3822
4020
  delete base["hasPendingQuestion"];
3823
4021
  delete base["pendingQuestionCount"];
@@ -4026,6 +4224,11 @@ function applyEvent(state, event) {
4026
4224
  ...event.payload.lastOutcome === void 0 ? {} : { lastOutcome: event.payload.lastOutcome }
4027
4225
  }
4028
4226
  };
4227
+ case "project.deploy_changed": return {
4228
+ ...state,
4229
+ sequence: Math.max(state.sequence, event.sequence),
4230
+ deployByProjectKey: event.payload.deploys
4231
+ };
4029
4232
  case "resources.updated": return {
4030
4233
  ...state,
4031
4234
  sequence: Math.max(state.sequence, event.sequence),
@@ -4738,6 +4941,10 @@ function applyEvent(state, event) {
4738
4941
  ...state,
4739
4942
  conversationsListRevision: state.conversationsListRevision + 1
4740
4943
  };
4944
+ case "conversation.pull_requests_changed": return {
4945
+ ...state,
4946
+ conversationsListRevision: state.conversationsListRevision + 1
4947
+ };
4741
4948
  case "conversation.permission_changed": {
4742
4949
  const { conversationName, waiting } = event.payload;
4743
4950
  if (!waiting) {
@@ -6787,6 +6994,20 @@ const COMPOSER_COMMAND_MANIFEST = [
6787
6994
  "aliases": [],
6788
6995
  "category": "Lifecycle"
6789
6996
  },
6997
+ {
6998
+ "id": "pan-untroubled",
6999
+ "path": ["untroubled"],
7000
+ "display": "/pan untroubled",
7001
+ "description": "Clear an agent troubled gate without spawning it",
7002
+ "args": [{
7003
+ "name": "id",
7004
+ "required": true,
7005
+ "variadic": false
7006
+ }],
7007
+ "options": [],
7008
+ "aliases": [],
7009
+ "category": "Lifecycle"
7010
+ },
6790
7011
  {
6791
7012
  "id": "pan-fork",
6792
7013
  "path": ["fork"],
@@ -6867,10 +7088,16 @@ const COMPOSER_COMMAND_MANIFEST = [
6867
7088
  },
6868
7089
  {
6869
7090
  "flags": "--issue <id>",
6870
- "description": "Issue ID to associate with the new conversation",
7091
+ "description": "Issue ID to associate with the new conversation; without --cwd the new conversation starts in the issue workspace when it exists",
6871
7092
  "required": true,
6872
7093
  "valueHint": "id"
6873
7094
  },
7095
+ {
7096
+ "flags": "--role <role>",
7097
+ "description": "Pane role for the new conversation: conversation (default), work, review, test, plan. With --issue, review makes the handoff the issue's Review row",
7098
+ "required": true,
7099
+ "valueHint": "role"
7100
+ },
6874
7101
  {
6875
7102
  "flags": "--title <title>",
6876
7103
  "description": "Title for the new conversation; defaults to \"Handoff: <focus>\" (first ~70 chars) or \"Handoff: <source title>\" without focus",
@@ -7279,7 +7506,7 @@ const COMPOSER_COMMAND_MANIFEST = [
7279
7506
  },
7280
7507
  {
7281
7508
  "flags": "--force",
7282
- "description": "Clear paused and pending-operator-decision gates and start anyway",
7509
+ "description": "Clear paused, troubled, and pending-operator-decision gates and start anyway",
7283
7510
  "required": false,
7284
7511
  "valueHint": null
7285
7512
  },
@@ -8234,7 +8461,7 @@ const COMPOSER_COMMAND_MANIFEST = [
8234
8461
  "id": "pan-workspace-reap",
8235
8462
  "path": ["workspace", "reap"],
8236
8463
  "display": "/pan workspace reap",
8237
- "description": "List or remove orphaned unhealthy workspace Docker stacks",
8464
+ "description": "List or remove orphaned unhealthy workspace Docker stacks and orphaned workspace networks",
8238
8465
  "args": [],
8239
8466
  "options": [
8240
8467
  {
@@ -8245,7 +8472,7 @@ const COMPOSER_COMMAND_MANIFEST = [
8245
8472
  },
8246
8473
  {
8247
8474
  "flags": "--apply",
8248
- "description": "Run docker compose down -v --remove-orphans for candidates",
8475
+ "description": "Run docker compose down -v --remove-orphans for stack candidates and remove orphaned workspace networks",
8249
8476
  "required": false,
8250
8477
  "valueHint": null
8251
8478
  },
@@ -10062,87 +10289,6 @@ const COMPOSER_COMMAND_MANIFEST = [
10062
10289
  "aliases": [],
10063
10290
  "category": "Admin"
10064
10291
  },
10065
- {
10066
- "id": "pan-admin-specialists-logs",
10067
- "path": [
10068
- "admin",
10069
- "specialists",
10070
- "logs"
10071
- ],
10072
- "display": "/pan admin specialists logs",
10073
- "description": "View specialist run logs",
10074
- "args": [
10075
- {
10076
- "name": "project",
10077
- "required": false,
10078
- "variadic": false
10079
- },
10080
- {
10081
- "name": "type",
10082
- "required": false,
10083
- "variadic": false
10084
- },
10085
- {
10086
- "name": "runId",
10087
- "required": false,
10088
- "variadic": false
10089
- }
10090
- ],
10091
- "options": [
10092
- {
10093
- "flags": "--json",
10094
- "description": "Output in JSON format",
10095
- "required": false,
10096
- "valueHint": null
10097
- },
10098
- {
10099
- "flags": "--limit <count>",
10100
- "description": "Number of runs to show (default: 10)",
10101
- "required": true,
10102
- "valueHint": "count"
10103
- },
10104
- {
10105
- "flags": "--tail",
10106
- "description": "Follow active run log in real-time",
10107
- "required": false,
10108
- "valueHint": null
10109
- }
10110
- ],
10111
- "aliases": [],
10112
- "category": "Admin"
10113
- },
10114
- {
10115
- "id": "pan-admin-specialists-cleanup-logs",
10116
- "path": [
10117
- "admin",
10118
- "specialists",
10119
- "cleanup-logs"
10120
- ],
10121
- "display": "/pan admin specialists cleanup-logs",
10122
- "description": "Clean up old specialist logs",
10123
- "args": [{
10124
- "name": "project",
10125
- "required": false,
10126
- "variadic": false
10127
- }, {
10128
- "name": "type",
10129
- "required": false,
10130
- "variadic": false
10131
- }],
10132
- "options": [{
10133
- "flags": "--force",
10134
- "description": "Skip confirmation prompt",
10135
- "required": false,
10136
- "valueHint": null
10137
- }, {
10138
- "flags": "--all",
10139
- "description": "Clean up logs for all projects",
10140
- "required": false,
10141
- "valueHint": null
10142
- }],
10143
- "aliases": [],
10144
- "category": "Admin"
10145
- },
10146
10292
  {
10147
10293
  "id": "pan-admin-remote",
10148
10294
  "path": ["admin", "remote"],
@@ -11033,6 +11179,80 @@ const COMPOSER_COMMAND_MANIFEST = [
11033
11179
  "aliases": [],
11034
11180
  "category": "Conversation"
11035
11181
  },
11182
+ {
11183
+ "id": "pan-conversations-link-pr",
11184
+ "path": ["conversations", "link-pr"],
11185
+ "display": "/pan conversations link-pr",
11186
+ "description": "Link a pull request (URL, #42, or owner/repo#42) to a conversation",
11187
+ "args": [{
11188
+ "name": "query",
11189
+ "required": true,
11190
+ "variadic": false
11191
+ }, {
11192
+ "name": "ref",
11193
+ "required": true,
11194
+ "variadic": false
11195
+ }],
11196
+ "options": [{
11197
+ "flags": "--source <source>",
11198
+ "description": "agent or manual (default: agent inside an agent, else manual)",
11199
+ "required": true,
11200
+ "valueHint": "source"
11201
+ }],
11202
+ "aliases": [],
11203
+ "category": "Conversation"
11204
+ },
11205
+ {
11206
+ "id": "pan-conversations-unlink-pr",
11207
+ "path": ["conversations", "unlink-pr"],
11208
+ "display": "/pan conversations unlink-pr",
11209
+ "description": "Unlink a pull request from a conversation (the sweep will not re-add it)",
11210
+ "args": [{
11211
+ "name": "query",
11212
+ "required": true,
11213
+ "variadic": false
11214
+ }, {
11215
+ "name": "ref",
11216
+ "required": true,
11217
+ "variadic": false
11218
+ }],
11219
+ "options": [],
11220
+ "aliases": [],
11221
+ "category": "Conversation"
11222
+ },
11223
+ {
11224
+ "id": "pan-conversations-prs",
11225
+ "path": ["conversations", "prs"],
11226
+ "display": "/pan conversations prs",
11227
+ "description": "List the pull requests linked to a conversation (* = the one shown)",
11228
+ "args": [{
11229
+ "name": "query",
11230
+ "required": true,
11231
+ "variadic": false
11232
+ }],
11233
+ "options": [{
11234
+ "flags": "--json",
11235
+ "description": "Output { links, effective } as JSON",
11236
+ "required": false,
11237
+ "valueHint": null
11238
+ }],
11239
+ "aliases": [],
11240
+ "category": "Conversation"
11241
+ },
11242
+ {
11243
+ "id": "pan-conversations-auto-archive-on-merge",
11244
+ "path": ["conversations", "auto-archive-on-merge"],
11245
+ "display": "/pan conversations auto-archive-on-merge",
11246
+ "description": "Show or set (on|off) archiving operator conversations once all their linked PRs are merged or closed (default off)",
11247
+ "args": [{
11248
+ "name": "value",
11249
+ "required": false,
11250
+ "variadic": false
11251
+ }],
11252
+ "options": [],
11253
+ "aliases": [],
11254
+ "category": "Conversation"
11255
+ },
11036
11256
  {
11037
11257
  "id": "pan-conversations-heal",
11038
11258
  "path": ["conversations", "heal"],
@@ -12342,9 +12562,27 @@ const COMPOSER_COMMAND_MANIFEST = [
12342
12562
  "required": true,
12343
12563
  "valueHint": "duration"
12344
12564
  },
12565
+ {
12566
+ "flags": "--deacon",
12567
+ "description": "Start Cloister/Deacon on the reloaded dashboard (always on after a reload)",
12568
+ "required": false,
12569
+ "valueHint": null
12570
+ },
12345
12571
  {
12346
12572
  "flags": "--no-deacon",
12347
- "description": "Skip Cloister/Deacon auto-start after reload",
12573
+ "description": "Refused: a Deacon-off dashboard is a peer and cannot hold the host dashboard port",
12574
+ "required": false,
12575
+ "valueHint": null
12576
+ },
12577
+ {
12578
+ "flags": "--resume",
12579
+ "description": "Enable agent auto-resume on the reloaded dashboard (default: keep the running dashboard's resume gate)",
12580
+ "required": false,
12581
+ "valueHint": null
12582
+ },
12583
+ {
12584
+ "flags": "--no-resume",
12585
+ "description": "Disable agent auto-resume on the reloaded dashboard (default: keep the running dashboard's resume gate)",
12348
12586
  "required": false,
12349
12587
  "valueHint": null
12350
12588
  }
@@ -13186,9 +13424,7 @@ const COMPOSER_COMMAND_MANIFEST = [
13186
13424
  ];
13187
13425
  const COMPOSER_COMMAND_INSERT_OVERRIDES = {
13188
13426
  "plan finalize": "/pan plan finalize ",
13189
- "admin specialists done": "/pan admin specialists done",
13190
- "admin specialists logs": "/pan admin specialists logs",
13191
- "admin specialists cleanup-logs": "/pan admin specialists cleanup-logs"
13427
+ "admin specialists done": "/pan admin specialists done"
13192
13428
  };
13193
13429
  const COMPOSER_COMMAND_VARIANTS = [
13194
13430
  {
@@ -13366,7 +13602,8 @@ const AGENT_ROLES = [
13366
13602
  "test",
13367
13603
  "uat",
13368
13604
  "strike",
13369
- "plan"
13605
+ "plan",
13606
+ "conversation"
13370
13607
  ];
13371
13608
  function isTerminalBackendName(value) {
13372
13609
  return typeof value === "string" && TERMINAL_BACKEND_NAMES.includes(value);
@@ -13420,6 +13657,16 @@ const DirectoryTranscriptRef = effect.Schema.Union([
13420
13657
  })
13421
13658
  ]);
13422
13659
  const DirectoryEntryLocation = effect.Schema.Literals(["local", "remote"]);
13660
+ const DirectoryPauseBy = effect.Schema.Literals([
13661
+ "operator",
13662
+ "scheduler",
13663
+ "machine"
13664
+ ]);
13665
+ const DirectoryPause = effect.Schema.Struct({
13666
+ by: DirectoryPauseBy,
13667
+ reason: effect.Schema.NullOr(effect.Schema.String),
13668
+ since: effect.Schema.NullOr(effect.Schema.String)
13669
+ });
13423
13670
  const DirectoryEntry = effect.Schema.Struct({
13424
13671
  id: effect.Schema.String,
13425
13672
  kind: DirectoryEntryKind,
@@ -13442,11 +13689,19 @@ const DirectoryEntry = effect.Schema.Struct({
13442
13689
  lastActivityAt: effect.Schema.NullOr(effect.Schema.String),
13443
13690
  costUsd: effect.Schema.NullOr(effect.Schema.Number),
13444
13691
  source: DirectoryEntrySource,
13445
- transcript: effect.Schema.NullOr(DirectoryTranscriptRef)
13692
+ transcript: effect.Schema.NullOr(DirectoryTranscriptRef),
13693
+ /** The pause gate from state.json, native agents only (PAN-4197). */
13694
+ pause: effect.Schema.optional(DirectoryPause)
13446
13695
  });
13696
+ /**
13697
+ * `window`: live entries plus finished ones active inside `windowHours`.
13698
+ * `live` (PAN-4197): what is running or waiting now; `windowHours` is 0.
13699
+ */
13700
+ const DirectoryScope = effect.Schema.Literals(["window", "live"]);
13447
13701
  const AgentDirectoryResponse = effect.Schema.Struct({
13448
13702
  generatedAt: effect.Schema.String,
13449
13703
  windowHours: effect.Schema.Number,
13704
+ scope: effect.Schema.optional(DirectoryScope),
13450
13705
  entries: effect.Schema.Array(DirectoryEntry)
13451
13706
  });
13452
13707
  //#endregion
@@ -13466,6 +13721,73 @@ function companionTerminalKindFor(conversation) {
13466
13721
  const COMPANION_TERMINAL_OPEN_BODY_KEYS = [];
13467
13722
  const COMPANION_TERMINAL_CLOSE_BODY_KEYS = ["generation"];
13468
13723
  //#endregion
13724
+ //#region src/pull-request-ref.ts
13725
+ const SCHEME = String.raw`(?:https?:\/\/)?`;
13726
+ const HOST = String.raw`([a-z0-9.-]+(?::\d+)?)`;
13727
+ const TAIL = String.raw`(?:\/[^?#]*)?(?:[?#].*)?$`;
13728
+ const GITLAB_MR = new RegExp(String.raw`^${SCHEME}${HOST}\/((?:[\w.-]+\/)+[\w.-]+)\/-\/merge_requests\/(\d+)${TAIL}`, "i");
13729
+ const GITHUB_PR = new RegExp(String.raw`^${SCHEME}${HOST}\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)${TAIL}`, "i");
13730
+ const GITLAB_MR_LEGACY = new RegExp(String.raw`^${SCHEME}${HOST}\/((?:[\w.-]+\/)+[\w.-]+)\/merge_requests\/(\d+)${TAIL}`, "i");
13731
+ const SHORT_REF = /^(?:((?:[\w.-]+\/)+[\w.-]+))?[#!](\d+)$/;
13732
+ function canonicalPullRequestUrl(key, forge) {
13733
+ return forge === "gitlab" ? `https://${key.host}/${key.repository}/-/merge_requests/${key.number}` : `https://${key.host}/${key.repository}/pull/${key.number}`;
13734
+ }
13735
+ function build(host, repository, number, forge) {
13736
+ if (!Number.isSafeInteger(number) || number <= 0) return null;
13737
+ const key = {
13738
+ host: host.toLowerCase(),
13739
+ repository: repository.toLowerCase().replace(/\.git$/, ""),
13740
+ number
13741
+ };
13742
+ return {
13743
+ ...key,
13744
+ forge,
13745
+ url: canonicalPullRequestUrl(key, forge)
13746
+ };
13747
+ }
13748
+ /**
13749
+ * Accepts a full PR/MR URL, `#42`, or `owner/repo#42` (`!42` for GitLab MRs).
13750
+ * The short forms need `defaultRepo` for host and forge (`#42` for the
13751
+ * repository too) and return null without it. Anything else returns null.
13752
+ */
13753
+ function parsePullRequestRef(input, defaultRepo) {
13754
+ const text = input.trim();
13755
+ if (!text) return null;
13756
+ for (const [pattern, forge] of [
13757
+ [GITLAB_MR, "gitlab"],
13758
+ [GITHUB_PR, "github"],
13759
+ [GITLAB_MR_LEGACY, "gitlab"]
13760
+ ]) {
13761
+ const match = pattern.exec(text);
13762
+ if (match) return build(match[1], match[2], Number(match[3]), forge);
13763
+ }
13764
+ const short = SHORT_REF.exec(text);
13765
+ if (!short || !defaultRepo) return null;
13766
+ return build(defaultRepo.host, short[1] ?? defaultRepo.repository, Number(short[2]), defaultRepo.forge);
13767
+ }
13768
+ /**
13769
+ * The repository behind a git remote URL (https, ssh://, or scp-style), or null
13770
+ * when the host names neither GitHub nor GitLab.
13771
+ */
13772
+ function repoFromRemoteUrl(remote) {
13773
+ if (!remote) return null;
13774
+ const text = remote.trim();
13775
+ const match = /^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)(?::\d+)?\/(.+?)(?:\.git)?\/?$/i.exec(text) ?? /^(?:[^@/]+@)?([^/:]+):(.+?)(?:\.git)?\/?$/i.exec(text);
13776
+ if (!match) return null;
13777
+ const host = match[1].toLowerCase();
13778
+ const forge = host.includes("gitlab") ? "gitlab" : host.includes("github") ? "github" : null;
13779
+ if (!forge) return null;
13780
+ return {
13781
+ host,
13782
+ repository: match[2].toLowerCase(),
13783
+ forge
13784
+ };
13785
+ }
13786
+ /** True when the ref's host and repository match one of the configured repos. */
13787
+ function isConfiguredPullRequestRepo(ref, repos) {
13788
+ return repos.some((repo) => repo.host === ref.host && repo.repository === ref.repository);
13789
+ }
13790
+ //#endregion
13469
13791
  exports.ACP_BEHAVIOR = ACP_BEHAVIOR;
13470
13792
  exports.AGENT_ROLES = AGENT_ROLES;
13471
13793
  exports.Activity = Activity;
@@ -13572,6 +13894,8 @@ exports.ConversationEvent = ConversationEvent;
13572
13894
  exports.ConversationFilter = ConversationFilter;
13573
13895
  exports.ConversationMovedEvent = ConversationMovedEvent;
13574
13896
  exports.ConversationPermissionChangedEvent = ConversationPermissionChangedEvent;
13897
+ exports.ConversationPullRequests = ConversationPullRequests;
13898
+ exports.ConversationPullRequestsChangedEvent = ConversationPullRequestsChangedEvent;
13575
13899
  exports.ConversationResponse = ConversationResponse;
13576
13900
  exports.ConversationTitleChangedEvent = ConversationTitleChangedEvent;
13577
13901
  exports.CostEventRecordedEvent = CostEventRecordedEvent;
@@ -13591,6 +13915,9 @@ exports.DirectoryEntryKind = DirectoryEntryKind;
13591
13915
  exports.DirectoryEntryLocation = DirectoryEntryLocation;
13592
13916
  exports.DirectoryEntrySource = DirectoryEntrySource;
13593
13917
  exports.DirectoryEntryState = DirectoryEntryState;
13918
+ exports.DirectoryPause = DirectoryPause;
13919
+ exports.DirectoryPauseBy = DirectoryPauseBy;
13920
+ exports.DirectoryScope = DirectoryScope;
13594
13921
  exports.DirectoryTranscriptRef = DirectoryTranscriptRef;
13595
13922
  exports.DiscoveredSessionSnapshot = DiscoveredSessionSnapshot;
13596
13923
  exports.DomainEvent = DomainEvent;
@@ -13720,6 +14047,7 @@ exports.OrderBookLane = OrderBookLane;
13720
14047
  exports.OrderBookPosture = OrderBookPosture;
13721
14048
  exports.OrderBookSettings = OrderBookSettings;
13722
14049
  exports.OrderBookStatus = OrderBookStatus;
14050
+ exports.PIPELINE_MEMBERSHIP_LOADING_CODE = PIPELINE_MEMBERSHIP_LOADING_CODE;
13723
14051
  exports.PanRpcError = PanRpcError;
13724
14052
  exports.PanRpcGroup = PanRpcGroup;
13725
14053
  exports.PendingTurn = PendingTurn;
@@ -13743,8 +14071,18 @@ exports.ProjectCiSuite = ProjectCiSuite;
13743
14071
  exports.ProjectCiSuiteObservedEvent = ProjectCiSuiteObservedEvent;
13744
14072
  exports.ProjectContextLayerRecord = ProjectContextLayerRecord;
13745
14073
  exports.ProjectContextLayerTarget = ProjectContextLayerTarget;
14074
+ exports.ProjectDeployChangedEvent = ProjectDeployChangedEvent;
14075
+ exports.ProjectDeployPhase = ProjectDeployPhase;
14076
+ exports.ProjectDeploySnapshot = ProjectDeploySnapshot;
13746
14077
  exports.ProjectSessionTree = ProjectSessionTree;
13747
14078
  exports.ProposedPlan = ProposedPlan;
14079
+ exports.PullRequestKey = PullRequestKey;
14080
+ exports.PullRequestLink = PullRequestLink;
14081
+ exports.PullRequestLinkListing = PullRequestLinkListing;
14082
+ exports.PullRequestLinkSource = PullRequestLinkSource;
14083
+ exports.PullRequestLinkedConversation = PullRequestLinkedConversation;
14084
+ exports.PullRequestSnapshot = PullRequestSnapshot;
14085
+ exports.PullRequestState = PullRequestState;
13748
14086
  exports.RagDecision = RagDecision;
13749
14087
  exports.RagDecisionSource = RagDecisionSource;
13750
14088
  exports.ReadFileAtPathInput = ReadFileAtPathInput;
@@ -13857,6 +14195,7 @@ exports.WriteFileAtPathRpc = WriteFileAtPathRpc;
13857
14195
  exports.applyEvent = applyEvent;
13858
14196
  exports.applyEvents = applyEvents;
13859
14197
  exports.applyIssueDelta = applyIssueDelta;
14198
+ exports.canonicalPullRequestUrl = canonicalPullRequestUrl;
13860
14199
  exports.companionTerminalKindFor = companionTerminalKindFor;
13861
14200
  exports.compareIssueIds = compareIssueIds;
13862
14201
  exports.createIssueDelta = createIssueDelta;
@@ -13872,13 +14211,18 @@ exports.getRuntimeBehavior = getRuntimeBehavior;
13872
14211
  exports.isAgentRole = isAgentRole;
13873
14212
  exports.isAgentSessionName = isAgentSessionName;
13874
14213
  exports.isChannelReplyKind = isChannelReplyKind;
14214
+ exports.isConfiguredPullRequestRepo = isConfiguredPullRequestRepo;
13875
14215
  exports.isIssueAttention = isIssueAttention;
13876
14216
  exports.isIssueState = isIssueState;
13877
14217
  exports.isTerminalBackendName = isTerminalBackendName;
13878
14218
  exports.isTerminalTurnDiffSummaryStatus = isTerminalTurnDiffSummaryStatus;
13879
14219
  exports.normalizeChannelReplyPayload = normalizeChannelReplyPayload;
13880
14220
  exports.omitTurnDiffSummariesForAgent = omitTurnDiffSummariesForAgent;
14221
+ exports.parsePullRequestRef = parsePullRequestRef;
13881
14222
  exports.projectLegacySystemHealthSummary = projectLegacySystemHealthSummary;
14223
+ exports.repoFromRemoteUrl = repoFromRemoteUrl;
14224
+ exports.resolveEffectivePullRequest = resolveEffectivePullRequest;
13882
14225
  exports.syncSnapshot = syncSnapshot;
13883
14226
  exports.trimMemoryObservations = trimMemoryObservations;
13884
14227
  exports.trimTurnDiffSummaries = trimTurnDiffSummaries;
14228
+ exports.withPaneLivenessAlias = withPaneLivenessAlias;