@rubytech/taskmaster 1.0.81 → 1.0.82
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/workspace.js +17 -1
- package/dist/build-info.json +3 -3
- package/dist/gateway/server-methods/files.js +12 -2
- package/dist/gateway/server-methods/web.js +32 -1
- package/dist/routing/resolve-route.js +25 -2
- package/dist/web/auth-store.js +11 -2
- package/dist/web/login-qr.js +8 -8
- package/package.json +1 -1
package/dist/agents/workspace.js
CHANGED
|
@@ -132,7 +132,12 @@ export async function ensureAgentWorkspace(params) {
|
|
|
132
132
|
.access(bootstrapDonePath)
|
|
133
133
|
.then(() => true)
|
|
134
134
|
.catch(() => false);
|
|
135
|
-
|
|
135
|
+
// Only write BOOTSTRAP.md for brand-new workspaces. If other workspace
|
|
136
|
+
// files already exist (IDENTITY.md, SOUL.md, etc.) the workspace has been
|
|
137
|
+
// bootstrapped — don't re-create BOOTSTRAP.md even if .bootstrap-done is
|
|
138
|
+
// missing. This prevents the onboarding flow from re-activating after a
|
|
139
|
+
// restart, session reset, or AI failure to write the sentinel.
|
|
140
|
+
if (!bootstrapDone && isBrandNewWorkspace) {
|
|
136
141
|
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
|
|
137
142
|
}
|
|
138
143
|
await ensureGitRepo(dir, isBrandNewWorkspace);
|
|
@@ -179,8 +184,19 @@ export async function loadWorkspaceBootstrapFiles(dir) {
|
|
|
179
184
|
filePath: path.join(resolvedDir, DEFAULT_BOOTSTRAP_FILENAME),
|
|
180
185
|
},
|
|
181
186
|
];
|
|
187
|
+
// If .bootstrap-done exists, treat BOOTSTRAP.md as absent so the onboarding
|
|
188
|
+
// flow is never injected into agent context on a bootstrapped workspace.
|
|
189
|
+
const bootstrapDonePath = path.join(resolvedDir, BOOTSTRAP_DONE_SENTINEL);
|
|
190
|
+
const bootstrapDone = await fs
|
|
191
|
+
.access(bootstrapDonePath)
|
|
192
|
+
.then(() => true)
|
|
193
|
+
.catch(() => false);
|
|
182
194
|
const result = [];
|
|
183
195
|
for (const entry of entries) {
|
|
196
|
+
if (bootstrapDone && entry.name === DEFAULT_BOOTSTRAP_FILENAME) {
|
|
197
|
+
result.push({ name: entry.name, path: entry.filePath, missing: true });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
184
200
|
try {
|
|
185
201
|
const content = await fs.readFile(entry.filePath, "utf-8");
|
|
186
202
|
result.push({
|
package/dist/build-info.json
CHANGED
|
@@ -6,9 +6,19 @@ import { ErrorCodes, errorShape } from "../protocol/index.js";
|
|
|
6
6
|
const MAX_PREVIEW_BYTES = 256 * 1024; // 256 KB for preview
|
|
7
7
|
const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024; // 5 MB for download
|
|
8
8
|
const MAX_UPLOAD_BYTES = 5 * 1024 * 1024; // 5 MB for upload
|
|
9
|
+
/**
|
|
10
|
+
* Multi-agent workspaces set each agent's workspace to a subdirectory
|
|
11
|
+
* (e.g. ~/taskmaster/agents/admin). The files page should show the
|
|
12
|
+
* workspace root (~/taskmaster), not the agent subdir.
|
|
13
|
+
*/
|
|
14
|
+
function stripAgentSubdir(agentWorkspaceDir) {
|
|
15
|
+
const normalised = agentWorkspaceDir.replace(/\/+$/, "");
|
|
16
|
+
const match = normalised.match(/^(.+)\/agents\/[^/]+$/);
|
|
17
|
+
return match ? match[1] : normalised;
|
|
18
|
+
}
|
|
9
19
|
function resolveWorkspaceRoot() {
|
|
10
20
|
const cfg = loadConfig();
|
|
11
|
-
return resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
|
|
21
|
+
return stripAgentSubdir(resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
|
|
12
22
|
}
|
|
13
23
|
/**
|
|
14
24
|
* Resolve workspace root from request params.
|
|
@@ -20,7 +30,7 @@ function resolveWorkspaceForRequest(params) {
|
|
|
20
30
|
if (!agentId)
|
|
21
31
|
return resolveWorkspaceRoot();
|
|
22
32
|
const cfg = loadConfig();
|
|
23
|
-
return resolveAgentWorkspaceDir(cfg, agentId);
|
|
33
|
+
return stripAgentSubdir(resolveAgentWorkspaceDir(cfg, agentId));
|
|
24
34
|
}
|
|
25
35
|
/**
|
|
26
36
|
* Validate and resolve a relative path within the workspace.
|
|
@@ -4,6 +4,15 @@ import { ErrorCodes, errorShape, formatValidationErrors, validateWebLoginStartPa
|
|
|
4
4
|
import { formatForLog } from "../ws-log.js";
|
|
5
5
|
const WEB_LOGIN_METHODS = new Set(["web.login.start", "web.login.wait"]);
|
|
6
6
|
const resolveWebLoginProvider = () => listChannelPlugins().find((plugin) => (plugin.gatewayMethods ?? []).some((method) => WEB_LOGIN_METHODS.has(method))) ?? null;
|
|
7
|
+
/**
|
|
8
|
+
* Given an admin agent ID, find the matching public agent.
|
|
9
|
+
* "admin" → "public", "foo-admin" → "foo-public".
|
|
10
|
+
*/
|
|
11
|
+
function resolvePublicCounterpart(adminAgentId, agents) {
|
|
12
|
+
const lower = adminAgentId.toLowerCase();
|
|
13
|
+
const target = lower === "admin" ? "public" : adminAgentId.replace(/-admin$/i, "-public");
|
|
14
|
+
return agents.find((a) => (a.id ?? "").toLowerCase() === target.toLowerCase())?.id ?? undefined;
|
|
15
|
+
}
|
|
7
16
|
/**
|
|
8
17
|
* After a successful WhatsApp QR pairing, auto-create a paired admin binding
|
|
9
18
|
* for the self phone number. This is the single code path for all businesses:
|
|
@@ -101,9 +110,31 @@ async function ensurePairedAdminBinding(selfPhone, accountId) {
|
|
|
101
110
|
},
|
|
102
111
|
meta: { paired: true },
|
|
103
112
|
};
|
|
113
|
+
const newBindings = [newBinding];
|
|
114
|
+
// Ensure a channel-level catch-all for the public agent so unbound DMs
|
|
115
|
+
// route to public, not admin. Without this, the routing default fallback
|
|
116
|
+
// sends every unknown phone number to admin.
|
|
117
|
+
const publicAgentId = resolvePublicCounterpart(adminAgentId, agents);
|
|
118
|
+
if (publicAgentId) {
|
|
119
|
+
const hasPublicCatchAll = bindings.some((b) => b.agentId === publicAgentId &&
|
|
120
|
+
b.match.channel === "whatsapp" &&
|
|
121
|
+
!b.match.peer &&
|
|
122
|
+
!b.match.guildId &&
|
|
123
|
+
!b.match.teamId);
|
|
124
|
+
if (!hasPublicCatchAll) {
|
|
125
|
+
newBindings.push({
|
|
126
|
+
agentId: publicAgentId,
|
|
127
|
+
match: {
|
|
128
|
+
channel: "whatsapp",
|
|
129
|
+
...(accountId ? { accountId } : {}),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
console.log(`[web] ensurePairedAdminBinding: created catch-all binding for ${publicAgentId} (account=${effectiveAccount})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
104
135
|
const updatedCfg = {
|
|
105
136
|
...cfg,
|
|
106
|
-
bindings: [...bindings,
|
|
137
|
+
bindings: [...bindings, ...newBindings],
|
|
107
138
|
};
|
|
108
139
|
await writeConfigFile(updatedCfg);
|
|
109
140
|
console.log(`[web] ensurePairedAdminBinding: created paired binding for ${adminAgentId} → ${selfPhone} (account=${effectiveAccount})`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
1
|
+
import { resolveDefaultAgentId, listAgentIds } from "../agents/agent-scope.js";
|
|
2
2
|
import { listBindings } from "./bindings.js";
|
|
3
3
|
import { buildAgentMainSessionKey, buildAgentPeerSessionKey, DEFAULT_ACCOUNT_ID, DEFAULT_MAIN_KEY, normalizeAgentId, sanitizeAgentId, } from "./session-key.js";
|
|
4
4
|
export { DEFAULT_ACCOUNT_ID, DEFAULT_AGENT_ID } from "./session-key.js";
|
|
@@ -136,5 +136,28 @@ export function resolveAgentRoute(input) {
|
|
|
136
136
|
const anyAccountMatch = bindings.find((b) => b.match?.accountId?.trim() === "*" && !b.match?.peer && !b.match?.guildId && !b.match?.teamId);
|
|
137
137
|
if (anyAccountMatch)
|
|
138
138
|
return choose(anyAccountMatch.agentId, "binding.channel");
|
|
139
|
-
|
|
139
|
+
// Admin agents should only be reached via explicit peer binding.
|
|
140
|
+
// When falling to default for a DM, prefer a public counterpart so that
|
|
141
|
+
// unbound phone numbers never route to admin.
|
|
142
|
+
const defaultId = resolveDefaultAgentId(input.cfg);
|
|
143
|
+
if (peer) {
|
|
144
|
+
const publicFallback = resolvePublicFallback(input.cfg, defaultId);
|
|
145
|
+
if (publicFallback)
|
|
146
|
+
return choose(publicFallback, "default");
|
|
147
|
+
}
|
|
148
|
+
return choose(defaultId, "default");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* When the default agent is an admin agent and a public counterpart exists,
|
|
152
|
+
* return the public agent ID. This prevents unbound DMs from reaching admin.
|
|
153
|
+
*/
|
|
154
|
+
function resolvePublicFallback(cfg, defaultId) {
|
|
155
|
+
const lower = defaultId.toLowerCase();
|
|
156
|
+
const isAdmin = lower === "admin" || lower.endsWith("-admin");
|
|
157
|
+
if (!isAdmin)
|
|
158
|
+
return undefined;
|
|
159
|
+
const allIds = listAgentIds(cfg);
|
|
160
|
+
// "foo-admin" → "foo-public"; "admin" → "public"
|
|
161
|
+
const publicId = lower === "admin" ? "public" : defaultId.replace(/-admin$/i, "-public");
|
|
162
|
+
return allIds.find((id) => id.toLowerCase() === publicId.toLowerCase());
|
|
140
163
|
}
|
package/dist/web/auth-store.js
CHANGED
|
@@ -107,8 +107,17 @@ async function clearLegacyBaileysAuthState(authDir) {
|
|
|
107
107
|
export async function logoutWeb(params) {
|
|
108
108
|
const runtime = params.runtime ?? defaultRuntime;
|
|
109
109
|
const resolvedAuthDir = resolveUserPath(params.authDir ?? resolveDefaultWebAuthDir());
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// Check whether the auth directory exists on disk — not whether creds are
|
|
111
|
+
// valid JSON. Corrupt or partial state must still be cleaned up.
|
|
112
|
+
let dirExists = false;
|
|
113
|
+
try {
|
|
114
|
+
await fs.access(resolvedAuthDir);
|
|
115
|
+
dirExists = true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// directory doesn't exist
|
|
119
|
+
}
|
|
120
|
+
if (!dirExists) {
|
|
112
121
|
runtime.log(info("No WhatsApp Web session found; nothing to delete."));
|
|
113
122
|
return false;
|
|
114
123
|
}
|
package/dist/web/login-qr.js
CHANGED
|
@@ -79,14 +79,14 @@ export async function startWebLoginWithQr(opts = {}) {
|
|
|
79
79
|
message: `WhatsApp is already linked (${who}). Say "relink" if you want a fresh QR.`,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
82
|
+
// Always clear credentials before generating a new QR. Even when
|
|
83
|
+
// webAuthExists() returns false, corrupt or partial files may remain on disk
|
|
84
|
+
// and cause Baileys to generate QR codes that WhatsApp rejects.
|
|
85
|
+
await logoutWeb({
|
|
86
|
+
authDir: account.authDir,
|
|
87
|
+
isLegacyAuthDir: account.isLegacyAuthDir,
|
|
88
|
+
runtime,
|
|
89
|
+
});
|
|
90
90
|
const existing = activeLogins.get(account.accountId);
|
|
91
91
|
if (existing && isLoginFresh(existing) && existing.qrDataUrl) {
|
|
92
92
|
return {
|