agent-relay-server 0.26.0 → 0.27.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/package.json +2 -2
- package/public/index.html +116 -63
- package/src/routes.ts +9 -8
- package/src/workspace-actions.ts +32 -2
- package/src/workspace-merge.ts +5 -1
- package/src/workspace-orphans.ts +152 -14
- package/src/workspace-phase.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Lightweight HTTP message relay for inter-agent communication across machines",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"CONTRIBUTING.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"agent-relay-sdk": "0.2.
|
|
36
|
+
"agent-relay-sdk": "0.2.16"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prepack": "bun run build:dashboard:bundle >&2",
|
package/public/index.html
CHANGED
|
@@ -11806,36 +11806,36 @@ function downloadText(filename, text, type) {
|
|
|
11806
11806
|
}
|
|
11807
11807
|
//#endregion
|
|
11808
11808
|
//#region ../sdk/src/provider-catalog.ts
|
|
11809
|
-
var CLAUDE_LOW_TO_MAX = [
|
|
11809
|
+
var CLAUDE_LOW_TO_MAX$1 = [
|
|
11810
11810
|
"low",
|
|
11811
11811
|
"medium",
|
|
11812
11812
|
"high",
|
|
11813
11813
|
"max"
|
|
11814
11814
|
];
|
|
11815
|
-
var CLAUDE_LOW_TO_XHIGH_MAX = [
|
|
11815
|
+
var CLAUDE_LOW_TO_XHIGH_MAX$1 = [
|
|
11816
11816
|
"low",
|
|
11817
11817
|
"medium",
|
|
11818
11818
|
"high",
|
|
11819
11819
|
"xhigh",
|
|
11820
11820
|
"max"
|
|
11821
11821
|
];
|
|
11822
|
-
var CODEX_REASONING = [
|
|
11822
|
+
var CODEX_REASONING$1 = [
|
|
11823
11823
|
"low",
|
|
11824
11824
|
"medium",
|
|
11825
11825
|
"high",
|
|
11826
11826
|
"xhigh"
|
|
11827
11827
|
];
|
|
11828
|
-
var CONTEXT_200K = {
|
|
11828
|
+
var CONTEXT_200K$1 = {
|
|
11829
11829
|
value: 2e5,
|
|
11830
11830
|
source: "catalog",
|
|
11831
11831
|
confidence: "declared"
|
|
11832
11832
|
};
|
|
11833
|
-
var CONTEXT_1M = {
|
|
11833
|
+
var CONTEXT_1M$1 = {
|
|
11834
11834
|
value: 1e6,
|
|
11835
11835
|
source: "catalog",
|
|
11836
11836
|
confidence: "declared"
|
|
11837
11837
|
};
|
|
11838
|
-
var CODE_MODEL_CAPABILITIES = {
|
|
11838
|
+
var CODE_MODEL_CAPABILITIES$1 = {
|
|
11839
11839
|
modalities: {
|
|
11840
11840
|
input: {
|
|
11841
11841
|
text: true,
|
|
@@ -11852,88 +11852,96 @@ var CODE_MODEL_CAPABILITIES = {
|
|
|
11852
11852
|
source: "catalog",
|
|
11853
11853
|
confidence: "declared"
|
|
11854
11854
|
};
|
|
11855
|
-
function codeModel(limits) {
|
|
11855
|
+
function codeModel$1(limits) {
|
|
11856
11856
|
return {
|
|
11857
11857
|
...limits ? { limits } : {},
|
|
11858
|
-
capabilities: CODE_MODEL_CAPABILITIES
|
|
11858
|
+
capabilities: CODE_MODEL_CAPABILITIES$1
|
|
11859
11859
|
};
|
|
11860
11860
|
}
|
|
11861
|
-
var PROVIDER_CATALOG = {
|
|
11861
|
+
var PROVIDER_CATALOG$1 = {
|
|
11862
11862
|
claude: {
|
|
11863
11863
|
provider: "claude",
|
|
11864
11864
|
label: "Claude Code",
|
|
11865
11865
|
defaultModel: "sonnet-4.6",
|
|
11866
11866
|
models: [
|
|
11867
|
+
{
|
|
11868
|
+
alias: "fable-5",
|
|
11869
|
+
label: "Fable 5",
|
|
11870
|
+
providerModel: "claude-fable-5",
|
|
11871
|
+
efforts: CLAUDE_LOW_TO_XHIGH_MAX$1,
|
|
11872
|
+
defaultEffort: "medium",
|
|
11873
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_1M$1 })
|
|
11874
|
+
},
|
|
11867
11875
|
{
|
|
11868
11876
|
alias: "opus-4.8",
|
|
11869
11877
|
label: "Opus 4.8",
|
|
11870
11878
|
providerModel: "claude-opus-4-8",
|
|
11871
|
-
efforts: CLAUDE_LOW_TO_XHIGH_MAX,
|
|
11879
|
+
efforts: CLAUDE_LOW_TO_XHIGH_MAX$1,
|
|
11872
11880
|
defaultEffort: "medium",
|
|
11873
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11881
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11874
11882
|
},
|
|
11875
11883
|
{
|
|
11876
11884
|
alias: "opus-4.8[1m]",
|
|
11877
11885
|
label: "Opus 4.8 1M",
|
|
11878
11886
|
providerModel: "claude-opus-4-8[1m]",
|
|
11879
|
-
efforts: CLAUDE_LOW_TO_XHIGH_MAX,
|
|
11887
|
+
efforts: CLAUDE_LOW_TO_XHIGH_MAX$1,
|
|
11880
11888
|
defaultEffort: "medium",
|
|
11881
|
-
...codeModel({ contextWindowTokens: CONTEXT_1M })
|
|
11889
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_1M$1 })
|
|
11882
11890
|
},
|
|
11883
11891
|
{
|
|
11884
11892
|
alias: "opus-4.7",
|
|
11885
11893
|
label: "Opus 4.7",
|
|
11886
11894
|
providerModel: "claude-opus-4-7",
|
|
11887
|
-
efforts: CLAUDE_LOW_TO_XHIGH_MAX,
|
|
11895
|
+
efforts: CLAUDE_LOW_TO_XHIGH_MAX$1,
|
|
11888
11896
|
defaultEffort: "medium",
|
|
11889
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11897
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11890
11898
|
},
|
|
11891
11899
|
{
|
|
11892
11900
|
alias: "opus-4.7[1m]",
|
|
11893
11901
|
label: "Opus 4.7 1M",
|
|
11894
11902
|
providerModel: "claude-opus-4-7[1m]",
|
|
11895
|
-
efforts: CLAUDE_LOW_TO_XHIGH_MAX,
|
|
11903
|
+
efforts: CLAUDE_LOW_TO_XHIGH_MAX$1,
|
|
11896
11904
|
defaultEffort: "medium",
|
|
11897
|
-
...codeModel({ contextWindowTokens: CONTEXT_1M })
|
|
11905
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_1M$1 })
|
|
11898
11906
|
},
|
|
11899
11907
|
{
|
|
11900
11908
|
alias: "opus-4.6",
|
|
11901
11909
|
label: "Opus 4.6",
|
|
11902
11910
|
providerModel: "claude-opus-4-6",
|
|
11903
|
-
efforts: CLAUDE_LOW_TO_MAX,
|
|
11911
|
+
efforts: CLAUDE_LOW_TO_MAX$1,
|
|
11904
11912
|
defaultEffort: "medium",
|
|
11905
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11913
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11906
11914
|
},
|
|
11907
11915
|
{
|
|
11908
11916
|
alias: "opus-4.6[1m]",
|
|
11909
11917
|
label: "Opus 4.6 1M",
|
|
11910
11918
|
providerModel: "claude-opus-4-6[1m]",
|
|
11911
|
-
efforts: CLAUDE_LOW_TO_MAX,
|
|
11919
|
+
efforts: CLAUDE_LOW_TO_MAX$1,
|
|
11912
11920
|
defaultEffort: "medium",
|
|
11913
|
-
...codeModel({ contextWindowTokens: CONTEXT_1M })
|
|
11921
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_1M$1 })
|
|
11914
11922
|
},
|
|
11915
11923
|
{
|
|
11916
11924
|
alias: "sonnet-4.6",
|
|
11917
11925
|
label: "Sonnet 4.6",
|
|
11918
11926
|
providerModel: "claude-sonnet-4-6",
|
|
11919
|
-
efforts: CLAUDE_LOW_TO_MAX,
|
|
11927
|
+
efforts: CLAUDE_LOW_TO_MAX$1,
|
|
11920
11928
|
defaultEffort: "medium",
|
|
11921
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11929
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11922
11930
|
},
|
|
11923
11931
|
{
|
|
11924
11932
|
alias: "sonnet-4.6[1m]",
|
|
11925
11933
|
label: "Sonnet 4.6 1M",
|
|
11926
11934
|
providerModel: "claude-sonnet-4-6[1m]",
|
|
11927
|
-
efforts: CLAUDE_LOW_TO_MAX,
|
|
11935
|
+
efforts: CLAUDE_LOW_TO_MAX$1,
|
|
11928
11936
|
defaultEffort: "medium",
|
|
11929
|
-
...codeModel({ contextWindowTokens: CONTEXT_1M })
|
|
11937
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_1M$1 })
|
|
11930
11938
|
},
|
|
11931
11939
|
{
|
|
11932
11940
|
alias: "haiku",
|
|
11933
11941
|
label: "Haiku",
|
|
11934
11942
|
providerModel: "haiku",
|
|
11935
11943
|
efforts: [],
|
|
11936
|
-
...codeModel()
|
|
11944
|
+
...codeModel$1()
|
|
11937
11945
|
}
|
|
11938
11946
|
]
|
|
11939
11947
|
},
|
|
@@ -11946,49 +11954,49 @@ var PROVIDER_CATALOG = {
|
|
|
11946
11954
|
alias: "gpt-5.5",
|
|
11947
11955
|
label: "GPT-5.5",
|
|
11948
11956
|
providerModel: "gpt-5.5",
|
|
11949
|
-
efforts: CODEX_REASONING,
|
|
11957
|
+
efforts: CODEX_REASONING$1,
|
|
11950
11958
|
defaultEffort: "medium",
|
|
11951
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11959
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11952
11960
|
},
|
|
11953
11961
|
{
|
|
11954
11962
|
alias: "gpt-5.4",
|
|
11955
11963
|
label: "GPT-5.4",
|
|
11956
11964
|
providerModel: "gpt-5.4",
|
|
11957
|
-
efforts: CODEX_REASONING,
|
|
11965
|
+
efforts: CODEX_REASONING$1,
|
|
11958
11966
|
defaultEffort: "medium",
|
|
11959
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11967
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11960
11968
|
},
|
|
11961
11969
|
{
|
|
11962
11970
|
alias: "gpt-5.4-mini",
|
|
11963
11971
|
label: "GPT-5.4 Mini",
|
|
11964
11972
|
providerModel: "gpt-5.4-mini",
|
|
11965
|
-
efforts: CODEX_REASONING,
|
|
11973
|
+
efforts: CODEX_REASONING$1,
|
|
11966
11974
|
defaultEffort: "medium",
|
|
11967
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11975
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11968
11976
|
},
|
|
11969
11977
|
{
|
|
11970
11978
|
alias: "gpt-5.3-codex",
|
|
11971
11979
|
label: "GPT-5.3 Codex",
|
|
11972
11980
|
providerModel: "gpt-5.3-codex",
|
|
11973
|
-
efforts: CODEX_REASONING,
|
|
11981
|
+
efforts: CODEX_REASONING$1,
|
|
11974
11982
|
defaultEffort: "medium",
|
|
11975
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11983
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11976
11984
|
},
|
|
11977
11985
|
{
|
|
11978
11986
|
alias: "gpt-5.3-codex-spark",
|
|
11979
11987
|
label: "GPT-5.3 Codex Spark",
|
|
11980
11988
|
providerModel: "gpt-5.3-codex-spark",
|
|
11981
|
-
efforts: CODEX_REASONING,
|
|
11989
|
+
efforts: CODEX_REASONING$1,
|
|
11982
11990
|
defaultEffort: "high",
|
|
11983
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11991
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11984
11992
|
},
|
|
11985
11993
|
{
|
|
11986
11994
|
alias: "gpt-5.2",
|
|
11987
11995
|
label: "GPT-5.2",
|
|
11988
11996
|
providerModel: "gpt-5.2",
|
|
11989
|
-
efforts: CODEX_REASONING,
|
|
11997
|
+
efforts: CODEX_REASONING$1,
|
|
11990
11998
|
defaultEffort: "medium",
|
|
11991
|
-
...codeModel({ contextWindowTokens: CONTEXT_200K })
|
|
11999
|
+
...codeModel$1({ contextWindowTokens: CONTEXT_200K$1 })
|
|
11992
12000
|
}
|
|
11993
12001
|
]
|
|
11994
12002
|
}
|
|
@@ -11999,7 +12007,7 @@ function firstAvailableProvider(orchestrator) {
|
|
|
11999
12007
|
return orchestrator?.providers[0] ?? "claude";
|
|
12000
12008
|
}
|
|
12001
12009
|
function defaultModelFor(provider) {
|
|
12002
|
-
return PROVIDER_CATALOG[provider]?.defaultModel ?? "";
|
|
12010
|
+
return PROVIDER_CATALOG$1[provider]?.defaultModel ?? "";
|
|
12003
12011
|
}
|
|
12004
12012
|
function pathWithinBase(path, baseDir) {
|
|
12005
12013
|
const value = path.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -13876,7 +13884,7 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
13876
13884
|
const provCaps = agent.providerCapabilities;
|
|
13877
13885
|
const provider = provCaps?.model?.provider || (typeof agent.meta?.provider === "string" ? agent.meta.provider : "");
|
|
13878
13886
|
const reportedModel = provCaps?.model?.alias || provCaps?.model?.id || "";
|
|
13879
|
-
const model = ((provider ? PROVIDER_CATALOG[provider] : void 0)?.models.find((m) => m.alias === reportedModel || m.providerModel === reportedModel))?.alias || "";
|
|
13887
|
+
const model = ((provider ? PROVIDER_CATALOG$1[provider] : void 0)?.models.find((m) => m.alias === reportedModel || m.providerModel === reportedModel))?.alias || "";
|
|
13880
13888
|
const effort = provCaps?.model?.effort || "";
|
|
13881
13889
|
const cwd = typeof agent.meta?.cwd === "string" ? agent.meta.cwd : "";
|
|
13882
13890
|
const approval = provCaps?.session?.approvalMode || (typeof agent.meta?.approvalMode === "string" ? agent.meta.approvalMode : "guarded");
|
|
@@ -91948,10 +91956,10 @@ function wrap(middleware, callback) {
|
|
|
91948
91956
|
//#endregion
|
|
91949
91957
|
//#region node_modules/vfile/lib/minpath.browser.js
|
|
91950
91958
|
var minpath = {
|
|
91951
|
-
basename: basename$
|
|
91952
|
-
dirname,
|
|
91959
|
+
basename: basename$3,
|
|
91960
|
+
dirname: dirname$1,
|
|
91953
91961
|
extname,
|
|
91954
|
-
join,
|
|
91962
|
+
join: join$1,
|
|
91955
91963
|
sep: "/"
|
|
91956
91964
|
};
|
|
91957
91965
|
/**
|
|
@@ -91964,7 +91972,7 @@ var minpath = {
|
|
|
91964
91972
|
* @returns {string}
|
|
91965
91973
|
* Stem or basename.
|
|
91966
91974
|
*/
|
|
91967
|
-
function basename$
|
|
91975
|
+
function basename$3(path, extname) {
|
|
91968
91976
|
if (extname !== void 0 && typeof extname !== "string") throw new TypeError("\"ext\" argument must be a string");
|
|
91969
91977
|
assertPath$1(path);
|
|
91970
91978
|
let start = 0;
|
|
@@ -92016,7 +92024,7 @@ function basename$2(path, extname) {
|
|
|
92016
92024
|
* @returns {string}
|
|
92017
92025
|
* File path.
|
|
92018
92026
|
*/
|
|
92019
|
-
function dirname(path) {
|
|
92027
|
+
function dirname$1(path) {
|
|
92020
92028
|
assertPath$1(path);
|
|
92021
92029
|
if (path.length === 0) return ".";
|
|
92022
92030
|
let end = -1;
|
|
@@ -92077,7 +92085,7 @@ function extname(path) {
|
|
|
92077
92085
|
* @returns {string}
|
|
92078
92086
|
* File path.
|
|
92079
92087
|
*/
|
|
92080
|
-
function join(...segments) {
|
|
92088
|
+
function join$1(...segments) {
|
|
92081
92089
|
let index = -1;
|
|
92082
92090
|
/** @type {string | undefined} */
|
|
92083
92091
|
let joined;
|
|
@@ -101370,10 +101378,10 @@ function mergeObjects(target, ...sources) {
|
|
|
101370
101378
|
});
|
|
101371
101379
|
return target;
|
|
101372
101380
|
}
|
|
101373
|
-
function basename$
|
|
101381
|
+
function basename$2(path) {
|
|
101374
101382
|
const idx = ~path.lastIndexOf("/") || ~path.lastIndexOf("\\");
|
|
101375
101383
|
if (idx === 0) return path;
|
|
101376
|
-
else if (~idx === path.length - 1) return basename$
|
|
101384
|
+
else if (~idx === path.length - 1) return basename$2(path.substring(0, path.length - 1));
|
|
101377
101385
|
else return path.substr(~idx + 1);
|
|
101378
101386
|
}
|
|
101379
101387
|
function strcmp(a, b) {
|
|
@@ -102426,7 +102434,7 @@ var init_dist$2 = __esmMin((() => {
|
|
|
102426
102434
|
this._contentNameIsCapturing = RegexSource.hasCaptures(this._contentName);
|
|
102427
102435
|
}
|
|
102428
102436
|
get debugName() {
|
|
102429
|
-
const location = this.$location ? `${basename$
|
|
102437
|
+
const location = this.$location ? `${basename$2(this.$location.filename)}:${this.$location.line}` : "unknown";
|
|
102430
102438
|
return `${this.constructor.name}#${this.id} @ ${location}`;
|
|
102431
102439
|
}
|
|
102432
102440
|
getName(lineText, captureIndices) {
|
|
@@ -108590,7 +108598,7 @@ function getShiki() {
|
|
|
108590
108598
|
function byteLength(value) {
|
|
108591
108599
|
return new TextEncoder().encode(value).length;
|
|
108592
108600
|
}
|
|
108593
|
-
function basename(path) {
|
|
108601
|
+
function basename$1(path) {
|
|
108594
108602
|
return path.split("/").filter(Boolean).pop() || path;
|
|
108595
108603
|
}
|
|
108596
108604
|
function detectShebangLanguage(content) {
|
|
@@ -108609,7 +108617,7 @@ function detectShebangLanguage(content) {
|
|
|
108609
108617
|
}
|
|
108610
108618
|
function detectCodeLanguage(path, mediaType, languageHint, content) {
|
|
108611
108619
|
if (languageHint) return languageHint;
|
|
108612
|
-
const name = basename(path).toLowerCase();
|
|
108620
|
+
const name = basename$1(path).toLowerCase();
|
|
108613
108621
|
if (BASENAME_LANGUAGES[name]) return BASENAME_LANGUAGES[name];
|
|
108614
108622
|
if (name.endsWith(".d.ts")) return "typescript";
|
|
108615
108623
|
const shebangLanguage = detectShebangLanguage(content);
|
|
@@ -129418,6 +129426,51 @@ function OrchestratorsView() {
|
|
|
129418
129426
|
});
|
|
129419
129427
|
}
|
|
129420
129428
|
//#endregion
|
|
129429
|
+
//#region ../sdk/dist/types.js
|
|
129430
|
+
/** Terminal workspace statuses shared across server + dashboard filters. */
|
|
129431
|
+
var TERMINAL_WORKSPACE_STATUS_VALUES = [
|
|
129432
|
+
"cleaned",
|
|
129433
|
+
"merged",
|
|
129434
|
+
"abandoned"
|
|
129435
|
+
];
|
|
129436
|
+
var CONTEXT_200K = {
|
|
129437
|
+
value: 2e5,
|
|
129438
|
+
source: "catalog",
|
|
129439
|
+
confidence: "declared"
|
|
129440
|
+
};
|
|
129441
|
+
var CONTEXT_1M = {
|
|
129442
|
+
value: 1e6,
|
|
129443
|
+
source: "catalog",
|
|
129444
|
+
confidence: "declared"
|
|
129445
|
+
};
|
|
129446
|
+
var CODE_MODEL_CAPABILITIES = {
|
|
129447
|
+
modalities: {
|
|
129448
|
+
input: {
|
|
129449
|
+
text: true,
|
|
129450
|
+
image: true
|
|
129451
|
+
},
|
|
129452
|
+
output: { text: true }
|
|
129453
|
+
},
|
|
129454
|
+
tools: {
|
|
129455
|
+
code: true,
|
|
129456
|
+
review: true,
|
|
129457
|
+
debug: true,
|
|
129458
|
+
refactor: true
|
|
129459
|
+
},
|
|
129460
|
+
source: "catalog",
|
|
129461
|
+
confidence: "declared"
|
|
129462
|
+
};
|
|
129463
|
+
function codeModel(limits) {
|
|
129464
|
+
return {
|
|
129465
|
+
...limits ? { limits } : {},
|
|
129466
|
+
capabilities: CODE_MODEL_CAPABILITIES
|
|
129467
|
+
};
|
|
129468
|
+
}
|
|
129469
|
+
({ ...codeModel({ contextWindowTokens: CONTEXT_1M }) }), { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_1M }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_1M }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_1M }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_1M }) }, { ...codeModel() }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) }, { ...codeModel({ contextWindowTokens: CONTEXT_200K }) };
|
|
129470
|
+
(0, (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
129471
|
+
module.exports = {};
|
|
129472
|
+
})))().tmpdir)();
|
|
129473
|
+
//#endregion
|
|
129421
129474
|
//#region src/components/views/workspaces.tsx
|
|
129422
129475
|
var LIVE_STATUSES = new Set([
|
|
129423
129476
|
"active",
|
|
@@ -129474,7 +129527,7 @@ var STATUS_ORDER = {
|
|
|
129474
129527
|
abandoned: 7,
|
|
129475
129528
|
cleaned: 8
|
|
129476
129529
|
};
|
|
129477
|
-
var TERMINAL_STATUSES = new Set(
|
|
129530
|
+
var TERMINAL_STATUSES = new Set(TERMINAL_WORKSPACE_STATUS_VALUES);
|
|
129478
129531
|
function shortPath(path) {
|
|
129479
129532
|
const parts = path.split("/").filter(Boolean);
|
|
129480
129533
|
if (parts.length <= 3) return path || "-";
|
|
@@ -133144,7 +133197,7 @@ function blankForm(orchestratorId = "") {
|
|
|
133144
133197
|
orchestratorId,
|
|
133145
133198
|
targetMode: "existing_agent",
|
|
133146
133199
|
provider: "codex",
|
|
133147
|
-
model: PROVIDER_CATALOG.codex.defaultModel || "",
|
|
133200
|
+
model: PROVIDER_CATALOG$1.codex.defaultModel || "",
|
|
133148
133201
|
effort: "",
|
|
133149
133202
|
profile: "default-relay",
|
|
133150
133203
|
label: "",
|
|
@@ -133183,7 +133236,7 @@ function automationToForm(automation) {
|
|
|
133183
133236
|
orchestratorId: automation.orchestratorId,
|
|
133184
133237
|
targetMode: policy.mode,
|
|
133185
133238
|
provider: policy.mode === "on_demand_agent" ? policy.provider : policy.selector.provider || "codex",
|
|
133186
|
-
model: policy.mode === "on_demand_agent" ? policy.model || PROVIDER_CATALOG[policy.provider].defaultModel || "" : "",
|
|
133239
|
+
model: policy.mode === "on_demand_agent" ? policy.model || PROVIDER_CATALOG$1[policy.provider].defaultModel || "" : "",
|
|
133187
133240
|
effort: policy.mode === "on_demand_agent" ? policy.effort || "" : "",
|
|
133188
133241
|
profile: policy.mode === "on_demand_agent" ? policy.profile || "default-relay" : "default-relay",
|
|
133189
133242
|
label: policy.mode === "existing_agent" ? policy.selector.label || "" : "",
|
|
@@ -133593,21 +133646,21 @@ function AutomationCard({ automation, runs, now, selected, onEdit, onRun, onTogg
|
|
|
133593
133646
|
}
|
|
133594
133647
|
function AutomationEditor({ form, selected, saving, orchestrators, agentProfiles, onChange, onSave }) {
|
|
133595
133648
|
const providers = orchestrators.find((orch) => orch.id === form.orchestratorId)?.providers || [];
|
|
133596
|
-
const models = PROVIDER_CATALOG[form.provider]?.models || [];
|
|
133649
|
+
const models = PROVIDER_CATALOG$1[form.provider]?.models || [];
|
|
133597
133650
|
const efforts = models.find((model) => model.alias === form.model)?.efforts || [];
|
|
133598
133651
|
function selectOrchestrator(orchestratorId) {
|
|
133599
133652
|
const provider = orchestrators.find((orch) => orch.id === orchestratorId)?.providers[0] || form.provider;
|
|
133600
133653
|
onChange({
|
|
133601
133654
|
orchestratorId,
|
|
133602
133655
|
provider,
|
|
133603
|
-
model: PROVIDER_CATALOG[provider]?.defaultModel || "",
|
|
133656
|
+
model: PROVIDER_CATALOG$1[provider]?.defaultModel || "",
|
|
133604
133657
|
effort: ""
|
|
133605
133658
|
});
|
|
133606
133659
|
}
|
|
133607
133660
|
function selectProvider(provider) {
|
|
133608
133661
|
onChange({
|
|
133609
133662
|
provider,
|
|
133610
|
-
model: PROVIDER_CATALOG[provider]?.defaultModel || "",
|
|
133663
|
+
model: PROVIDER_CATALOG$1[provider]?.defaultModel || "",
|
|
133611
133664
|
effort: ""
|
|
133612
133665
|
});
|
|
133613
133666
|
}
|
|
@@ -157363,7 +157416,7 @@ function OrchestratorSpawnModal() {
|
|
|
157363
157416
|
const agentProfiles = useRelayStore((s) => s.agentProfiles);
|
|
157364
157417
|
const orch = orchestrators.find((o) => o.id === orchId);
|
|
157365
157418
|
const providers = orch?.providers || [];
|
|
157366
|
-
const models = PROVIDER_CATALOG[provider]?.models || [];
|
|
157419
|
+
const models = PROVIDER_CATALOG$1[provider]?.models || [];
|
|
157367
157420
|
const selectedModel = models.find((m) => m.alias === model);
|
|
157368
157421
|
const efforts = selectedModel?.efforts || [];
|
|
157369
157422
|
const canSpawn = Boolean(orch && providers.length > 0 && cwd.trim());
|
|
@@ -157373,7 +157426,7 @@ function OrchestratorSpawnModal() {
|
|
|
157373
157426
|
set({
|
|
157374
157427
|
spawnOrchId: nextId,
|
|
157375
157428
|
spawnProvider: nextProvider,
|
|
157376
|
-
spawnModel: nextProvider ? PROVIDER_CATALOG[nextProvider]?.defaultModel || "" : "",
|
|
157429
|
+
spawnModel: nextProvider ? PROVIDER_CATALOG$1[nextProvider]?.defaultModel || "" : "",
|
|
157377
157430
|
spawnEffort: "",
|
|
157378
157431
|
spawnCwd: nextOrch?.baseDir || ""
|
|
157379
157432
|
});
|
|
@@ -157381,7 +157434,7 @@ function OrchestratorSpawnModal() {
|
|
|
157381
157434
|
function selectProvider(nextProvider) {
|
|
157382
157435
|
set({
|
|
157383
157436
|
spawnProvider: nextProvider,
|
|
157384
|
-
spawnModel: PROVIDER_CATALOG[nextProvider]?.defaultModel || "",
|
|
157437
|
+
spawnModel: PROVIDER_CATALOG$1[nextProvider]?.defaultModel || "",
|
|
157385
157438
|
spawnEffort: ""
|
|
157386
157439
|
});
|
|
157387
157440
|
}
|
|
@@ -157710,7 +157763,7 @@ function ManagedPolicyModal() {
|
|
|
157710
157763
|
const submitPolicy = useRelayStore((s) => s.submitPolicy);
|
|
157711
157764
|
const orch = orchestrators.find((o) => o.id === m.orchestratorId);
|
|
157712
157765
|
const providers = orch?.providers || [];
|
|
157713
|
-
const models = PROVIDER_CATALOG[m.provider]?.models || [];
|
|
157766
|
+
const models = PROVIDER_CATALOG$1[m.provider]?.models || [];
|
|
157714
157767
|
const selectedModel = models.find((model) => model.alias === m.model);
|
|
157715
157768
|
const efforts = selectedModel?.efforts || [];
|
|
157716
157769
|
const isEditing = Boolean(m.editing);
|
|
@@ -157726,7 +157779,7 @@ function ManagedPolicyModal() {
|
|
|
157726
157779
|
update({
|
|
157727
157780
|
orchestratorId,
|
|
157728
157781
|
provider,
|
|
157729
|
-
model: PROVIDER_CATALOG[provider]?.defaultModel || "",
|
|
157782
|
+
model: PROVIDER_CATALOG$1[provider]?.defaultModel || "",
|
|
157730
157783
|
effort: ""
|
|
157731
157784
|
});
|
|
157732
157785
|
}
|
|
@@ -157734,7 +157787,7 @@ function ManagedPolicyModal() {
|
|
|
157734
157787
|
update({
|
|
157735
157788
|
provider,
|
|
157736
157789
|
rig: "",
|
|
157737
|
-
model: PROVIDER_CATALOG[provider]?.defaultModel || "",
|
|
157790
|
+
model: PROVIDER_CATALOG$1[provider]?.defaultModel || "",
|
|
157738
157791
|
effort: ""
|
|
157739
157792
|
});
|
|
157740
157793
|
}
|
package/src/routes.ts
CHANGED
|
@@ -167,7 +167,7 @@ import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, VALID_EFF
|
|
|
167
167
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
168
168
|
import { buildManagedSpawnParams, effectiveManagedPolicyWorkspaceMode } from "./managed-policy";
|
|
169
169
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams, type SpawnModelParams } from "./spawn-command";
|
|
170
|
-
import {
|
|
170
|
+
import { isOwnerAlive, withOwnerOnline } from "./workspace-merge";
|
|
171
171
|
import { claimMetadataPatch, workspaceActiveClaim } from "./workspace-claim";
|
|
172
172
|
import {
|
|
173
173
|
applyWorkspaceAction,
|
|
@@ -3813,7 +3813,7 @@ const getWorkspaces: Handler = (req) => {
|
|
|
3813
3813
|
const repoRoot = cleanString(url.searchParams.get("repoRoot") ?? undefined, "repoRoot", { max: 1000 });
|
|
3814
3814
|
const ownerAgentId = cleanString(url.searchParams.get("agentId") ?? undefined, "agentId", { max: 240 });
|
|
3815
3815
|
const status = optionalEnum(url.searchParams.get("status") ?? undefined, "status", VALID_WORKSPACE_STATUSES) as WorkspaceStatus | undefined;
|
|
3816
|
-
return json(listWorkspaces({ repoRoot, ownerAgentId, status }));
|
|
3816
|
+
return json(listWorkspaces({ repoRoot, ownerAgentId, status }).map(withOwnerOnline));
|
|
3817
3817
|
} catch (e) {
|
|
3818
3818
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
3819
3819
|
throw e;
|
|
@@ -3823,7 +3823,7 @@ const getWorkspaces: Handler = (req) => {
|
|
|
3823
3823
|
const getWorkspaceById: Handler = (_req, params) => {
|
|
3824
3824
|
const workspace = getWorkspace(params.id!);
|
|
3825
3825
|
if (!workspace) return error("workspace not found", 404);
|
|
3826
|
-
return json(workspace);
|
|
3826
|
+
return json(withOwnerOnline(workspace));
|
|
3827
3827
|
};
|
|
3828
3828
|
|
|
3829
3829
|
// Per-repo coordination state: persistent steward records (survive offline gaps)
|
|
@@ -4006,7 +4006,7 @@ const getWorkspaceDiagnostics: Handler = async (_req, params) => {
|
|
|
4006
4006
|
const workspace = getWorkspace(params.id!);
|
|
4007
4007
|
if (!workspace) return error("workspace not found", 404);
|
|
4008
4008
|
const owner = workspace.ownerAgentId ? getAgent(workspace.ownerAgentId) : null;
|
|
4009
|
-
const ownerOnline =
|
|
4009
|
+
const ownerOnline = isOwnerAlive(workspace.ownerAgentId);
|
|
4010
4010
|
const orch = listOrchestrators().find((candidate) => isPathWithinBase(workspace.sourceCwd, candidate.baseDir));
|
|
4011
4011
|
const orchOnline = Boolean(orch) && orch!.status === "online";
|
|
4012
4012
|
const fetched = await fetchWorkspaceGitState(workspace);
|
|
@@ -4055,7 +4055,7 @@ const postWorkspaceCleanupStale: Handler = async (req) => {
|
|
|
4055
4055
|
const cleaned: string[] = [];
|
|
4056
4056
|
for (const ws of candidates) {
|
|
4057
4057
|
const owner = ws.ownerAgentId ? getAgent(ws.ownerAgentId) : null;
|
|
4058
|
-
const ownerOnline =
|
|
4058
|
+
const ownerOnline = isOwnerAlive(ws.ownerAgentId);
|
|
4059
4059
|
if (ownerOnline) continue; // never clean a live owner's worktree
|
|
4060
4060
|
if (offlineOwnerOnly && !ws.ownerAgentId) { /* no owner recorded — still eligible */ }
|
|
4061
4061
|
if (workspaceActiveClaim(ws)) continue; // respect steward claims
|
|
@@ -4117,7 +4117,7 @@ const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
4117
4117
|
// plus the directive projection + land receipt so the CLI `--wait` and any
|
|
4118
4118
|
// HTTP caller get the same legible answer.
|
|
4119
4119
|
return json({
|
|
4120
|
-
workspace: waited.workspace,
|
|
4120
|
+
workspace: withOwnerOnline(waited.workspace),
|
|
4121
4121
|
guidance: describeWorkspacePhase(waited.workspace),
|
|
4122
4122
|
...(landed ? { landed } : {}),
|
|
4123
4123
|
fromStatus: waited.fromStatus,
|
|
@@ -4125,7 +4125,7 @@ const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
4125
4125
|
timedOut: waited.timedOut,
|
|
4126
4126
|
});
|
|
4127
4127
|
}
|
|
4128
|
-
return json(workspace);
|
|
4128
|
+
return json(withOwnerOnline(workspace));
|
|
4129
4129
|
}
|
|
4130
4130
|
|
|
4131
4131
|
// Everything else delegates to the shared core (one home, shared with the
|
|
@@ -4138,6 +4138,7 @@ const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
4138
4138
|
metadata: cleanMeta(parsed.body.metadata) ?? {},
|
|
4139
4139
|
strategy: optionalEnum(parsed.body.strategy, "strategy", ["pr", "rebase-ff", "auto"] as const, "auto") as WorkspaceMergeStrategy,
|
|
4140
4140
|
deleteBranch: typeof parsed.body.deleteBranch === "boolean" ? parsed.body.deleteBranch : undefined,
|
|
4141
|
+
force: parsed.body.force === true,
|
|
4141
4142
|
prTitle: cleanString(parsed.body.prTitle, "prTitle", { max: 240 }),
|
|
4142
4143
|
prBody: cleanString(parsed.body.prBody, "prBody", { max: 8000 }),
|
|
4143
4144
|
purpose: cleanString(parsed.body.purpose, "purpose", { max: 120 }),
|
|
@@ -4146,7 +4147,7 @@ const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
4146
4147
|
});
|
|
4147
4148
|
if (!result.ok) return error(result.error, result.httpStatus);
|
|
4148
4149
|
if (result.command) emitCommand(result.command);
|
|
4149
|
-
const payload: Record<string, unknown> = { workspace: result.workspace };
|
|
4150
|
+
const payload: Record<string, unknown> = { workspace: withOwnerOnline(result.workspace) };
|
|
4150
4151
|
if (result.command) payload.command = result.command;
|
|
4151
4152
|
if (result.claim !== undefined) payload.claim = result.claim;
|
|
4152
4153
|
return json(payload, result.httpStatus);
|
package/src/workspace-actions.ts
CHANGED
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
updateWorkspaceStatus,
|
|
20
20
|
} from "./db";
|
|
21
21
|
import { emitActivityEvent } from "./sse";
|
|
22
|
-
import { requestWorkspaceMerge } from "./workspace-merge";
|
|
22
|
+
import { isOwnerAlive, requestWorkspaceMerge } from "./workspace-merge";
|
|
23
23
|
import { claimMetadataPatch, workspaceActiveClaim } from "./workspace-claim";
|
|
24
|
+
import { TERMINAL_WORKSPACE_STATUSES } from "./workspace-phase";
|
|
24
25
|
import type { Command, WorkspaceMergeStrategy, WorkspaceRecord, WorkspaceStatus } from "./types";
|
|
25
26
|
|
|
26
27
|
// Single source of truth for the action verb set. The route's `optionalEnum` and
|
|
@@ -50,6 +51,8 @@ export interface ApplyWorkspaceActionInput {
|
|
|
50
51
|
deleteBranch?: boolean;
|
|
51
52
|
prTitle?: string;
|
|
52
53
|
prBody?: string;
|
|
54
|
+
// cleanup
|
|
55
|
+
force?: boolean;
|
|
53
56
|
// claim / release-claim
|
|
54
57
|
purpose?: string;
|
|
55
58
|
// deps-refresh
|
|
@@ -203,6 +206,20 @@ export function applyWorkspaceAction(workspace: WorkspaceRecord, input: ApplyWor
|
|
|
203
206
|
const nextStatus = STATUS_BY_ACTION[action];
|
|
204
207
|
if (!nextStatus) return { ok: false, httpStatus: 400, error: `unsupported action: ${action}` };
|
|
205
208
|
|
|
209
|
+
if (
|
|
210
|
+
action === "cleanup" &&
|
|
211
|
+
input.force !== true &&
|
|
212
|
+
workspace.mode === "isolated" &&
|
|
213
|
+
!TERMINAL_WORKSPACE_STATUSES.has(workspace.status) &&
|
|
214
|
+
isOwnerAlive(workspace.ownerAgentId)
|
|
215
|
+
) {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
httpStatus: 409,
|
|
219
|
+
error: `workspace ${workspace.id} owner is still online; pass force:true to clean it up intentionally`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
206
223
|
const updated = updateWorkspaceStatus(workspace.id, nextStatus, {
|
|
207
224
|
...metadata,
|
|
208
225
|
...(detail ? { detail } : {}),
|
|
@@ -215,7 +232,7 @@ export function applyWorkspaceAction(workspace: WorkspaceRecord, input: ApplyWor
|
|
|
215
232
|
let command: Command | undefined;
|
|
216
233
|
if (requiresCommand) {
|
|
217
234
|
// Only `cleanup` reaches here — `merge` returned early via the shared helper.
|
|
218
|
-
const built = buildWorkspaceCleanupCommand(workspace, agentId ?? "dashboard");
|
|
235
|
+
const built = buildWorkspaceCleanupCommand(workspace, agentId ?? "dashboard", { force: input.force === true });
|
|
219
236
|
if (!built.ok) return { ok: false, httpStatus: built.status, error: built.error };
|
|
220
237
|
command = built.command;
|
|
221
238
|
}
|
|
@@ -237,7 +254,20 @@ export function applyWorkspaceAction(workspace: WorkspaceRecord, input: ApplyWor
|
|
|
237
254
|
export function buildWorkspaceCleanupCommand(
|
|
238
255
|
workspace: WorkspaceRecord,
|
|
239
256
|
requestedBy: string,
|
|
257
|
+
opts: { force?: boolean } = {},
|
|
240
258
|
): { ok: true; command: Command } | { ok: false; status: number; error: string } {
|
|
259
|
+
if (
|
|
260
|
+
opts.force !== true &&
|
|
261
|
+
workspace.mode === "isolated" &&
|
|
262
|
+
!TERMINAL_WORKSPACE_STATUSES.has(workspace.status) &&
|
|
263
|
+
isOwnerAlive(workspace.ownerAgentId)
|
|
264
|
+
) {
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
status: 409,
|
|
268
|
+
error: `workspace ${workspace.id} owner is still online; pass force:true to clean it up intentionally`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
241
271
|
const owners = listOrchestrators().filter((candidate) => isPathWithinBase(workspace.sourceCwd, candidate.baseDir));
|
|
242
272
|
const owner = owners.find((candidate) => candidate.status === "online") ?? owners[0];
|
|
243
273
|
if (!owner) return { ok: false, status: 409, error: "no orchestrator owns this workspace path; use DELETE /api/workspaces/:id to purge the record" };
|
package/src/workspace-merge.ts
CHANGED
|
@@ -32,12 +32,16 @@ export type RequestWorkspaceMergeResult =
|
|
|
32
32
|
|
|
33
33
|
// The owner is "alive" while its relay agent exists and isn't offline (online or
|
|
34
34
|
// a borderline-stale disconnect both count — don't nuke a worktree on a blip).
|
|
35
|
-
function isOwnerAlive(ownerAgentId: string | undefined): boolean {
|
|
35
|
+
export function isOwnerAlive(ownerAgentId: string | undefined): boolean {
|
|
36
36
|
if (!ownerAgentId) return false;
|
|
37
37
|
const agent = getAgent(ownerAgentId);
|
|
38
38
|
return Boolean(agent) && agent!.status !== "offline";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function withOwnerOnline<T extends { ownerAgentId?: string }>(workspace: T): T & { ownerOnline: boolean } {
|
|
42
|
+
return { ...workspace, ownerOnline: isOwnerAlive(workspace.ownerAgentId) };
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* Dispatch a base merge for an isolated workspace, serialized by the per-repo
|
|
43
47
|
* merge lease (issue #157). Single source of truth shared by the manual
|
package/src/workspace-orphans.ts
CHANGED
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
// prune` (a no-op while the directory exists) never was.
|
|
13
13
|
|
|
14
14
|
import { resolve } from "node:path";
|
|
15
|
-
import { RELAY_TOKEN_HEADER
|
|
16
|
-
import type { WorkspaceMergePreview, WorkspaceOrphan, WorkspaceProbe, WorkspaceRecord } from "./types";
|
|
17
|
-
import { createActivityEvent, listOrchestrators, listWorkspaces } from "./db";
|
|
15
|
+
import { RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
16
|
+
import type { WorkspaceMergePreview, WorkspaceOrphan, WorkspaceProbe, WorkspaceRecord, WorkspaceStatus } from "./types";
|
|
17
|
+
import { createActivityEvent, getWorkspace, listOrchestrators, listWorkspaces, updateWorkspaceStatus } from "./db";
|
|
18
18
|
import { createCommand } from "./commands-db";
|
|
19
19
|
import { emitRelayEvent } from "./events";
|
|
20
20
|
import { isPathWithinBase } from "./utils";
|
|
21
21
|
import { TERMINAL_WORKSPACE_STATUSES, worktreeReapable, type WorktreeReapState } from "./workspace-phase";
|
|
22
|
+
import { isOwnerAlive } from "./workspace-merge";
|
|
22
23
|
|
|
23
24
|
// Don't re-flag the same un-landed orphan every sweep — surface it once, then
|
|
24
25
|
// stay quiet for this window. In-memory (keyed by worktree path) like the
|
|
@@ -28,6 +29,7 @@ const UNLANDED_FLAG_COOLDOWN_MS = Number(process.env.AGENT_RELAY_ORPHAN_FLAG_COO
|
|
|
28
29
|
// remove them (parity with the session reaper's detect-only switch).
|
|
29
30
|
const orphanWorktreeReapEnabled = () => process.env.AGENT_RELAY_ORPHAN_WORKTREE_REAP !== "0";
|
|
30
31
|
const flaggedAt = new Map<string, number>();
|
|
32
|
+
const IN_FLIGHT_MISSING_WORKTREE_STATUSES = new Set<WorkspaceStatus>(["merge_planned", "cleanup_requested"]);
|
|
31
33
|
|
|
32
34
|
export function resetOrphanWorktreeStateForTests(): void {
|
|
33
35
|
flaggedAt.clear();
|
|
@@ -77,6 +79,39 @@ async function fetchWorktreeReapState(apiUrl: string, worktreePath: string, base
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
type MissingBranchProbe = { kind: "gone" } | { kind: "preview"; preview: WorkspaceMergePreview } | { kind: "unavailable" };
|
|
83
|
+
|
|
84
|
+
async function fetchBranchReapState(
|
|
85
|
+
apiUrl: string,
|
|
86
|
+
repoRoot: string,
|
|
87
|
+
branch: string | undefined,
|
|
88
|
+
baseRef?: string,
|
|
89
|
+
baseSha?: string,
|
|
90
|
+
): Promise<MissingBranchProbe> {
|
|
91
|
+
if (!branch) return { kind: "unavailable" };
|
|
92
|
+
const query = new URLSearchParams({ repoRoot, branch, checkPr: "1" });
|
|
93
|
+
if (baseRef) query.set("baseRef", baseRef);
|
|
94
|
+
if (baseSha) query.set("baseSha", baseSha);
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(`${apiUrl}/api/workspace/branch-merge-preview?${query.toString()}`, {
|
|
97
|
+
headers: relayHeaders(),
|
|
98
|
+
signal: AbortSignal.timeout(8_000),
|
|
99
|
+
});
|
|
100
|
+
if (res.status === 404) return { kind: "gone" };
|
|
101
|
+
if (!res.ok) return { kind: "unavailable" };
|
|
102
|
+
return { kind: "preview", preview: await res.json() as WorkspaceMergePreview };
|
|
103
|
+
} catch {
|
|
104
|
+
return { kind: "unavailable" };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function previewReapable(preview: WorkspaceMergePreview): boolean | undefined {
|
|
109
|
+
if (preview.error) return undefined;
|
|
110
|
+
const hasSignal = preview.landed === true || typeof preview.ahead === "number" || typeof preview.unmergedAhead === "number";
|
|
111
|
+
if (!hasSignal) return undefined;
|
|
112
|
+
return worktreeReapable({ landed: preview.landed, ahead: preview.ahead, unmergedAhead: preview.unmergedAhead, dirtyCount: 0 });
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
function onlineOrchestrators(): OnlineOrchestrator[] {
|
|
81
116
|
return listOrchestrators()
|
|
82
117
|
.filter((orch) => orch.status === "online" && orch.apiUrl && orch.agentId)
|
|
@@ -93,7 +128,16 @@ function knownRepoRoots(workspaces: WorkspaceRecord[]): string[] {
|
|
|
93
128
|
export interface CollectOrphansResult {
|
|
94
129
|
orphans: WorkspaceOrphan[];
|
|
95
130
|
/** Live isolated rows whose worktree is missing on disk (DB→disk drift). */
|
|
96
|
-
missingWorktrees: Array<{
|
|
131
|
+
missingWorktrees: Array<{
|
|
132
|
+
workspaceId: string;
|
|
133
|
+
worktreePath: string;
|
|
134
|
+
repoRoot: string;
|
|
135
|
+
status: WorkspaceStatus;
|
|
136
|
+
branch?: string;
|
|
137
|
+
baseRef?: string;
|
|
138
|
+
baseSha?: string;
|
|
139
|
+
ownerAgentId?: string;
|
|
140
|
+
}>;
|
|
97
141
|
reason?: string;
|
|
98
142
|
}
|
|
99
143
|
|
|
@@ -128,7 +172,16 @@ export async function collectWorkspaceOrphans(): Promise<CollectOrphansResult> {
|
|
|
128
172
|
// DB→disk drift: a live isolated row whose worktree is no longer on disk.
|
|
129
173
|
for (const [path, ws] of liveRowsByPath) {
|
|
130
174
|
if (ws.mode === "isolated" && !onDisk.has(path)) {
|
|
131
|
-
missingWorktrees.push({
|
|
175
|
+
missingWorktrees.push({
|
|
176
|
+
workspaceId: ws.id,
|
|
177
|
+
worktreePath: ws.worktreePath,
|
|
178
|
+
repoRoot,
|
|
179
|
+
status: ws.status,
|
|
180
|
+
branch: ws.branch,
|
|
181
|
+
baseRef: ws.baseRef,
|
|
182
|
+
baseSha: ws.baseSha,
|
|
183
|
+
ownerAgentId: ws.ownerAgentId,
|
|
184
|
+
});
|
|
132
185
|
}
|
|
133
186
|
}
|
|
134
187
|
|
|
@@ -208,6 +261,8 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
|
|
|
208
261
|
const reapEnabled = orphanWorktreeReapEnabled();
|
|
209
262
|
const reaped: string[] = [];
|
|
210
263
|
const flagged: string[] = [];
|
|
264
|
+
const autoAbandoned: string[] = [];
|
|
265
|
+
const flaggedMissingWorktrees: string[] = [];
|
|
211
266
|
const now = Date.now();
|
|
212
267
|
|
|
213
268
|
for (const orphan of orphans) {
|
|
@@ -255,22 +310,103 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
|
|
|
255
310
|
});
|
|
256
311
|
}
|
|
257
312
|
|
|
258
|
-
// DB→disk drift is observability-only: a live row whose worktree vanished is
|
|
259
|
-
// surfaced, not auto-deleted (the row may still be mid-land or recoverable).
|
|
260
313
|
for (const missing of missingWorktrees) {
|
|
261
|
-
const
|
|
314
|
+
const workspace = getWorkspace(missing.workspaceId);
|
|
315
|
+
if (!workspace || TERMINAL_WORKSPACE_STATUSES.has(workspace.status) || workspace.mode !== "isolated" || !workspace.worktreePath) continue;
|
|
316
|
+
const key = `missing:${workspace.worktreePath}`;
|
|
317
|
+
const ownerAlive = isOwnerAlive(workspace.ownerAgentId);
|
|
318
|
+
const inFlight = IN_FLIGHT_MISSING_WORKTREE_STATUSES.has(workspace.status);
|
|
262
319
|
const last = flaggedAt.get(key) ?? 0;
|
|
320
|
+
if (ownerAlive || inFlight) {
|
|
321
|
+
if (now - last < UNLANDED_FLAG_COOLDOWN_MS) continue;
|
|
322
|
+
flaggedAt.set(key, now);
|
|
323
|
+
createActivityEvent({
|
|
324
|
+
clientId: `workspace-row-no-worktree-${workspace.id}-${now}`,
|
|
325
|
+
kind: "state",
|
|
326
|
+
title: "Workspace row has no worktree on disk",
|
|
327
|
+
body: `Workspace ${workspace.id} (${workspace.status}) points at ${workspace.worktreePath}, which no longer exists on disk — disk/DB drift.`,
|
|
328
|
+
meta: workspace.id,
|
|
329
|
+
icon: "ti-unlink",
|
|
330
|
+
view: "orchestrators",
|
|
331
|
+
metadata: {
|
|
332
|
+
source: "server",
|
|
333
|
+
maintenanceJobId: "workspace-orphan-reaper",
|
|
334
|
+
workspaceId: workspace.id,
|
|
335
|
+
worktreePath: workspace.worktreePath,
|
|
336
|
+
status: workspace.status,
|
|
337
|
+
ownerAlive,
|
|
338
|
+
inFlight,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const orch = orchestrators.find((candidate) => candidate.apiUrl && isPathWithinBase(workspace.repoRoot, candidate.baseDir));
|
|
345
|
+
const probe = orch?.apiUrl
|
|
346
|
+
? await fetchBranchReapState(orch.apiUrl, workspace.repoRoot, workspace.branch, workspace.baseRef, workspace.baseSha)
|
|
347
|
+
: { kind: "unavailable" } as const;
|
|
348
|
+
const safeToAbandon = probe.kind === "gone"
|
|
349
|
+
? true
|
|
350
|
+
: probe.kind === "preview"
|
|
351
|
+
? previewReapable(probe.preview)
|
|
352
|
+
: undefined;
|
|
353
|
+
|
|
354
|
+
if (safeToAbandon === true) {
|
|
355
|
+
const reasonText = probe.kind === "gone" ? "missing worktree; branch ref gone" : "missing worktree; branch already landed";
|
|
356
|
+
const updated = updateWorkspaceStatus(workspace.id, "abandoned", {
|
|
357
|
+
autoAbandoned: true,
|
|
358
|
+
abandonedReason: reasonText,
|
|
359
|
+
abandonedAt: now,
|
|
360
|
+
});
|
|
361
|
+
if (!updated) continue;
|
|
362
|
+
autoAbandoned.push(workspace.id);
|
|
363
|
+
flaggedAt.delete(key);
|
|
364
|
+
createActivityEvent({
|
|
365
|
+
clientId: `workspace-row-auto-abandoned-${workspace.id}-${now}`,
|
|
366
|
+
kind: "state",
|
|
367
|
+
title: "Workspace auto-abandoned",
|
|
368
|
+
body: `${workspace.branch ?? workspace.id} in ${workspace.repoRoot} — worktree missing and ${probe.kind === "gone" ? "branch ref is gone" : "branch has fully landed"}`,
|
|
369
|
+
meta: workspace.branch ?? workspace.id,
|
|
370
|
+
icon: "ti-clock-x",
|
|
371
|
+
view: "orchestrators",
|
|
372
|
+
metadata: {
|
|
373
|
+
source: "server",
|
|
374
|
+
maintenanceJobId: "workspace-orphan-reaper",
|
|
375
|
+
workspaceId: workspace.id,
|
|
376
|
+
worktreePath: workspace.worktreePath,
|
|
377
|
+
branch: workspace.branch,
|
|
378
|
+
reason: reasonText,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
263
384
|
if (now - last < UNLANDED_FLAG_COOLDOWN_MS) continue;
|
|
264
385
|
flaggedAt.set(key, now);
|
|
386
|
+
flaggedMissingWorktrees.push(workspace.id);
|
|
387
|
+
const detail = probe.kind === "preview"
|
|
388
|
+
? `${probe.preview.unmergedAhead ?? probe.preview.ahead ?? "?"} un-landed commit(s) still recoverable on branch ${workspace.branch ?? workspace.id}`
|
|
389
|
+
: workspace.branch
|
|
390
|
+
? `host could not confirm whether branch ${workspace.branch} has landed`
|
|
391
|
+
: "host could not confirm whether recoverable branch work remains";
|
|
265
392
|
createActivityEvent({
|
|
266
|
-
clientId: `workspace-row-
|
|
393
|
+
clientId: `workspace-row-needs-attention-${workspace.id}-${now}`,
|
|
267
394
|
kind: "state",
|
|
268
|
-
title: "
|
|
269
|
-
body:
|
|
270
|
-
meta:
|
|
271
|
-
icon: "ti-
|
|
395
|
+
title: "Missing-worktree workspace needs attention",
|
|
396
|
+
body: `${workspace.id} in ${workspace.repoRoot} has no worktree on disk and cannot be auto-abandoned safely — ${detail}. Recover via the branch if needed, then abandon or clean it up explicitly.`,
|
|
397
|
+
meta: workspace.id,
|
|
398
|
+
icon: "ti-alert-triangle",
|
|
272
399
|
view: "orchestrators",
|
|
273
|
-
metadata: {
|
|
400
|
+
metadata: {
|
|
401
|
+
source: "server",
|
|
402
|
+
maintenanceJobId: "workspace-orphan-reaper",
|
|
403
|
+
workspaceId: workspace.id,
|
|
404
|
+
worktreePath: workspace.worktreePath,
|
|
405
|
+
branch: workspace.branch,
|
|
406
|
+
status: workspace.status,
|
|
407
|
+
probe: probe.kind,
|
|
408
|
+
...(probe.kind === "preview" ? { ahead: probe.preview.ahead, unmergedAhead: probe.preview.unmergedAhead, landed: probe.preview.landed } : {}),
|
|
409
|
+
},
|
|
274
410
|
});
|
|
275
411
|
}
|
|
276
412
|
|
|
@@ -283,6 +419,8 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
|
|
|
283
419
|
scanned: orphans.length,
|
|
284
420
|
reaped,
|
|
285
421
|
flagged,
|
|
422
|
+
autoAbandoned,
|
|
423
|
+
flaggedMissingWorktrees,
|
|
286
424
|
missingWorktrees: missingWorktrees.map((m) => m.workspaceId),
|
|
287
425
|
reapEnabled,
|
|
288
426
|
};
|
package/src/workspace-phase.ts
CHANGED
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
// "handed off, healthy, wait" guidance, and `actionNeeded:false` is the explicit
|
|
15
15
|
// anti-panic signal.
|
|
16
16
|
|
|
17
|
+
import { TERMINAL_WORKSPACE_STATUS_VALUES } from "agent-relay-sdk";
|
|
17
18
|
import type { WorkspaceRecord, WorkspaceStatus } from "./types";
|
|
18
19
|
|
|
19
20
|
// Statuses where the worktree's lifecycle is over — landed or torn down. Single
|
|
20
21
|
// home; imported by maintenance (stale reap), routes (orphan scan), and the MCP
|
|
21
22
|
// initialize primer (don't brief an agent on a dead workspace). Was duplicated.
|
|
22
|
-
export const TERMINAL_WORKSPACE_STATUSES = new Set<WorkspaceStatus>(
|
|
23
|
+
export const TERMINAL_WORKSPACE_STATUSES = new Set<WorkspaceStatus>(TERMINAL_WORKSPACE_STATUS_VALUES);
|
|
23
24
|
|
|
24
25
|
// The "handed off, waiting to land" statuses — an agent has finished and the
|
|
25
26
|
// auto-merge-back is responsible for getting the branch onto base. SINGLE HOME:
|