@zq-silk/yui 2.22.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/artifacts/artifactCapability.js +6 -6
- package/dist/artifacts/gitArtifactRef.js +10 -10
- package/dist/artifacts/jobArtifact.js +5 -5
- package/dist/artifacts/managedGit.js +15 -12
- package/dist/artifacts/taskArtifactRepository.js +35 -33
- package/dist/cli/commandCatalog.js +2 -2
- package/dist/cli/updateCommand.js +20 -3
- package/dist/cli/updateOrchestrator.js +13 -0
- package/dist/cli/updatePorts.js +27 -0
- package/dist/cli.js +9 -2
- package/dist/commands/configCommands.js +64 -1
- package/dist/commands/projectCommands.js +9 -9
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +1 -1
- package/dist/commands/taskPublicationAdoptCommand.js +1 -1
- package/dist/commands/taskRoleRuntimeStatus.js +4 -0
- package/dist/commands/taskUpstreamCommands.js +1 -1
- package/dist/config/configCatalog.js +3 -0
- package/dist/config/gitConfig.js +20 -0
- package/dist/config/yuiConfig.js +1 -0
- package/dist/context/currentSkills.js +32 -1
- package/dist/context/sessionBootstrapManifest.js +17 -4
- package/dist/context/sessionSkillNotice.js +33 -12
- package/dist/controlPlaneCli.js +4 -4
- package/dist/controller/clientRuntime.js +37 -18
- package/dist/controller/controller.js +16 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +2 -0
- package/dist/controller/hostLifecycle.js +153 -0
- package/dist/controller/hostLifecycleChecks.js +27 -0
- package/dist/controller/hostUpgrade.js +344 -0
- package/dist/controller/jobArtifactControl.js +3 -3
- package/dist/controller/jobDefinitionControl.js +1 -1
- package/dist/controller/runtime.js +67 -16
- package/dist/controller/sessionNotify.js +5 -46
- package/dist/execution/codexThreadNaming.js +54 -43
- package/dist/executor/fileRoleLaunchPlanner.js +18 -3
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/kernel/builtinCapabilities.js +3 -3
- package/dist/repository/gitCommand.js +153 -0
- package/dist/repository/gitCompatibility.js +3 -2
- package/dist/repository/gitWorkspace.js +579 -563
- package/dist/repository/gitWorktreeInventory.js +15 -19
- package/dist/repository/projectRefresh.js +1 -1
- package/dist/repository/taskBaseFreshness.js +1 -4
- package/dist/repository/taskWorkspacePreparer.js +1 -1
- package/dist/runtime/agentEndpoint.js +11 -1
- package/dist/runtime/agentEndpointIdentity.js +2 -0
- package/dist/runtime/agentHost.js +167 -42
- package/dist/runtime/agentHostImplementation.js +3 -1
- package/dist/runtime/codexAppServerRuntime.js +3 -0
- package/dist/runtime/codexInteractiveHost.js +62 -17
- package/dist/runtime/codexNativeAccess.js +1 -1
- package/dist/runtime/contextRecovery.js +15 -0
- package/dist/runtime/hostSessionLifecycle.js +83 -0
- package/dist/runtime/hostUpgradeGate.js +43 -0
- package/dist/runtime/lifecycleChecks.js +47 -0
- package/dist/runtime/lifecycleReservation.js +9 -1
- package/dist/runtime/nativeExecutable.js +5 -2
- package/dist/runtime/nativeTitleReceipt.js +31 -0
- package/dist/runtime/nativeTitleSync.js +192 -0
- package/dist/runtime/platform.js +11 -0
- package/dist/runtime/providerRetry.js +4 -4
- package/dist/runtime/structuredProviderHost.js +42 -10
- package/dist/storage/sqliteSchema.js +30 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +7 -1
- package/dist/web/webConversation.js +2 -2
- package/dist/web/webMaterials.js +19 -19
- package/dist/web/webTaskSurface.js +3 -3
- package/dist/workspace/workItemChangeSetManager.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +96 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +70 -1
- package/docs/project-refresh.md +3 -2
- package/docs/project-refresh.zh-CN.md +2 -1
- package/docs/release-workflow.md +86 -2
- package/docs/release-workflow.zh-CN.md +65 -1
- package/docs/roles-and-configuration.md +34 -0
- package/docs/roles-and-configuration.zh-CN.md +27 -0
- package/docs/storage-baseline.md +14 -1
- package/docs/storage-baseline.zh-CN.md +7 -1
- package/docs/testing/verification-levels.md +7 -7
- package/docs/testing/verification-levels.zh-CN.md +6 -6
- package/i18n/README.zh-CN.md +8 -4
- package/package.json +1 -1
- package/skills/yui-runtime/SKILL.md +7 -2
- package/skills/yui-runtime/VERSION +1 -1
- package/dist/runtime/native/darwin-x64/claude-process-owner +0 -0
- package/dist/runtime/native/darwin-x64/process-identity +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/zhangqian-silk/yui/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
[](#contributing)
|
|
10
10
|
|
|
11
11
|
Give your Agents work to carry forward, not just another chat to answer.
|
|
@@ -46,15 +46,20 @@ For the self-built coding Agent (without a Controller or Yui Home), see
|
|
|
46
46
|
[`yui agent`](docs/native-agent.md). Its explicit configuration and product state
|
|
47
47
|
are separate from the orchestration setup below.
|
|
48
48
|
|
|
49
|
-
You need Linux x64 (glibc) or macOS
|
|
49
|
+
You need Linux x64 (glibc) or macOS Apple Silicon with native arm64 Node.js, Git 2.29.0 or newer, tmux, and
|
|
50
50
|
Node.js `^20.17.0`, `^22.9.0`, `^24.0.0` or `^26.0.0`. On macOS install tmux with
|
|
51
51
|
`brew install tmux`. For the simplest setup, have Codex CLI or Claude Code CLI
|
|
52
52
|
installed and ready to use with your own account. Yui coordinates those
|
|
53
53
|
Agents; it does not supply model access.
|
|
54
54
|
|
|
55
|
-
The npm package includes prebuilt Yui helpers for Linux x64
|
|
56
|
-
Apple Silicon. The same package is tested on
|
|
55
|
+
The npm package includes prebuilt Yui helpers for Linux x64 and macOS
|
|
56
|
+
Apple Silicon. The same package is tested on both platforms before
|
|
57
57
|
publication; Yui selects only the helper for the running platform.
|
|
58
|
+
Starting with 2.25.0, macOS Intel and x64 Node.js under Rosetta are unsupported.
|
|
59
|
+
Intel Mac users must stay on 2.23.0; Apple Silicon users must switch to native
|
|
60
|
+
arm64 Node.js. npm's OS/CPU fields cannot express these platform pairs, so npm
|
|
61
|
+
installation may succeed on an unsupported combination, but Yui rejects it
|
|
62
|
+
before loading either CLI product or opening the Home. No install scripts are added.
|
|
58
63
|
|
|
59
64
|
For Claude, Yui passes through your authentication environment and native
|
|
60
65
|
configuration directory; Claude selects the API key or login method using its
|
|
@@ -32,13 +32,13 @@ function requireTextContent(content) {
|
|
|
32
32
|
* `artifact.save`: write `content` to `relativePath` in the Task's local repo,
|
|
33
33
|
* commit exactly that path, and return the resulting commit-pinned reference.
|
|
34
34
|
*/
|
|
35
|
-
export async function saveArtifactCapability(home, taskId, input) {
|
|
35
|
+
export async function saveArtifactCapability(home, taskId, input, getConfig) {
|
|
36
36
|
const bytes = artifactInputBytes(input);
|
|
37
37
|
return saveArtifactFile(home, taskId, {
|
|
38
38
|
relativePath: input.relativePath, bytes,
|
|
39
39
|
...(input.message === undefined ? {} : { message: input.message }),
|
|
40
40
|
...(input.expectedHead === undefined ? {} : { expectedHead: input.expectedHead })
|
|
41
|
-
});
|
|
41
|
+
}, getConfig);
|
|
42
42
|
}
|
|
43
43
|
/** Shared bounded text/static-image input; never executes HTML or scripts. */
|
|
44
44
|
export function artifactInputBytes(input) {
|
|
@@ -66,8 +66,8 @@ export function artifactInputBytes(input) {
|
|
|
66
66
|
* frozen evidence. Images return explicit base64 and bounded raster metadata;
|
|
67
67
|
* text returns UTF-8. Both carry the resolved commit and exact digest.
|
|
68
68
|
*/
|
|
69
|
-
export async function readArtifactCapability(home, taskId, input) {
|
|
70
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
69
|
+
export async function readArtifactCapability(home, taskId, input, getConfig) {
|
|
70
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
71
71
|
if (!repo.exists())
|
|
72
72
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
73
73
|
const safeRelative = safeRelativeArtifactPath(input.relativePath);
|
|
@@ -101,8 +101,8 @@ export function presentArtifactContent(content) {
|
|
|
101
101
|
* `artifact.list`: tracked artifacts at HEAD (empty when the Task has no repo
|
|
102
102
|
* yet). This is an ordinary current read; it does not freeze anything.
|
|
103
103
|
*/
|
|
104
|
-
export async function listArtifactsCapability(home, taskId) {
|
|
105
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
104
|
+
export async function listArtifactsCapability(home, taskId, getConfig) {
|
|
105
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
106
106
|
if (!repo.exists())
|
|
107
107
|
return [];
|
|
108
108
|
return repo.list();
|
|
@@ -67,9 +67,9 @@ export function parseGitArtifactRef(value, taskId) {
|
|
|
67
67
|
* though for a correctly pinned commit this can only happen if the recorded
|
|
68
68
|
* digest was wrong, since the commit freezes the bytes.
|
|
69
69
|
*/
|
|
70
|
-
export async function resolveGitArtifact(home, ref) {
|
|
70
|
+
export async function resolveGitArtifact(home, ref, getConfig) {
|
|
71
71
|
const valid = validateGitArtifactRef(ref);
|
|
72
|
-
const repo = openTaskArtifactRepository(home, valid.taskId);
|
|
72
|
+
const repo = openTaskArtifactRepository(home, valid.taskId, getConfig);
|
|
73
73
|
if (!repo.exists()) {
|
|
74
74
|
throw new Error(`Artifact repository is unavailable for ${valid.taskId}.`);
|
|
75
75
|
}
|
|
@@ -88,8 +88,8 @@ export async function resolveGitArtifact(home, ref) {
|
|
|
88
88
|
* for the resulting state. The returned reference is self-certifying, so a
|
|
89
89
|
* caller can record it directly as frozen evidence.
|
|
90
90
|
*/
|
|
91
|
-
export async function saveArtifactFile(home, taskId, input) {
|
|
92
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
91
|
+
export async function saveArtifactFile(home, taskId, input, getConfig) {
|
|
92
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
93
93
|
const relativePath = safeRelativeArtifactPath(input.relativePath);
|
|
94
94
|
const result = await repo.save({
|
|
95
95
|
files: [{ relativePath, bytes: input.bytes }],
|
|
@@ -105,15 +105,15 @@ export async function saveArtifactFile(home, taskId, input) {
|
|
|
105
105
|
digest: content.digest
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
export async function readCurrentArtifact(home, taskId, relativePath) {
|
|
109
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
108
|
+
export async function readCurrentArtifact(home, taskId, relativePath, getConfig) {
|
|
109
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
110
110
|
if (!repo.exists())
|
|
111
111
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
112
112
|
return repo.read(safeRelativeArtifactPath(relativePath));
|
|
113
113
|
}
|
|
114
114
|
/** Ordinary listing: tracked artifacts at HEAD (ASYNC, not frozen). Empty when no repo yet. */
|
|
115
|
-
export async function listCurrentArtifacts(home, taskId) {
|
|
116
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
115
|
+
export async function listCurrentArtifacts(home, taskId, getConfig) {
|
|
116
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
117
117
|
if (!repo.exists())
|
|
118
118
|
return [];
|
|
119
119
|
return repo.list();
|
|
@@ -123,8 +123,8 @@ export async function listCurrentArtifacts(home, taskId) {
|
|
|
123
123
|
* references (ASYNC). Used at Candidate submission / completion time to capture
|
|
124
124
|
* exactly what exists now. Fails if any path is not tracked at HEAD.
|
|
125
125
|
*/
|
|
126
|
-
export async function pinCurrentArtifacts(home, taskId, relativePaths) {
|
|
127
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
126
|
+
export async function pinCurrentArtifacts(home, taskId, relativePaths, getConfig) {
|
|
127
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
128
128
|
if (!repo.exists())
|
|
129
129
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
130
130
|
const head = await repo.head();
|
|
@@ -57,7 +57,7 @@ export function jobArtifactOwner(run) {
|
|
|
57
57
|
}
|
|
58
58
|
/** Each Run has a distinct working tree and commit fence even for identical paths.
|
|
59
59
|
* The retained storage is unrelated to execution workspaces/logs/checkpoints. */
|
|
60
|
-
export function openJobArtifactRepository(home, owner, sourceRun) {
|
|
60
|
+
export function openJobArtifactRepository(home, owner, sourceRun, getConfig) {
|
|
61
61
|
validateScope(owner.scope);
|
|
62
62
|
validateScope(sourceRun.scope);
|
|
63
63
|
if (!isDeepStrictEqual(owner.scope, sourceRun.scope))
|
|
@@ -65,13 +65,13 @@ export function openJobArtifactRepository(home, owner, sourceRun) {
|
|
|
65
65
|
const scopePath = owner.scope.kind === "global" ? ["global"] : ["task", owner.scope.taskId];
|
|
66
66
|
const path = join(home, "job-artifacts", ...scopePath, identity(owner.jobId), identity(sourceRun.runId));
|
|
67
67
|
const lockKey = `job-${createHash("sha256").update(JSON.stringify([jobScopeKey(owner.scope), owner.jobId, sourceRun.runId])).digest("hex")}`;
|
|
68
|
-
return openVersionedArtifactRepository(home, lockKey, path);
|
|
68
|
+
return openVersionedArtifactRepository(home, lockKey, path, getConfig);
|
|
69
69
|
}
|
|
70
|
-
export async function readJobArtifactReference(home, ref) {
|
|
70
|
+
export async function readJobArtifactReference(home, ref, getConfig) {
|
|
71
71
|
validateJobArtifactReference(ref);
|
|
72
72
|
const repo = ref.owner.kind === "task"
|
|
73
|
-
? openTaskArtifactRepository(home, ref.owner.taskId)
|
|
74
|
-
: openJobArtifactRepository(home, ref.owner, ref.sourceRun);
|
|
73
|
+
? openTaskArtifactRepository(home, ref.owner.taskId, getConfig)
|
|
74
|
+
: openJobArtifactRepository(home, ref.owner, ref.sourceRun, getConfig);
|
|
75
75
|
const content = await repo.read(ref.relativePath, ref.version);
|
|
76
76
|
if (content.digest !== ref.digest)
|
|
77
77
|
throw new Error("Artifact digest mismatch; preserved evidence was not replaced.");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
1
|
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
4
|
-
|
|
2
|
+
import { executeGitCommand, GitCommandError, LOCAL_GIT_TIMEOUT_MS } from "../repository/gitCommand.js";
|
|
3
|
+
import { resolveGitLocalTimeoutSeconds } from "../config/gitConfig.js";
|
|
5
4
|
/**
|
|
6
5
|
* Hardened, isolated Git runner for per-Task artifact repositories.
|
|
7
6
|
*
|
|
@@ -65,7 +64,9 @@ export class ManagedGitError extends Error {
|
|
|
65
64
|
stderr;
|
|
66
65
|
args;
|
|
67
66
|
constructor(args, stderr, cause) {
|
|
68
|
-
super(
|
|
67
|
+
super(cause instanceof Error
|
|
68
|
+
? cause.message.replace(/^Git command/u, "Managed git command")
|
|
69
|
+
: stderr.length === 0 ? "Managed git command failed." : `Managed git command failed: ${stderr}`, { cause });
|
|
69
70
|
this.name = "ManagedGitError";
|
|
70
71
|
this.stderr = stderr;
|
|
71
72
|
this.args = args;
|
|
@@ -124,14 +125,14 @@ function assertSafeArguments(args) {
|
|
|
124
125
|
*/
|
|
125
126
|
async function spawnManagedGit(repoPath, args, options) {
|
|
126
127
|
assertSafeArguments(args);
|
|
127
|
-
const
|
|
128
|
+
const timeoutMs = options?.timeoutMs ?? (options?.getConfig === undefined
|
|
129
|
+
? LOCAL_GIT_TIMEOUT_MS : resolveGitLocalTimeoutSeconds(options.getConfig().gitLocalTimeoutSeconds) * 1_000);
|
|
130
|
+
const program = supportedGit(timeoutMs);
|
|
128
131
|
try {
|
|
129
|
-
const result = await
|
|
130
|
-
|
|
131
|
-
env: managedGitEnvironment(),
|
|
132
|
+
const result = await executeGitCommand(program.path, [...HARDENING_FLAGS, "-C", repoPath, ...args], {
|
|
133
|
+
environment: managedGitEnvironment(),
|
|
132
134
|
maxBuffer: options?.maxBuffer ?? 16 * 1024 * 1024,
|
|
133
|
-
|
|
134
|
-
windowsHide: true
|
|
135
|
+
budget: { totalMs: timeoutMs }
|
|
135
136
|
});
|
|
136
137
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
137
138
|
}
|
|
@@ -160,14 +161,16 @@ export async function managedGitBuffer(repoPath, args, options) {
|
|
|
160
161
|
const { stdout } = await spawnManagedGit(repoPath, args, options);
|
|
161
162
|
return stdout;
|
|
162
163
|
}
|
|
163
|
-
/**
|
|
164
|
+
/** Return false for a completed non-zero exit, but preserve timeouts/process failures. */
|
|
164
165
|
export async function managedGitSucceeds(repoPath, args, options) {
|
|
165
166
|
try {
|
|
166
167
|
await spawnManagedGit(repoPath, args, options);
|
|
167
168
|
return true;
|
|
168
169
|
}
|
|
169
170
|
catch (error) {
|
|
170
|
-
if (error instanceof ManagedGitError
|
|
171
|
+
if (error instanceof ManagedGitError && error.cause instanceof GitCommandError
|
|
172
|
+
&& error.cause.timeoutKind === undefined && error.cause.signal === null
|
|
173
|
+
&& typeof error.cause.code === "number")
|
|
171
174
|
return false;
|
|
172
175
|
throw error;
|
|
173
176
|
}
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { lstat, mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, relative } from "node:path";
|
|
5
5
|
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
6
|
+
import { resolveGitLocalTimeoutSeconds } from "../config/gitConfig.js";
|
|
6
7
|
import { acquireArtifactCommitLock } from "./artifactCommitLock.js";
|
|
7
8
|
import { resolveContainedArtifactPath, safeRelativeArtifactPath, taskArtifactRepoPath } from "./artifactPaths.js";
|
|
8
9
|
import { ManagedGitError, externalProgramConfigViolations, managedGit, managedGitBuffer, managedGitSucceeds, requireCommitId } from "./managedGit.js";
|
|
@@ -88,14 +89,14 @@ export class ArtifactHeadConflictError extends Error {
|
|
|
88
89
|
* (e.g. `store.rootDirectory()`), so ownership is derived from the Task, never
|
|
89
90
|
* from ambient state.
|
|
90
91
|
*/
|
|
91
|
-
export function openTaskArtifactRepository(home, taskId) {
|
|
92
|
+
export function openTaskArtifactRepository(home, taskId, getConfig) {
|
|
92
93
|
const repoPath = taskArtifactRepoPath(home, taskId);
|
|
93
|
-
const repo = openVersionedArtifactRepository(home, taskId, repoPath);
|
|
94
|
+
const repo = openVersionedArtifactRepository(home, taskId, repoPath, getConfig);
|
|
94
95
|
return { ...repo, taskId, save: async (input) => ({ ...await repo.save(input), taskId }) };
|
|
95
96
|
}
|
|
96
97
|
/** Shared local-only storage. Callers derive paths from validated owning identities,
|
|
97
98
|
* never from a user-supplied filesystem location. Task layout/commits stay intact. */
|
|
98
|
-
export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
99
|
+
export function openVersionedArtifactRepository(home, key, repoPath, getConfig) {
|
|
99
100
|
const taskId = key; // Existing error fields retain their public spelling.
|
|
100
101
|
const checkBoundary = async () => {
|
|
101
102
|
await resolveContainedArtifactPath(home, `${relative(home, repoPath).split("\\").join("/")}/boundary-check`);
|
|
@@ -115,21 +116,21 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
115
116
|
await checkBoundary();
|
|
116
117
|
if (!exists())
|
|
117
118
|
return null;
|
|
118
|
-
return currentHead(repoPath);
|
|
119
|
+
return currentHead(repoPath, getConfig);
|
|
119
120
|
};
|
|
120
121
|
const ensureUnlocked = async () => {
|
|
121
122
|
await checkBoundary();
|
|
122
|
-
supportedGit();
|
|
123
|
+
supportedGit(resolveGitLocalTimeoutSeconds(getConfig?.().gitLocalTimeoutSeconds) * 1_000);
|
|
123
124
|
if (exists()) {
|
|
124
|
-
await assertNoRemote(taskId, repoPath);
|
|
125
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
128
|
await mkdir(repoPath, { recursive: true, mode: 0o700 });
|
|
128
|
-
await managedGit(repoPath, ["init", "-b", ARTIFACT_BRANCH]);
|
|
129
|
+
await managedGit(repoPath, ["init", "-b", ARTIFACT_BRANCH], { getConfig });
|
|
129
130
|
// An empty root commit guarantees HEAD exists, so scoped per-path commits
|
|
130
131
|
// and expected-HEAD checks work uniformly from the very first save.
|
|
131
|
-
await managedGit(repoPath, ["commit", "--allow-empty", "-m", "init artifact repo"]);
|
|
132
|
-
await assertNoRemote(taskId, repoPath);
|
|
132
|
+
await managedGit(repoPath, ["commit", "--allow-empty", "-m", "init artifact repo"], { getConfig });
|
|
133
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
133
134
|
};
|
|
134
135
|
const ensure = async () => {
|
|
135
136
|
const release = await acquireArtifactCommitLock(home, key);
|
|
@@ -169,10 +170,10 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
169
170
|
const release = await acquireArtifactCommitLock(home, key);
|
|
170
171
|
try {
|
|
171
172
|
await ensureUnlocked();
|
|
172
|
-
await assertNoRemote(taskId, repoPath);
|
|
173
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
173
174
|
// Expected-HEAD check under the lock: a conflict stops without writing.
|
|
174
175
|
if (input.expectedHead !== undefined) {
|
|
175
|
-
const actual = await currentHead(repoPath);
|
|
176
|
+
const actual = await currentHead(repoPath, getConfig);
|
|
176
177
|
if (actual !== input.expectedHead) {
|
|
177
178
|
throw new ArtifactHeadConflictError(taskId, input.expectedHead, actual);
|
|
178
179
|
}
|
|
@@ -186,17 +187,17 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
186
187
|
}
|
|
187
188
|
const pathspecs = prepared.map((file) => file.relativePath);
|
|
188
189
|
// Stage exactly the saved paths so new files become known to Git...
|
|
189
|
-
await managedGit(repoPath, ["add", "--", ...pathspecs]);
|
|
190
|
+
await managedGit(repoPath, ["add", "--", ...pathspecs], { getConfig });
|
|
190
191
|
// ...but if nothing actually changed, do not create an empty commit.
|
|
191
192
|
const noChanges = await managedGitSucceeds(repoPath, [
|
|
192
193
|
"diff", "--cached", "--quiet", "--", ...pathspecs
|
|
193
|
-
]);
|
|
194
|
+
], { getConfig });
|
|
194
195
|
if (noChanges) {
|
|
195
|
-
return { commit: await currentHead(repoPath), savedPaths: pathspecs };
|
|
196
|
+
return { commit: await currentHead(repoPath, getConfig), savedPaths: pathspecs };
|
|
196
197
|
}
|
|
197
198
|
// `--only <paths>` records exactly these paths; any other WIP stays WIP.
|
|
198
|
-
await managedGit(repoPath, ["commit", "--only", "-m", message, "--", ...pathspecs]);
|
|
199
|
-
return { commit: await currentHead(repoPath), savedPaths: pathspecs };
|
|
199
|
+
await managedGit(repoPath, ["commit", "--only", "-m", message, "--", ...pathspecs], { getConfig });
|
|
200
|
+
return { commit: await currentHead(repoPath, getConfig), savedPaths: pathspecs };
|
|
200
201
|
}
|
|
201
202
|
finally {
|
|
202
203
|
release();
|
|
@@ -208,30 +209,31 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
208
209
|
// A pinned commit reads frozen evidence; otherwise read the current HEAD.
|
|
209
210
|
// Both resolve `<commit>:<path>`, an OBJECT spec (never the working tree, so
|
|
210
211
|
// a read never follows a symlink or sees unrelated WIP).
|
|
211
|
-
const pinned = await artifactCommit(repoPath, commit);
|
|
212
|
+
const pinned = await artifactCommit(repoPath, commit, getConfig);
|
|
212
213
|
const objectSpec = `${pinned}:${safeRelative}`;
|
|
213
214
|
// `<commit>:<path>` for a DIRECTORY resolves to a tree, and `git show` would
|
|
214
215
|
// print a tree listing rather than fail — silently returning directory
|
|
215
216
|
// metadata as if it were file bytes. Verify the object is a blob first, so a
|
|
216
217
|
// read only ever yields real file content.
|
|
217
|
-
const objectType = (await managedGit(repoPath, ["cat-file", "-t", objectSpec]).catch((error) => {
|
|
218
|
+
const objectType = (await managedGit(repoPath, ["cat-file", "-t", objectSpec], { getConfig }).catch((error) => {
|
|
218
219
|
if (error instanceof ManagedGitError) {
|
|
219
|
-
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}
|
|
220
|
+
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}: ${error.message}`, { cause: error });
|
|
220
221
|
}
|
|
221
222
|
throw error;
|
|
222
223
|
})).trim();
|
|
223
224
|
if (objectType !== "blob") {
|
|
224
225
|
throw new Error(`Artifact ${safeRelative} at ${pinned} is not a file (${objectType}).`);
|
|
225
226
|
}
|
|
226
|
-
const size = Number((await managedGit(repoPath, ["cat-file", "-s", objectSpec])).trim());
|
|
227
|
+
const size = Number((await managedGit(repoPath, ["cat-file", "-s", objectSpec], { getConfig })).trim());
|
|
227
228
|
if (!Number.isSafeInteger(size) || size > MAX_ARTIFACT_BYTES) {
|
|
228
229
|
throw new Error(`Artifact ${safeRelative} exceeds the ${MAX_ARTIFACT_BYTES}-byte limit.`);
|
|
229
230
|
}
|
|
230
231
|
const bytes = await managedGitBuffer(repoPath, ["cat-file", "blob", objectSpec], {
|
|
232
|
+
getConfig,
|
|
231
233
|
maxBuffer: MAX_ARTIFACT_BYTES + 4096
|
|
232
234
|
}).catch((error) => {
|
|
233
235
|
if (error instanceof ManagedGitError) {
|
|
234
|
-
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}
|
|
236
|
+
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}: ${error.message}`, { cause: error });
|
|
235
237
|
}
|
|
236
238
|
throw error;
|
|
237
239
|
});
|
|
@@ -246,10 +248,10 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
246
248
|
await checkBoundary();
|
|
247
249
|
if (!exists())
|
|
248
250
|
return [];
|
|
249
|
-
const pinned = await artifactCommit(repoPath, commit);
|
|
251
|
+
const pinned = await artifactCommit(repoPath, commit, getConfig);
|
|
250
252
|
const output = await managedGit(repoPath, [
|
|
251
253
|
"ls-tree", "-r", "-z", "--long", "--full-tree", pinned
|
|
252
|
-
]);
|
|
254
|
+
], { getConfig });
|
|
253
255
|
const entries = [];
|
|
254
256
|
for (const record of output.split("\0").filter(Boolean)) {
|
|
255
257
|
// Format: "<mode> <type> <object> <size>\t<path>"
|
|
@@ -271,7 +273,7 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
271
273
|
return [];
|
|
272
274
|
const output = await managedGit(repoPath, [
|
|
273
275
|
"status", "--porcelain=v1", "--untracked-files=all", "-z"
|
|
274
|
-
]);
|
|
276
|
+
], { getConfig });
|
|
275
277
|
// Porcelain v1 -z records: "XY <path>\0" (rename adds a second \0 field).
|
|
276
278
|
const changes = [];
|
|
277
279
|
for (const record of output.split("\0").filter(Boolean)) {
|
|
@@ -283,16 +285,16 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
283
285
|
};
|
|
284
286
|
return { repoPath, ensure, exists, head, save, read, list, workingChanges };
|
|
285
287
|
}
|
|
286
|
-
async function currentHead(repoPath) {
|
|
287
|
-
return requireCommitId(await managedGit(repoPath, ["rev-parse", "HEAD^{commit}"]));
|
|
288
|
+
async function currentHead(repoPath, getConfig) {
|
|
289
|
+
return requireCommitId(await managedGit(repoPath, ["rev-parse", "HEAD^{commit}"], { getConfig }));
|
|
288
290
|
}
|
|
289
|
-
async function artifactCommit(repoPath, value) {
|
|
291
|
+
async function artifactCommit(repoPath, value, getConfig) {
|
|
290
292
|
if (value === undefined)
|
|
291
|
-
return currentHead(repoPath);
|
|
293
|
+
return currentHead(repoPath, getConfig);
|
|
292
294
|
const commit = requireCommitId(value);
|
|
293
|
-
const type = await managedGit(repoPath, ["cat-file", "-t", commit]).catch(error => {
|
|
295
|
+
const type = await managedGit(repoPath, ["cat-file", "-t", commit], { getConfig }).catch(error => {
|
|
294
296
|
if (error instanceof ManagedGitError)
|
|
295
|
-
throw new Error(`Artifact commit ${commit} is unavailable
|
|
297
|
+
throw new Error(`Artifact commit ${commit} is unavailable: ${error.message}`, { cause: error });
|
|
296
298
|
throw error;
|
|
297
299
|
});
|
|
298
300
|
if (type.trim() !== "commit") {
|
|
@@ -306,8 +308,8 @@ async function artifactCommit(repoPath, value) {
|
|
|
306
308
|
* program on add/diff/commit. Both are external violations — we report and stop,
|
|
307
309
|
* never silently remove them.
|
|
308
310
|
*/
|
|
309
|
-
async function assertNoRemote(taskId, repoPath) {
|
|
310
|
-
const output = await managedGit(repoPath, ["remote"]);
|
|
311
|
+
async function assertNoRemote(taskId, repoPath, getConfig) {
|
|
312
|
+
const output = await managedGit(repoPath, ["remote"], { getConfig });
|
|
311
313
|
const remotes = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
312
314
|
if (remotes.length > 0) {
|
|
313
315
|
throw new ArtifactRemoteViolationError(taskId, remotes);
|
|
@@ -315,7 +317,7 @@ async function assertNoRemote(taskId, repoPath) {
|
|
|
315
317
|
// A repo's own `.git/config` is read even with global/system config disabled,
|
|
316
318
|
// so a committed `.gitattributes` filter paired with a repo-local
|
|
317
319
|
// `filter.<n>.clean = <cmd>` would run `<cmd>` on `git add`. Detect and stop.
|
|
318
|
-
const configListZ = await managedGit(repoPath, ["config", "--local", "--list", "-z"]);
|
|
320
|
+
const configListZ = await managedGit(repoPath, ["config", "--local", "--list", "-z"], { getConfig });
|
|
319
321
|
const violations = externalProgramConfigViolations(configListZ);
|
|
320
322
|
if (violations.length > 0) {
|
|
321
323
|
throw new ArtifactManagedConfigViolationError(taskId, violations);
|
|
@@ -1289,8 +1289,8 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1289
1289
|
{
|
|
1290
1290
|
name: "update", discovery: { surface: "public", audiences: ["public", "operator"] },
|
|
1291
1291
|
summary: "Install the latest or an exact published Yui version after storage preflight.",
|
|
1292
|
-
usage: "yui update [--version <exact-version>]",
|
|
1293
|
-
options: ["--version"]
|
|
1292
|
+
usage: "yui update [--version <exact-version>] | --hosts-status",
|
|
1293
|
+
options: ["--version", "--hosts-status"]
|
|
1294
1294
|
},
|
|
1295
1295
|
{
|
|
1296
1296
|
name: "upgrade", discovery: { surface: "public", audiences: ["public", "operator"] },
|
|
@@ -14,9 +14,16 @@ import { usageError } from "../errors/cliError.js";
|
|
|
14
14
|
* Returns a process exit code: 0 on success or already-current, 5 on abort.
|
|
15
15
|
*/
|
|
16
16
|
export function runUpdateCommand(args = [], environment = process.env, spawn, write = (text) => process.stdout.write(text), ports) {
|
|
17
|
-
const
|
|
17
|
+
const statusOnly = args.length === 1 && args[0] === "--hosts-status";
|
|
18
|
+
const version = statusOnly ? undefined : parseUpdateVersion(args);
|
|
18
19
|
const home = resolveYuiHome(environment);
|
|
19
20
|
const resolvedPorts = ports ?? createUpdatePorts(environment, spawn);
|
|
21
|
+
if (statusOnly) {
|
|
22
|
+
if (!resolvedPorts.hostStatus)
|
|
23
|
+
throw usageError("Host status inspection is unavailable.");
|
|
24
|
+
write(`${renderHosts(resolvedPorts.hostStatus(home)).join("\n")}\n`);
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
20
27
|
const result = runUpdate(resolvedPorts, { home, version });
|
|
21
28
|
write(`${renderUpdateResult(result)}\n`);
|
|
22
29
|
if (result.outcome === "aborted")
|
|
@@ -29,7 +36,12 @@ function parseUpdateVersion(args) {
|
|
|
29
36
|
if (args.length === 2 && args[0] === "--version" && isConcreteVersion(args[1])) {
|
|
30
37
|
return args[1].trim();
|
|
31
38
|
}
|
|
32
|
-
throw usageError("Update usage: yui update [--version <exact-version>]");
|
|
39
|
+
throw usageError("Update usage: yui update [--version <exact-version>] | --hosts-status");
|
|
40
|
+
}
|
|
41
|
+
function renderHosts(hosts) {
|
|
42
|
+
return hosts.length === 0 ? ["Hosts: no recorded active Sessions."] : hosts.map(host => `Host ${host.owner.scope === "task" ? `${host.owner.taskId}/` : "global/"}${host.owner.roleName} `
|
|
43
|
+
+ `(${host.nativeSessionId}): ${host.status}; loaded Host=${host.hostImplementation?.generation ?? "unreported"}; `
|
|
44
|
+
+ `loaded Endpoint=${host.endpointImplementation?.generation ?? "unreported"}${host.detail ? ` — ${host.detail}` : ""}`);
|
|
33
45
|
}
|
|
34
46
|
/** Render an {@link UpdateResult} as concise, CLI-style text. */
|
|
35
47
|
export function renderUpdateResult(result) {
|
|
@@ -66,7 +78,12 @@ export function renderUpdateResult(result) {
|
|
|
66
78
|
return [
|
|
67
79
|
rendered,
|
|
68
80
|
...(result.outcome === "updated" ? [
|
|
69
|
-
"
|
|
81
|
+
result.controllerStarted ? "Controller: target startup verified." : "Controller: not started by this update.",
|
|
82
|
+
"Hosts: graceful switching is separate from installation; accepted input is never replayed.",
|
|
83
|
+
...(result.hosts === undefined ? ["Hosts: not confirmed; existing Hosts may still run older code."]
|
|
84
|
+
: renderHosts(result.hosts)),
|
|
85
|
+
"Inspect later without reinstalling: yui update --hosts-status",
|
|
86
|
+
...(result.hostUpgradeError === undefined ? [] : [result.hostUpgradeError])
|
|
70
87
|
] : []),
|
|
71
88
|
...(result.controllerReconciliation === undefined ? [] : [
|
|
72
89
|
`Controller reconciliation: ${JSON.stringify(result.controllerReconciliation)}`
|
|
@@ -250,9 +250,22 @@ function activateAndVerify(ports, staged, home, lifecycle, preflight) {
|
|
|
250
250
|
: restoreControllerOrReport(ports, home, lifecycle, failure);
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
+
let hosts;
|
|
254
|
+
let hostUpgradeError;
|
|
255
|
+
if (lifecycle.ensureRunning && ports.upgradeHosts) {
|
|
256
|
+
try {
|
|
257
|
+
hosts = ports.upgradeHosts(home);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
hostUpgradeError = `Host upgrade request outcome is unknown: ${messageOf(error)}. Inspect existing requests; do not replay inputs.`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
253
263
|
return {
|
|
254
264
|
outcome: "updated",
|
|
255
265
|
version: staged.version,
|
|
266
|
+
...(lifecycle.ensureRunning ? { controllerStarted: true } : {}),
|
|
267
|
+
...(hosts === undefined ? {} : { hosts }),
|
|
268
|
+
...(hostUpgradeError === undefined ? {} : { hostUpgradeError }),
|
|
256
269
|
...(migration?.backupPath === undefined ? {} : { backupPath: migration.backupPath })
|
|
257
270
|
};
|
|
258
271
|
}
|
package/dist/cli/updatePorts.js
CHANGED
|
@@ -236,9 +236,36 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
236
236
|
},
|
|
237
237
|
restoreController(home, identity) {
|
|
238
238
|
restoreControllerIdentity(home, identity, environment, spawn);
|
|
239
|
+
},
|
|
240
|
+
upgradeHosts(home) {
|
|
241
|
+
if (!verifiedActivatedBinary)
|
|
242
|
+
throw new Error("Host upgrade requires the verified activated binary.");
|
|
243
|
+
const root = dirname(dirname(activatedControllerEntrypoint(verifiedActivatedBinary)));
|
|
244
|
+
return requestHosts(root, home, "runtime.upgrade-hosts", environment, spawn);
|
|
245
|
+
},
|
|
246
|
+
hostStatus(home) {
|
|
247
|
+
return requestHosts(fileURLToPath(new URL("../", import.meta.url)), home, "runtime.host-upgrade-status", environment, spawn);
|
|
239
248
|
}
|
|
240
249
|
};
|
|
241
250
|
}
|
|
251
|
+
function requestHosts(root, home, method, environment, spawn) {
|
|
252
|
+
const helper = [
|
|
253
|
+
"const [root, home] = process.argv.slice(1);",
|
|
254
|
+
"(async () => {",
|
|
255
|
+
" const { pathToFileURL } = await import('node:url');",
|
|
256
|
+
" const { callController } = await import(pathToFileURL(root + '/core/controllerClient.js').href);",
|
|
257
|
+
" const { AGENT_HOST_IMPLEMENTATION } = await import(pathToFileURL(root + '/runtime/agentHostImplementation.js').href);",
|
|
258
|
+
` const data = await callController(home, ${JSON.stringify(method)}, { generation: AGENT_HOST_IMPLEMENTATION.generation });`,
|
|
259
|
+
" process.stdout.write(JSON.stringify({ok: true, data}));",
|
|
260
|
+
"})().catch(error => { process.stderr.write(String(error)); process.exitCode = 5; });"
|
|
261
|
+
].join(" ");
|
|
262
|
+
const result = spawn(process.execPath, ["-e", helper, root, home], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
263
|
+
const data = parseSuccessEnvelopeData(result);
|
|
264
|
+
if (result.error || result.status !== 0 || !Array.isArray(data?.hosts)) {
|
|
265
|
+
throw new Error(`Host request was not confirmed: ${result.stderr.toString("utf8")}`);
|
|
266
|
+
}
|
|
267
|
+
return data.hosts;
|
|
268
|
+
}
|
|
242
269
|
const UPDATE_CLI_PATH = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
243
270
|
const UPDATE_CLIENT_RUNTIME_PATH = fileURLToPath(new URL("../controller/clientRuntime.js", import.meta.url));
|
|
244
271
|
const UPDATE_INVENTORY_PATH = fileURLToPath(new URL("../controller/resourceInventoryLinux.js", import.meta.url));
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { unsupportedPlatformMessage } from "./runtime/platform.js";
|
|
2
3
|
// No static control-plane imports: the product never resolves/initializes Yui Home.
|
|
3
4
|
const arguments_ = process.argv.slice(2);
|
|
4
5
|
let commandOffset = 0;
|
|
5
6
|
while (arguments_[commandOffset] === '--json')
|
|
6
7
|
commandOffset++;
|
|
7
8
|
const commandArguments = arguments_.slice(commandOffset);
|
|
8
|
-
|
|
9
|
+
const platformFailure = unsupportedPlatformMessage();
|
|
10
|
+
if (platformFailure !== undefined) {
|
|
11
|
+
process.stderr.write(`${arguments_.includes("--json")
|
|
12
|
+
? JSON.stringify({ ok: false, code: "unsupported_platform", message: platformFailure })
|
|
13
|
+
: platformFailure}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
|
16
|
+
else if (commandArguments[0] === 'agent' || (commandArguments[0] === 'help' && commandArguments[1] === 'agent')) {
|
|
9
17
|
let outputFailed = false;
|
|
10
18
|
// Cover early help/config writes as well as execution UI disconnection.
|
|
11
19
|
process.stdout.on('error', () => { outputFailed = true; process.exitCode = 1; });
|
|
@@ -31,4 +39,3 @@ if (commandArguments[0] === 'agent' || (commandArguments[0] === 'help' && comman
|
|
|
31
39
|
else {
|
|
32
40
|
await import('./controlPlaneCli.js');
|
|
33
41
|
}
|
|
34
|
-
export {};
|
|
@@ -2,7 +2,7 @@ import { mkdirSync, realpathSync } from "node:fs";
|
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
|
|
4
4
|
import { resolveTimeZone } from "../config/timeZone.js";
|
|
5
|
-
import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
5
|
+
import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveGitLocalTimeoutSeconds, resolveGitRemoteInactivityTimeoutSeconds, resolveGitRemoteTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
6
6
|
import { usageError } from "../errors/cliError.js";
|
|
7
7
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
8
8
|
import { REVIEW_TRIGGERS } from "../review/reviewConfig.js";
|
|
@@ -75,6 +75,9 @@ export function effectiveConfigData(config, domain) {
|
|
|
75
75
|
controllerTaskConcurrency: resolveControllerTaskConcurrency(config.controllerTaskConcurrency),
|
|
76
76
|
agentLaunchInactivityTimeoutSeconds: resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds),
|
|
77
77
|
deliveryTimeoutSeconds: resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds),
|
|
78
|
+
gitLocalTimeoutSeconds: resolveGitLocalTimeoutSeconds(config.gitLocalTimeoutSeconds),
|
|
79
|
+
gitRemoteInactivityTimeoutSeconds: resolveGitRemoteInactivityTimeoutSeconds(config.gitRemoteInactivityTimeoutSeconds),
|
|
80
|
+
gitRemoteTimeoutSeconds: resolveGitRemoteTimeoutSeconds(config.gitRemoteTimeoutSeconds),
|
|
78
81
|
tmuxBin: resolveTmuxBin(config.tmuxBin),
|
|
79
82
|
tmuxHistoryLimit: resolveTmuxHistoryLimit(config.tmuxHistoryLimit),
|
|
80
83
|
telemetryEnabled: resolveTelemetryEnabled(config.telemetryEnabled),
|
|
@@ -393,6 +396,66 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
393
396
|
return `Delivery timeout reset to ${DEFAULT_DELIVERY_TIMEOUT_SECONDS} seconds\n`;
|
|
394
397
|
}
|
|
395
398
|
},
|
|
399
|
+
{
|
|
400
|
+
key: "git-local-timeout-seconds",
|
|
401
|
+
showLabel: "Git local total timeout",
|
|
402
|
+
showValue: (config) => `${resolveGitLocalTimeoutSeconds(config.gitLocalTimeoutSeconds)} seconds`,
|
|
403
|
+
set(args, store) {
|
|
404
|
+
const usage = "Tools config set usage: yui config tools set git-local-timeout-seconds <1-86400>.";
|
|
405
|
+
if (args.length !== 1)
|
|
406
|
+
throw usageError(usage);
|
|
407
|
+
const gitLocalTimeoutSeconds = validatedConfigValue(() => resolveGitLocalTimeoutSeconds(Number(args[0])), usage);
|
|
408
|
+
saveConfigKey(store, (config) => ({ ...config, gitLocalTimeoutSeconds }));
|
|
409
|
+
return `Git local total timeout set to ${gitLocalTimeoutSeconds} seconds\n`;
|
|
410
|
+
},
|
|
411
|
+
clear(store) {
|
|
412
|
+
saveConfigKey(store, (config) => {
|
|
413
|
+
const { gitLocalTimeoutSeconds: _removed, ...rest } = config;
|
|
414
|
+
return rest;
|
|
415
|
+
});
|
|
416
|
+
return "Git local total timeout reset to default\n";
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
key: "git-remote-inactivity-timeout-seconds",
|
|
421
|
+
showLabel: "Git remote inactivity timeout",
|
|
422
|
+
showValue: (config) => `${resolveGitRemoteInactivityTimeoutSeconds(config.gitRemoteInactivityTimeoutSeconds)} seconds`,
|
|
423
|
+
set(args, store) {
|
|
424
|
+
const usage = "Tools config set usage: yui config tools set git-remote-inactivity-timeout-seconds <1-86400>.";
|
|
425
|
+
if (args.length !== 1)
|
|
426
|
+
throw usageError(usage);
|
|
427
|
+
const gitRemoteInactivityTimeoutSeconds = validatedConfigValue(() => resolveGitRemoteInactivityTimeoutSeconds(Number(args[0])), usage);
|
|
428
|
+
saveConfigKey(store, (config) => ({ ...config, gitRemoteInactivityTimeoutSeconds }));
|
|
429
|
+
return `Git remote inactivity timeout set to ${gitRemoteInactivityTimeoutSeconds} seconds\n`;
|
|
430
|
+
},
|
|
431
|
+
clear(store) {
|
|
432
|
+
saveConfigKey(store, (config) => {
|
|
433
|
+
const { gitRemoteInactivityTimeoutSeconds: _removed, ...rest } = config;
|
|
434
|
+
return rest;
|
|
435
|
+
});
|
|
436
|
+
return "Git remote inactivity timeout reset to default\n";
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
key: "git-remote-timeout-seconds",
|
|
441
|
+
showLabel: "Git remote total timeout",
|
|
442
|
+
showValue: (config) => `${resolveGitRemoteTimeoutSeconds(config.gitRemoteTimeoutSeconds)} seconds`,
|
|
443
|
+
set(args, store) {
|
|
444
|
+
const usage = "Tools config set usage: yui config tools set git-remote-timeout-seconds <1-86400>.";
|
|
445
|
+
if (args.length !== 1)
|
|
446
|
+
throw usageError(usage);
|
|
447
|
+
const gitRemoteTimeoutSeconds = validatedConfigValue(() => resolveGitRemoteTimeoutSeconds(Number(args[0])), usage);
|
|
448
|
+
saveConfigKey(store, (config) => ({ ...config, gitRemoteTimeoutSeconds }));
|
|
449
|
+
return `Git remote total timeout set to ${gitRemoteTimeoutSeconds} seconds\n`;
|
|
450
|
+
},
|
|
451
|
+
clear(store) {
|
|
452
|
+
saveConfigKey(store, (config) => {
|
|
453
|
+
const { gitRemoteTimeoutSeconds: _removed, ...rest } = config;
|
|
454
|
+
return rest;
|
|
455
|
+
});
|
|
456
|
+
return "Git remote total timeout reset to default\n";
|
|
457
|
+
}
|
|
458
|
+
},
|
|
396
459
|
{
|
|
397
460
|
key: "tmux-bin",
|
|
398
461
|
showLabel: "Tmux bin",
|