@webpresso/agent-kit 3.1.25 → 3.1.26
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/catalog/agent/rules/changeset-release.md +5 -1
- package/dist/esm/cli/commands/quality-log-store.d.ts +6 -0
- package/dist/esm/cli/commands/quality-log-store.js +55 -15
- package/dist/esm/cli/commands/self-install-guard.d.ts +19 -3
- package/dist/esm/cli/commands/self-install-guard.js +43 -3
- package/dist/esm/paths/state-root.js +30 -3
- package/package.json +12 -12
|
@@ -180,7 +180,11 @@ for the current repository and package configuration contract.
|
|
|
180
180
|
`npm view <package> version`. For consumer setup smoke, use
|
|
181
181
|
`vp install -g @webpresso/agent-kit@latest`, `wp setup`, and
|
|
182
182
|
`wp sync --check`.
|
|
183
|
-
6.
|
|
183
|
+
6. If the Version Packages PR has no WP check / CI is `action_required` with
|
|
184
|
+
zero jobs, follow
|
|
185
|
+
[`docs/runbooks/version-packages-bot-ci.md`](../../../docs/runbooks/version-packages-bot-ci.md)
|
|
186
|
+
(`gh run rerun` or Approve workflows). Do not admin-merge without WP check.
|
|
187
|
+
7. After the OIDC publish path succeeds, set npm package publishing access to
|
|
184
188
|
require two-factor authentication and disallow tokens, then revoke obsolete
|
|
185
189
|
automation tokens. Do not remove a credential that another verified release
|
|
186
190
|
path still needs.
|
|
@@ -10,6 +10,12 @@ export interface CliLogEntry {
|
|
|
10
10
|
readonly summary?: string;
|
|
11
11
|
}
|
|
12
12
|
export declare function isCliLogCommandName(value: string): value is CliLogCommandName;
|
|
13
|
+
/**
|
|
14
|
+
* Create a durable CLI log sink. When the preferred repo state surface is not
|
|
15
|
+
* writable (EPERM/EACCES/EROFS — common in Codex/read-only sandboxes), fall
|
|
16
|
+
* back to TMPDIR so `wp audit`/`wp test` still execute. The fallback is loud
|
|
17
|
+
* on stderr; it is not a silent swallow.
|
|
18
|
+
*/
|
|
13
19
|
export declare function createCliLogSink(command: CliLogCommandName, cwd?: string): CliLogSink;
|
|
14
20
|
export interface CliLogSink {
|
|
15
21
|
readonly command: CliLogCommandName;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { closeSync, createWriteStream, mkdirSync, openSync, readdirSync, readFileSync, statSync, renameSync, utimesSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
import { getSurfacePath } from "#paths/state-root.js";
|
|
5
6
|
import { readTrustedJsonFile } from "#shared-utils/read-json-file.js";
|
|
@@ -18,9 +19,17 @@ const ORPHAN_LOG_GRACE_MS = 60_000;
|
|
|
18
19
|
export function isCliLogCommandName(value) {
|
|
19
20
|
return CLI_LOG_COMMANDS.includes(value);
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
function isUnwritableFsError(error) {
|
|
23
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
24
|
+
return false;
|
|
25
|
+
const code = error.code;
|
|
26
|
+
return code === "EPERM" || code === "EACCES" || code === "EROFS";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Open a CLI log sink in an explicit directory (preferred state surface or fallback).
|
|
30
|
+
*/
|
|
31
|
+
function openCliLogSink(commandDir, command, cwd) {
|
|
22
32
|
const id = createLogId();
|
|
23
|
-
const commandDir = getCommandLogDir(command, cwd);
|
|
24
33
|
mkdirSync(commandDir, { recursive: true });
|
|
25
34
|
const absoluteLogPath = join(commandDir, `${id}.log`);
|
|
26
35
|
const activeMarkerPath = getActiveMarkerPath(absoluteLogPath);
|
|
@@ -80,7 +89,8 @@ export function createCliLogSink(command, cwd = process.cwd()) {
|
|
|
80
89
|
};
|
|
81
90
|
try {
|
|
82
91
|
markLogRecentlyFinalized(absoluteLogPath);
|
|
83
|
-
|
|
92
|
+
// Index/lock live next to the log file so fallback dirs stay consistent.
|
|
93
|
+
writeLogEntryInDir(entry, commandDir);
|
|
84
94
|
}
|
|
85
95
|
finally {
|
|
86
96
|
rmSync(activeMarkerPath, { force: true });
|
|
@@ -89,6 +99,28 @@ export function createCliLogSink(command, cwd = process.cwd()) {
|
|
|
89
99
|
},
|
|
90
100
|
};
|
|
91
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a durable CLI log sink. When the preferred repo state surface is not
|
|
104
|
+
* writable (EPERM/EACCES/EROFS — common in Codex/read-only sandboxes), fall
|
|
105
|
+
* back to TMPDIR so `wp audit`/`wp test` still execute. The fallback is loud
|
|
106
|
+
* on stderr; it is not a silent swallow.
|
|
107
|
+
*/
|
|
108
|
+
export function createCliLogSink(command, cwd = process.cwd()) {
|
|
109
|
+
const preferredDir = getCommandLogDir(command, cwd);
|
|
110
|
+
try {
|
|
111
|
+
return openCliLogSink(preferredDir, command, cwd);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!isUnwritableFsError(error))
|
|
115
|
+
throw error;
|
|
116
|
+
const fallbackDir = join(tmpdir(), "webpresso", "cli-logs", command);
|
|
117
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
118
|
+
? String(error.code)
|
|
119
|
+
: "unknown";
|
|
120
|
+
process.stderr.write(`wp: CLI log directory not writable (${preferredDir}): ${code}; using ${fallbackDir}\n`);
|
|
121
|
+
return openCliLogSink(fallbackDir, command, cwd);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
92
124
|
export function readCliLogEntries(command, cwd = process.cwd()) {
|
|
93
125
|
const indexPath = getCommandIndexPath(command, cwd);
|
|
94
126
|
try {
|
|
@@ -104,11 +136,21 @@ export function readCliLogEntry(command, ordinal = 1, cwd = process.cwd()) {
|
|
|
104
136
|
return;
|
|
105
137
|
return readCliLogEntries(command, cwd)[ordinal - 1];
|
|
106
138
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
139
|
+
function readCliLogEntriesFromDir(commandDir) {
|
|
140
|
+
const indexPath = join(commandDir, "index.json");
|
|
141
|
+
try {
|
|
142
|
+
const parsed = readTrustedJsonFile(indexPath);
|
|
143
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function writeLogEntryInDir(entry, commandDir) {
|
|
150
|
+
withCommandIndexLockInDir(commandDir, () => {
|
|
151
|
+
const indexPath = join(commandDir, "index.json");
|
|
110
152
|
mkdirSync(dirname(indexPath), { recursive: true });
|
|
111
|
-
const currentEntries =
|
|
153
|
+
const currentEntries = readCliLogEntriesFromDir(commandDir).filter((current) => current.id !== entry.id);
|
|
112
154
|
const nextEntries = [entry, ...currentEntries].slice(0, 10);
|
|
113
155
|
const retainedLogPaths = new Set(nextEntries.map((item) => item.logPath));
|
|
114
156
|
for (const removed of currentEntries.slice(9)) {
|
|
@@ -116,7 +158,7 @@ function writeLogEntry(entry, cwd) {
|
|
|
116
158
|
rmSync(removed.logPath, { force: true });
|
|
117
159
|
}
|
|
118
160
|
}
|
|
119
|
-
|
|
161
|
+
pruneInactiveOrphanedLogFilesInDir(commandDir, retainedLogPaths);
|
|
120
162
|
const index = {
|
|
121
163
|
version: 1,
|
|
122
164
|
command: entry.command,
|
|
@@ -125,8 +167,7 @@ function writeLogEntry(entry, cwd) {
|
|
|
125
167
|
writeIndexAtomically(indexPath, index);
|
|
126
168
|
});
|
|
127
169
|
}
|
|
128
|
-
function
|
|
129
|
-
const commandDir = getCommandLogDir(command, cwd);
|
|
170
|
+
function withCommandIndexLockInDir(commandDir, fn) {
|
|
130
171
|
mkdirSync(commandDir, { recursive: true });
|
|
131
172
|
const lockPath = join(commandDir, "index.lock");
|
|
132
173
|
const started = Date.now();
|
|
@@ -157,13 +198,12 @@ function writeIndexAtomically(indexPath, index) {
|
|
|
157
198
|
writeJsonFile(tmpPath, index);
|
|
158
199
|
renameSync(tmpPath, indexPath);
|
|
159
200
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
for (const file of readdirSync(directory)) {
|
|
201
|
+
function pruneInactiveOrphanedLogFilesInDir(commandDir, retainedLogPaths) {
|
|
202
|
+
mkdirSync(commandDir, { recursive: true });
|
|
203
|
+
for (const file of readdirSync(commandDir)) {
|
|
164
204
|
if (!file.endsWith(".log"))
|
|
165
205
|
continue;
|
|
166
|
-
const absolutePath = join(
|
|
206
|
+
const absolutePath = join(commandDir, file);
|
|
167
207
|
if (!retainedLogPaths.has(absolutePath) && canPruneLogPath(absolutePath)) {
|
|
168
208
|
rmSync(absolutePath, { force: true });
|
|
169
209
|
}
|
|
@@ -86,11 +86,27 @@ export interface SelfInstallGuardDeps {
|
|
|
86
86
|
*/
|
|
87
87
|
export declare function runSelfInstallGuard(rawArgs: readonly string[], deps?: SelfInstallGuardDeps): Promise<number>;
|
|
88
88
|
/**
|
|
89
|
-
* How wp re-invokes itself:
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* How wp re-invokes itself:
|
|
90
|
+
* - Prefer a **stable** global launcher (`~/.vite-plus/bin/wp` or PATH `wp`
|
|
91
|
+
* outside an installId package tree). Re-execing `node …/agent-kit#id/…/cli.js`
|
|
92
|
+
* dies after `vp update -g` deletes the pre-swap tree mid-guard.
|
|
93
|
+
* - Bun-compiled binary: process.execPath IS the binary (argv[1] is /$bunfs).
|
|
94
|
+
* - Source/dev lane without a stable launcher: `<runtime> <script>`.
|
|
92
95
|
*/
|
|
93
96
|
export declare function buildSelfInvocationCommand(args: readonly string[], options?: {
|
|
94
97
|
readonly execPath?: string;
|
|
95
98
|
readonly argv1?: string;
|
|
99
|
+
/** Injectable stable launcher for tests; `null` forces legacy argv1 path. */
|
|
100
|
+
readonly stableWp?: string | null;
|
|
96
101
|
}): string[];
|
|
102
|
+
/**
|
|
103
|
+
* A launcher that survives installId package-tree deletion during global update.
|
|
104
|
+
* Rejects paths under `agent-kit#<uuid>/` (versioned store payload).
|
|
105
|
+
*/
|
|
106
|
+
export declare function resolveStableWpLauncher(options?: {
|
|
107
|
+
readonly home?: string;
|
|
108
|
+
readonly pathEnv?: string;
|
|
109
|
+
readonly existsSync?: (path: string) => boolean;
|
|
110
|
+
}): string | null;
|
|
111
|
+
/** True when path lives inside a vite-plus installId package tree. */
|
|
112
|
+
export declare function isVersionedAgentKitPayloadPath(filePath: string): boolean;
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
* resource-side fencing.
|
|
46
46
|
*/
|
|
47
47
|
import { spawn } from "node:child_process";
|
|
48
|
+
import { existsSync } from "node:fs";
|
|
49
|
+
import path from "node:path";
|
|
48
50
|
import { buildVpGlobalInstallCommand, resolveBundledVpCommand, } from "#cli/auto-update/detect-pm.js";
|
|
49
51
|
import { INSTALL_LOCK_HEARTBEAT_MS, claimInstallLock, refreshInstallLock, releaseInstallLock, resolveInstallLockPath, } from "#cli/auto-update/install-lock.js";
|
|
50
52
|
import { probeGlobalWpResolvable } from "#cli/commands/init/package-root.js";
|
|
@@ -146,17 +148,55 @@ function extractChildCommand(rawArgs) {
|
|
|
146
148
|
return rawArgs.slice(separator + 1);
|
|
147
149
|
}
|
|
148
150
|
/**
|
|
149
|
-
* How wp re-invokes itself:
|
|
150
|
-
*
|
|
151
|
-
*
|
|
151
|
+
* How wp re-invokes itself:
|
|
152
|
+
* - Prefer a **stable** global launcher (`~/.vite-plus/bin/wp` or PATH `wp`
|
|
153
|
+
* outside an installId package tree). Re-execing `node …/agent-kit#id/…/cli.js`
|
|
154
|
+
* dies after `vp update -g` deletes the pre-swap tree mid-guard.
|
|
155
|
+
* - Bun-compiled binary: process.execPath IS the binary (argv[1] is /$bunfs).
|
|
156
|
+
* - Source/dev lane without a stable launcher: `<runtime> <script>`.
|
|
152
157
|
*/
|
|
153
158
|
export function buildSelfInvocationCommand(args, options = {}) {
|
|
159
|
+
const stableWp = options.stableWp !== undefined ? options.stableWp : resolveStableWpLauncher();
|
|
160
|
+
if (typeof stableWp === "string" && stableWp.length > 0) {
|
|
161
|
+
return [stableWp, ...args];
|
|
162
|
+
}
|
|
154
163
|
const execPath = options.execPath ?? process.execPath;
|
|
155
164
|
const argv1 = options.argv1 ?? process.argv[1] ?? "";
|
|
156
165
|
if (argv1.startsWith("/$bunfs"))
|
|
157
166
|
return [execPath, ...args];
|
|
158
167
|
return [execPath, argv1, ...args];
|
|
159
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* A launcher that survives installId package-tree deletion during global update.
|
|
171
|
+
* Rejects paths under `agent-kit#<uuid>/` (versioned store payload).
|
|
172
|
+
*/
|
|
173
|
+
export function resolveStableWpLauncher(options = {}) {
|
|
174
|
+
const exists = options.existsSync ?? existsSync;
|
|
175
|
+
const home = options.home ?? process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
176
|
+
const candidates = [];
|
|
177
|
+
if (home) {
|
|
178
|
+
candidates.push(path.join(home, ".vite-plus", "bin", "wp"));
|
|
179
|
+
}
|
|
180
|
+
const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
|
|
181
|
+
for (const dir of pathEnv.split(path.delimiter)) {
|
|
182
|
+
if (!dir)
|
|
183
|
+
continue;
|
|
184
|
+
candidates.push(path.join(dir, "wp"));
|
|
185
|
+
}
|
|
186
|
+
for (const candidate of candidates) {
|
|
187
|
+
if (!exists(candidate))
|
|
188
|
+
continue;
|
|
189
|
+
if (isVersionedAgentKitPayloadPath(candidate))
|
|
190
|
+
continue;
|
|
191
|
+
return candidate;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
/** True when path lives inside a vite-plus installId package tree. */
|
|
196
|
+
export function isVersionedAgentKitPayloadPath(filePath) {
|
|
197
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
198
|
+
return /\/@webpresso\/agent-kit#[^/]+\//u.test(normalized);
|
|
199
|
+
}
|
|
160
200
|
function defaultSpawnChild(command) {
|
|
161
201
|
const [executable, ...args] = command;
|
|
162
202
|
// detached: own process group, so lost-ownership kills reap descendants.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import envPaths from "env-paths";
|
|
7
8
|
import lockfile from "proper-lockfile";
|
|
@@ -67,18 +68,44 @@ function normalizeCwdForCache(cwd) {
|
|
|
67
68
|
function sha256Hex(value) {
|
|
68
69
|
return createHash("sha256").update(value).digest("hex");
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Probe whether a directory can hold state files.
|
|
73
|
+
* Used so quality CLIs (wp audit/test/…) keep working in sandboxes that
|
|
74
|
+
* deny writes under Application Support (e.g. Codex outside-voice).
|
|
75
|
+
*/
|
|
76
|
+
function canWriteStateRoot(root) {
|
|
77
|
+
try {
|
|
78
|
+
mkdirSync(root, { recursive: true });
|
|
79
|
+
const probe = join(root, `.wp-write-probe-${process.pid}`);
|
|
80
|
+
writeFileSync(probe, "ok");
|
|
81
|
+
rmSync(probe, { force: true });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
70
88
|
export function getStateRoot() {
|
|
71
89
|
if (cachedStateRoot !== null)
|
|
72
90
|
return cachedStateRoot;
|
|
73
91
|
const override = process.env.WP_STATE_ROOT;
|
|
74
92
|
if (override && override.trim().length > 0) {
|
|
93
|
+
// Explicit override is authoritative — fail later if unwritable rather than
|
|
94
|
+
// silently relocating the user's chosen root.
|
|
75
95
|
cachedStateRoot = override;
|
|
76
96
|
return override;
|
|
77
97
|
}
|
|
78
98
|
const paths = envPaths("webpresso", { suffix: "" });
|
|
79
99
|
const root = paths.data;
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
if (canWriteStateRoot(root)) {
|
|
101
|
+
cachedStateRoot = root;
|
|
102
|
+
return root;
|
|
103
|
+
}
|
|
104
|
+
const fallback = join(tmpdir(), "webpresso-state");
|
|
105
|
+
mkdirSync(fallback, { recursive: true });
|
|
106
|
+
process.stderr.write(`wp: state root not writable (${root}); using ${fallback}\n`);
|
|
107
|
+
cachedStateRoot = fallback;
|
|
108
|
+
return fallback;
|
|
82
109
|
}
|
|
83
110
|
export function getRepoKey() {
|
|
84
111
|
if (cachedRepoKey !== null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpresso/agent-kit",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript-first agent harness for guarded develop/deploy workflows: wp CLI gates, MCP tools, hooks, memory, worktrees, secrets, audits, and evidence checks.",
|
|
6
6
|
"keywords": [
|
|
@@ -499,16 +499,16 @@
|
|
|
499
499
|
"setupWpActionRef": "c2c71a7a4be446fc6858e6b57bf55a11ccfa2d88"
|
|
500
500
|
},
|
|
501
501
|
"optionalDependencies": {
|
|
502
|
-
"@webpresso/agent-kit-runtime-darwin-arm64": "3.1.
|
|
503
|
-
"@webpresso/agent-kit-runtime-darwin-x64": "3.1.
|
|
504
|
-
"@webpresso/agent-kit-runtime-linux-x64": "3.1.
|
|
505
|
-
"@webpresso/agent-kit-runtime-linux-arm64": "3.1.
|
|
506
|
-
"@webpresso/agent-kit-runtime-windows-x64": "3.1.
|
|
507
|
-
"@webpresso/agent-kit-session-memory-darwin-x64": "3.1.
|
|
508
|
-
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.1.
|
|
509
|
-
"@webpresso/agent-kit-session-memory-linux-x64": "3.1.
|
|
510
|
-
"@webpresso/agent-kit-session-memory-linux-arm64": "3.1.
|
|
511
|
-
"@webpresso/agent-kit-session-memory-win32-x64": "3.1.
|
|
512
|
-
"@webpresso/agent-kit-session-memory-win32-arm64": "3.1.
|
|
502
|
+
"@webpresso/agent-kit-runtime-darwin-arm64": "3.1.26",
|
|
503
|
+
"@webpresso/agent-kit-runtime-darwin-x64": "3.1.26",
|
|
504
|
+
"@webpresso/agent-kit-runtime-linux-x64": "3.1.26",
|
|
505
|
+
"@webpresso/agent-kit-runtime-linux-arm64": "3.1.26",
|
|
506
|
+
"@webpresso/agent-kit-runtime-windows-x64": "3.1.26",
|
|
507
|
+
"@webpresso/agent-kit-session-memory-darwin-x64": "3.1.26",
|
|
508
|
+
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.1.26",
|
|
509
|
+
"@webpresso/agent-kit-session-memory-linux-x64": "3.1.26",
|
|
510
|
+
"@webpresso/agent-kit-session-memory-linux-arm64": "3.1.26",
|
|
511
|
+
"@webpresso/agent-kit-session-memory-win32-x64": "3.1.26",
|
|
512
|
+
"@webpresso/agent-kit-session-memory-win32-arm64": "3.1.26"
|
|
513
513
|
}
|
|
514
514
|
}
|