orkestrator 2.26.1-alpha → 2.26.3-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +2 -2
- package/dist/main.js +432 -63
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -7444,7 +7444,7 @@ import path4 from "path";
|
|
|
7444
7444
|
// ../../apps/backend/package.json
|
|
7445
7445
|
var package_default = {
|
|
7446
7446
|
name: "@orkestrator/backend",
|
|
7447
|
-
version: "2.26.
|
|
7447
|
+
version: "2.26.3-beta",
|
|
7448
7448
|
private: true,
|
|
7449
7449
|
type: "module",
|
|
7450
7450
|
exports: {
|
|
@@ -7458,7 +7458,7 @@ var package_default = {
|
|
|
7458
7458
|
"start:web": "bun dist/main.js --fallback-host 127.0.0.1",
|
|
7459
7459
|
"start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
|
|
7460
7460
|
test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
|
|
7461
|
-
"test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} --parallel=${ORKESTRATOR_TEST_WORKERS:-2}",
|
|
7461
|
+
"test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} $ORKESTRATOR_BACKEND_TEST_IGNORE --parallel=${ORKESTRATOR_BACKEND_TEST_WORKERS:-${ORKESTRATOR_TEST_WORKERS:-2}}",
|
|
7462
7462
|
typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
|
|
7463
7463
|
},
|
|
7464
7464
|
dependencies: {
|
package/dist/main.js
CHANGED
|
@@ -818,7 +818,7 @@ var package_default;
|
|
|
818
818
|
var init_package = __esm(() => {
|
|
819
819
|
package_default = {
|
|
820
820
|
name: "@orkestrator/backend",
|
|
821
|
-
version: "2.26.
|
|
821
|
+
version: "2.26.3-beta",
|
|
822
822
|
private: true,
|
|
823
823
|
type: "module",
|
|
824
824
|
exports: {
|
|
@@ -832,7 +832,7 @@ var init_package = __esm(() => {
|
|
|
832
832
|
"start:web": "bun dist/main.js --fallback-host 127.0.0.1",
|
|
833
833
|
"start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
|
|
834
834
|
test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
|
|
835
|
-
"test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} --parallel=${ORKESTRATOR_TEST_WORKERS:-2}",
|
|
835
|
+
"test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} $ORKESTRATOR_BACKEND_TEST_IGNORE --parallel=${ORKESTRATOR_BACKEND_TEST_WORKERS:-${ORKESTRATOR_TEST_WORKERS:-2}}",
|
|
836
836
|
typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
|
|
837
837
|
},
|
|
838
838
|
dependencies: {
|
|
@@ -3220,6 +3220,79 @@ var init_resource_events = __esm(() => {
|
|
|
3220
3220
|
RESOURCE_MANIFEST_KIND_SET = new Set(RESOURCE_MANIFEST_KINDS);
|
|
3221
3221
|
});
|
|
3222
3222
|
|
|
3223
|
+
// ../protocol/src/controller-settings.ts
|
|
3224
|
+
function pickInheritedGlobalSettings(global2) {
|
|
3225
|
+
const source = global2;
|
|
3226
|
+
const picked = {};
|
|
3227
|
+
for (const key of INHERITED_GLOBAL_SETTING_KEYS) {
|
|
3228
|
+
if (source[key] !== undefined)
|
|
3229
|
+
picked[key] = source[key];
|
|
3230
|
+
}
|
|
3231
|
+
return picked;
|
|
3232
|
+
}
|
|
3233
|
+
function parseInheritedGlobalSettings(value) {
|
|
3234
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
3235
|
+
throw new Error("Expected settings to be an object.");
|
|
3236
|
+
}
|
|
3237
|
+
let serialized;
|
|
3238
|
+
try {
|
|
3239
|
+
serialized = JSON.stringify(value);
|
|
3240
|
+
} catch {
|
|
3241
|
+
throw new Error("Settings must be plain JSON.");
|
|
3242
|
+
}
|
|
3243
|
+
if (new TextEncoder().encode(serialized).byteLength > MAX_INHERITED_SETTINGS_BYTES) {
|
|
3244
|
+
throw new Error("Settings are too large.");
|
|
3245
|
+
}
|
|
3246
|
+
const parsed = JSON.parse(serialized);
|
|
3247
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3248
|
+
throw new Error("Expected settings to be an object.");
|
|
3249
|
+
}
|
|
3250
|
+
return pickInheritedGlobalSettings(parsed);
|
|
3251
|
+
}
|
|
3252
|
+
function emptyControllerSettingsStore() {
|
|
3253
|
+
return { version: 1, entries: [] };
|
|
3254
|
+
}
|
|
3255
|
+
function parseControllerSettingsStore(value) {
|
|
3256
|
+
if (!value || typeof value !== "object")
|
|
3257
|
+
return emptyControllerSettingsStore();
|
|
3258
|
+
const entries = value.entries;
|
|
3259
|
+
if (!Array.isArray(entries))
|
|
3260
|
+
return emptyControllerSettingsStore();
|
|
3261
|
+
const parsed = [];
|
|
3262
|
+
for (const entry of entries) {
|
|
3263
|
+
if (!entry || typeof entry !== "object")
|
|
3264
|
+
continue;
|
|
3265
|
+
const record = entry;
|
|
3266
|
+
if (!isBackendId(record.controllerId) || typeof record.changedAt !== "string" || !Number.isFinite(Date.parse(record.changedAt)))
|
|
3267
|
+
continue;
|
|
3268
|
+
try {
|
|
3269
|
+
parsed.push({
|
|
3270
|
+
controllerId: record.controllerId,
|
|
3271
|
+
settings: parseInheritedGlobalSettings(record.settings),
|
|
3272
|
+
changedAt: record.changedAt
|
|
3273
|
+
});
|
|
3274
|
+
} catch {}
|
|
3275
|
+
}
|
|
3276
|
+
return { version: 1, entries: parsed };
|
|
3277
|
+
}
|
|
3278
|
+
var SYNC_CONTROLLER_SETTINGS_COMMAND = "sync_controller_settings", INHERITED_GLOBAL_SETTING_KEYS, MAX_INHERITED_SETTINGS_BYTES, INHERITED_KEYS;
|
|
3279
|
+
var init_controller_settings = __esm(() => {
|
|
3280
|
+
init_federation();
|
|
3281
|
+
INHERITED_GLOBAL_SETTING_KEYS = [
|
|
3282
|
+
"agentSettings",
|
|
3283
|
+
"favoriteModels",
|
|
3284
|
+
"reviewInstruction",
|
|
3285
|
+
"openCodeModelProviders",
|
|
3286
|
+
"codexMaxConcurrentThreads",
|
|
3287
|
+
"claudeBackgroundCommandLimitHours",
|
|
3288
|
+
"terminalAppearance",
|
|
3289
|
+
"terminalScrollback",
|
|
3290
|
+
"agentMessaging"
|
|
3291
|
+
];
|
|
3292
|
+
MAX_INHERITED_SETTINGS_BYTES = 256 * 1024;
|
|
3293
|
+
INHERITED_KEYS = new Set(INHERITED_GLOBAL_SETTING_KEYS);
|
|
3294
|
+
});
|
|
3295
|
+
|
|
3223
3296
|
// ../../apps/backend/src/core/github-cli-binary.ts
|
|
3224
3297
|
function setManagedGitHubCli(executable) {
|
|
3225
3298
|
managedGitHubCli = executable;
|
|
@@ -3967,7 +4040,7 @@ var SYSTEM_INSTRUCTIONS_FRAME_OPEN = "<orkestrator-system-instructions>", SYSTEM
|
|
|
3967
4040
|
1. Inspect the current Git status and changes. Commit only relevant safe changes using the repository's commit conventions and hooks. Never skip hooks, force a clean tree, delete unrelated files, push, merge, rebase, reset, switch branches, or create a worktree. Do not implement features or fix validation failures. If unrelated or sensitive changes prevent a clean worktree, report the limitation.
|
|
3968
4041
|
2. Discover validation requirements afresh from the CURRENT repository. First read the repository's agent instructions \u2014 AGENTS.md (and CLAUDE.md or an equivalent) at the root and in the directories that contain changed paths, plus any testing guide they point to \u2014 and follow what they say about which tests, checks, and builds a given kind of change needs. Then use directory structure, changed paths, CI workflows, manifests, task definitions, toolchain configuration, and relevant scripts. Do not assume a language, package manager, fixed list of files, or that the codebase resembles an earlier review. Follow repository-specific test entrypoints. Do not infer that a command covers another merely from its name.
|
|
3969
4042
|
3. Select validation proportionate to the change. Produce at most 32 commands covering the tests, static checks, and build that the changed paths can affect, plus any repository-specific requirements. Some suites are only needed in some situations: when the repository instructions scope a suite to particular paths, platforms, or kinds of change, include it only when this change meets that condition, and omit suites for areas the change cannot affect (for example a documentation-only change does not need an unrelated app's test suite). Include the full relevant coverage when the instructions require it for every change, or when a change's reach is uncertain (shared configuration, dependencies, lockfiles, toolchain, or code shared across areas). Summarize deliberately omitted suites and why in one concise limitation. Do not RUN validation, install dependencies, inspect validation output, or perform the code review. Command execution, timing, artifact paths, and exit codes belong to the backend. A skipped requirement needs an explicit limitation; an empty plan requires a limitation.
|
|
3970
|
-
4. Each command has a unique short id, a non-interactive shell command, a workspace-relative cwd (usually "."), and dependsOn listing prerequisite ids EARLIER in the array. Split independent work so it can run concurrently. Inspect what each selected stage actually runs before adding another command: if the full test stage already runs the production build, omit a separate build command instead of repeating it. Shared build prerequisites run once; avoid overlapping aggregate commands that repeat the same validation. Preserve necessary build/test dependencies and setup/cleanup semantics. Prefer a repository-owned one-shot lifecycle task for service-backed checks. Never place a foreground server/supervisor before its dependent tests in a sequential shell command: readiness output does not mean that the starter exits. Only when no repository-owned lifecycle task exists may tightly coupled setup, test, and cleanup use a shell trap, with explicit readiness, bounded waits, supervision, and cleanup.
|
|
4043
|
+
4. Each command has a unique short id, a non-interactive shell command, a workspace-relative cwd (usually "."), and dependsOn listing prerequisite ids EARLIER in the array. Split independent work so it can run concurrently. Inspect what each selected stage actually runs before adding another command: if the full test stage already runs the production build, omit a separate build command instead of repeating it. Shared build prerequisites run once; avoid overlapping aggregate commands that repeat the same validation. List each command once: never plan the same command twice, such as a cold and a warm run or a confirmation rerun, because every command already runs exactly once against the committed snapshot and the backend records any repeat as covered by its first copy. Preserve necessary build/test dependencies and setup/cleanup semantics. Prefer a repository-owned one-shot lifecycle task for service-backed checks. Never place a foreground server/supervisor before its dependent tests in a sequential shell command: readiness output does not mean that the starter exits. Only when no repository-owned lifecycle task exists may tightly coupled setup, test, and cleanup use a shell trap, with explicit readiness, bounded waits, supervision, and cleanup.
|
|
3971
4044
|
5. resources names directories or shared services the command writes or consumes exclusively (for example a generated output directory, test database, or simulator). Commands with the same resource serialize. Use workspace: prefixes for workspace directories and services (for example workspace:dist); use ["workspace:*"] when interference within this workspace is uncertain. Use host: prefixes for genuinely host-shared resources (for example host:tcp:1422 or host:ios-simulator), and ["host:*"] only when host-wide interference is uncertain. Legacy unprefixed resource names and ["*"] retain host-wide scope for compatibility: do not use them for workspace-only uncertainty. Empty resources means you have verified parallel safety. weight=2 marks internally parallel or memory-heavy work and weight=1 lighter work; weight only sizes a command until its real CPU use has been measured, and the host starts commands with partial capacity when busy. Do not guess a command is lightweight. timeoutMs must be between 1000 and 7200000 and appropriate to this project. Never request watch mode, interactive input, background servers without cleanup, or a detached process. If .orkestrator-test-scheduler.json is present, its cooperativeCommands are eligible alternatives, NOT a checklist. Select only the required coverage, using each selected command verbatim from cwd="." without shell wrappers or compound commands; those runners reserve their own host capacity. Select repository-owned commandProfiles entries verbatim from cwd="." as separate commands too; do not synthesize setup/test/cleanup wrappers around an available one-shot task. Only cooperativeCommands reserve their own capacity; ordinary profiled commands are admitted by the review worker. Follow exact commandProfiles resource and coverage declarations when present. A profile covers list declares commands whose validation is already included: choose the covering command once instead of also scheduling its covered alternatives. Waiting for capacity does not count against timeoutMs.
|
|
3972
4045
|
6. Read the final full HEAD commit SHA into headRef. Commands run against that HEAD. Generated or untracked files created during validation are recorded as environment state changes and do not block the review. Include actual missing prerequisites and coverage uncertainty in limitations. Do not include secrets or environment-variable values in the plan.
|
|
3973
4046
|
|
|
@@ -19837,9 +19910,11 @@ var init_storage_projects = __esm(() => {
|
|
|
19837
19910
|
|
|
19838
19911
|
// ../../apps/backend/src/core/storage-config.ts
|
|
19839
19912
|
import path20 from "path";
|
|
19913
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
19840
19914
|
var StorageConfig;
|
|
19841
19915
|
var init_storage_config = __esm(() => {
|
|
19842
19916
|
init_coordinator_web_research();
|
|
19917
|
+
init_controller_settings();
|
|
19843
19918
|
init_agent_settings();
|
|
19844
19919
|
init_debug_logging();
|
|
19845
19920
|
init_notification_sounds();
|
|
@@ -19849,7 +19924,55 @@ var init_storage_config = __esm(() => {
|
|
|
19849
19924
|
init_storage_shared();
|
|
19850
19925
|
init_storage_projects();
|
|
19851
19926
|
StorageConfig = class StorageConfig extends StorageProjects {
|
|
19927
|
+
inheritedGlobalSettings = null;
|
|
19852
19928
|
async loadConfig() {
|
|
19929
|
+
return this.withInheritedSettings(await this.loadOwnConfig());
|
|
19930
|
+
}
|
|
19931
|
+
announceInheritedSettingsChanged() {
|
|
19932
|
+
this.announce("config", "app");
|
|
19933
|
+
}
|
|
19934
|
+
withInheritedSettings(config) {
|
|
19935
|
+
const inherited = this.inheritedGlobalSettings?.() ?? null;
|
|
19936
|
+
if (!inherited)
|
|
19937
|
+
return config;
|
|
19938
|
+
const values = { ...config.global };
|
|
19939
|
+
const defaults = defaultConfig().global;
|
|
19940
|
+
for (const key of INHERITED_GLOBAL_SETTING_KEYS) {
|
|
19941
|
+
delete values[key];
|
|
19942
|
+
if (defaults[key] !== undefined)
|
|
19943
|
+
values[key] = defaults[key];
|
|
19944
|
+
}
|
|
19945
|
+
const global2 = { ...values, ...inherited };
|
|
19946
|
+
const agentSettings = normalizeAgentSettings(global2.agentSettings);
|
|
19947
|
+
const enabled = normalizeAgentPlatforms(global2.enabledAgentPlatforms, []);
|
|
19948
|
+
global2.agentSettings = enabled.length > 0 ? {
|
|
19949
|
+
...agentSettings,
|
|
19950
|
+
defaultAgent: firstEnabledAgentPlatform(enabled, agentSettings.defaultAgent)
|
|
19951
|
+
} : agentSettings;
|
|
19952
|
+
return { ...config, global: global2 };
|
|
19953
|
+
}
|
|
19954
|
+
keepOwnInheritedValues(incoming, own) {
|
|
19955
|
+
const inherited = this.inheritedGlobalSettings?.() ?? null;
|
|
19956
|
+
if (!inherited)
|
|
19957
|
+
return incoming;
|
|
19958
|
+
const effective = this.withInheritedSettings({
|
|
19959
|
+
version: "",
|
|
19960
|
+
global: own,
|
|
19961
|
+
repositories: {}
|
|
19962
|
+
}).global;
|
|
19963
|
+
const next = { ...incoming };
|
|
19964
|
+
const ownValues = own;
|
|
19965
|
+
for (const key of INHERITED_GLOBAL_SETTING_KEYS) {
|
|
19966
|
+
if (!isDeepStrictEqual2(next[key], effective[key]))
|
|
19967
|
+
continue;
|
|
19968
|
+
if (ownValues[key] === undefined)
|
|
19969
|
+
delete next[key];
|
|
19970
|
+
else
|
|
19971
|
+
next[key] = ownValues[key];
|
|
19972
|
+
}
|
|
19973
|
+
return next;
|
|
19974
|
+
}
|
|
19975
|
+
async loadOwnConfig() {
|
|
19853
19976
|
const configExists = await fs6.access(this.configFile()).then(() => true, () => false);
|
|
19854
19977
|
const config = await this.loadJsonCached(this.configFile(), defaultConfig);
|
|
19855
19978
|
const normalized = normalizePersistedConfig(config);
|
|
@@ -19887,17 +20010,19 @@ var init_storage_config = __esm(() => {
|
|
|
19887
20010
|
async saveConfig(config, options2 = {}) {
|
|
19888
20011
|
const validated = validateConfigReviewInstruction(config);
|
|
19889
20012
|
await this.enqueueConfigMutation(async () => {
|
|
19890
|
-
const
|
|
20013
|
+
const own = await this.loadOwnConfig();
|
|
20014
|
+
const current = options2.preserveCredentials ? own : null;
|
|
20015
|
+
const ownGlobal = this.keepOwnInheritedValues(validated.global, own.global);
|
|
19891
20016
|
const next = current ? {
|
|
19892
20017
|
...validated,
|
|
19893
20018
|
global: {
|
|
19894
|
-
...
|
|
20019
|
+
...ownGlobal,
|
|
19895
20020
|
...current.global.githubToken ? { githubToken: current.global.githubToken } : {},
|
|
19896
20021
|
...current.global.anthropicApiKey ? { anthropicApiKey: current.global.anthropicApiKey } : {},
|
|
19897
20022
|
...current.global.cursorApiKey ? { cursorApiKey: current.global.cursorApiKey } : {},
|
|
19898
20023
|
...current.global.openCodeZenApiKey ? { openCodeZenApiKey: current.global.openCodeZenApiKey } : {}
|
|
19899
20024
|
}
|
|
19900
|
-
} : validated;
|
|
20025
|
+
} : { ...validated, global: ownGlobal };
|
|
19901
20026
|
await this.saveSensitiveJson(this.configFile(), next);
|
|
19902
20027
|
});
|
|
19903
20028
|
this.announce("config", "app");
|
|
@@ -19985,7 +20110,7 @@ var init_storage_config = __esm(() => {
|
|
|
19985
20110
|
async saveDesktopConnections(desktopConnections) {
|
|
19986
20111
|
const validated = parseStoredDesktopConnections(desktopConnections);
|
|
19987
20112
|
await this.enqueueConfigMutation(async () => {
|
|
19988
|
-
const config = await this.
|
|
20113
|
+
const config = await this.loadOwnConfig();
|
|
19989
20114
|
config.desktopConnections = validated;
|
|
19990
20115
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
19991
20116
|
});
|
|
@@ -19997,16 +20122,16 @@ var init_storage_config = __esm(() => {
|
|
|
19997
20122
|
}
|
|
19998
20123
|
async updateRepositoryConfig(projectId, repoConfig) {
|
|
19999
20124
|
return this.enqueueConfigMutation(async () => {
|
|
20000
|
-
const config = await this.
|
|
20125
|
+
const config = await this.loadOwnConfig();
|
|
20001
20126
|
config.repositories[projectId] = { ...defaultRepositoryConfig(), ...repoConfig };
|
|
20002
20127
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20003
20128
|
this.announce("config", "app");
|
|
20004
|
-
return config;
|
|
20129
|
+
return this.withInheritedSettings(config);
|
|
20005
20130
|
});
|
|
20006
20131
|
}
|
|
20007
20132
|
async updateRepositorySettings(projectId, repoConfig) {
|
|
20008
20133
|
return this.enqueueConfigMutation(async () => {
|
|
20009
|
-
const config = await this.
|
|
20134
|
+
const config = await this.loadOwnConfig();
|
|
20010
20135
|
const current = config.repositories[projectId] ?? defaultRepositoryConfig();
|
|
20011
20136
|
const userSettings = { ...repoConfig };
|
|
20012
20137
|
delete userSettings.lastEnvironmentType;
|
|
@@ -20021,12 +20146,12 @@ var init_storage_config = __esm(() => {
|
|
|
20021
20146
|
};
|
|
20022
20147
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20023
20148
|
this.announce("config", "app");
|
|
20024
|
-
return config;
|
|
20149
|
+
return this.withInheritedSettings(config);
|
|
20025
20150
|
});
|
|
20026
20151
|
}
|
|
20027
20152
|
async patchRepositoryConfig(projectId, updates) {
|
|
20028
20153
|
return this.enqueueConfigMutation(async () => {
|
|
20029
|
-
const config = await this.
|
|
20154
|
+
const config = await this.loadOwnConfig();
|
|
20030
20155
|
const current = config.repositories[projectId] ?? defaultRepositoryConfig();
|
|
20031
20156
|
config.repositories[projectId] = {
|
|
20032
20157
|
...defaultRepositoryConfig(),
|
|
@@ -20035,7 +20160,7 @@ var init_storage_config = __esm(() => {
|
|
|
20035
20160
|
};
|
|
20036
20161
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20037
20162
|
this.announce("config", "app");
|
|
20038
|
-
return config;
|
|
20163
|
+
return this.withInheritedSettings(config);
|
|
20039
20164
|
});
|
|
20040
20165
|
}
|
|
20041
20166
|
async updateGlobalConfig(globalConfig, options2 = {}) {
|
|
@@ -20091,7 +20216,7 @@ var init_storage_config = __esm(() => {
|
|
|
20091
20216
|
}
|
|
20092
20217
|
};
|
|
20093
20218
|
return this.enqueueConfigMutation(async () => {
|
|
20094
|
-
const config = await this.
|
|
20219
|
+
const config = await this.loadOwnConfig();
|
|
20095
20220
|
validated.coordinatorProviderTierDefaultVersion = Math.max(validated.coordinatorProviderTierDefaultVersion ?? COORDINATOR_PROVIDER_TIER_DEFAULT_VERSION, normalizeCoordinatorProviderTierDefaultVersion(config.global.coordinatorProviderTierDefaultVersion));
|
|
20096
20221
|
if (validated.mcpManagement === undefined && config.global.mcpManagement !== undefined)
|
|
20097
20222
|
validated.mcpManagement = config.global.mcpManagement;
|
|
@@ -20105,21 +20230,29 @@ var init_storage_config = __esm(() => {
|
|
|
20105
20230
|
validated.githubCliReminderDismissed = true;
|
|
20106
20231
|
else
|
|
20107
20232
|
delete validated.githubCliReminderDismissed;
|
|
20233
|
+
const ownGlobal = this.keepOwnInheritedValues(validated, config.global);
|
|
20234
|
+
if (options2.preserveInheritedSettings) {
|
|
20235
|
+
for (const key of INHERITED_GLOBAL_SETTING_KEYS) {
|
|
20236
|
+
delete ownGlobal[key];
|
|
20237
|
+
if (config.global[key] !== undefined)
|
|
20238
|
+
Object.assign(ownGlobal, { [key]: config.global[key] });
|
|
20239
|
+
}
|
|
20240
|
+
}
|
|
20108
20241
|
config.global = options2.preserveCredentials ? {
|
|
20109
|
-
...
|
|
20242
|
+
...ownGlobal,
|
|
20110
20243
|
...config.global.githubToken ? { githubToken: config.global.githubToken } : {},
|
|
20111
20244
|
...config.global.anthropicApiKey ? { anthropicApiKey: config.global.anthropicApiKey } : {},
|
|
20112
20245
|
...config.global.cursorApiKey ? { cursorApiKey: config.global.cursorApiKey } : {},
|
|
20113
20246
|
...config.global.openCodeZenApiKey ? { openCodeZenApiKey: config.global.openCodeZenApiKey } : {}
|
|
20114
|
-
} :
|
|
20247
|
+
} : ownGlobal;
|
|
20115
20248
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20116
20249
|
this.announce("config", "app");
|
|
20117
|
-
return config;
|
|
20250
|
+
return this.withInheritedSettings(config);
|
|
20118
20251
|
});
|
|
20119
20252
|
}
|
|
20120
20253
|
async updateWorktreeLocations(locations) {
|
|
20121
20254
|
return this.enqueueConfigMutation(async () => {
|
|
20122
|
-
const config = await this.
|
|
20255
|
+
const config = await this.loadOwnConfig();
|
|
20123
20256
|
const next = {
|
|
20124
20257
|
...locations.environments ? { environments: locations.environments } : {},
|
|
20125
20258
|
...locations.dev ? { dev: locations.dev } : {}
|
|
@@ -20130,89 +20263,89 @@ var init_storage_config = __esm(() => {
|
|
|
20130
20263
|
delete config.global.worktreeLocations;
|
|
20131
20264
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20132
20265
|
this.announce("config", "app");
|
|
20133
|
-
return config;
|
|
20266
|
+
return this.withInheritedSettings(config);
|
|
20134
20267
|
});
|
|
20135
20268
|
}
|
|
20136
20269
|
async updateMcpManagementRollout(settings) {
|
|
20137
20270
|
return this.enqueueConfigMutation(async () => {
|
|
20138
|
-
const config = await this.
|
|
20271
|
+
const config = await this.loadOwnConfig();
|
|
20139
20272
|
config.global = { ...config.global, mcpManagement: settings };
|
|
20140
20273
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20141
20274
|
this.announce("config", "app");
|
|
20142
|
-
return config;
|
|
20275
|
+
return this.withInheritedSettings(config);
|
|
20143
20276
|
});
|
|
20144
20277
|
}
|
|
20145
20278
|
async updateNotificationSoundSettings(settings) {
|
|
20146
20279
|
const notificationSounds = normalizeNotificationSoundSettings(settings);
|
|
20147
20280
|
return this.enqueueConfigMutation(async () => {
|
|
20148
|
-
const config = await this.
|
|
20281
|
+
const config = await this.loadOwnConfig();
|
|
20149
20282
|
config.global = {
|
|
20150
20283
|
...config.global,
|
|
20151
20284
|
notificationSounds
|
|
20152
20285
|
};
|
|
20153
20286
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20154
20287
|
this.announce("config", "app");
|
|
20155
|
-
return config;
|
|
20288
|
+
return this.withInheritedSettings(config);
|
|
20156
20289
|
});
|
|
20157
20290
|
}
|
|
20158
20291
|
async setGitHubCliReminderDismissed(dismissed) {
|
|
20159
20292
|
return this.enqueueConfigMutation(async () => {
|
|
20160
|
-
const config = await this.
|
|
20293
|
+
const config = await this.loadOwnConfig();
|
|
20161
20294
|
if (dismissed)
|
|
20162
20295
|
config.global.githubCliReminderDismissed = true;
|
|
20163
20296
|
else
|
|
20164
20297
|
delete config.global.githubCliReminderDismissed;
|
|
20165
20298
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20166
20299
|
this.announce("config", "app");
|
|
20167
|
-
return config;
|
|
20300
|
+
return this.withInheritedSettings(config);
|
|
20168
20301
|
});
|
|
20169
20302
|
}
|
|
20170
20303
|
async setGitHubToken(token) {
|
|
20171
20304
|
return this.enqueueConfigMutation(async () => {
|
|
20172
|
-
const config = await this.
|
|
20305
|
+
const config = await this.loadOwnConfig();
|
|
20173
20306
|
if (token === null)
|
|
20174
20307
|
delete config.global.githubToken;
|
|
20175
20308
|
else
|
|
20176
20309
|
config.global.githubToken = token;
|
|
20177
20310
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20178
20311
|
this.announce("config", "app");
|
|
20179
|
-
return config;
|
|
20312
|
+
return this.withInheritedSettings(config);
|
|
20180
20313
|
});
|
|
20181
20314
|
}
|
|
20182
20315
|
async setCursorApiKey(apiKey) {
|
|
20183
20316
|
return this.enqueueConfigMutation(async () => {
|
|
20184
|
-
const config = await this.
|
|
20317
|
+
const config = await this.loadOwnConfig();
|
|
20185
20318
|
if (apiKey === null)
|
|
20186
20319
|
delete config.global.cursorApiKey;
|
|
20187
20320
|
else
|
|
20188
20321
|
config.global.cursorApiKey = apiKey;
|
|
20189
20322
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20190
20323
|
this.announce("config", "app");
|
|
20191
|
-
return config;
|
|
20324
|
+
return this.withInheritedSettings(config);
|
|
20192
20325
|
});
|
|
20193
20326
|
}
|
|
20194
20327
|
async setAnthropicApiKey(apiKey) {
|
|
20195
20328
|
return this.enqueueConfigMutation(async () => {
|
|
20196
|
-
const config = await this.
|
|
20329
|
+
const config = await this.loadOwnConfig();
|
|
20197
20330
|
if (apiKey === null)
|
|
20198
20331
|
delete config.global.anthropicApiKey;
|
|
20199
20332
|
else
|
|
20200
20333
|
config.global.anthropicApiKey = apiKey;
|
|
20201
20334
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20202
20335
|
this.announce("config", "app");
|
|
20203
|
-
return config;
|
|
20336
|
+
return this.withInheritedSettings(config);
|
|
20204
20337
|
});
|
|
20205
20338
|
}
|
|
20206
20339
|
async setOpenCodeZenApiKey(apiKey) {
|
|
20207
20340
|
return this.enqueueConfigMutation(async () => {
|
|
20208
|
-
const config = await this.
|
|
20341
|
+
const config = await this.loadOwnConfig();
|
|
20209
20342
|
if (apiKey === null)
|
|
20210
20343
|
delete config.global.openCodeZenApiKey;
|
|
20211
20344
|
else
|
|
20212
20345
|
config.global.openCodeZenApiKey = apiKey;
|
|
20213
20346
|
await this.saveSensitiveJson(this.configFile(), config);
|
|
20214
20347
|
this.announce("config", "app");
|
|
20215
|
-
return config;
|
|
20348
|
+
return this.withInheritedSettings(config);
|
|
20216
20349
|
});
|
|
20217
20350
|
}
|
|
20218
20351
|
};
|
|
@@ -26771,6 +26904,7 @@ var init_storage_preview_services = __esm(() => {
|
|
|
26771
26904
|
// ../../apps/backend/src/core/storage-nodes.ts
|
|
26772
26905
|
var StorageNodes;
|
|
26773
26906
|
var init_storage_nodes = __esm(() => {
|
|
26907
|
+
init_controller_settings();
|
|
26774
26908
|
init_node_store_format();
|
|
26775
26909
|
init_storage_preview_services();
|
|
26776
26910
|
StorageNodes = class StorageNodes extends StoragePreviewServices {
|
|
@@ -26791,6 +26925,21 @@ var init_storage_nodes = __esm(() => {
|
|
|
26791
26925
|
portalLayoutFile() {
|
|
26792
26926
|
return this.file("portal-layout.json");
|
|
26793
26927
|
}
|
|
26928
|
+
controllerSettingsFile() {
|
|
26929
|
+
return this.file("controller-settings.json");
|
|
26930
|
+
}
|
|
26931
|
+
async getControllerSettings() {
|
|
26932
|
+
return parseControllerSettingsStore(await this.loadJson(this.controllerSettingsFile(), () => null));
|
|
26933
|
+
}
|
|
26934
|
+
async mutateControllerSettings(mutate) {
|
|
26935
|
+
return this.serializeMutation(this.controllerSettingsFile(), "controller settings", async () => {
|
|
26936
|
+
const current = parseControllerSettingsStore(await this.loadJson(this.controllerSettingsFile(), () => null));
|
|
26937
|
+
const { next, result } = mutate(structuredClone(current));
|
|
26938
|
+
if (next)
|
|
26939
|
+
await this.saveJson(this.controllerSettingsFile(), { ...next, version: 1 });
|
|
26940
|
+
return result;
|
|
26941
|
+
});
|
|
26942
|
+
}
|
|
26794
26943
|
async getPortalLayout() {
|
|
26795
26944
|
return parsePortalLayout(await this.loadJson(this.portalLayoutFile(), () => null));
|
|
26796
26945
|
}
|
|
@@ -27195,7 +27344,7 @@ var init_storage_public_operations = __esm(() => {
|
|
|
27195
27344
|
}
|
|
27196
27345
|
async patchRepositorySettingsAtRevision(projectId, expectedRevision, apply) {
|
|
27197
27346
|
const next = await this.enqueueConfigMutation(async () => {
|
|
27198
|
-
const config = await this.
|
|
27347
|
+
const config = await this.loadOwnConfig();
|
|
27199
27348
|
const stored = config.repositories[projectId];
|
|
27200
27349
|
if (expectedRevision !== undefined && repositorySettingsRevision(stored) !== expectedRevision) {
|
|
27201
27350
|
throw new PublicActionError("revision-conflict", "The project settings changed since they were read; re-read and apply the change again", { details: { currentRevision: repositorySettingsRevision(stored) } });
|
|
@@ -108134,10 +108283,16 @@ class TmuxSession {
|
|
|
108134
108283
|
forceNextObservation = false;
|
|
108135
108284
|
observationInFlight;
|
|
108136
108285
|
inputMutex = new AsyncMutex;
|
|
108137
|
-
|
|
108286
|
+
pollTick;
|
|
108287
|
+
now;
|
|
108288
|
+
sleep;
|
|
108289
|
+
constructor(environmentId, tabId, backend, runtimeRootPrefix, resumeSessionId, claudeCommand, timing = {}) {
|
|
108138
108290
|
this.environmentId = environmentId;
|
|
108139
108291
|
this.tabId = tabId;
|
|
108140
108292
|
this.backend = backend;
|
|
108293
|
+
this.pollTick = timing.pollTick ?? ((intervalMs) => delay(intervalMs));
|
|
108294
|
+
this.now = timing.now ?? (() => Date.now());
|
|
108295
|
+
this.sleep = timing.sleep ?? ((delayMs) => delay(delayMs));
|
|
108141
108296
|
this.resumed = resumeSessionId !== undefined;
|
|
108142
108297
|
this.firstPromptClaimed = this.resumed;
|
|
108143
108298
|
this.sessionId = resumeSessionId ?? randomUUID38();
|
|
@@ -108397,7 +108552,7 @@ ${help.stderr}`;
|
|
|
108397
108552
|
}
|
|
108398
108553
|
async sendInitialPromptWhenReady(prompt, launchedNew) {
|
|
108399
108554
|
if (launchedNew)
|
|
108400
|
-
await
|
|
108555
|
+
await this.sleep(800);
|
|
108401
108556
|
await this.waitForTuiInputReady();
|
|
108402
108557
|
await this.submit(prompt);
|
|
108403
108558
|
this.setBusyState(true);
|
|
@@ -108409,17 +108564,27 @@ ${help.stderr}`;
|
|
|
108409
108564
|
this.busyStartedAt = busy ? Date.now() : null;
|
|
108410
108565
|
}
|
|
108411
108566
|
async waitForTuiInputReady() {
|
|
108412
|
-
const deadline =
|
|
108413
|
-
while (
|
|
108567
|
+
const deadline = this.now() + 10 * 60 * 1000;
|
|
108568
|
+
while (this.now() < deadline) {
|
|
108414
108569
|
if (!await this.tmuxAlive().catch(() => false)) {
|
|
108415
108570
|
throw new Error("tmux session stopped before Claude was ready");
|
|
108416
108571
|
}
|
|
108417
|
-
|
|
108572
|
+
let snapshot;
|
|
108573
|
+
try {
|
|
108574
|
+
snapshot = await this.capturePane();
|
|
108575
|
+
} catch {
|
|
108576
|
+
await this.sleep(500);
|
|
108577
|
+
continue;
|
|
108578
|
+
}
|
|
108418
108579
|
if (paneHasClaudeExited(snapshot))
|
|
108419
108580
|
throw new Error("Claude exited before the initial prompt was sent");
|
|
108420
|
-
if (!paneHasSelectionPrompt(snapshot))
|
|
108581
|
+
if (!paneHasSelectionPrompt(snapshot)) {
|
|
108582
|
+
if (!await this.tmuxAlive().catch(() => false)) {
|
|
108583
|
+
throw new Error("tmux session stopped before Claude was ready");
|
|
108584
|
+
}
|
|
108421
108585
|
return;
|
|
108422
|
-
|
|
108586
|
+
}
|
|
108587
|
+
await this.sleep(500);
|
|
108423
108588
|
}
|
|
108424
108589
|
throw new Error("timed out waiting for Claude to leave its startup prompt");
|
|
108425
108590
|
}
|
|
@@ -108434,7 +108599,7 @@ ${help.stderr}`;
|
|
|
108434
108599
|
(async () => {
|
|
108435
108600
|
try {
|
|
108436
108601
|
while (!this.stopRequested) {
|
|
108437
|
-
await
|
|
108602
|
+
await this.pollTick(POLL_INTERVAL_MS);
|
|
108438
108603
|
if (this.stopRequested)
|
|
108439
108604
|
break;
|
|
108440
108605
|
tick += 1;
|
|
@@ -108887,11 +109052,11 @@ ${help.stderr}`;
|
|
|
108887
109052
|
}
|
|
108888
109053
|
}
|
|
108889
109054
|
async waitForPaneFastMode(target, initialSnapshot, command) {
|
|
108890
|
-
const deadline =
|
|
108891
|
-
while (
|
|
109055
|
+
const deadline = this.now() + FAST_MODE_SWITCH_TIMEOUT_MS;
|
|
109056
|
+
while (this.now() < deadline) {
|
|
108892
109057
|
const snapshot = await this.capturePane();
|
|
108893
109058
|
if (snapshot === initialSnapshot) {
|
|
108894
|
-
await
|
|
109059
|
+
await this.sleep(FAST_MODE_POLL_MS);
|
|
108895
109060
|
continue;
|
|
108896
109061
|
}
|
|
108897
109062
|
if (paneHasClaudeExited(snapshot)) {
|
|
@@ -108907,7 +109072,7 @@ ${help.stderr}`;
|
|
|
108907
109072
|
const observed = fastModeFromPane(response);
|
|
108908
109073
|
if (observed === target)
|
|
108909
109074
|
return;
|
|
108910
|
-
await
|
|
109075
|
+
await this.sleep(FAST_MODE_POLL_MS);
|
|
108911
109076
|
}
|
|
108912
109077
|
throw new Error(`Claude did not confirm fast mode ${target ? "on" : "off"}`);
|
|
108913
109078
|
}
|
|
@@ -108944,16 +109109,16 @@ ${help.stderr}`;
|
|
|
108944
109109
|
return permissionModeFromPane(snapshot);
|
|
108945
109110
|
}
|
|
108946
109111
|
async waitForPanePermissionMode(targetMode) {
|
|
108947
|
-
const deadline =
|
|
109112
|
+
const deadline = this.now() + PERMISSION_MODE_SWITCH_TIMEOUT_MS;
|
|
108948
109113
|
let lastObservedMode;
|
|
108949
|
-
while (
|
|
109114
|
+
while (this.now() < deadline) {
|
|
108950
109115
|
const observedMode = await this.capturePanePermissionMode();
|
|
108951
109116
|
if (observedMode) {
|
|
108952
109117
|
lastObservedMode = observedMode;
|
|
108953
109118
|
if (observedMode === targetMode)
|
|
108954
109119
|
return observedMode;
|
|
108955
109120
|
}
|
|
108956
|
-
await
|
|
109121
|
+
await this.sleep(PERMISSION_MODE_POLL_MS);
|
|
108957
109122
|
}
|
|
108958
109123
|
const observed = lastObservedMode ? `; observed ${lastObservedMode}` : "";
|
|
108959
109124
|
throw new Error(`Claude did not enter ${targetMode}${observed}`);
|
|
@@ -108971,20 +109136,20 @@ ${help.stderr}`;
|
|
|
108971
109136
|
});
|
|
108972
109137
|
}
|
|
108973
109138
|
async waitForCommandIdle() {
|
|
108974
|
-
const started =
|
|
109139
|
+
const started = this.now();
|
|
108975
109140
|
const deadline = started + COMMAND_IDLE_TIMEOUT_MS;
|
|
108976
109141
|
const noHookDeadline = started + COMMAND_NO_HOOK_SETTLE_MS;
|
|
108977
109142
|
let sawBusy = this.busy;
|
|
108978
|
-
while (
|
|
109143
|
+
while (this.now() < deadline) {
|
|
108979
109144
|
if (this.busy) {
|
|
108980
109145
|
sawBusy = true;
|
|
108981
109146
|
} else if (sawBusy) {
|
|
108982
|
-
await
|
|
109147
|
+
await this.sleep(COMMAND_AFTER_IDLE_SETTLE_MS);
|
|
108983
109148
|
return;
|
|
108984
|
-
} else if (
|
|
109149
|
+
} else if (this.now() >= noHookDeadline) {
|
|
108985
109150
|
return;
|
|
108986
109151
|
}
|
|
108987
|
-
await
|
|
109152
|
+
await this.sleep(50);
|
|
108988
109153
|
}
|
|
108989
109154
|
console.warn("[tmux] timed out waiting for Claude slash command to settle", this.tmuxSession);
|
|
108990
109155
|
}
|
|
@@ -109255,12 +109420,12 @@ function resolveBundledClaudePath(context) {
|
|
|
109255
109420
|
function resolvePinnedClaudeCommand(context, backend) {
|
|
109256
109421
|
return backend.kind === "container" ? undefined : resolveBundledClaudePath(context);
|
|
109257
109422
|
}
|
|
109258
|
-
async function getOrCreateSession(context, environmentId, tabId, resumeSessionId) {
|
|
109423
|
+
async function getOrCreateSession(context, environmentId, tabId, resumeSessionId, timing) {
|
|
109259
109424
|
const existing = tmuxManager.get(environmentId, tabId);
|
|
109260
109425
|
if (existing)
|
|
109261
109426
|
return existing;
|
|
109262
109427
|
const backend = await resolveBackend(environmentId, context);
|
|
109263
|
-
const session = new TmuxSession(environmentId, tabId, backend, runtimeRootPrefixForContext(context), resumeSessionId, resolvePinnedClaudeCommand(context, backend));
|
|
109428
|
+
const session = new TmuxSession(environmentId, tabId, backend, runtimeRootPrefixForContext(context), resumeSessionId, resolvePinnedClaudeCommand(context, backend), timing);
|
|
109264
109429
|
tmuxManager.insert(environmentId, tabId, session);
|
|
109265
109430
|
return session;
|
|
109266
109431
|
}
|
|
@@ -109982,7 +110147,7 @@ function registerTmuxBackendCommands(registerCommand, options2 = {}) {
|
|
|
109982
110147
|
else
|
|
109983
110148
|
await killOrphanSession(context, envId, tab);
|
|
109984
110149
|
}
|
|
109985
|
-
const session = await getOrCreateSession(context, envId, tab, resumeId);
|
|
110150
|
+
const session = await getOrCreateSession(context, envId, tab, resumeId, options2.sessionTiming);
|
|
109986
110151
|
if (firstPrompt)
|
|
109987
110152
|
session.claimFirstPromptForNaming();
|
|
109988
110153
|
await installWorkspaceHooks(session.backend, session.workspaceHookPaths);
|
|
@@ -112637,6 +112802,19 @@ async function main() {
|
|
|
112637
112802
|
if (index !== candidate.index && cmd.cwd === "." && !coveredBy.has(index) && !Array.from(coveredBy.values()).includes(index) && candidate.covers.includes(cmd.command) && !dependsOn(candidate.index, index) && cmd.dependsOn.every(id => dependsOn(candidate.index, commands.findIndex(dependency => dependency.id === id)))) coveredBy.set(index, candidate.index);
|
|
112638
112803
|
}
|
|
112639
112804
|
}
|
|
112805
|
+
// A repeated identical command (same command line and directory) adds no
|
|
112806
|
+
// evidence, only its cost again. The first copy covers it, unless the copy
|
|
112807
|
+
// waits on setup the first does not, which may make the rerun meaningful.
|
|
112808
|
+
const repeats = new Set();
|
|
112809
|
+
for (const [index, cmd] of commands.entries()) {
|
|
112810
|
+
if (coveredBy.has(index)) continue;
|
|
112811
|
+
const first = commands.findIndex(other => other.command === cmd.command && other.cwd === cmd.cwd);
|
|
112812
|
+
if (first === index) continue;
|
|
112813
|
+
const prerequisites = cmd.dependsOn.map(id => commands.findIndex(dependency => dependency.id === id));
|
|
112814
|
+
if (!prerequisites.every(dependency => dependency === first || dependsOn(first, dependency))) continue;
|
|
112815
|
+
coveredBy.set(index, coveredBy.get(first) ?? first);
|
|
112816
|
+
repeats.add(index);
|
|
112817
|
+
}
|
|
112640
112818
|
const pending = new Set(commands.map((_, i) => i));
|
|
112641
112819
|
while ((pending.size || tasks.size) && !stopping) {
|
|
112642
112820
|
for (const index of Array.from(pending)) {
|
|
@@ -112652,7 +112830,7 @@ async function main() {
|
|
|
112652
112830
|
if (coveringIndex !== undefined) {
|
|
112653
112831
|
const covering = run.results[coveringIndex];
|
|
112654
112832
|
if (["pending", "queued", "running"].includes(covering.status)) continue;
|
|
112655
|
-
Object.assign(run.results[index], { status: covering.status === "passed" ? "skipped" : "incomplete", limitation: covering.status === "passed" ? "Covered by " + covering.id + " (repository-declared coverage)" : "Covering validation " + covering.id + " did not pass" });
|
|
112833
|
+
Object.assign(run.results[index], { status: covering.status === "passed" ? "skipped" : "incomplete", limitation: covering.status === "passed" ? "Covered by " + covering.id + (repeats.has(index) ? " (identical command)" : " (repository-declared coverage)") : "Covering validation " + covering.id + " did not pass" });
|
|
112656
112834
|
pending.delete(index);
|
|
112657
112835
|
persist();
|
|
112658
112836
|
continue;
|
|
@@ -113178,11 +113356,13 @@ async function main() {
|
|
|
113178
113356
|
let timedOut = false;
|
|
113179
113357
|
let cancelled = false;
|
|
113180
113358
|
let killTimer = null;
|
|
113359
|
+
// Tests may only shorten the TERM-to-KILL grace; production uses 5s.
|
|
113360
|
+
const killGraceMs = Math.max(100, Math.min(5000, Number(process.env.ORKESTRATOR_TEST_EXEC_KILL_GRACE_MS) || 5000));
|
|
113181
113361
|
const terminate = () => {
|
|
113182
113362
|
if (!pgid) return;
|
|
113183
113363
|
signalGroup(pgid, "SIGTERM");
|
|
113184
113364
|
if (!killTimer) {
|
|
113185
|
-
killTimer = setTimeout(() => signalGroup(pgid, "SIGKILL"),
|
|
113365
|
+
killTimer = setTimeout(() => signalGroup(pgid, "SIGKILL"), killGraceMs);
|
|
113186
113366
|
}
|
|
113187
113367
|
};
|
|
113188
113368
|
const pump = (stream, fd, name) => stream.on("data", (chunk) => {
|
|
@@ -129964,6 +130144,7 @@ var NODE_EXCLUDED_COMMANDS = new Set([
|
|
|
129964
130144
|
"detach_controller_here",
|
|
129965
130145
|
"allow_controller",
|
|
129966
130146
|
"list_controllers",
|
|
130147
|
+
"sync_controller_settings",
|
|
129967
130148
|
"set_command_node_enabled",
|
|
129968
130149
|
"create_client_grant",
|
|
129969
130150
|
"renew_client_grant",
|
|
@@ -130481,9 +130662,113 @@ class NodePortal {
|
|
|
130481
130662
|
}
|
|
130482
130663
|
}
|
|
130483
130664
|
|
|
130665
|
+
// ../../apps/backend/src/core/index.ts
|
|
130666
|
+
init_controller_settings();
|
|
130667
|
+
|
|
130484
130668
|
// ../../apps/backend/src/core/nodes/node-service.ts
|
|
130485
130669
|
init_command_nodes();
|
|
130486
130670
|
init_federation();
|
|
130671
|
+
init_controller_settings();
|
|
130672
|
+
|
|
130673
|
+
// ../../apps/backend/src/core/nodes/node-controller-settings.ts
|
|
130674
|
+
init_controller_settings();
|
|
130675
|
+
init_federation();
|
|
130676
|
+
import { isDeepStrictEqual } from "util";
|
|
130677
|
+
|
|
130678
|
+
class LeafControllerSettings {
|
|
130679
|
+
options;
|
|
130680
|
+
store = { version: 1, entries: [] };
|
|
130681
|
+
mutations = Promise.resolve();
|
|
130682
|
+
serialize(run) {
|
|
130683
|
+
const next = this.mutations.then(run);
|
|
130684
|
+
this.mutations = next.catch(() => {
|
|
130685
|
+
return;
|
|
130686
|
+
});
|
|
130687
|
+
return next;
|
|
130688
|
+
}
|
|
130689
|
+
constructor(options2) {
|
|
130690
|
+
this.options = options2;
|
|
130691
|
+
}
|
|
130692
|
+
async start() {
|
|
130693
|
+
this.store = await this.options.storage.getControllerSettings();
|
|
130694
|
+
}
|
|
130695
|
+
async receive(args) {
|
|
130696
|
+
return this.serialize(() => this.receiveSettings(args));
|
|
130697
|
+
}
|
|
130698
|
+
async receiveSettings(args) {
|
|
130699
|
+
const controllerId = args.controllerId;
|
|
130700
|
+
if (!isBackendId(controllerId))
|
|
130701
|
+
throw new Error("Expected controllerId to be a node identity.");
|
|
130702
|
+
if (this.options.attachedName(controllerId) === null) {
|
|
130703
|
+
throw new Error("That command node is not attached to this node.");
|
|
130704
|
+
}
|
|
130705
|
+
const settings = parseInheritedGlobalSettings(args.settings);
|
|
130706
|
+
const before = this.source();
|
|
130707
|
+
const changedAt = new Date(this.options.now()).toISOString();
|
|
130708
|
+
const changed = await this.options.storage.mutateControllerSettings((current) => {
|
|
130709
|
+
const existing = current.entries.find((entry) => entry.controllerId === controllerId);
|
|
130710
|
+
if (existing && isDeepStrictEqual(existing.settings, settings)) {
|
|
130711
|
+
return { next: null, result: { value: current, changed: false } };
|
|
130712
|
+
}
|
|
130713
|
+
const value = {
|
|
130714
|
+
version: 1,
|
|
130715
|
+
entries: [
|
|
130716
|
+
...current.entries.filter((entry) => entry.controllerId !== controllerId),
|
|
130717
|
+
{ controllerId, settings, changedAt }
|
|
130718
|
+
]
|
|
130719
|
+
};
|
|
130720
|
+
return { next: value, result: { value, changed: true } };
|
|
130721
|
+
});
|
|
130722
|
+
this.store = changed.value;
|
|
130723
|
+
if (changed.changed || !isDeepStrictEqual(before, this.source()))
|
|
130724
|
+
this.options.onChange();
|
|
130725
|
+
return { applied: this.source()?.controllerId === controllerId };
|
|
130726
|
+
}
|
|
130727
|
+
async forget(controllerId) {
|
|
130728
|
+
return this.serialize(() => this.forgetSettings(controllerId));
|
|
130729
|
+
}
|
|
130730
|
+
async forgetSettings(controllerId) {
|
|
130731
|
+
const hadEntry = this.store.entries.some((entry) => entry.controllerId === controllerId);
|
|
130732
|
+
this.store = {
|
|
130733
|
+
...this.store,
|
|
130734
|
+
entries: this.store.entries.filter((entry) => entry.controllerId !== controllerId)
|
|
130735
|
+
};
|
|
130736
|
+
if (hadEntry)
|
|
130737
|
+
this.options.onChange();
|
|
130738
|
+
const removed = await this.options.storage.mutateControllerSettings((current) => {
|
|
130739
|
+
const entries = current.entries.filter((entry) => entry.controllerId !== controllerId);
|
|
130740
|
+
if (entries.length === current.entries.length) {
|
|
130741
|
+
return { next: null, result: { value: current, removed: false } };
|
|
130742
|
+
}
|
|
130743
|
+
const value = { version: 1, entries };
|
|
130744
|
+
return { next: value, result: { value, removed: true } };
|
|
130745
|
+
});
|
|
130746
|
+
this.store = removed.value;
|
|
130747
|
+
}
|
|
130748
|
+
current() {
|
|
130749
|
+
return this.followed()?.settings ?? null;
|
|
130750
|
+
}
|
|
130751
|
+
source() {
|
|
130752
|
+
const entry = this.followed();
|
|
130753
|
+
if (!entry)
|
|
130754
|
+
return;
|
|
130755
|
+
return {
|
|
130756
|
+
controllerId: entry.controllerId,
|
|
130757
|
+
displayName: this.options.attachedName(entry.controllerId) ?? "",
|
|
130758
|
+
changedAt: entry.changedAt
|
|
130759
|
+
};
|
|
130760
|
+
}
|
|
130761
|
+
followed() {
|
|
130762
|
+
let best = null;
|
|
130763
|
+
for (const entry of this.store.entries) {
|
|
130764
|
+
if (this.options.attachedName(entry.controllerId) === null)
|
|
130765
|
+
continue;
|
|
130766
|
+
if (!best || entry.changedAt >= best.changedAt)
|
|
130767
|
+
best = entry;
|
|
130768
|
+
}
|
|
130769
|
+
return best;
|
|
130770
|
+
}
|
|
130771
|
+
}
|
|
130487
130772
|
|
|
130488
130773
|
// ../../apps/backend/src/core/nodes/node-ssh-prompts.ts
|
|
130489
130774
|
init_command_nodes();
|
|
@@ -130562,12 +130847,25 @@ class NodeService {
|
|
|
130562
130847
|
changeListeners = new Set;
|
|
130563
130848
|
detachListeners = new Set;
|
|
130564
130849
|
changeQueued = false;
|
|
130850
|
+
inheritedSettings;
|
|
130851
|
+
sentSettings = new WeakMap;
|
|
130852
|
+
unsupportedSettings = new WeakMap;
|
|
130853
|
+
settingsSends = new WeakMap;
|
|
130565
130854
|
constructor(options2) {
|
|
130566
130855
|
this.options = options2;
|
|
130567
130856
|
this.storage = options2.storage;
|
|
130568
130857
|
this.emit = options2.emit;
|
|
130569
130858
|
this.now = options2.now ?? Date.now;
|
|
130570
130859
|
this.prompts = new NodeSshPrompts(options2.emit);
|
|
130860
|
+
this.inheritedSettings = new LeafControllerSettings({
|
|
130861
|
+
storage: options2.storage,
|
|
130862
|
+
now: this.now,
|
|
130863
|
+
attachedName: (controllerId) => this.attachments.controllers.find((entry) => entry.controllerId === controllerId)?.displayName ?? null,
|
|
130864
|
+
onChange: () => {
|
|
130865
|
+
this.options.onInheritedSettingsChanged?.();
|
|
130866
|
+
this.emitStatus();
|
|
130867
|
+
}
|
|
130868
|
+
});
|
|
130571
130869
|
}
|
|
130572
130870
|
async start() {
|
|
130573
130871
|
if (this.started)
|
|
@@ -130576,6 +130874,7 @@ class NodeService {
|
|
|
130576
130874
|
await this.self();
|
|
130577
130875
|
this.attachments = await this.storage.getNodeAttachments();
|
|
130578
130876
|
this.registry = await this.storage.getNodeRegistry();
|
|
130877
|
+
await this.inheritedSettings.start();
|
|
130579
130878
|
this.lastMode = this.mode();
|
|
130580
130879
|
this.applyMode();
|
|
130581
130880
|
this.retryDetaches();
|
|
@@ -130628,6 +130927,9 @@ class NodeService {
|
|
|
130628
130927
|
const recent = this.mostRecentControllerId();
|
|
130629
130928
|
if (recent)
|
|
130630
130929
|
status.mostRecentControllerId = recent;
|
|
130930
|
+
const inherited = this.inheritedSettings.source();
|
|
130931
|
+
if (inherited)
|
|
130932
|
+
status.inheritedSettingsFrom = inherited;
|
|
130631
130933
|
return status;
|
|
130632
130934
|
}
|
|
130633
130935
|
mode() {
|
|
@@ -130702,6 +131004,9 @@ class NodeService {
|
|
|
130702
131004
|
if (registry.nodes.some((node) => node.nodeId === parsed.controllerId)) {
|
|
130703
131005
|
throw new NodeRefusalError(NODE_REFUSAL_CODES.controllerIsSubordinate, "That command node is registered under this node. Forget it here first; only two levels are allowed.");
|
|
130704
131006
|
}
|
|
131007
|
+
if (!this.attachments.controllers.some((entry) => entry.controllerId === parsed.controllerId)) {
|
|
131008
|
+
await this.inheritedSettings.forget(parsed.controllerId);
|
|
131009
|
+
}
|
|
130705
131010
|
const timestamp = new Date(this.now()).toISOString();
|
|
130706
131011
|
const next = await this.storage.mutateNodeAttachments((current) => {
|
|
130707
131012
|
if (current.detachedControllers.some((entry) => entry.controllerId === parsed.controllerId)) {
|
|
@@ -130765,6 +131070,7 @@ class NodeService {
|
|
|
130765
131070
|
this.applyMode();
|
|
130766
131071
|
this.emitStatus();
|
|
130767
131072
|
}
|
|
131073
|
+
await this.inheritedSettings.forget(controllerId);
|
|
130768
131074
|
return { detached: result.detached };
|
|
130769
131075
|
}
|
|
130770
131076
|
listControllers() {
|
|
@@ -130865,6 +131171,51 @@ class NodeService {
|
|
|
130865
131171
|
console.warn("[nodes] Could not record the latest controller contact:", errorCode(error));
|
|
130866
131172
|
}
|
|
130867
131173
|
}
|
|
131174
|
+
syncSettingsToLeaves() {
|
|
131175
|
+
for (const link of this.links.values())
|
|
131176
|
+
this.syncSettingsTo(link);
|
|
131177
|
+
}
|
|
131178
|
+
syncSettingsTo(link) {
|
|
131179
|
+
const source = this.options.inheritableSettings;
|
|
131180
|
+
if (!source || !link.connected || this.isLeaf())
|
|
131181
|
+
return;
|
|
131182
|
+
const nodeId = link.nodeId;
|
|
131183
|
+
const previous = this.settingsSends.get(link) ?? Promise.resolve();
|
|
131184
|
+
const next = previous.then(async () => {
|
|
131185
|
+
if (!link.connected || this.links.get(nodeId) !== link)
|
|
131186
|
+
return;
|
|
131187
|
+
const epoch = link.view.epoch;
|
|
131188
|
+
if (this.unsupportedSettings.get(link) === epoch)
|
|
131189
|
+
return;
|
|
131190
|
+
const settings = await source();
|
|
131191
|
+
if (!link.connected || this.links.get(nodeId) !== link || link.view.epoch !== epoch)
|
|
131192
|
+
return;
|
|
131193
|
+
const serialized = JSON.stringify(settings);
|
|
131194
|
+
const sent = this.sentSettings.get(link);
|
|
131195
|
+
if (sent && sent.epoch === epoch && sent.settings === serialized)
|
|
131196
|
+
return;
|
|
131197
|
+
try {
|
|
131198
|
+
await link.invoke(SYNC_CONTROLLER_SETTINGS_COMMAND, {
|
|
131199
|
+
controllerId: this.selfId,
|
|
131200
|
+
settings
|
|
131201
|
+
});
|
|
131202
|
+
this.sentSettings.set(link, { epoch, settings: serialized });
|
|
131203
|
+
} catch (error) {
|
|
131204
|
+
if (error instanceof Error && error.message === `Unknown command: ${SYNC_CONTROLLER_SETTINGS_COMMAND}`) {
|
|
131205
|
+
this.unsupportedSettings.set(link, epoch);
|
|
131206
|
+
}
|
|
131207
|
+
console.warn("[nodes] Could not send settings to a node:", errorCode(error));
|
|
131208
|
+
}
|
|
131209
|
+
});
|
|
131210
|
+
const settled = next.catch(() => {
|
|
131211
|
+
return;
|
|
131212
|
+
});
|
|
131213
|
+
this.settingsSends.set(link, settled);
|
|
131214
|
+
settled.then(() => {
|
|
131215
|
+
if (this.settingsSends.get(link) === settled)
|
|
131216
|
+
this.settingsSends.delete(link);
|
|
131217
|
+
});
|
|
131218
|
+
}
|
|
130868
131219
|
async listNodes() {
|
|
130869
131220
|
const registry = await this.currentRegistry();
|
|
130870
131221
|
return {
|
|
@@ -131216,7 +131567,10 @@ class NodeService {
|
|
|
131216
131567
|
ssh: this.sshFor(nodeId),
|
|
131217
131568
|
...this.options.timers ? { timers: this.options.timers } : {},
|
|
131218
131569
|
...this.options.heartbeatMs ? { heartbeatMs: this.options.heartbeatMs } : {},
|
|
131219
|
-
onChange: () =>
|
|
131570
|
+
onChange: () => {
|
|
131571
|
+
this.publishNodes();
|
|
131572
|
+
this.syncSettingsTo(link);
|
|
131573
|
+
},
|
|
131220
131574
|
onVerified: (update) => void this.recordVerified(link.nodeId, update),
|
|
131221
131575
|
onIdentity: (installationId) => this.adoptIdentity(link.nodeId, installationId)
|
|
131222
131576
|
});
|
|
@@ -158040,6 +158394,7 @@ function registerBackendProfileCommands(register) {
|
|
|
158040
158394
|
|
|
158041
158395
|
// ../../apps/backend/src/core/commands-registry-nodes.ts
|
|
158042
158396
|
init_command_nodes();
|
|
158397
|
+
init_controller_settings();
|
|
158043
158398
|
init_federation();
|
|
158044
158399
|
init_commands_validation();
|
|
158045
158400
|
init_node_files();
|
|
@@ -158098,6 +158453,10 @@ function registerNodeCommands(register) {
|
|
|
158098
158453
|
assertOnlyKeys(args, ["controllerId"], "arguments");
|
|
158099
158454
|
return nodes(context).detachController(args.controllerId, { byUser: true });
|
|
158100
158455
|
});
|
|
158456
|
+
register(SYNC_CONTROLLER_SETTINGS_COMMAND, async (args, context) => {
|
|
158457
|
+
assertOnlyKeys(args, ["controllerId", "settings"], "arguments");
|
|
158458
|
+
return nodes(context).inheritedSettings.receive(args);
|
|
158459
|
+
});
|
|
158101
158460
|
register("allow_controller", async (args, context) => {
|
|
158102
158461
|
assertOnlyKeys(args, ["controllerId"], "arguments");
|
|
158103
158462
|
return nodes(context).allowController(args.controllerId);
|
|
@@ -177662,8 +178021,13 @@ function registerProjectCommands(register, dependencies) {
|
|
|
177662
178021
|
const force = args.force === undefined ? false : asRequiredBoolean(args.force, "force");
|
|
177663
178022
|
return planUsageReader(context, platform, { force });
|
|
177664
178023
|
});
|
|
177665
|
-
register("update_global_config", async ({ global: global2 }, { storage }) => {
|
|
177666
|
-
const updated = await storage.updateGlobalConfig(stripRendererCredentials(asRecord3(global2, "global")), {
|
|
178024
|
+
register("update_global_config", async ({ global: global2, preserveInheritedSettings }, { storage }) => {
|
|
178025
|
+
const updated = await storage.updateGlobalConfig(stripRendererCredentials(asRecord3(global2, "global")), {
|
|
178026
|
+
preserveCredentials: true,
|
|
178027
|
+
...preserveInheritedSettings === undefined ? {} : {
|
|
178028
|
+
preserveInheritedSettings: asRequiredBoolean(preserveInheritedSettings, "preserveInheritedSettings")
|
|
178029
|
+
}
|
|
178030
|
+
});
|
|
177667
178031
|
configureTerminalHistoryRetention({
|
|
177668
178032
|
enabled: updated.global.terminalHistoryEnabled,
|
|
177669
178033
|
sessionMb: updated.global.terminalHistoryRetentionMb,
|
|
@@ -228563,9 +228927,12 @@ class OrkestratorBackend {
|
|
|
228563
228927
|
...options2.nodeLinks?.fetchImpl ? { fetchImpl: options2.nodeLinks.fetchImpl } : {},
|
|
228564
228928
|
...options2.nodeLinks?.timers ? { timers: options2.nodeLinks.timers } : {},
|
|
228565
228929
|
...options2.nodeLinks?.heartbeatMs ? { heartbeatMs: options2.nodeLinks.heartbeatMs } : {},
|
|
228566
|
-
...options2.nodeLinks?.createSsh ?? createNodeSshFactory(options2.dataDir) ? { createSsh: options2.nodeLinks?.createSsh ?? createNodeSshFactory(options2.dataDir) } : {}
|
|
228930
|
+
...options2.nodeLinks?.createSsh ?? createNodeSshFactory(options2.dataDir) ? { createSsh: options2.nodeLinks?.createSsh ?? createNodeSshFactory(options2.dataDir) } : {},
|
|
228931
|
+
inheritableSettings: async () => pickInheritedGlobalSettings((await storage.loadConfig()).global),
|
|
228932
|
+
onInheritedSettingsChanged: () => storage.announceInheritedSettingsChanged()
|
|
228567
228933
|
});
|
|
228568
228934
|
context.nodes = this.nodes;
|
|
228935
|
+
storage.inheritedGlobalSettings = () => this.nodes.inheritedSettings.current();
|
|
228569
228936
|
this.nodePortal = new NodePortal({
|
|
228570
228937
|
nodes: this.nodes,
|
|
228571
228938
|
emit: (event, payload) => options2.emit(event, payload),
|
|
@@ -228865,6 +229232,8 @@ class OrkestratorBackend {
|
|
|
228865
229232
|
}
|
|
228866
229233
|
if (change.resource === "prompt-queue")
|
|
228867
229234
|
this.activityJobs?.wake("tmux-queues");
|
|
229235
|
+
if (change.resource === "config")
|
|
229236
|
+
this.nodes.syncSettingsToLeaves();
|
|
228868
229237
|
});
|
|
228869
229238
|
this.reapPidServers = options2.startupReapers?.localServers ?? reapOrphanedLocalServers;
|
|
228870
229239
|
this.reapTmuxRuntimes = options2.startupReapers?.claudeTmuxRuntimes ?? reapOrphanedClaudeTmuxRuntimes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orkestrator",
|
|
3
|
-
"version": "2.26.
|
|
3
|
+
"version": "2.26.3-beta",
|
|
4
4
|
"description": "Run the Orkestrator backend service without the desktop application",
|
|
5
5
|
"homepage": "https://orkestrator.dev",
|
|
6
6
|
"repository": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"sharp": "0.35.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@orkestrator/protocol": "2.26.
|
|
43
|
-
"@orkestrator/toolchain": "2.26.
|
|
42
|
+
"@orkestrator/protocol": "2.26.3-beta",
|
|
43
|
+
"@orkestrator/toolchain": "2.26.3-beta"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"bun": ">=1.4.3"
|