ghostrail 0.17.1 → 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.d.ts.map +1 -1
- package/dist/board/page.js +44 -10
- package/dist/board/page.js.map +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 +64 -10
- 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 -4
- 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 +10 -1
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +116 -18
- 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 +47 -0
- package/dist/respond/merge.js.map +1 -1
- package/dist/respond/respond.d.ts +10 -0
- package/dist/respond/respond.d.ts.map +1 -1
- package/dist/respond/respond.js +62 -5
- 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 +25 -0
- package/dist/store/events.d.ts.map +1 -1
- package/dist/store/events.js +30 -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 +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>
|
|
@@ -57,6 +61,14 @@ const api = (p) => p + (tok ? (p.includes("?") ? "&" : "?") + "token=" + encodeU
|
|
|
57
61
|
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) =>
|
|
58
62
|
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
59
63
|
const enc = encodeURIComponent;
|
|
64
|
+
// Every link the dashboard renders leaves the page, so it opens in a new tab.
|
|
65
|
+
// \`rel\` goes with it: without it the opened page's script can reach back
|
|
66
|
+
// through \`window.opener\` and repoint the dashboard's own tab.
|
|
67
|
+
const NEWTAB = " target='_blank' rel='noopener noreferrer'";
|
|
68
|
+
// The item's label, linked to whatever the run's \`url\` points at (the tracker
|
|
69
|
+
// issue for a fix-loop run, the PR for a respond pass) when the row has one.
|
|
70
|
+
const itemLink = (r, label) =>
|
|
71
|
+
r.url ? "<a href='" + esc(r.url) + "'" + NEWTAB + ">" + esc(label) + "</a>" : esc(label);
|
|
60
72
|
|
|
61
73
|
// Expanded rows and fetched PR status outlive a re-render, so the 10s poll does
|
|
62
74
|
// not collapse what the operator opened. Keyed on ghostrail path plus run id,
|
|
@@ -84,7 +96,7 @@ function artifactsHtml(path, artifacts) {
|
|
|
84
96
|
if (!artifacts || artifacts.length === 0) return "";
|
|
85
97
|
const items = artifacts.map((a) => {
|
|
86
98
|
const href = api("/api/artifact?path=" + enc(path) + "&hash=" + enc(a.hash));
|
|
87
|
-
return "<li><a href='" + esc(href) + "'>" + esc(a.name) + "</a></li>";
|
|
99
|
+
return "<li><a href='" + esc(href) + "'" + NEWTAB + ">" + esc(a.name) + "</a></li>";
|
|
88
100
|
}).join("");
|
|
89
101
|
return "<div><b>Artifacts</b><ul>" + items + "</ul></div>";
|
|
90
102
|
}
|
|
@@ -92,7 +104,9 @@ function artifactsHtml(path, artifacts) {
|
|
|
92
104
|
function checkLineHtml(c) {
|
|
93
105
|
const label = c.conclusion || c.status;
|
|
94
106
|
const cls = c.conclusion === "success" ? "done" : c.conclusion ? "failed" : "muted";
|
|
95
|
-
const name = c.url
|
|
107
|
+
const name = c.url
|
|
108
|
+
? "<a href='" + esc(c.url) + "'" + NEWTAB + ">" + esc(c.name) + "</a>"
|
|
109
|
+
: esc(c.name);
|
|
96
110
|
return "<li>" + name + ": <span class='" + cls + "'>" + esc(label) + "</span></li>";
|
|
97
111
|
}
|
|
98
112
|
|
|
@@ -114,9 +128,19 @@ function prStatusHtml(status) {
|
|
|
114
128
|
return parts.join("");
|
|
115
129
|
}
|
|
116
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
|
+
|
|
117
138
|
function detailHtml(path, r) {
|
|
118
139
|
const parts = [];
|
|
119
|
-
parts.push(
|
|
140
|
+
parts.push(
|
|
141
|
+
"<div><b>Item</b> " + itemLink(r, r.itemId || "") +
|
|
142
|
+
(r.kind ? " (" + esc(r.kind) + ")" : "") + "</div>",
|
|
143
|
+
);
|
|
120
144
|
if (r.branch) parts.push("<div><b>Branch</b> " + esc(r.branch) + "</div>");
|
|
121
145
|
if (r.backend) parts.push("<div><b>Backend</b> " + esc(r.backend) + "</div>");
|
|
122
146
|
if (r.costUsd !== undefined) {
|
|
@@ -129,13 +153,17 @@ function detailHtml(path, r) {
|
|
|
129
153
|
if (r.error) parts.push("<div><b>Error</b><pre class=failed>" + esc(r.error) + "</pre></div>");
|
|
130
154
|
if (r.summary) parts.push("<div><b>Comment</b><pre>" + esc(r.summary) + "</pre></div>");
|
|
131
155
|
if (r.prUrl) {
|
|
132
|
-
parts.push(
|
|
156
|
+
parts.push(
|
|
157
|
+
"<div><b>PR</b> <a href='" + esc(r.prUrl) + "'" + NEWTAB + ">" + esc(r.prUrl) + "</a></div>",
|
|
158
|
+
);
|
|
133
159
|
}
|
|
160
|
+
parts.push(mergeButtonHtml(path, r));
|
|
134
161
|
parts.push(artifactsHtml(path, r.artifacts));
|
|
135
162
|
if (r.prNumber !== undefined) parts.push(prStatusHtml(prStatus.get(rowKey(path, r.runId))));
|
|
136
163
|
parts.push(
|
|
137
164
|
"<div class=muted>started " + esc(fmtWhen(r.startedAt)) +
|
|
138
|
-
", finished " + esc(fmtWhen(r.finishedAt))
|
|
165
|
+
(r.finishedAt ? ", finished " + esc(fmtWhen(r.finishedAt)) : ", still running") +
|
|
166
|
+
"</div>",
|
|
139
167
|
);
|
|
140
168
|
return parts.join("");
|
|
141
169
|
}
|
|
@@ -147,14 +175,16 @@ function runsHtml(g, gi) {
|
|
|
147
175
|
const body = g.runs.map((r, ri) => {
|
|
148
176
|
const open = expanded.has(rowKey(g.path, r.runId));
|
|
149
177
|
const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? "✓" : "✗")).join("");
|
|
150
|
-
const pr = r.prUrl
|
|
178
|
+
const pr = r.prUrl
|
|
179
|
+
? "<a href='" + esc(r.prUrl) + "'" + NEWTAB + ">#" + esc(r.prNumber) + "</a>"
|
|
180
|
+
: "";
|
|
151
181
|
const state = esc(r.state || ${JSON.stringify(IN_PROGRESS_STATE)});
|
|
152
182
|
const main = "<tr>" +
|
|
153
183
|
"<td><button class='twirl" + (open ? " open" : "") + "' id='t-" + gi + "-" + ri + "'" +
|
|
154
184
|
" aria-expanded='" + open + "' data-path='" + esc(g.path) + "' data-run='" + esc(r.runId) + "'" +
|
|
155
185
|
" title='show detail'>▶</button></td>" +
|
|
156
186
|
"<td class=muted>" + esc(r.runId.slice(0, 8)) + "</td>" +
|
|
157
|
-
"<td>" +
|
|
187
|
+
"<td>" + itemLink(r, r.title || r.itemId || "") + "</td>" +
|
|
158
188
|
"<td class='state " + state + "'>" + state + "</td>" +
|
|
159
189
|
"<td>" + esc(r.backend || "") + "</td>" +
|
|
160
190
|
"<td>" + gate + "</td>" +
|
|
@@ -181,6 +211,7 @@ function render(ghostrails) {
|
|
|
181
211
|
"<span class='name'>" + esc(g.name) + "</span>" +
|
|
182
212
|
"<span class='pill'>" + esc(g.state) + "</span>" +
|
|
183
213
|
"<span class='path'>" + esc(g.path) + "</span>" +
|
|
214
|
+
"<a class='review' href='" + esc(api("review?path=" + enc(g.path))) + "'>review</a>" +
|
|
184
215
|
"</div>" + runsHtml(g, gi) + "</div>"
|
|
185
216
|
).join("");
|
|
186
217
|
}
|
|
@@ -218,11 +249,34 @@ function toggle(path, runId, buttonId) {
|
|
|
218
249
|
if (expanded.has(k)) refreshPrStatus();
|
|
219
250
|
}
|
|
220
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
|
+
|
|
221
270
|
const list = document.getElementById("list");
|
|
222
271
|
|
|
223
272
|
list.addEventListener("click", (e) => {
|
|
224
|
-
const
|
|
225
|
-
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);
|
|
226
280
|
});
|
|
227
281
|
|
|
228
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"}
|