ghostrail 0.18.0 → 0.20.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.
- package/README.md +29 -0
- package/dist/agent/claude-code.d.ts +18 -1
- package/dist/agent/claude-code.d.ts.map +1 -1
- package/dist/agent/claude-code.js +36 -4
- package/dist/agent/claude-code.js.map +1 -1
- package/dist/board/page.js +1 -1
- package/dist/cli/commands.d.ts +12 -7
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +261 -43
- package/dist/cli/commands.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +5 -3
- package/dist/commands/help.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +20 -3
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +57 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +19 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/dashboard/index.d.ts +3 -2
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -2
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/page.d.ts.map +1 -1
- package/dist/dashboard/page.js +39 -3
- package/dist/dashboard/page.js.map +1 -1
- package/dist/dashboard/review-page.d.ts +14 -0
- package/dist/dashboard/review-page.d.ts.map +1 -0
- package/dist/dashboard/review-page.js +256 -0
- package/dist/dashboard/review-page.js.map +1 -0
- package/dist/dashboard/routes.d.ts +18 -0
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +24 -0
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/server.d.ts +19 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +71 -1
- package/dist/dashboard/server.js.map +1 -1
- package/dist/doctor/checks.d.ts +21 -0
- package/dist/doctor/checks.d.ts.map +1 -1
- package/dist/doctor/checks.js +44 -0
- package/dist/doctor/checks.js.map +1 -1
- package/dist/doctor/probes.d.ts +15 -0
- package/dist/doctor/probes.d.ts.map +1 -1
- package/dist/doctor/probes.js +33 -0
- package/dist/doctor/probes.js.map +1 -1
- package/dist/doctor/run.d.ts +2 -1
- package/dist/doctor/run.d.ts.map +1 -1
- package/dist/doctor/run.js +25 -2
- package/dist/doctor/run.js.map +1 -1
- package/dist/factory/build.d.ts +6 -0
- package/dist/factory/build.d.ts.map +1 -1
- package/dist/factory/build.js +17 -5
- package/dist/factory/build.js.map +1 -1
- package/dist/global/tempdir.d.ts +19 -0
- package/dist/global/tempdir.d.ts.map +1 -0
- package/dist/global/tempdir.js +47 -0
- package/dist/global/tempdir.js.map +1 -0
- package/dist/init/greenroom.d.ts +18 -0
- package/dist/init/greenroom.d.ts.map +1 -0
- package/dist/init/greenroom.js +24 -0
- package/dist/init/greenroom.js.map +1 -0
- package/dist/loop/engine.d.ts +9 -0
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +114 -7
- package/dist/loop/engine.js.map +1 -1
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.d.ts.map +1 -1
- package/dist/loop/index.js +1 -0
- package/dist/loop/index.js.map +1 -1
- package/dist/loop/ports.d.ts +7 -0
- package/dist/loop/ports.d.ts.map +1 -1
- package/dist/loop/record.d.ts +6 -1
- package/dist/loop/record.d.ts.map +1 -1
- package/dist/loop/retry.d.ts +50 -0
- package/dist/loop/retry.d.ts.map +1 -0
- package/dist/loop/retry.js +77 -0
- package/dist/loop/retry.js.map +1 -0
- package/dist/loop/schedule.d.ts +33 -0
- package/dist/loop/schedule.d.ts.map +1 -0
- package/dist/loop/schedule.js +28 -0
- package/dist/loop/schedule.js.map +1 -0
- package/dist/respond/authors.d.ts +38 -0
- package/dist/respond/authors.d.ts.map +1 -0
- package/dist/respond/authors.js +50 -0
- package/dist/respond/authors.js.map +1 -0
- package/dist/respond/merge.d.ts.map +1 -1
- package/dist/respond/merge.js +25 -19
- package/dist/respond/merge.js.map +1 -1
- package/dist/respond/respond.d.ts +7 -0
- package/dist/respond/respond.d.ts.map +1 -1
- package/dist/respond/respond.js +36 -16
- package/dist/respond/respond.js.map +1 -1
- package/dist/respond/run-id.d.ts +17 -0
- package/dist/respond/run-id.d.ts.map +1 -0
- package/dist/respond/run-id.js +23 -0
- package/dist/respond/run-id.js.map +1 -0
- package/dist/respond/run-row.d.ts +24 -0
- package/dist/respond/run-row.d.ts.map +1 -0
- package/dist/respond/run-row.js +28 -0
- package/dist/respond/run-row.js.map +1 -0
- package/dist/store/events.d.ts +12 -0
- package/dist/store/events.d.ts.map +1 -1
- package/dist/store/events.js +10 -3
- package/dist/store/events.js.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js.map +1 -1
- package/dist/triage/engine.d.ts +14 -0
- package/dist/triage/engine.d.ts.map +1 -1
- package/dist/triage/engine.js +73 -3
- package/dist/triage/engine.js.map +1 -1
- package/package.json +1 -1
- package/templates/code/ghostrail.toml +4 -0
- package/templates/content/ghostrail.toml +4 -0
package/dist/config/schema.d.ts
CHANGED
|
@@ -74,6 +74,18 @@ export interface GateConfig {
|
|
|
74
74
|
export interface RespondConfigSection {
|
|
75
75
|
/** Prompt file for the respond agent (reads `.ghostrail/feedback.md`). */
|
|
76
76
|
prompt: string;
|
|
77
|
+
/**
|
|
78
|
+
* Comment authors the respond pass ignores, on top of the source kind's
|
|
79
|
+
* built-in defaults and `GHOSTRAIL_SELF_LOGINS`. Bot logins, not
|
|
80
|
+
* credentials, so they live in the config rather than the secrets file.
|
|
81
|
+
*/
|
|
82
|
+
ignoreAuthors: string[];
|
|
83
|
+
/**
|
|
84
|
+
* How often `watch` runs this pass, in milliseconds. Undefined when
|
|
85
|
+
* `every = "off"`, which keeps `ghostrail respond` working by hand while
|
|
86
|
+
* leaving it out of the watch loop.
|
|
87
|
+
*/
|
|
88
|
+
everyMs?: number;
|
|
77
89
|
}
|
|
78
90
|
/** Optional triage stage: decompose large issues into ordered sub-issues. */
|
|
79
91
|
export interface TriageConfig {
|
|
@@ -81,6 +93,12 @@ export interface TriageConfig {
|
|
|
81
93
|
prompt: string;
|
|
82
94
|
/** Which issues form the triage queue (typically a `triage` label + assignee). */
|
|
83
95
|
eligible: EligibleFilter;
|
|
96
|
+
/**
|
|
97
|
+
* How often `watch` runs this pass, in milliseconds. Undefined when
|
|
98
|
+
* `every = "off"`, which keeps `ghostrail triage` working by hand while
|
|
99
|
+
* leaving it out of the watch loop.
|
|
100
|
+
*/
|
|
101
|
+
everyMs?: number;
|
|
84
102
|
}
|
|
85
103
|
/**
|
|
86
104
|
* A guardrail spending cap: a positive USD amount, or `"unlimited"` to
|
|
@@ -102,6 +120,30 @@ export interface Guardrails {
|
|
|
102
120
|
* concurrently; the per-tick total is still bounded by `maxItemsPerRun`.
|
|
103
121
|
*/
|
|
104
122
|
concurrency: number;
|
|
123
|
+
/**
|
|
124
|
+
* How many consecutive "persistent" failures (a red gate, or the agent
|
|
125
|
+
* itself reporting failure) an item may accrue before the factory stops
|
|
126
|
+
* claiming it and hands it to a human. Optional so ad hoc `Guardrails`
|
|
127
|
+
* literals built before this field existed keep compiling; a missing value
|
|
128
|
+
* falls back to {@link DEFAULT_MAX_FAILURES} wherever retry decisions are made.
|
|
129
|
+
*/
|
|
130
|
+
maxFailures?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Same allowance as {@link Guardrails.maxFailures}, but for "transient"
|
|
133
|
+
* failures (a provisioning error, an agent timeout) — counted separately so
|
|
134
|
+
* a flake does not spend down the allowance a real dead end needs, and does
|
|
135
|
+
* not give up too early on it either. Falls back to
|
|
136
|
+
* {@link DEFAULT_MAX_TRANSIENT_FAILURES} when unset.
|
|
137
|
+
*/
|
|
138
|
+
maxTransientFailures?: number;
|
|
139
|
+
/**
|
|
140
|
+
* Base delay, in milliseconds, of the exponential backoff applied after a
|
|
141
|
+
* failed attempt: `retryBackoffMs * 2^(failures - 1)`, capped at
|
|
142
|
+
* `retryBackoffCapMs`. Falls back to {@link DEFAULT_RETRY_BACKOFF_MS}.
|
|
143
|
+
*/
|
|
144
|
+
retryBackoffMs?: number;
|
|
145
|
+
/** Cap on the backoff computed from {@link Guardrails.retryBackoffMs}. Falls back to {@link DEFAULT_RETRY_BACKOFF_CAP_MS}. */
|
|
146
|
+
retryBackoffCapMs?: number;
|
|
105
147
|
}
|
|
106
148
|
export interface OutputConfig {
|
|
107
149
|
openPr: boolean;
|
|
@@ -134,8 +176,23 @@ export interface GhostrailConfig {
|
|
|
134
176
|
}
|
|
135
177
|
/** Default agent tool allowlist: read + write files and run shell/search only. */
|
|
136
178
|
export declare const DEFAULT_ALLOWED_TOOLS: readonly string[];
|
|
179
|
+
/** See {@link Guardrails.maxFailures}. */
|
|
180
|
+
export declare const DEFAULT_MAX_FAILURES = 3;
|
|
181
|
+
/** See {@link Guardrails.maxTransientFailures}. */
|
|
182
|
+
export declare const DEFAULT_MAX_TRANSIENT_FAILURES = 5;
|
|
183
|
+
/** See {@link Guardrails.retryBackoffMs}. */
|
|
184
|
+
export declare const DEFAULT_RETRY_BACKOFF_MS: number;
|
|
185
|
+
/** See {@link Guardrails.retryBackoffCapMs}. */
|
|
186
|
+
export declare const DEFAULT_RETRY_BACKOFF_CAP_MS: number;
|
|
137
187
|
/** Guardrail defaults, carried over from the reference factory. */
|
|
138
188
|
export declare const DEFAULT_GUARDRAILS: Guardrails;
|
|
189
|
+
/**
|
|
190
|
+
* How often `watch` runs each schedulable pass when its section sets no
|
|
191
|
+
* `every`. Respond answers a human who is waiting, so it runs often; triage
|
|
192
|
+
* costs an agent run and decomposing an issue is never urgent, so it does not.
|
|
193
|
+
*/
|
|
194
|
+
export declare const DEFAULT_RESPOND_EVERY_MS: number;
|
|
195
|
+
export declare const DEFAULT_TRIAGE_EVERY_MS: number;
|
|
139
196
|
export declare const DEFAULT_BASE_BRANCH = "main";
|
|
140
197
|
export declare const DEFAULT_BACKEND_KIND: BackendKind;
|
|
141
198
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY,+CAAgD,CAAC;AAC1E,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,aAAa,0EAMhB,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/config/schema.js
CHANGED
|
@@ -25,6 +25,14 @@ export const DEFAULT_ALLOWED_TOOLS = [
|
|
|
25
25
|
"Grep",
|
|
26
26
|
"Glob",
|
|
27
27
|
];
|
|
28
|
+
/** See {@link Guardrails.maxFailures}. */
|
|
29
|
+
export const DEFAULT_MAX_FAILURES = 3;
|
|
30
|
+
/** See {@link Guardrails.maxTransientFailures}. */
|
|
31
|
+
export const DEFAULT_MAX_TRANSIENT_FAILURES = 5;
|
|
32
|
+
/** See {@link Guardrails.retryBackoffMs}. */
|
|
33
|
+
export const DEFAULT_RETRY_BACKOFF_MS = 10 * 60_000;
|
|
34
|
+
/** See {@link Guardrails.retryBackoffCapMs}. */
|
|
35
|
+
export const DEFAULT_RETRY_BACKOFF_CAP_MS = 4 * 3_600_000;
|
|
28
36
|
/** Guardrail defaults, carried over from the reference factory. */
|
|
29
37
|
export const DEFAULT_GUARDRAILS = {
|
|
30
38
|
maxItemsPerRun: 3,
|
|
@@ -33,7 +41,18 @@ export const DEFAULT_GUARDRAILS = {
|
|
|
33
41
|
maxCostUsdPerRun: 5,
|
|
34
42
|
claimTtlMs: 2 * 3_600_000,
|
|
35
43
|
concurrency: 1,
|
|
44
|
+
maxFailures: DEFAULT_MAX_FAILURES,
|
|
45
|
+
maxTransientFailures: DEFAULT_MAX_TRANSIENT_FAILURES,
|
|
46
|
+
retryBackoffMs: DEFAULT_RETRY_BACKOFF_MS,
|
|
47
|
+
retryBackoffCapMs: DEFAULT_RETRY_BACKOFF_CAP_MS,
|
|
36
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* How often `watch` runs each schedulable pass when its section sets no
|
|
51
|
+
* `every`. Respond answers a human who is waiting, so it runs often; triage
|
|
52
|
+
* costs an agent run and decomposing an issue is never urgent, so it does not.
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_RESPOND_EVERY_MS = 5 * 60_000;
|
|
55
|
+
export const DEFAULT_TRIAGE_EVERY_MS = 30 * 60_000;
|
|
37
56
|
export const DEFAULT_BASE_BRANCH = "main";
|
|
38
57
|
export const DEFAULT_BACKEND_KIND = "local-worktree";
|
|
39
58
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAU,CAAC;AAG1E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB;IAChB,WAAW;IACX,aAAa;IACb,KAAK;IACL,QAAQ;CACA,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAU,CAAC;AAG1E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB;IAChB,WAAW;IACX,aAAa;IACb,KAAK;IACL,QAAQ;CACA,CAAC;AAkKX,mEAAmE;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAkB3C,kFAAkF;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC;AAEF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AACtC,mDAAmD;AACnD,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAChD,6CAA6C;AAC7C,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,MAAM,CAAC;AACpD,gDAAgD;AAChD,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,GAAG,SAAS,CAAC;AAE1D,mEAAmE;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,cAAc,EAAE,CAAC;IACjB,gBAAgB,EAAE,CAAC;IACnB,SAAS,EAAE,EAAE,GAAG,MAAM;IACtB,gBAAgB,EAAE,CAAC;IACnB,UAAU,EAAE,CAAC,GAAG,SAAS;IACzB,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,oBAAoB;IACjC,oBAAoB,EAAE,8BAA8B;IACpD,cAAc,EAAE,wBAAwB;IACxC,iBAAiB,EAAE,4BAA4B;CAChD,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,GAAG,MAAM,CAAC;AACnD,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,MAAM,CAAC;AAEnD,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAC1C,MAAM,CAAC,MAAM,oBAAoB,GAAgB,gBAAgB,CAAC;AAElE;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { DASHBOARD_PAGE_HTML } from "./page.js";
|
|
2
|
-
export {
|
|
3
|
-
export { type
|
|
2
|
+
export { REVIEW_PAGE_HTML } from "./review-page.js";
|
|
3
|
+
export { type DashboardDeps, type DashboardOptions, type GhostrailHealth, type GhostrailView, MERGE_TRIGGER_COMMENT, collectViews, createDashboardServer, defaultStateDir, } from "./server.js";
|
|
4
|
+
export { type DashboardRoute, isCrossOriginWrite, parseDashboardRoute, parseGhostrailQuery, parseWatchUpdate, } from "./routes.js";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,YAAY,EACZ,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,qBAAqB,EACrB,YAAY,EACZ,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
|
package/dist/dashboard/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { DASHBOARD_PAGE_HTML } from "./page.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { REVIEW_PAGE_HTML } from "./review-page.js";
|
|
3
|
+
export { MERGE_TRIGGER_COMMENT, collectViews, createDashboardServer, defaultStateDir, } from "./server.js";
|
|
4
|
+
export { isCrossOriginWrite, parseDashboardRoute, parseGhostrailQuery, parseWatchUpdate, } from "./routes.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAKL,YAAY,EACZ,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAKL,qBAAqB,EACrB,YAAY,EACZ,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QAwS/B,CAAC"}
|
package/dist/dashboard/page.js
CHANGED
|
@@ -34,7 +34,7 @@ export const DASHBOARD_PAGE_HTML = `<!doctype html>
|
|
|
34
34
|
table.runs th { font-weight: 600; opacity: .7; font-size: .85em; }
|
|
35
35
|
a { color: inherit; }
|
|
36
36
|
.state { font-weight: 600; }
|
|
37
|
-
.done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }
|
|
37
|
+
.done { color: #2a9d3a; } .failed { color: #d33; } .blocked, .abandoned { color: #d90; }
|
|
38
38
|
.noop, .skipped { opacity: .6; } .muted { opacity: .6; }
|
|
39
39
|
.twirl {
|
|
40
40
|
background: none; border: none; cursor: pointer; font: inherit; padding: 0 .3rem;
|
|
@@ -47,6 +47,10 @@ export const DASHBOARD_PAGE_HTML = `<!doctype html>
|
|
|
47
47
|
.detail-cell ul { margin: .25rem 0 0; padding-left: 1.2rem; }
|
|
48
48
|
.subtable { border-collapse: collapse; margin-top: .25rem; }
|
|
49
49
|
.subtable td { padding: .15rem .5rem .15rem 0; border-bottom: none; }
|
|
50
|
+
.review { margin-left: auto; font-size: .85em; }
|
|
51
|
+
.merge-row { display: flex; align-items: center; gap: .5rem; }
|
|
52
|
+
.merge-btn { font: inherit; }
|
|
53
|
+
.merge-status { font-size: .85em; opacity: .8; }
|
|
50
54
|
</style>
|
|
51
55
|
<h1>ghostrails</h1>
|
|
52
56
|
<p class="sub">The checkbox is what <code>ghostrail start</code> should bring up. The pill beside it is what is running right now.</p>
|
|
@@ -124,6 +128,13 @@ function prStatusHtml(status) {
|
|
|
124
128
|
return parts.join("");
|
|
125
129
|
}
|
|
126
130
|
|
|
131
|
+
function mergeButtonHtml(path, r) {
|
|
132
|
+
if (r.prNumber === undefined) return "";
|
|
133
|
+
return "<div class='merge-row'><button class='merge-btn' type='button' data-path='" + esc(path) +
|
|
134
|
+
"' data-run='" + esc(r.runId) + "'>resolve conflicts and merge</button>" +
|
|
135
|
+
"<span class='merge-status'></span></div>";
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
function detailHtml(path, r) {
|
|
128
139
|
const parts = [];
|
|
129
140
|
parts.push(
|
|
@@ -146,6 +157,7 @@ function detailHtml(path, r) {
|
|
|
146
157
|
"<div><b>PR</b> <a href='" + esc(r.prUrl) + "'" + NEWTAB + ">" + esc(r.prUrl) + "</a></div>",
|
|
147
158
|
);
|
|
148
159
|
}
|
|
160
|
+
parts.push(mergeButtonHtml(path, r));
|
|
149
161
|
parts.push(artifactsHtml(path, r.artifacts));
|
|
150
162
|
if (r.prNumber !== undefined) parts.push(prStatusHtml(prStatus.get(rowKey(path, r.runId))));
|
|
151
163
|
parts.push(
|
|
@@ -199,6 +211,7 @@ function render(ghostrails) {
|
|
|
199
211
|
"<span class='name'>" + esc(g.name) + "</span>" +
|
|
200
212
|
"<span class='pill'>" + esc(g.state) + "</span>" +
|
|
201
213
|
"<span class='path'>" + esc(g.path) + "</span>" +
|
|
214
|
+
"<a class='review' href='" + esc(api("review?path=" + enc(g.path))) + "'>review</a>" +
|
|
202
215
|
"</div>" + runsHtml(g, gi) + "</div>"
|
|
203
216
|
).join("");
|
|
204
217
|
}
|
|
@@ -236,11 +249,34 @@ function toggle(path, runId, buttonId) {
|
|
|
236
249
|
if (expanded.has(k)) refreshPrStatus();
|
|
237
250
|
}
|
|
238
251
|
|
|
252
|
+
async function postMerge(path, runId, button) {
|
|
253
|
+
const status = button.parentElement.querySelector(".merge-status");
|
|
254
|
+
button.disabled = true;
|
|
255
|
+
if (status) status.textContent = "posting…";
|
|
256
|
+
try {
|
|
257
|
+
const res = await fetch(
|
|
258
|
+
api("/api/merge?path=" + enc(path) + "&run=" + enc(runId)),
|
|
259
|
+
{ method: "POST" },
|
|
260
|
+
);
|
|
261
|
+
const data = await res.json();
|
|
262
|
+
if (!res.ok || data.posted !== true) throw new Error(data.error || res.statusText);
|
|
263
|
+
if (status) status.textContent = "posted — the next respond pass will pick it up";
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (status) status.textContent = "failed: " + err.message;
|
|
266
|
+
button.disabled = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
const list = document.getElementById("list");
|
|
240
271
|
|
|
241
272
|
list.addEventListener("click", (e) => {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
273
|
+
const twirlBtn = e.target.closest(".twirl");
|
|
274
|
+
if (twirlBtn) {
|
|
275
|
+
toggle(twirlBtn.dataset.path, twirlBtn.dataset.run, twirlBtn.id);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const mergeBtn = e.target.closest(".merge-btn");
|
|
279
|
+
if (mergeBtn) postMerge(mergeBtn.dataset.path, mergeBtn.dataset.run, mergeBtn);
|
|
244
280
|
});
|
|
245
281
|
|
|
246
282
|
list.addEventListener("change", async (e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAqKA,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmInE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The review-mode page: one ghostrail's runs, one at a time, with Prev/Next.
|
|
3
|
+
*
|
|
4
|
+
* Inline, like the dashboard's own page, so the package ships one file and the
|
|
5
|
+
* server needs no static asset handling. It reads `path` (which ghostrail),
|
|
6
|
+
* `run` (which run to open first), and `token` from `location.search`, the
|
|
7
|
+
* same way the dashboard page reads `token`.
|
|
8
|
+
*
|
|
9
|
+
* The detail rendering (gate steps, artifacts, PR status) intentionally mirrors
|
|
10
|
+
* `page.ts`'s: same information, same shape, because this is the same detail
|
|
11
|
+
* panel shown one run at a time instead of behind a twirl.
|
|
12
|
+
*/
|
|
13
|
+
export declare const REVIEW_PAGE_HTML: string;
|
|
14
|
+
//# sourceMappingURL=review-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-page.d.ts","sourceRoot":"","sources":["../../src/dashboard/review-page.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,QAiP5B,CAAC"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { IN_PROGRESS_STATE } from "../store/events.js";
|
|
2
|
+
/**
|
|
3
|
+
* The review-mode page: one ghostrail's runs, one at a time, with Prev/Next.
|
|
4
|
+
*
|
|
5
|
+
* Inline, like the dashboard's own page, so the package ships one file and the
|
|
6
|
+
* server needs no static asset handling. It reads `path` (which ghostrail),
|
|
7
|
+
* `run` (which run to open first), and `token` from `location.search`, the
|
|
8
|
+
* same way the dashboard page reads `token`.
|
|
9
|
+
*
|
|
10
|
+
* The detail rendering (gate steps, artifacts, PR status) intentionally mirrors
|
|
11
|
+
* `page.ts`'s: same information, same shape, because this is the same detail
|
|
12
|
+
* panel shown one run at a time instead of behind a twirl.
|
|
13
|
+
*/
|
|
14
|
+
export const REVIEW_PAGE_HTML = `<!doctype html>
|
|
15
|
+
<meta charset="utf-8">
|
|
16
|
+
<title>ghostrail review</title>
|
|
17
|
+
<style>
|
|
18
|
+
:root { color-scheme: light dark; }
|
|
19
|
+
body { font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 56rem; padding: 0 1rem; }
|
|
20
|
+
h1 { font-size: 1.3rem; margin: 0 0 .25rem; }
|
|
21
|
+
.sub { opacity: .7; margin: 0 0 1.5rem; }
|
|
22
|
+
.back { display: inline-block; margin-bottom: 1rem; }
|
|
23
|
+
.nav { display: flex; align-items: center; gap: .75rem; margin-bottom: 1rem; }
|
|
24
|
+
.nav button { font: inherit; padding: .3rem .8rem; }
|
|
25
|
+
.pos { opacity: .7; font-size: .9em; }
|
|
26
|
+
.panel { border: 1px solid color-mix(in oklab, currentColor 20%, transparent); border-radius: 8px; padding: 1rem 1.25rem; }
|
|
27
|
+
.panel > div { margin-bottom: .5rem; }
|
|
28
|
+
.panel pre { white-space: pre-wrap; word-break: break-word; margin: .25rem 0 0; font: inherit; }
|
|
29
|
+
.panel ul { margin: .25rem 0 0; padding-left: 1.2rem; }
|
|
30
|
+
.title { font-weight: 600; font-size: 1.1em; }
|
|
31
|
+
.state { font-weight: 600; }
|
|
32
|
+
.done { color: #2a9d3a; } .failed { color: #d33; } .blocked, .abandoned { color: #d90; }
|
|
33
|
+
.noop, .skipped { opacity: .6; } .muted { opacity: .6; }
|
|
34
|
+
a { color: inherit; }
|
|
35
|
+
.subtable { border-collapse: collapse; margin-top: .25rem; }
|
|
36
|
+
.subtable td { padding: .15rem .5rem .15rem 0; border-bottom: none; }
|
|
37
|
+
.merge-btn { font: inherit; }
|
|
38
|
+
.merge-status { margin-left: .5rem; font-size: .85em; }
|
|
39
|
+
.none { opacity: .55; }
|
|
40
|
+
.err { color: #b00; }
|
|
41
|
+
</style>
|
|
42
|
+
<a class="back" href="./">← back to the dashboard</a>
|
|
43
|
+
<h1 id="heading">review</h1>
|
|
44
|
+
<p class="sub" id="sub"></p>
|
|
45
|
+
<div class="nav" id="nav" hidden>
|
|
46
|
+
<button id="prev" type="button">← prev</button>
|
|
47
|
+
<span class="pos" id="pos"></span>
|
|
48
|
+
<button id="next" type="button">next →</button>
|
|
49
|
+
</div>
|
|
50
|
+
<div id="body">loading…</div>
|
|
51
|
+
<script>
|
|
52
|
+
const params = new URLSearchParams(location.search);
|
|
53
|
+
const ghostrailPath = params.get("path") || "";
|
|
54
|
+
const tok = params.get("token");
|
|
55
|
+
const api = (p) => p + (tok ? (p.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(tok) : "");
|
|
56
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) =>
|
|
57
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c] || c));
|
|
58
|
+
const enc = encodeURIComponent;
|
|
59
|
+
// Every link this page renders leaves it, so it opens in a new tab, with the
|
|
60
|
+
// \`rel\` that stops the opened page reaching back through \`window.opener\`. Same
|
|
61
|
+
// rule, same reason, as the dashboard page (TJ-1653).
|
|
62
|
+
const NEWTAB = " target='_blank' rel='noopener noreferrer'";
|
|
63
|
+
// The item's label, linked to whatever the run's \`url\` points at when the row
|
|
64
|
+
// carries one.
|
|
65
|
+
const itemLink = (r, label) =>
|
|
66
|
+
r.url ? "<a href='" + esc(r.url) + "'" + NEWTAB + ">" + esc(label) + "</a>" : esc(label);
|
|
67
|
+
|
|
68
|
+
let runs = [];
|
|
69
|
+
let index = 0;
|
|
70
|
+
let name = "";
|
|
71
|
+
const prStatus = new Map();
|
|
72
|
+
|
|
73
|
+
function fmtWhen(ts) {
|
|
74
|
+
return ts ? new Date(ts).toLocaleString() : "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function gateStepsHtml(gate) {
|
|
78
|
+
if (!gate || gate.length === 0) return "<div class=muted>no gate steps</div>";
|
|
79
|
+
const rows = gate.map((s) =>
|
|
80
|
+
"<tr><td>" + esc(s.name) + "</td><td class='" + (s.exitCode === 0 ? "done" : "failed") + "'>" +
|
|
81
|
+
(s.exitCode === 0 ? "passed" : "failed (exit " + esc(s.exitCode) + ")") + "</td><td class=muted>" +
|
|
82
|
+
esc(s.durationMs) + "ms</td></tr>"
|
|
83
|
+
).join("");
|
|
84
|
+
return "<table class=subtable>" + rows + "</table>";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function artifactsHtml(artifacts) {
|
|
88
|
+
if (!artifacts || artifacts.length === 0) return "";
|
|
89
|
+
const items = artifacts.map((a) => {
|
|
90
|
+
const href = api("/api/artifact?path=" + enc(ghostrailPath) + "&hash=" + enc(a.hash));
|
|
91
|
+
return "<li><a href='" + esc(href) + "'" + NEWTAB + ">" + esc(a.name) + "</a></li>";
|
|
92
|
+
}).join("");
|
|
93
|
+
return "<div><b>Artifacts</b><ul>" + items + "</ul></div>";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkLineHtml(c) {
|
|
97
|
+
const label = c.conclusion || c.status;
|
|
98
|
+
const cls = c.conclusion === "success" ? "done" : c.conclusion ? "failed" : "muted";
|
|
99
|
+
const label2 = c.url
|
|
100
|
+
? "<a href='" + esc(c.url) + "'" + NEWTAB + ">" + esc(c.name) + "</a>"
|
|
101
|
+
: esc(c.name);
|
|
102
|
+
return "<li>" + label2 + ": <span class='" + cls + "'>" + esc(label) + "</span></li>";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function prStatusHtml(status) {
|
|
106
|
+
if (!status) return "<div class=muted>loading CI status…</div>";
|
|
107
|
+
if (!status.available) {
|
|
108
|
+
return "<div class=muted>PR status unknown" + (status.error ? ": " + esc(status.error) : "") + "</div>";
|
|
109
|
+
}
|
|
110
|
+
const merged = status.state === "merged";
|
|
111
|
+
const parts = [
|
|
112
|
+
"<div><b>PR state:</b> " + esc(status.state) +
|
|
113
|
+
(merged && status.mergedAt ? " at " + esc(new Date(status.mergedAt).toLocaleString()) : "") + "</div>",
|
|
114
|
+
];
|
|
115
|
+
if (status.checks && status.checks.length > 0) {
|
|
116
|
+
parts.push("<div><b>CI checks</b><ul>" + status.checks.map(checkLineHtml).join("") + "</ul></div>");
|
|
117
|
+
} else {
|
|
118
|
+
parts.push("<div class=muted>no CI checks reported</div>");
|
|
119
|
+
}
|
|
120
|
+
return parts.join("");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function mergeButtonHtml(r) {
|
|
124
|
+
if (r.prNumber === undefined) return "";
|
|
125
|
+
return "<div><button class='merge-btn' type='button' data-run='" + esc(r.runId) +
|
|
126
|
+
"'>resolve conflicts and merge</button><span class='merge-status' id='merge-status'></span></div>";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function detailHtml(r) {
|
|
130
|
+
const parts = [];
|
|
131
|
+
parts.push(
|
|
132
|
+
"<div class=title>" + itemLink(r, r.title || r.itemId || "(untitled run)") + "</div>",
|
|
133
|
+
);
|
|
134
|
+
parts.push(
|
|
135
|
+
"<div><b>Item</b> " + itemLink(r, r.itemId || "") +
|
|
136
|
+
(r.kind ? " (" + esc(r.kind) + ")" : "") + "</div>",
|
|
137
|
+
);
|
|
138
|
+
const state = esc(r.state || ${JSON.stringify(IN_PROGRESS_STATE)});
|
|
139
|
+
parts.push("<div><b>State</b> <span class='state " + state + "'>" + state + "</span></div>");
|
|
140
|
+
if (r.branch) parts.push("<div><b>Branch</b> " + esc(r.branch) + "</div>");
|
|
141
|
+
if (r.backend) parts.push("<div><b>Backend</b> " + esc(r.backend) + "</div>");
|
|
142
|
+
if (r.costUsd !== undefined) {
|
|
143
|
+
parts.push("<div><b>Cost</b> $" + esc(Number(r.costUsd).toFixed(4)) + "</div>");
|
|
144
|
+
}
|
|
145
|
+
if (r.tokensIn !== undefined || r.tokensOut !== undefined) {
|
|
146
|
+
parts.push("<div><b>Tokens</b> in " + esc(r.tokensIn ?? 0) + " / out " + esc(r.tokensOut ?? 0) + "</div>");
|
|
147
|
+
}
|
|
148
|
+
parts.push("<div><b>Gate</b>" + gateStepsHtml(r.gate) + "</div>");
|
|
149
|
+
if (r.error) parts.push("<div><b>Error</b><pre class=failed>" + esc(r.error) + "</pre></div>");
|
|
150
|
+
if (r.summary) parts.push("<div><b>Comment</b><pre>" + esc(r.summary) + "</pre></div>");
|
|
151
|
+
if (r.prUrl) {
|
|
152
|
+
parts.push(
|
|
153
|
+
"<div><b>PR</b> <a href='" + esc(r.prUrl) + "'" + NEWTAB + ">" + esc(r.prUrl) + "</a></div>",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
parts.push(mergeButtonHtml(r));
|
|
157
|
+
parts.push(artifactsHtml(r.artifacts));
|
|
158
|
+
if (r.prNumber !== undefined) parts.push(prStatusHtml(prStatus.get(r.runId)));
|
|
159
|
+
parts.push(
|
|
160
|
+
"<div class=muted>started " + esc(fmtWhen(r.startedAt)) +
|
|
161
|
+
(r.finishedAt ? ", finished " + esc(fmtWhen(r.finishedAt)) : ", still running") +
|
|
162
|
+
"</div>",
|
|
163
|
+
);
|
|
164
|
+
return parts.join("");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function render() {
|
|
168
|
+
document.getElementById("sub").textContent = name ? name + " — " + ghostrailPath : ghostrailPath;
|
|
169
|
+
const nav = document.getElementById("nav");
|
|
170
|
+
const body = document.getElementById("body");
|
|
171
|
+
if (runs.length === 0) {
|
|
172
|
+
nav.hidden = true;
|
|
173
|
+
body.innerHTML = "<p class=none>no runs yet</p>";
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
nav.hidden = false;
|
|
177
|
+
document.getElementById("pos").textContent = "run " + (index + 1) + " of " + runs.length;
|
|
178
|
+
document.getElementById("prev").disabled = index <= 0;
|
|
179
|
+
document.getElementById("next").disabled = index >= runs.length - 1;
|
|
180
|
+
body.innerHTML = "<div class=panel>" + detailHtml(runs[index]) + "</div>";
|
|
181
|
+
const run = runs[index];
|
|
182
|
+
const next = new URLSearchParams(location.search);
|
|
183
|
+
next.set("run", run.runId);
|
|
184
|
+
history.replaceState(null, "", location.pathname + "?" + next.toString());
|
|
185
|
+
// Fetched once per run and cached: without the guard, a successful fetch
|
|
186
|
+
// triggers a re-render, which (with no guard) would ask for status again,
|
|
187
|
+
// forever.
|
|
188
|
+
if (run.prNumber !== undefined && !prStatus.has(run.runId)) loadPrStatus(run.runId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function loadPrStatus(runId) {
|
|
192
|
+
let status;
|
|
193
|
+
try {
|
|
194
|
+
status = await (
|
|
195
|
+
await fetch(api("/api/pr-status?path=" + enc(ghostrailPath) + "&run=" + enc(runId)))
|
|
196
|
+
).json();
|
|
197
|
+
} catch {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
prStatus.set(runId, status);
|
|
201
|
+
if (runs[index] && runs[index].runId === runId) render();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function postMerge(runId, button) {
|
|
205
|
+
const status = document.getElementById("merge-status");
|
|
206
|
+
button.disabled = true;
|
|
207
|
+
if (status) status.textContent = "posting…";
|
|
208
|
+
try {
|
|
209
|
+
const res = await fetch(
|
|
210
|
+
api("/api/merge?path=" + enc(ghostrailPath) + "&run=" + enc(runId)),
|
|
211
|
+
{ method: "POST" },
|
|
212
|
+
);
|
|
213
|
+
const data = await res.json();
|
|
214
|
+
if (!res.ok || data.posted !== true) throw new Error(data.error || res.statusText);
|
|
215
|
+
if (status) status.textContent = "posted — the next respond pass will pick it up";
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (status) status.textContent = "failed: " + err.message;
|
|
218
|
+
button.disabled = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
document.getElementById("body").addEventListener("click", (e) => {
|
|
223
|
+
const btn = e.target.closest(".merge-btn");
|
|
224
|
+
if (btn) postMerge(btn.dataset.run, btn);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
document.getElementById("prev").addEventListener("click", () => {
|
|
228
|
+
if (index > 0) { index -= 1; render(); }
|
|
229
|
+
});
|
|
230
|
+
document.getElementById("next").addEventListener("click", () => {
|
|
231
|
+
if (index < runs.length - 1) { index += 1; render(); }
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
async function load() {
|
|
235
|
+
if (!ghostrailPath) {
|
|
236
|
+
document.getElementById("body").innerHTML = "<p class=err>no ?path= given</p>";
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch(api("/api/runs?path=" + enc(ghostrailPath)));
|
|
241
|
+
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
242
|
+
const data = await res.json();
|
|
243
|
+
name = data.name || "";
|
|
244
|
+
runs = data.runs || [];
|
|
245
|
+
const wanted = params.get("run");
|
|
246
|
+
const found = wanted ? runs.findIndex((r) => r.runId === wanted) : -1;
|
|
247
|
+
index = found >= 0 ? found : 0;
|
|
248
|
+
render();
|
|
249
|
+
} catch (err) {
|
|
250
|
+
document.getElementById("body").innerHTML = "<p class=err>" + esc(err.message) + "</p>";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
load();
|
|
254
|
+
</script>
|
|
255
|
+
`;
|
|
256
|
+
//# sourceMappingURL=review-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-page.js","sourceRoot":"","sources":["../../src/dashboard/review-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA4HC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqHjE,CAAC"}
|
|
@@ -6,6 +6,8 @@ import type { IncomingHttpHeaders } from "node:http";
|
|
|
6
6
|
*/
|
|
7
7
|
export type DashboardRoute = {
|
|
8
8
|
kind: "page";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "review";
|
|
9
11
|
} | {
|
|
10
12
|
kind: "ghostrails";
|
|
11
13
|
} | {
|
|
@@ -14,11 +16,27 @@ export type DashboardRoute = {
|
|
|
14
16
|
kind: "prStatus";
|
|
15
17
|
} | {
|
|
16
18
|
kind: "artifact";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "runs";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "merge";
|
|
17
23
|
} | {
|
|
18
24
|
kind: "notfound";
|
|
19
25
|
};
|
|
20
26
|
/** Map a request pathname to a route. Total; never throws. */
|
|
21
27
|
export declare function parseDashboardRoute(pathname: string): DashboardRoute;
|
|
28
|
+
/**
|
|
29
|
+
* The ghostrail a request names, or undefined when `path` is missing or
|
|
30
|
+
* empty.
|
|
31
|
+
*
|
|
32
|
+
* Shares `parseDetailQuery`'s reasoning for using a query parameter rather
|
|
33
|
+
* than a path segment (a ghostrail's identity does not survive one), but
|
|
34
|
+
* `GET /api/runs` needs only `path`, not a second key naming a run or a
|
|
35
|
+
* hash within it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseGhostrailQuery(params: URLSearchParams): {
|
|
38
|
+
path: string;
|
|
39
|
+
} | undefined;
|
|
22
40
|
/**
|
|
23
41
|
* The ghostrail a detail request is about, plus the run or artifact within it.
|
|
24
42
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAoBpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIzF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,KAAK,GAAG,MAAM,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAI7E;AA2BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAYxE;AAOD;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAY3F"}
|
package/dist/dashboard/routes.js
CHANGED
|
@@ -7,6 +7,11 @@ export function parseDashboardRoute(pathname) {
|
|
|
7
7
|
// for something that means the root by every reading.
|
|
8
8
|
if (parts.length === 0)
|
|
9
9
|
return { kind: "page" };
|
|
10
|
+
// The review page is its own top-level page, not an /api route: it is a
|
|
11
|
+
// second HTML surface (own script, own polling), the same way the board's
|
|
12
|
+
// page and the dashboard's page are each their own top-level route.
|
|
13
|
+
if (parts.length === 1 && parts[0] === "review")
|
|
14
|
+
return { kind: "review" };
|
|
10
15
|
if (parts[0] !== "api" || parts.length !== 2)
|
|
11
16
|
return { kind: "notfound" };
|
|
12
17
|
if (parts[1] === "ghostrails")
|
|
@@ -19,8 +24,27 @@ export function parseDashboardRoute(pathname) {
|
|
|
19
24
|
return { kind: "prStatus" };
|
|
20
25
|
if (parts[1] === "artifact")
|
|
21
26
|
return { kind: "artifact" };
|
|
27
|
+
if (parts[1] === "runs")
|
|
28
|
+
return { kind: "runs" };
|
|
29
|
+
if (parts[1] === "merge")
|
|
30
|
+
return { kind: "merge" };
|
|
22
31
|
return { kind: "notfound" };
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The ghostrail a request names, or undefined when `path` is missing or
|
|
35
|
+
* empty.
|
|
36
|
+
*
|
|
37
|
+
* Shares `parseDetailQuery`'s reasoning for using a query parameter rather
|
|
38
|
+
* than a path segment (a ghostrail's identity does not survive one), but
|
|
39
|
+
* `GET /api/runs` needs only `path`, not a second key naming a run or a
|
|
40
|
+
* hash within it.
|
|
41
|
+
*/
|
|
42
|
+
export function parseGhostrailQuery(params) {
|
|
43
|
+
const path = params.get("path");
|
|
44
|
+
if (path === null || path.length === 0)
|
|
45
|
+
return undefined;
|
|
46
|
+
return { path };
|
|
47
|
+
}
|
|
24
48
|
/**
|
|
25
49
|
* The ghostrail a detail request is about, plus the run or artifact within it.
|
|
26
50
|
*
|