@rubytech/taskmaster 1.0.90 → 1.0.91
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/agents/apply-patch.js +3 -1
- package/dist/agents/bash-tools.exec.js +3 -1
- package/dist/agents/bash-tools.process.js +3 -1
- package/dist/build-info.json +2 -2
- package/dist/gateway/server-methods/update.js +8 -7
- package/dist/memory/layout.js +24 -33
- package/package.json +1 -1
- package/scripts/install.sh +22 -0
|
@@ -19,7 +19,9 @@ const applyPatchSchema = Type.Object({
|
|
|
19
19
|
description: "Patch content using the *** Begin Patch/End Patch format.",
|
|
20
20
|
}),
|
|
21
21
|
});
|
|
22
|
-
export function createApplyPatchTool(options = {}
|
|
22
|
+
export function createApplyPatchTool(options = {}
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
|
|
24
|
+
) {
|
|
23
25
|
const cwd = options.cwd ?? process.cwd();
|
|
24
26
|
const sandboxRoot = options.sandboxRoot;
|
|
25
27
|
return {
|
|
@@ -513,7 +513,9 @@ async function runExecProcess(opts) {
|
|
|
513
513
|
kill: () => killSession(session),
|
|
514
514
|
};
|
|
515
515
|
}
|
|
516
|
-
export function createExecTool(defaults
|
|
516
|
+
export function createExecTool(defaults
|
|
517
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
|
|
518
|
+
) {
|
|
517
519
|
const defaultBackgroundMs = clampNumber(defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"), 10_000, 10, 120_000);
|
|
518
520
|
const allowBackground = defaults?.allowBackground ?? true;
|
|
519
521
|
const defaultTimeoutSec = typeof defaults?.timeoutSec === "number" && defaults.timeoutSec > 0
|
|
@@ -15,7 +15,9 @@ const processSchema = Type.Object({
|
|
|
15
15
|
offset: Type.Optional(Type.Number({ description: "Log offset" })),
|
|
16
16
|
limit: Type.Optional(Type.Number({ description: "Log length" })),
|
|
17
17
|
});
|
|
18
|
-
export function createProcessTool(defaults
|
|
18
|
+
export function createProcessTool(defaults
|
|
19
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
|
|
20
|
+
) {
|
|
19
21
|
if (defaults?.cleanupMs !== undefined) {
|
|
20
22
|
setJobTtlMs(defaults.cleanupMs);
|
|
21
23
|
}
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from "../../config/config.js";
|
|
2
2
|
import { resolveTaskmasterPackageRoot } from "../../infra/taskmaster-root.js";
|
|
3
|
-
import { scheduleGatewaySigusr1Restart
|
|
3
|
+
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
|
4
4
|
import { formatDoctorNonInteractiveHint, readRestartSentinel, writeRestartSentinel, } from "../../infra/restart-sentinel.js";
|
|
5
5
|
import { checkUpdateStatus, compareSemverStrings } from "../../infra/update-check.js";
|
|
6
6
|
import { normalizeUpdateChannel, resolveEffectiveUpdateChannel, } from "../../infra/update-channels.js";
|
|
@@ -248,12 +248,13 @@ export const updateHandlers = {
|
|
|
248
248
|
// Non-critical — unit file just won't have watchdog directives
|
|
249
249
|
// until the next `daemon install --force`.
|
|
250
250
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
// Exit cleanly and let the supervisor (systemd Restart=always /
|
|
252
|
+
// launchd KeepAlive) restart us with the new code on disk.
|
|
253
|
+
// This avoids a race condition where spawnSync("systemctl restart")
|
|
254
|
+
// returns before our SIGTERM handler releases the port, causing the
|
|
255
|
+
// new process to fail with "port already in use" and crash-loop.
|
|
256
|
+
log.info("exiting for supervisor restart after global update");
|
|
257
|
+
process.exit(0);
|
|
257
258
|
})();
|
|
258
259
|
}, delayMs);
|
|
259
260
|
restart = { ok: true };
|
package/dist/memory/layout.js
CHANGED
|
@@ -5,28 +5,30 @@
|
|
|
5
5
|
* ~/taskmaster/memory/ ← shared canonical location
|
|
6
6
|
* public/ ← shared: both agents read/write
|
|
7
7
|
* shared/ ← shared: both agents read/write
|
|
8
|
+
* users/ ← shared: both agents read/write (app-layer scoped)
|
|
9
|
+
* groups/ ← shared: both agents read/write (app-layer scoped)
|
|
8
10
|
* ~/taskmaster/agents/admin/memory/ ← physical directory
|
|
9
11
|
* admin/ ← physical: admin-only data
|
|
10
12
|
* public → ../../../memory/public (symlink to shared)
|
|
11
13
|
* shared → ../../../memory/shared (symlink to shared)
|
|
12
|
-
* users/
|
|
14
|
+
* users → ../../../memory/users (symlink to shared)
|
|
15
|
+
* groups → ../../../memory/groups (symlink to shared)
|
|
13
16
|
* ~/taskmaster/agents/public/memory/ ← physical directory
|
|
14
17
|
* public → ../../../memory/public (symlink to shared)
|
|
15
18
|
* shared → ../../../memory/shared (symlink to shared)
|
|
16
|
-
* users/
|
|
19
|
+
* users → ../../../memory/users (symlink to shared)
|
|
20
|
+
* groups → ../../../memory/groups (symlink to shared)
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
22
|
+
* public/, shared/, users/, and groups/ are symlinked — both agents share
|
|
23
|
+
* the same data. Access control is enforced by the application layer (scope config).
|
|
24
|
+
* admin/ only exists in the admin agent workspace.
|
|
21
25
|
*/
|
|
22
26
|
import fs from "node:fs/promises";
|
|
23
27
|
import path from "node:path";
|
|
24
28
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
25
29
|
const log = createSubsystemLogger("memory-layout");
|
|
26
30
|
/** Shared scope folders symlinked from each agent workspace. */
|
|
27
|
-
const SHARED_FOLDERS = ["public", "shared"];
|
|
28
|
-
/** Agent-specific folders that belong in per-agent memory, not at the shared root. */
|
|
29
|
-
const AGENT_SPECIFIC_FOLDERS = ["admin", "users", "groups"];
|
|
31
|
+
const SHARED_FOLDERS = ["public", "shared", "users", "groups"];
|
|
30
32
|
/** The relative symlink target from agents/{id}/memory/{scope} → ../../../memory/{scope} */
|
|
31
33
|
const SYMLINK_TARGET_PREFIX = "../../../memory";
|
|
32
34
|
async function exists(p) {
|
|
@@ -83,14 +85,14 @@ async function mergeDir(src, dest) {
|
|
|
83
85
|
/**
|
|
84
86
|
* Ensure the correct memory layout for a multi-agent workspace.
|
|
85
87
|
*
|
|
86
|
-
* - Creates the shared
|
|
88
|
+
* - Creates the shared canonical directories at workspace root
|
|
87
89
|
* - For each agent dir, ensures `memory/` is a physical directory with
|
|
88
|
-
* symlinks for
|
|
90
|
+
* symlinks for shared folders and a physical `admin/` for the admin agent
|
|
89
91
|
* - Migrates from the old full-symlink layout if found
|
|
90
|
-
* - Consolidates data from
|
|
92
|
+
* - Consolidates data from physical dirs back into the shared location
|
|
91
93
|
*
|
|
92
94
|
* @param workspaceRoot The workspace root (e.g. ~/taskmaster)
|
|
93
|
-
* @param agentDirs Agent workspace directories
|
|
95
|
+
* @param agentDirs Agent workspace directories
|
|
94
96
|
*/
|
|
95
97
|
export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
96
98
|
const sharedMemoryDir = path.join(workspaceRoot, "memory");
|
|
@@ -104,11 +106,8 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
|
104
106
|
// Case 1: Full memory symlink (old layout) — migrate to per-subfolder
|
|
105
107
|
if (await isSymlink(memoryPath)) {
|
|
106
108
|
log.info(`migrating full memory symlink to per-subfolder layout: ${agentDir}`);
|
|
107
|
-
// The symlink points to the shared memory dir — data is already there.
|
|
108
|
-
// Remove the full symlink and create a physical dir with per-subfolder symlinks.
|
|
109
109
|
await fs.unlink(memoryPath);
|
|
110
110
|
await fs.mkdir(memoryPath, { recursive: true });
|
|
111
|
-
// Data is already in the shared location, just create symlinks and physical dirs
|
|
112
111
|
}
|
|
113
112
|
// Ensure memory/ is a physical directory
|
|
114
113
|
if (!(await isDirectory(memoryPath))) {
|
|
@@ -136,12 +135,10 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
|
136
135
|
/* ignore if race */
|
|
137
136
|
});
|
|
138
137
|
}
|
|
139
|
-
//
|
|
138
|
+
// admin/ is the only per-agent physical directory — exists only in admin workspace
|
|
140
139
|
if (isAdmin) {
|
|
141
140
|
await fs.mkdir(path.join(memoryPath, "admin"), { recursive: true });
|
|
142
141
|
}
|
|
143
|
-
await fs.mkdir(path.join(memoryPath, "users"), { recursive: true });
|
|
144
|
-
await fs.mkdir(path.join(memoryPath, "groups"), { recursive: true });
|
|
145
142
|
// Remove admin/ from non-admin agent workspaces (it should never exist there)
|
|
146
143
|
if (!isAdmin) {
|
|
147
144
|
const adminPath = path.join(memoryPath, "admin");
|
|
@@ -151,7 +148,6 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
|
151
148
|
}
|
|
152
149
|
else {
|
|
153
150
|
log.warn(`non-admin agent has data in memory/admin/ — moving to admin agent`);
|
|
154
|
-
// Find admin agent dir and merge there
|
|
155
151
|
const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
|
|
156
152
|
if (adminAgent) {
|
|
157
153
|
const adminMemAdmin = path.join(adminAgent.dir, "memory", "admin");
|
|
@@ -163,57 +159,52 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
|
163
159
|
}
|
|
164
160
|
}
|
|
165
161
|
}
|
|
166
|
-
// Sweep orphaned
|
|
167
|
-
// After full-symlink migration, admin/,
|
|
168
|
-
//
|
|
169
|
-
// Move them to the admin agent's workspace (most privileged, owns historical data).
|
|
162
|
+
// Sweep orphaned admin-only data from the shared root.
|
|
163
|
+
// After full-symlink migration, admin/, notes/, and loose files may remain
|
|
164
|
+
// at the shared root. These belong in the admin agent's workspace.
|
|
170
165
|
await migrateOrphanedSharedData(sharedMemoryDir, agentDirs);
|
|
171
166
|
}
|
|
172
167
|
/**
|
|
173
|
-
* Move
|
|
168
|
+
* Move admin-only data that was left at the shared memory root
|
|
174
169
|
* (from the old full-symlink layout) into the admin agent's workspace.
|
|
175
170
|
*
|
|
176
|
-
*
|
|
177
|
-
* Everything else
|
|
171
|
+
* Shared folders (public, shared, users, groups) belong at the shared root.
|
|
172
|
+
* Everything else (admin, notes, loose .md files) is admin-only.
|
|
178
173
|
*/
|
|
179
174
|
async function migrateOrphanedSharedData(sharedMemoryDir, agentDirs) {
|
|
180
175
|
const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
|
|
181
176
|
if (!adminAgent)
|
|
182
|
-
return;
|
|
177
|
+
return;
|
|
183
178
|
const adminMemoryPath = path.join(adminAgent.dir, "memory");
|
|
184
179
|
let entries;
|
|
185
180
|
try {
|
|
186
181
|
entries = await fs.readdir(sharedMemoryDir);
|
|
187
182
|
}
|
|
188
183
|
catch {
|
|
189
|
-
return;
|
|
184
|
+
return;
|
|
190
185
|
}
|
|
191
186
|
const sharedSet = new Set(SHARED_FOLDERS);
|
|
192
187
|
for (const name of entries) {
|
|
193
188
|
// Skip shared folders — they belong here
|
|
194
189
|
if (sharedSet.has(name))
|
|
195
190
|
continue;
|
|
196
|
-
// Skip
|
|
191
|
+
// Skip hidden files
|
|
197
192
|
if (name.startsWith("."))
|
|
198
193
|
continue;
|
|
199
194
|
const srcPath = path.join(sharedMemoryDir, name);
|
|
200
195
|
if (await isDirectory(srcPath)) {
|
|
201
196
|
const destPath = path.join(adminMemoryPath, name);
|
|
202
197
|
if (await isEmptyDir(srcPath)) {
|
|
203
|
-
// Empty orphan dir — just remove it
|
|
204
198
|
await fs.rm(srcPath, { recursive: true }).catch(() => { });
|
|
205
199
|
continue;
|
|
206
200
|
}
|
|
207
|
-
// Merge into admin agent's workspace
|
|
208
201
|
log.info(`migrating orphaned ${name}/ from shared root to admin agent`);
|
|
209
202
|
await mergeDir(srcPath, destPath);
|
|
210
|
-
// Remove source if now empty
|
|
211
203
|
if (await isEmptyDir(srcPath)) {
|
|
212
204
|
await fs.rm(srcPath, { recursive: true }).catch(() => { });
|
|
213
205
|
}
|
|
214
206
|
}
|
|
215
207
|
else if (name.endsWith(".md")) {
|
|
216
|
-
// Loose .md files — move to admin agent's memory root
|
|
217
208
|
const destPath = path.join(adminMemoryPath, name);
|
|
218
209
|
if (!(await exists(destPath))) {
|
|
219
210
|
log.info(`migrating orphaned ${name} from shared root to admin agent`);
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -105,6 +105,28 @@ if ! check_node; then
|
|
|
105
105
|
install_node
|
|
106
106
|
fi
|
|
107
107
|
|
|
108
|
+
# ── Stop existing daemon before upgrade ──────────────────────────────────────
|
|
109
|
+
# The old gateway process holds the port. It must be stopped before npm
|
|
110
|
+
# replaces the files on disk, otherwise the new daemon cannot bind.
|
|
111
|
+
|
|
112
|
+
if command -v taskmaster >/dev/null 2>&1; then
|
|
113
|
+
echo ""
|
|
114
|
+
echo "Stopping existing gateway daemon..."
|
|
115
|
+
if [ "$PLATFORM" = "linux" ] && [ "$(id -u)" = "0" ] && [ "${SUDO_USER:-}" != "" ]; then
|
|
116
|
+
# Running as root via sudo — must run as the real user for systemctl --user
|
|
117
|
+
_STOP_USER="${SUDO_USER}"
|
|
118
|
+
_STOP_UID=$(id -u "$_STOP_USER")
|
|
119
|
+
sudo -u "$_STOP_USER" \
|
|
120
|
+
XDG_RUNTIME_DIR="/run/user/$_STOP_UID" \
|
|
121
|
+
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$_STOP_UID/bus" \
|
|
122
|
+
taskmaster gateway stop 2>/dev/null || true
|
|
123
|
+
else
|
|
124
|
+
taskmaster gateway stop 2>/dev/null || true
|
|
125
|
+
fi
|
|
126
|
+
# Give the process a moment to release the port
|
|
127
|
+
sleep 2
|
|
128
|
+
fi
|
|
129
|
+
|
|
108
130
|
# ── Install Taskmaster via npm ───────────────────────────────────────────────
|
|
109
131
|
|
|
110
132
|
echo ""
|