@openagt/dashboard 0.25.0 → 0.26.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 (133) hide show
  1. package/dist/bridge-browser.d.ts +6 -4
  2. package/dist/bridge-browser.d.ts.map +1 -1
  3. package/dist/bridge-browser.js +68 -21
  4. package/dist/bridge-browser.js.map +1 -1
  5. package/dist/browser-stream.d.ts.map +1 -1
  6. package/dist/browser-stream.js +11 -0
  7. package/dist/browser-stream.js.map +1 -1
  8. package/dist/daemon-runtime.d.ts +8 -1
  9. package/dist/daemon-runtime.d.ts.map +1 -1
  10. package/dist/daemon-runtime.js +19 -2
  11. package/dist/daemon-runtime.js.map +1 -1
  12. package/dist/daemon.d.ts +5 -6
  13. package/dist/daemon.d.ts.map +1 -1
  14. package/dist/daemon.js +68 -33
  15. package/dist/daemon.js.map +1 -1
  16. package/dist/dashboard/agent-handoff.d.ts +2 -1
  17. package/dist/dashboard/agent-handoff.d.ts.map +1 -1
  18. package/dist/dashboard/agent-handoff.js +30 -4
  19. package/dist/dashboard/agent-handoff.js.map +1 -1
  20. package/dist/dashboard/cache.js +4 -1
  21. package/dist/dashboard/cache.js.map +1 -1
  22. package/dist/dashboard/index.d.ts +1 -1
  23. package/dist/dashboard/index.d.ts.map +1 -1
  24. package/dist/dashboard/interventions.d.ts +8 -11
  25. package/dist/dashboard/interventions.d.ts.map +1 -1
  26. package/dist/dashboard/interventions.js +106 -133
  27. package/dist/dashboard/interventions.js.map +1 -1
  28. package/dist/dashboard/keys.d.ts +2 -2
  29. package/dist/dashboard/keys.js +4 -4
  30. package/dist/dashboard/keys.js.map +1 -1
  31. package/dist/dashboard/overview.d.ts.map +1 -1
  32. package/dist/dashboard/overview.js +5 -6
  33. package/dist/dashboard/overview.js.map +1 -1
  34. package/dist/dashboard/pull-requests.d.ts +27 -1
  35. package/dist/dashboard/pull-requests.d.ts.map +1 -1
  36. package/dist/dashboard/pull-requests.js +55 -1
  37. package/dist/dashboard/pull-requests.js.map +1 -1
  38. package/dist/dashboard/rpc-serve.d.ts +4 -1
  39. package/dist/dashboard/rpc-serve.d.ts.map +1 -1
  40. package/dist/dashboard/rpc-serve.js.map +1 -1
  41. package/dist/dashboard/server.d.ts +3 -1
  42. package/dist/dashboard/server.d.ts.map +1 -1
  43. package/dist/dashboard/server.js +1 -0
  44. package/dist/dashboard/server.js.map +1 -1
  45. package/dist/dashboard/types.d.ts +18 -0
  46. package/dist/dashboard/types.d.ts.map +1 -1
  47. package/dist/dashboard/unpublished.d.ts +43 -0
  48. package/dist/dashboard/unpublished.d.ts.map +1 -0
  49. package/dist/dashboard/unpublished.js +74 -0
  50. package/dist/dashboard/unpublished.js.map +1 -0
  51. package/dist/dashboard-bundle/assets/main-CTqah6if.js +145 -0
  52. package/dist/dashboard-bundle/assets/main-iqtcpdTX.css +2 -0
  53. package/dist/dashboard-bundle/assets/module-Dy8zK3Qd.js +14 -0
  54. package/dist/dashboard-bundle/host/module.js +1 -1
  55. package/dist/dashboard-bundle/index.html +3 -3
  56. package/dist/dashboard-rpc/context.d.ts +2 -0
  57. package/dist/dashboard-rpc/context.d.ts.map +1 -1
  58. package/dist/dashboard-rpc/context.js +4 -0
  59. package/dist/dashboard-rpc/context.js.map +1 -1
  60. package/dist/dashboard-rpc/index.d.ts +1 -1
  61. package/dist/dashboard-rpc/index.d.ts.map +1 -1
  62. package/dist/dashboard-rpc/index.js +1 -1
  63. package/dist/dashboard-rpc/index.js.map +1 -1
  64. package/dist/dashboard-rpc/projects.d.ts +11 -1
  65. package/dist/dashboard-rpc/projects.d.ts.map +1 -1
  66. package/dist/dashboard-rpc/projects.js +15 -1
  67. package/dist/dashboard-rpc/projects.js.map +1 -1
  68. package/dist/dashboard-rpc/reads.d.ts +18 -1
  69. package/dist/dashboard-rpc/reads.d.ts.map +1 -1
  70. package/dist/dashboard-rpc/reads.js +40 -2
  71. package/dist/dashboard-rpc/reads.js.map +1 -1
  72. package/dist/dashboard-rpc/test-context.d.ts.map +1 -1
  73. package/dist/dashboard-rpc/test-context.js +3 -2
  74. package/dist/dashboard-rpc/test-context.js.map +1 -1
  75. package/dist/join-dashboard.d.ts +21 -0
  76. package/dist/join-dashboard.d.ts.map +1 -0
  77. package/dist/join-dashboard.js +22 -0
  78. package/dist/join-dashboard.js.map +1 -0
  79. package/dist/module-types/dashboard/components/ui/checkbox.d.ts +1 -1
  80. package/dist/module-types/dashboard/components/ui/context-menu.d.ts +2 -2
  81. package/dist/module-types/dashboard/components/ui/dropdown-menu.d.ts +2 -2
  82. package/dist/module-types/dashboard/components/ui/popover.d.ts +1 -1
  83. package/dist/module-types/dashboard/components/ui/radio-group.d.ts +2 -2
  84. package/dist/module-types/dashboard/components/ui/scroll-area.d.ts +1 -1
  85. package/dist/module-types/dashboard/components/ui/select.d.ts +1 -1
  86. package/dist/module-types/dashboard/components/ui/separator.d.ts +1 -1
  87. package/dist/module-types/dashboard/components/ui/switch.d.ts +1 -1
  88. package/dist/module-types/dashboard/components/ui/tooltip.d.ts +3 -3
  89. package/dist/module-types/dashboard/lib/data-link.d.ts +1 -1
  90. package/dist/module-types/dashboard/lib/route.d.ts +1 -24
  91. package/dist/module-types/src/bridge-browser.d.ts +6 -4
  92. package/dist/module-types/src/dashboard/agent-handoff.d.ts +2 -1
  93. package/dist/module-types/src/dashboard/interventions.d.ts +8 -11
  94. package/dist/module-types/src/dashboard/keys.d.ts +2 -2
  95. package/dist/module-types/src/dashboard/pull-requests.d.ts +27 -1
  96. package/dist/module-types/src/dashboard/rpc-serve.d.ts +4 -1
  97. package/dist/module-types/src/dashboard/types.d.ts +18 -0
  98. package/dist/module-types/src/dashboard/unpublished.d.ts +42 -0
  99. package/dist/module-types/src/dashboard-rpc/context.d.ts +2 -0
  100. package/dist/module-types/src/dashboard-rpc/index.d.ts +1 -1
  101. package/dist/module-types/src/dashboard-rpc/projects.d.ts +11 -1
  102. package/dist/module-types/src/dashboard-rpc/reads.d.ts +18 -1
  103. package/dist/module-types/src/project-address.d.ts +32 -0
  104. package/dist/module-types/src/store/agent-store.d.ts +13 -0
  105. package/dist/module-types/src/store/branches.d.ts +28 -2
  106. package/dist/module-types/src/store/index.d.ts +1 -1
  107. package/dist/module.css +1 -0
  108. package/dist/project-address.d.ts +33 -0
  109. package/dist/project-address.d.ts.map +1 -0
  110. package/dist/project-address.js +57 -0
  111. package/dist/project-address.js.map +1 -0
  112. package/dist/serve.d.ts +2 -0
  113. package/dist/serve.d.ts.map +1 -1
  114. package/dist/serve.js +1 -0
  115. package/dist/serve.js.map +1 -1
  116. package/dist/store/agent-store.d.ts +13 -0
  117. package/dist/store/agent-store.d.ts.map +1 -1
  118. package/dist/store/agent-store.js.map +1 -1
  119. package/dist/store/branches.d.ts +28 -2
  120. package/dist/store/branches.d.ts.map +1 -1
  121. package/dist/store/branches.js +38 -22
  122. package/dist/store/branches.js.map +1 -1
  123. package/dist/store/index.d.ts +1 -1
  124. package/dist/store/index.d.ts.map +1 -1
  125. package/dist/store/index.js +1 -1
  126. package/dist/store/index.js.map +1 -1
  127. package/dist/store/test-branches.d.ts.map +1 -1
  128. package/dist/store/test-branches.js +6 -0
  129. package/dist/store/test-branches.js.map +1 -1
  130. package/package.json +13 -13
  131. package/dist/dashboard-bundle/assets/main-CTyvdSpb.js +0 -145
  132. package/dist/dashboard-bundle/assets/main-CvaxZfR1.css +0 -2
  133. package/dist/dashboard-bundle/assets/module-Ba-5IEOx.js +0 -25
@@ -1,5 +1,5 @@
1
1
  import type { ComponentProps } from 'react';
2
- import { Switch as SwitchPrimitive } from '@base-ui-components/react/switch';
2
+ import { Switch as SwitchPrimitive } from '@base-ui/react/switch';
3
3
  type SwitchProps = Omit<ComponentProps<typeof SwitchPrimitive.Root>, 'disabled'> & {
4
4
  disabled?: boolean | undefined;
5
5
  };
@@ -1,7 +1,7 @@
1
1
  import type { ComponentProps } from 'react';
2
- import { Tooltip as TooltipPrimitive } from '@base-ui-components/react/tooltip';
3
- declare const TooltipProvider: import("react").FC<import("@base-ui-components/react/tooltip").TooltipProviderProps>;
4
- declare const Tooltip: typeof TooltipPrimitive.Root;
2
+ import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';
3
+ declare const TooltipProvider: import("react").FC<import("@base-ui/react").TooltipProviderProps>;
4
+ declare const Tooltip: <Payload>(props: TooltipPrimitive.Root.Props<Payload>) => React.JSX.Element;
5
5
  declare function TooltipTrigger(props: ComponentProps<typeof TooltipPrimitive.Trigger>): import("react").JSX.Element;
6
6
  declare function TooltipContent({ className, sideOffset, side, align, children, ...props }: ComponentProps<typeof TooltipPrimitive.Popup> & {
7
7
  sideOffset?: number;
@@ -1,4 +1,4 @@
1
- import { type Route } from './route.js';
1
+ import type { Route } from './route.js';
2
2
  /** A repository path's page segment and the rest: `tickets/a.md` → `{ segment: 'tickets', rest: ['a.md'] }`; nothing for a path with no page-shaped first segment or no rest. */
3
3
  export declare function dataLinkParts(href: string): {
4
4
  segment: string;
@@ -1,27 +1,4 @@
1
- /**
2
- * Whether a segment can name a page a module adds (#1774): a lowercase letter, then lowercase
3
- * letters and digits, and not a word the dashboard keeps. Whichever module claims the segment gets
4
- * it, and the shell says "no such page" when none does, or when the picked project has no package
5
- * that adds it.
6
- */
7
- export declare function isPageSegment(segment: string): boolean;
8
- /** Each registered project's address, and the project an address or an id names. */
9
- export interface ProjectAddresses {
10
- /** The project's address: its name, or its id where the name cannot be one. An unknown id is its own address. */
11
- addressOf(projectId: string): string;
12
- /** The project a first segment names, by its address or by its id; undefined for none. */
13
- projectOf(segment: string): string | undefined;
14
- }
15
- /**
16
- * The addresses of the registered projects, in the registry's order, beside the pages' words.
17
- * A project's name is its id without the `-<hash>`: its folder's name, as the id writes it. The
18
- * name is its address unless a page has that word, since pages win, or a project before it in the
19
- * registry already has that name: the first one added keeps it, so adding a second clone changes
20
- * no address in use.
21
- */
22
- export declare function projectAddresses(projectIds: readonly string[], pageWords: readonly string[]): ProjectAddresses;
23
- /** No project registered: every first segment is a page's, or a project's nobody registered. */
24
- export declare const NO_PROJECTS: ProjectAddresses;
1
+ import { type ProjectAddresses } from '../../src/project-address.js';
25
2
  /** What the dashboard is looking at, as carried by the URL. */
26
3
  export interface Route {
27
4
  /**
@@ -168,6 +168,8 @@ export interface BridgeBrowserOptions {
168
168
  extension?: BridgeExtensionSource | undefined;
169
169
  /** Progress, one line at a time, for the dashboard to show while the launch runs. */
170
170
  report?: ((detail: string) => void) | undefined;
171
+ /** Gives the launch up: a step under way is not waited for, and a Chrome already spawned is killed. */
172
+ signal?: AbortSignal | undefined;
171
173
  }
172
174
  /**
173
175
  * Launch the bridge browser and get it ready: binary, profile, extension, developer mode, token,
@@ -204,12 +206,12 @@ export interface BridgeBrowserOwner {
204
206
  stop(): Promise<void>;
205
207
  act(action: BridgeBrowserAction): Promise<void>;
206
208
  }
207
- /** How the owner launches a browser: the daemon's real launch, or a test's fake. */
208
- export type BridgeBrowserLauncher = (report: (detail: string) => void) => Promise<BridgeBrowser>;
209
+ /** How the owner launches a browser: the daemon's real launch, or a test's fake. `signal` aborts when a stop gives the launch up. */
210
+ export type BridgeBrowserLauncher = (report: (detail: string) => void, signal: AbortSignal) => Promise<BridgeBrowser>;
209
211
  /**
210
212
  * The one bridge browser a daemon runs. A launch is slow — the first one downloads Chrome — so
211
- * the status carries the step it is on, and a stop that lands mid-launch closes the browser the
212
- * launch then hands over rather than leaving it running unowned. A browser that exits on its own
213
+ * the status carries the step it is on, and a stop that lands mid-launch gives the launch up, and
214
+ * closes the browser a launch that went on regardless hands over, rather than leaving it unowned. A browser that exits on its own
213
215
  * (the user quit it) is reported as stopped with the reason, and not relaunched: quitting it was
214
216
  * an act, and the dashboard offers a restart.
215
217
  */
@@ -272,7 +272,7 @@ export declare function openRemoteBranchPullRequest(cwd: string, agent: HandoffA
272
272
  * Open a PR for a finished session, deciding from what the agent recorded which cases should not
273
273
  * open one. Reads the branch's handoff first: a branch that no longer exists, or a session that
274
274
  * changed nothing, is a clear error rather than an empty PR, and a branch that already has a PR
275
- * returns that one. Title is its branch, else the id; body is the intent, plus which session did it. This is the handoff decision the
275
+ * returns that one. Title and body say what its commits did ({@link commitsPrText}). This is the handoff decision the
276
276
  * dashboard's open-PR button offers; the RPC layer only resolves which run it is about, and the
277
277
  * two providers do the pushing and the opening.
278
278
  */
@@ -281,6 +281,7 @@ export declare function openAgentPullRequest(cwd: string, agent: AgentMeta, opti
281
281
  branches?: BranchesFor;
282
282
  gitHost?: GitHostFor;
283
283
  pr?: BranchPrLookup;
284
+ git?: GitRunner;
284
285
  }): Promise<HandoffResult>;
285
286
  /**
286
287
  * The little a handoff needs to know about the agent it is for: which branch, and what to say on
@@ -14,22 +14,25 @@ export interface Intervention {
14
14
  projectName: string;
15
15
  /**
16
16
  * `pr` = an open PR to review/merge or close; `awaiting` = a run waiting on the question it ended on (#636/#1774);
17
- * `unpushed` = a finished agent whose branch has commits that were never pushed (#860).
17
+ * `uncommitted` = a finished run whose checkout holds files it never committed; `unpushed` = a finished
18
+ * run whose branch has commits that were never pushed (#860).
18
19
  */
19
- kind: 'pr' | 'awaiting' | 'unpushed';
20
+ kind: 'pr' | 'awaiting' | 'uncommitted' | 'unpushed';
20
21
  title: string;
21
- /** The PR on the git host (`pr` only): where a click takes the user. */
22
+ /** The PR on the git host (`pr` only): where a click takes the user when no run of this dashboard opened it. */
22
23
  url?: string;
23
24
  /** The PR number (`pr` only). */
24
25
  number?: number;
25
26
  /** The question's id (`awaiting` only) — its stable identity, so it notifies exactly once. */
26
27
  awaitId?: string;
27
- /** Which run this is about (`awaiting` #738 / `unpushed`): a project has several agents. */
28
+ /** Which run this is about (every kind but a `pr` opened outside this dashboard's runs): a project has several agents. */
28
29
  agentId?: string;
29
- /** The branch the work is sitting on (`unpushed` only). */
30
+ /** The branch the work is sitting on (`uncommitted` and `unpushed` only). */
30
31
  branch?: string;
31
32
  /** How many commits are waiting (`unpushed` only). */
32
33
  commits?: number;
34
+ /** How many files were left uncommitted (`uncommitted` only). */
35
+ files?: number;
33
36
  /** When the PR was opened (`pr`) or the agent last updated (the other two), ISO, for ordering. */
34
37
  createdAt?: string;
35
38
  }
@@ -44,12 +47,6 @@ export interface InterventionsDeps {
44
47
  agents?: (cwd: string) => Promise<AgentMeta[]>;
45
48
  /** The project's checkouts and branches (default the project's provider, {@link projectBranches}): where a branch's state is read; drives the `unpushed` source (#860). */
46
49
  branches?: BranchesFor;
47
- /**
48
- * How many of a project's most recent finished agents to inspect for unpushed work. Each one costs
49
- * a handful of git reads, and this runs on a poll, so old history is not re-walked every minute:
50
- * work that has sat unpushed for dozens of agents is not news, and the agent list stays the record.
51
- */
52
- handoffLimit?: number;
53
50
  }
54
51
  /**
55
52
  * Build the cross-project interventions queue: every registered project's open PRs, plus any run
@@ -14,8 +14,8 @@ import type { Intervention } from './interventions.js';
14
14
  */
15
15
  /**
16
16
  * The stable identity of an intervention. A PR is its url (survives title edits and re-sorts);
17
- * the other two have no url and are keyed on the project plus the thing waiting — the agent and
18
- * the gate it is parked on, or the agent whose branch is unpushed.
17
+ * the others have no url and are keyed on the project plus the thing waiting — the agent and
18
+ * the gate it is parked on, or the agent whose work is uncommitted or unpushed.
19
19
  *
20
20
  * The agent is part of the awaiting key because a gate id is only unique within its own agent:
21
21
  * every agent's first gate is `await-choices`. Two agents running in one project (#736) and both
@@ -91,8 +91,25 @@ export declare function prView(cwd: string, branch: string, gitHost?: GitHostFor
91
91
  * deciding whether to offer "Open PR". A read with no branch to ask about answers nothing at once.
92
92
  */
93
93
  export declare function cachedPrView(cwd: string, branch?: string): Promise<Cached<LinkedPr | undefined>>;
94
- /** Forget a branch's PR, after an action that changes whether it has one. */
94
+ /** Forget a branch's PR, after an action that changes whether it has one, and with it how the project's PRs stand and which are open. */
95
95
  export declare function forgetPr(cwd: string, branch?: string): void;
96
+ /** How a run's pull request stands, as its row in the list of runs shows it. */
97
+ export type PrState = 'open' | 'draft' | 'merged' | 'closed';
98
+ /**
99
+ * How each of a project's newest pull requests stands, by its address: what the list of runs marks
100
+ * each run's PR with. One listing of the project's requests, whatever their state, rather than one
101
+ * per run; a request older than what the git host lists has no state here. A listing the git host
102
+ * cannot answer throws.
103
+ */
104
+ export declare function prStates(cwd: string, gitHost?: GitHostFor): Promise<Map<string, PrState>>;
105
+ /**
106
+ * The cached form of {@link prStates} (#1028): the list of runs is polled every few seconds, and a
107
+ * PR's state changes far less often. A listing that fails is kept like an answer, as the states
108
+ * last read, so a git host that cannot answer is asked again a minute later, not at every poll;
109
+ * and while a read is on its way, after a request was opened or merged here, the rows keep the
110
+ * states they had.
111
+ */
112
+ export declare function cachedPrStates(cwd: string, read?: (cwd: string) => Promise<Map<string, PrState>>): Promise<Map<string, PrState>>;
96
113
  /** The cached form of {@link prsForBranch}, shared through the same read-through cache (#1028). */
97
114
  export declare function cachedPrsForBranch(cwd: string, branch: string): Promise<Cached<LinkedPr[]>>;
98
115
  /** Forget a branch's PR history, after an action that changes it (opening one). */
@@ -124,3 +141,12 @@ export declare function pickAgentPr(prs: LinkedPr[], since?: string, order?: 'fi
124
141
  * it cannot decide what it never hears about. A project with no git host package has none open.
125
142
  */
126
143
  export declare function openPrs(cwd: string, gitHost?: GitHostFor): Promise<OpenPr[]>;
144
+ /**
145
+ * The cached form of {@link openPrs} (#1028), for the Human Queue: it asked the git host at every
146
+ * poll of every page that shows it, and the page waited. The first ask waits for the answer; after
147
+ * that the list last read answers at once and is read again behind it once it is
148
+ * {@link OPEN_PRS_TTL_MS} old. A request opened or merged here is read again at once
149
+ * ({@link forgetPr}). A first read that fails rejects, as {@link openPrs} does; a later one keeps
150
+ * the list last read.
151
+ */
152
+ export declare function cachedOpenPrs(cwd: string, read?: (cwd: string) => Promise<OpenPr[]>): Promise<OpenPr[]>;
@@ -5,13 +5,15 @@ import type { QuotaSource } from './quota.js';
5
5
  import type { ModelsSource } from './models.js';
6
6
  import type { BridgeBrowserOwner } from '../bridge-browser.js';
7
7
  import type { ProjectErrorsReader } from '../project-errors.js';
8
- import type { AddProjectResult, RemoveProjectResult, StartAgentOptions, StartAgentResult } from './types.js';
8
+ import type { AddProjectResult, RemoveProjectResult, StartAgentOptions, StartAgentResult, StartedInResult } from './types.js';
9
9
  import type { AgentMeta } from '../store/index.js';
10
10
  import type { RemoteTarget } from './remote-run.js';
11
11
  /** Wired by the daemon so `sendStart` can reach the daemon's own `startAgent` closure. */
12
12
  export type StartAgentHandler = (prompt: string, options: StartAgentOptions, projectId?: string) => StartAgentResult | Promise<StartAgentResult>;
13
13
  /** Wired by the daemon so `sendAddProject` can install + register a repo (#433). */
14
14
  export type AddProjectHandler = (path: string, share: boolean) => AddProjectResult | Promise<AddProjectResult>;
15
+ /** Wired by the daemon so `sendStartedAgain` can find or add the project a second `openagent` was started in. */
16
+ export type StartedInHandler = (folder: string) => StartedInResult | Promise<StartedInResult>;
15
17
  /** Wired by the daemon so `sendRemoveProject` can take a project off the list, by its id, and with `files` remove what OpenAgent left in its folder. */
16
18
  export type RemoveProjectHandler = (projectId: string, files: boolean) => RemoveProjectResult | Promise<RemoveProjectResult>;
17
19
  /** Resolve an agent to its live event stream: the relay feeds `onEvents` from its own in-memory stream
@@ -63,6 +65,7 @@ export interface RemoteAgents {
63
65
  export interface DashboardContext {
64
66
  startAgent: StartAgentHandler;
65
67
  addProject: AddProjectHandler;
68
+ startedIn: StartedInHandler;
66
69
  removeProject: RemoveProjectHandler;
67
70
  /** The in-memory event stream for an agent relayed from a saved machine (#1067), else undefined. */
68
71
  eventsSource: EventsSource;
@@ -28,6 +28,24 @@ export type AddProjectResult = {
28
28
  ok: false;
29
29
  error: string;
30
30
  };
31
+ /**
32
+ * The project a folder `openagent` was started in is, on the dashboard's list: the listed project
33
+ * holding the folder, or the folder, added now. `id` always opens its page; `address` is the one
34
+ * the page writes (`src/project-address.ts`).
35
+ */
36
+ export interface StartedProject {
37
+ id: string;
38
+ path: string;
39
+ added: boolean;
40
+ address: string;
41
+ }
42
+ /** The {@link StartedProject}, or why the folder could not be added. */
43
+ export type StartedInResult = ({
44
+ ok: true;
45
+ } & StartedProject) | {
46
+ ok: false;
47
+ error: string;
48
+ };
31
49
  /**
32
50
  * What removing OpenAgent's files from a project's folder did: what went, what stayed with the
33
51
  * reason, and what could not be done, each line in words for a person. Paths are from the
@@ -0,0 +1,42 @@
1
+ import type { AgentMeta } from '../store/index.js';
2
+ import { type BranchesFor } from '../store/branches.js';
3
+ /**
4
+ * A finished run's work that never left this machine: the branch it is on, the commits it holds
5
+ * that were never published, and the files its checkout left uncommitted. Either count may be 0,
6
+ * never both.
7
+ */
8
+ export interface Unpublished {
9
+ branch: string;
10
+ commits: number;
11
+ uncommitted: number;
12
+ }
13
+ /**
14
+ * The finished runs of a project whose work never left this machine (#860), by run id: what the
15
+ * Human Queue lists as "Not committed" and "Not published" and the list of runs marks on each row,
16
+ * read alike so the two never disagree.
17
+ *
18
+ * Only a branch the branches provider made for the run counts. A branch of the person's own is
19
+ * theirs: a run continued on it, or a branch made before the history was rewritten, which is
20
+ * thousands of commits past the base.
21
+ *
22
+ * Files its checkout left uncommitted wait on the person whatever else is true of the branch: the
23
+ * run's next step is to commit them, a pull request or none. Not for a run waiting on its question,
24
+ * which is that run's next step, and which keeps its checkout as it was. The checkout on a branch is
25
+ * the newest run's: a run continued on the branch of an earlier one works in a checkout of its own,
26
+ * and what it leaves there is not the earlier run's.
27
+ *
28
+ * Commits wait when they are the branch's own beyond the base, not in the default branch, never
29
+ * pushed, in a project with a remote to push to. A branch pushed once whose copy on the remote is
30
+ * gone since, as a merged pull request's is, left the machine. So did a run with a pull request: its
31
+ * request is what the queue and its row show, and a branch pushed without an upstream reads, once
32
+ * its request merged and the remote's copy went, as never pushed.
33
+ *
34
+ * Every run is asked, in one quick read of the provider (`ahead`: counts, no file lists). A run
35
+ * started from another branch is asked apart, from the commit it started at, or that branch's work
36
+ * would count as its own; a run started from the default branch is counted from it, which already
37
+ * leaves out whatever it took in later, so the commit it started at changes nothing and all such
38
+ * runs share one read. A subagent's branch is its main agent's to land, never a person's to push,
39
+ * and a run still working is still writing its branch. A provider that cannot be found, or does not
40
+ * answer for every branch asked, throws: "nothing waits" would be a false answer.
41
+ */
42
+ export declare function unpublishedRuns(cwd: string, agents: readonly AgentMeta[], branches?: BranchesFor): Promise<Map<string, Unpublished>>;
@@ -51,5 +51,7 @@ export declare function contextBridgeBrowser(): BridgeBrowserOwner;
51
51
  export declare function contextStartAgent(): DashboardContext['startAgent'];
52
52
  /** Install + register a repo (#433), which only the daemon can do. */
53
53
  export declare function contextAddProject(): DashboardContext['addProject'];
54
+ /** Find or add the project a second `openagent` was started in (#2141), which only the daemon can do. */
55
+ export declare function contextStartedIn(): DashboardContext['startedIn'];
54
56
  /** Take a project off the list, which only the daemon can do: it runs the project's close hooks first. */
55
57
  export declare function contextRemoveProject(): DashboardContext['removeProject'];
@@ -1,7 +1,7 @@
1
1
  export { onAgents, onAgent, onAgentsDoing, onDocs, onQueue, onOverview, onRecentAgents, onInterventions, onOpenQuestions, onActivity, onDashboard, onGitHostHome, onRepositoryOffer, onGitStatus, onProjectFiles, onRetainedWorktrees, onAgentWorktree, onAgentHandoff, onBridgeQuestion, onBridgeStatus, onBridgeToken, onBridgeEvents, onBridgeAnswer, onBridgeBrowser } from './reads.js';
2
2
  export { sendStop, sendChoice, sendBridgeAnswer, sendBridgeAnswerCancel, sendBridgeBrowser, sendMessage, sendStart, sendOpenInApp, sendRemoveWorktree, sendDeleteAgent, sendOpenPullRequest, sendPush, sendMergeBranch, sendMerge, sendCreateRepository } from './control.js';
3
3
  export { streamAgentEvents, type LiveFeedEvent, type PartialMessage, type StreamSync } from './events.js';
4
- export { onProjects, sendAddProject, onCloneTarget, sendAddProjectFromAddress, sendRemoveProject, onRecordsReach, sendShareRecords, sendPickProjectDirectory, onOnboarding, onCommands, onStartCheck, type ProjectLauncher } from './projects.js';
4
+ export { onProjects, sendAddProject, sendStartedAgain, onCloneTarget, sendAddProjectFromAddress, sendRemoveProject, onRecordsReach, sendShareRecords, sendPickProjectDirectory, onOnboarding, onCommands, onStartCheck, type ProjectLauncher } from './projects.js';
5
5
  export { onPreferences, savePreferences, patchPreferences, onProjectPresets, saveProjectPresets, onEditors, type SavePreferencesResult, type PatchPreferencesResult, } from './preferences.js';
6
6
  export { type EditorInfo } from '../dashboard/open-in-app.js';
7
7
  export { onQuota } from './quota.js';
@@ -3,7 +3,7 @@ import { type BranchReach } from '@openagt/agent-data';
3
3
  import { type StartReadiness } from '../project-hooks.js';
4
4
  import { type PickDirectoryResult } from '../pick-directory.js';
5
5
  import type { ProjectSummary } from '../dashboard/projects.js';
6
- import type { AddProjectResult, OnboardingSuggestion, RemoveProjectResult } from '../dashboard/types.js';
6
+ import type { AddProjectResult, OnboardingSuggestion, RemoveProjectResult, StartedInResult } from '../dashboard/types.js';
7
7
  export declare function onProjects(): Promise<ProjectSummary[]>;
8
8
  /**
9
9
  * Add a project from the dashboard (#396/#433): install the repo and register it so it joins the
@@ -15,6 +15,16 @@ export declare function onProjects(): Promise<ProjectSummary[]>;
15
15
  * repository's remote. Nothing is pushed for a project added with `false`.
16
16
  */
17
17
  export declare function sendAddProject(path: string, share: boolean): Promise<AddProjectResult>;
18
+ /**
19
+ * A second `openagent` started while this dashboard listens on its port (#2141): it serves
20
+ * nothing itself, and hands over the folder it was started in, if it is in a repository. The
21
+ * answer says which OpenAgent runs here, and the project that holds the folder, else the folder,
22
+ * added. A page's own call is answered the same, and adds no more than Add project.
23
+ */
24
+ export declare function sendStartedAgain(folder: string | null): Promise<{
25
+ version: string;
26
+ project?: StartedInResult;
27
+ }>;
18
28
  /** Where the repository a person typed the address of would be cloned to on this computer, or why what was typed is no address: asked before anything is cloned. */
19
29
  export declare function onCloneTarget(address: string): Promise<{
20
30
  ok: true;
@@ -1,5 +1,5 @@
1
1
  import type { BridgeBrowserStatus } from '../bridge-browser.js';
2
- import { type AgentDoing, type AgentMeta } from '../store/index.js';
2
+ import { type AgentDoing, type AgentMeta, type BranchesFor } from '../store/index.js';
3
3
  import { type GitHostHome } from '../store/git-host.js';
4
4
  import { type RepositoryOffer } from '../store/repository.js';
5
5
  import { type WorkspaceDoc } from '../dashboard/docs.js';
@@ -12,6 +12,7 @@ import { type Activity } from '../dashboard/activity.js';
12
12
  import { type DashboardData } from '../dashboard/dashboard.js';
13
13
  import { type GitStatus } from '../dashboard/git-status.js';
14
14
  import { type AgentHandoff } from '../dashboard/agent-handoff.js';
15
+ import { type PrState } from '../dashboard/pull-requests.js';
15
16
  import type { AgentWorktree } from '../dashboard/types.js';
16
17
  import type { OpenAgentEvent } from '../events.js';
17
18
  import type { BridgeEvent, BridgeHello, BridgeQuestion } from '../dashboard/bridge-endpoints.js';
@@ -37,6 +38,22 @@ import type { SavedMachine } from '../dashboard/rpc-serve.js';
37
38
  * instead of losing it.
38
39
  */
39
40
  export declare function onAgents(projectId: string): Promise<AgentMeta[]>;
41
+ /**
42
+ * What a run's row marks beside its name, for both lists of runs, a project's and all projects'
43
+ * together: how its pull request stands, and the work it left on this machine.
44
+ */
45
+ export declare function withRowMarks(cwd: string, agents: AgentMeta[]): Promise<AgentMeta[]>;
46
+ /**
47
+ * Each run that has a pull request, marked with how the request stands, so its row can show it:
48
+ * from one cached listing of the project's requests, read only when one of the runs has a request.
49
+ */
50
+ export declare function withPrStates(cwd: string, agents: AgentMeta[], states?: (cwd: string) => Promise<Map<string, PrState>>): Promise<AgentMeta[]>;
51
+ /**
52
+ * Each finished run whose work never left this machine, marked with how many files it left
53
+ * uncommitted and how many commits wait, so its row can say so as the Human Queue does: the same
54
+ * rule, {@link unpublishedRuns}. A provider that cannot be read marks nothing.
55
+ */
56
+ export declare function withUnpublished(cwd: string, agents: AgentMeta[], branches?: BranchesFor): Promise<AgentMeta[]>;
40
57
  /**
41
58
  * A project's runs with the ones this daemon follows on a machine (#1067), which live in its
42
59
  * memory. A run this computer sent that has no record here is listed first, as followed, so a
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The words the dashboard keeps for itself, after a project's address and in its place: `overview`,
3
+ * a project's Overview, and `settings` (#958), where Settings was a page. Settings is a dialog over
4
+ * the page you are on now, carried as `?settings=`; the old address still opens it, over the
5
+ * Overview. No module's page takes either.
6
+ */
7
+ export declare const OVERVIEW_SEGMENT = "overview";
8
+ export declare const SETTINGS_SEGMENT = "settings";
9
+ /**
10
+ * Whether a segment can name a page a module adds (#1774): a lowercase letter, then lowercase
11
+ * letters and digits, and not a word the dashboard keeps. Whichever module claims the segment gets
12
+ * it, and the shell says "no such page" when none does, or when the picked project has no package
13
+ * that adds it.
14
+ */
15
+ export declare function isPageSegment(segment: string): boolean;
16
+ /** Each registered project's address, and the project an address or an id names. */
17
+ export interface ProjectAddresses {
18
+ /** The project's address: its name, or its id where the name cannot be one. An unknown id is its own address. */
19
+ addressOf(projectId: string): string;
20
+ /** The project a first segment names, by its address or by its id; undefined for none. */
21
+ projectOf(segment: string): string | undefined;
22
+ }
23
+ /**
24
+ * The addresses of the registered projects, in the registry's order, beside the pages' words.
25
+ * A project's name is its id without the `-<hash>`: its folder's name, as the id writes it. The
26
+ * name is its address unless a page has that word, since pages win, or a project before it in the
27
+ * registry already has that name: the first one added keeps it, so adding a second clone changes
28
+ * no address in use.
29
+ */
30
+ export declare function projectAddresses(projectIds: readonly string[], pageWords: readonly string[]): ProjectAddresses;
31
+ /** No project registered: every first segment is a page's, or a project's nobody registered. */
32
+ export declare const NO_PROJECTS: ProjectAddresses;
@@ -151,6 +151,19 @@ export interface AgentMeta {
151
151
  * machine can ask whether the process is alive.
152
152
  */
153
153
  saving?: boolean;
154
+ /**
155
+ * How the run's pull request stands ({@link pr}), as the git host last said. Not stored: annotated
156
+ * on the way to the dashboard like {@link saving}, since it changes on the git host, not here.
157
+ */
158
+ prState?: 'open' | 'draft' | 'merged' | 'closed';
159
+ /**
160
+ * How many commits of the run's own wait on its branch, never pushed: its work has not left this
161
+ * machine. Not stored: annotated on the way to the dashboard like {@link prState}, since a push
162
+ * changes it, not the run.
163
+ */
164
+ unpublished?: number;
165
+ /** How many files the run's checkout left uncommitted, annotated like {@link unpublished}. */
166
+ uncommitted?: number;
154
167
  /**
155
168
  * The loopback port the agent's browser preview is listening on (#813), or absent when the agent
156
169
  * has no browser. What lets the daemon proxy the pane: the port is allocated per agent and the
@@ -13,10 +13,12 @@
13
13
  * `<command> show [--from <commit>] <branch>...` what each branch holds and where it stands, as an array of {@link BranchState}, in the order asked;
14
14
  * measured from `<commit>`, where the branches started, in place of the default branch;
15
15
  * a full commit id in place of a branch is read as a branch that ends there
16
+ * `<command> ahead [--from <commit>] <branch>...` how far each branch is past the base, whether the remote has it, and what its checkout left uncommitted,
17
+ * as an array of {@link BranchAhead}: no file lists, so every run's branch can be asked on a poll
16
18
  * `<command> merge <b>` merge the branch into the default branch, on this machine, then delete it
17
19
  * `<command> push --branch <b>` push the branch to the remote; a branch only the remote has is answered as it is
18
20
  * `<command> remove <id> [--from <commit>] [--discard]` reclaim a run's checkout once its branch holds everything, pushing nothing, its branch measured from the commit it started from; `--discard` drops uncommitted work
19
- * `list` and `show` read this machine, no network: OpenAgent polls. `show` answers the branch's
21
+ * `list`, `show` and `ahead` read this machine, no network: OpenAgent polls. `show` answers the branch's
20
22
  * git facts only; its pull request is the git host provider's (`git-host.ts`), as every pull request is.
21
23
  *
22
24
  * That is the whole contract: git, and nothing beyond it (#1820). OpenAgent reads checkouts to
@@ -24,7 +26,7 @@
24
26
  * the Human Queue's unpushed rows. Opening a pull request is the push here, then the git host
25
27
  * provider's `open`. What a checkout is, where it lives, how a branch is pushed is the package's.
26
28
  *
27
- * The shapes, owned here: {@link Checkout}, {@link BranchState}.
29
+ * The shapes, owned here: {@link Checkout}, {@link BranchState}, {@link BranchAhead}.
28
30
  */
29
31
  /** A run's checkout: where it works, as a provider lists it. */
30
32
  export interface Checkout {
@@ -74,6 +76,26 @@ export interface BranchState {
74
76
  /** The uncommitted paths in the checkout that is on this branch; absent when no checkout is. */
75
77
  pendingFiles?: string[];
76
78
  }
79
+ /** How far a branch is past its base and whether the remote has it, as a provider answers `ahead`: counts, no files. */
80
+ export interface BranchAhead {
81
+ branch: string;
82
+ /** The branch exists on this machine. */
83
+ exists: boolean;
84
+ /** A branch the provider made for a run, not one of the person's own. */
85
+ agent: boolean;
86
+ /** How many commits of its own it has beyond the base. */
87
+ ahead: number;
88
+ /** The project has a remote to push to. */
89
+ hasRemote: boolean;
90
+ /** The remote has the branch at this same tip. */
91
+ pushed: boolean;
92
+ /** It was pushed, and the remote's copy is gone since, as a merged pull request's is. */
93
+ gone: boolean;
94
+ /** The project's default branch already contains it. */
95
+ merged: boolean;
96
+ /** How many paths the run's checkout on it left uncommitted; 0 with no checkout. */
97
+ uncommitted: number;
98
+ }
77
99
  /** What pushing a branch did: `pushed` is false when only the remote had it, so there was nothing here to push. */
78
100
  export type PushOutcome = {
79
101
  ok: true;
@@ -124,6 +146,8 @@ export interface BranchesSource {
124
146
  * branch that ended there held.
125
147
  */
126
148
  show(branches: readonly string[], from?: string): Promise<BranchState[]>;
149
+ /** How far each branch is past its base and whether the remote has it, in the order asked; `[]` when nothing can be read. `from` as for {@link show}. */
150
+ ahead(branches: readonly string[], from?: string): Promise<BranchAhead[]>;
127
151
  /** Push a branch to the remote: the checkout on it under its clean rule, else the branch itself. */
128
152
  push(branch: string): Promise<PushOutcome>;
129
153
  /** Merge a branch into the project's default branch, on this machine, then delete it; a merge that conflicts changes nothing. */
@@ -148,6 +172,8 @@ export type BranchesReader = BranchesFor & {
148
172
  export declare function parseCheckouts(output: unknown): Checkout[];
149
173
  /** The states read back from a provider's `show`: the objects with a branch and the three verdicts, lists cleaned; anything else is no state. */
150
174
  export declare function parseBranchStates(output: unknown): BranchState[];
175
+ /** The counts read back from a provider's `ahead`: the objects with a branch and every verdict; anything else is no answer. */
176
+ export declare function parseBranchesAhead(output: unknown): BranchAhead[];
151
177
  /**
152
178
  * The production {@link BranchesReader}: the project's provider, looked up among the packages the project has (`built-in.ts`),
153
179
  * one source per project kept while the same command provides — so the cache holds across the
@@ -1,7 +1,7 @@
1
1
  export { nodeStoreFs, listAgents, readAllAgents, findAgent, isPidAlive, readLiveMeta, loadAgentEvents, readLiveMetas, readDoing, type AgentDoing, readFinishedDiary, type StoreFs, type AgentMeta, type LiveAgent, type AgentStatus, } from './agent-store.js';
2
2
  export { findCheckout, resolveAgentCheckout, resolveAgentDiary, type AgentDiarySource } from './agent-checkout.js';
3
3
  export { projectRuns, providedRuns, noRuns, parseRunCard, isRunId, type RunsFor, type RunsReader, type RunsSource, type RunsWrite, type RunCard, type RunPatch, type RunStatus, type AnyDiaryLine, type FinishedRun } from './runs.js';
4
- export { projectBranches, providedBranches, noBranches, parseCheckouts, parseBranchStates, type BranchesFor, type BranchesReader, type BranchesSource, type RemoveOutcome as BranchRemoveOutcome, type Checkout, type BranchState, type BranchCommit, type BranchFile, type PushOutcome as BranchPushOutcome } from './branches.js';
4
+ export { projectBranches, providedBranches, noBranches, parseCheckouts, parseBranchStates, parseBranchesAhead, type BranchesFor, type BranchesReader, type BranchesSource, type RemoveOutcome as BranchRemoveOutcome, type Checkout, type BranchState, type BranchAhead, type BranchCommit, type BranchFile, type PushOutcome as BranchPushOutcome } from './branches.js';
5
5
  export { projectGitHost, providedGitHost, noGitHost, parseRequests, type GitHostFor, type GitHostReader, type GitHostSource, type GitHostRequest, type GitHostHome, type RequestsOutcome as GitHostRequestsOutcome, type OpenOutcome as GitHostOpenOutcome, type MergeOutcome as GitHostMergeOutcome } from './git-host.js';
6
6
  export { projectQueue, providedQueue, noQueue, parseQueueEntries, type QueueFor, type QueueReader, type QueueSource } from './queue.js';
7
7
  export { providedDataChanged } from './provided.js';
package/dist/module.css CHANGED
@@ -34,6 +34,7 @@
34
34
  --color-warning: var(--warning);
35
35
  --color-danger: var(--danger);
36
36
  --color-info: var(--info);
37
+ --color-merged: var(--merged);
37
38
 
38
39
  /* The shadcn sidebar reads its own `--sidebar-*` family so a sidebar can be themed apart from
39
40
  the page. Its background is its own (`--sidebar`: in the dark theme a step below the page, as
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The words the dashboard keeps for itself, after a project's address and in its place: `overview`,
3
+ * a project's Overview, and `settings` (#958), where Settings was a page. Settings is a dialog over
4
+ * the page you are on now, carried as `?settings=`; the old address still opens it, over the
5
+ * Overview. No module's page takes either.
6
+ */
7
+ export declare const OVERVIEW_SEGMENT = "overview";
8
+ export declare const SETTINGS_SEGMENT = "settings";
9
+ /**
10
+ * Whether a segment can name a page a module adds (#1774): a lowercase letter, then lowercase
11
+ * letters and digits, and not a word the dashboard keeps. Whichever module claims the segment gets
12
+ * it, and the shell says "no such page" when none does, or when the picked project has no package
13
+ * that adds it.
14
+ */
15
+ export declare function isPageSegment(segment: string): boolean;
16
+ /** Each registered project's address, and the project an address or an id names. */
17
+ export interface ProjectAddresses {
18
+ /** The project's address: its name, or its id where the name cannot be one. An unknown id is its own address. */
19
+ addressOf(projectId: string): string;
20
+ /** The project a first segment names, by its address or by its id; undefined for none. */
21
+ projectOf(segment: string): string | undefined;
22
+ }
23
+ /**
24
+ * The addresses of the registered projects, in the registry's order, beside the pages' words.
25
+ * A project's name is its id without the `-<hash>`: its folder's name, as the id writes it. The
26
+ * name is its address unless a page has that word, since pages win, or a project before it in the
27
+ * registry already has that name: the first one added keeps it, so adding a second clone changes
28
+ * no address in use.
29
+ */
30
+ export declare function projectAddresses(projectIds: readonly string[], pageWords: readonly string[]): ProjectAddresses;
31
+ /** No project registered: every first segment is a page's, or a project's nobody registered. */
32
+ export declare const NO_PROJECTS: ProjectAddresses;
33
+ //# sourceMappingURL=project-address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-address.d.ts","sourceRoot":"","sources":["../src/project-address.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAC1C,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,iHAAiH;IACjH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;IACpC,0FAA0F;IAC1F,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAC/C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAe9G;AAED,gGAAgG;AAChG,eAAO,MAAM,WAAW,EAAE,gBAA2C,CAAA"}
@@ -0,0 +1,57 @@
1
+ // A project's address in the dashboard's URL (#784): what the page shows, and what `openagent`
2
+ // prints for a project it opens, so the two say one thing.
3
+ //
4
+ // A project's address is its folder's name: the id with its `-<hash>` off. The id itself is the
5
+ // address only where the name cannot be one: a project before it in the registry has the same
6
+ // folder name, or the name is a page's word. Only the address is a name: the id stays what
7
+ // preferences, records and other machines know the project by, and it still opens the project.
8
+ //
9
+ // Pure and browser-safe: the dashboard imports it, and so does the server.
10
+ /**
11
+ * The words the dashboard keeps for itself, after a project's address and in its place: `overview`,
12
+ * a project's Overview, and `settings` (#958), where Settings was a page. Settings is a dialog over
13
+ * the page you are on now, carried as `?settings=`; the old address still opens it, over the
14
+ * Overview. No module's page takes either.
15
+ */
16
+ export const OVERVIEW_SEGMENT = 'overview';
17
+ export const SETTINGS_SEGMENT = 'settings';
18
+ /**
19
+ * Whether a segment can name a page a module adds (#1774): a lowercase letter, then lowercase
20
+ * letters and digits, and not a word the dashboard keeps. Whichever module claims the segment gets
21
+ * it, and the shell says "no such page" when none does, or when the picked project has no package
22
+ * that adds it.
23
+ */
24
+ export function isPageSegment(segment) {
25
+ return /^[a-z][a-z0-9]*$/.test(segment) && segment !== OVERVIEW_SEGMENT && segment !== SETTINGS_SEGMENT;
26
+ }
27
+ /**
28
+ * The addresses of the registered projects, in the registry's order, beside the pages' words.
29
+ * A project's name is its id without the `-<hash>`: its folder's name, as the id writes it. The
30
+ * name is its address unless a page has that word, since pages win, or a project before it in the
31
+ * registry already has that name: the first one added keeps it, so adding a second clone changes
32
+ * no address in use.
33
+ */
34
+ export function projectAddresses(projectIds, pageWords) {
35
+ const taken = new Set([OVERVIEW_SEGMENT, SETTINGS_SEGMENT, ...pageWords, ...projectIds]);
36
+ const byId = new Map();
37
+ const byAddress = new Map();
38
+ for (const id of projectIds) {
39
+ const name = nameOf(id);
40
+ const address = name && !taken.has(name) ? name : id;
41
+ taken.add(address);
42
+ byId.set(id, address);
43
+ byAddress.set(address, id);
44
+ }
45
+ return {
46
+ addressOf: id => byId.get(id) ?? id,
47
+ projectOf: segment => byAddress.get(segment) ?? (byId.has(segment) ? segment : undefined),
48
+ };
49
+ }
50
+ /** No project registered: every first segment is a page's, or a project's nobody registered. */
51
+ export const NO_PROJECTS = projectAddresses([], []);
52
+ /** A project id's name: the id without its last `-<hash>`; empty for an id with no dash. */
53
+ function nameOf(projectId) {
54
+ const dash = projectId.lastIndexOf('-');
55
+ return dash > 0 ? projectId.slice(0, dash) : '';
56
+ }
57
+ //# sourceMappingURL=project-address.js.map