appostle-installer 0.0.14 → 0.0.15
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/appostle.js +680 -239
- package/dist/appostle.js.map +4 -4
- package/dist/worker.js +20662 -20102
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/appostle.js
CHANGED
|
@@ -1832,7 +1832,13 @@ function extractTimestamps(record) {
|
|
|
1832
1832
|
// Fork lineage — preserved across resume so the in-memory ManagedAgent
|
|
1833
1833
|
// can flow `parentAgentId` into snapshots that drive the tab Split icon.
|
|
1834
1834
|
...record.parentAgentId ? { parentAgentId: record.parentAgentId } : {},
|
|
1835
|
-
...record.forkedFromMessageUuid ? { forkedFromMessageUuid: record.forkedFromMessageUuid } : {}
|
|
1835
|
+
...record.forkedFromMessageUuid ? { forkedFromMessageUuid: record.forkedFromMessageUuid } : {},
|
|
1836
|
+
// Multi-tenant ownership — closes the daemon-restart gap. Old records
|
|
1837
|
+
// lack these fields → ownerUserId stays undefined (→ null in
|
|
1838
|
+
// registerSession), agent rehydrates as unscoped.
|
|
1839
|
+
ownerUserId: record.ownerUserId ?? null,
|
|
1840
|
+
sharedWithUserIds: record.sharedWithUserIds ?? [],
|
|
1841
|
+
ownerUsername: record.ownerUsername ?? null
|
|
1836
1842
|
};
|
|
1837
1843
|
}
|
|
1838
1844
|
function hasRegisteredProvider(registeredProviders, value) {
|
|
@@ -1916,6 +1922,11 @@ function toAgentPayload(agent, options) {
|
|
|
1916
1922
|
title: options?.title ?? null,
|
|
1917
1923
|
labels: agent.labels,
|
|
1918
1924
|
internal: agent.internal,
|
|
1925
|
+
// Surface ownership so the client can render an owner badge / detect
|
|
1926
|
+
// "shared with me" agents. `sharedWithUserIds` deliberately stays off
|
|
1927
|
+
// the snapshot — only owners read the full ACL, via the dedicated
|
|
1928
|
+
// `list_agent_shared_users_request` RPC.
|
|
1929
|
+
ownerUserId: agent.ownerUserId,
|
|
1919
1930
|
// Fork lineage — the client's tab descriptor uses `parentAgentId` to
|
|
1920
1931
|
// mark the agent as a fork (Split glyph). Carry it from the live
|
|
1921
1932
|
// ManagedAgent so the marker doesn't disappear once the agent is
|
|
@@ -3982,7 +3993,19 @@ var AgentSnapshotPayloadSchema = z11.object({
|
|
|
3982
3993
|
* lists at display time. The agent itself is a real, full-featured session
|
|
3983
3994
|
* in every other respect; `internal` is a UI visibility hint, nothing more.
|
|
3984
3995
|
*/
|
|
3985
|
-
internal: z11.boolean().optional()
|
|
3996
|
+
internal: z11.boolean().optional(),
|
|
3997
|
+
/**
|
|
3998
|
+
* Multi-tenant ownership (Phase 2c/4). Surfaces the auth-server user-id
|
|
3999
|
+
* of the agent's creator so the app can render an owner badge and
|
|
4000
|
+
* detect "shared with me" state (owner !== current user). Optional/
|
|
4001
|
+
* nullable for legacy agents created before per-agent ownership
|
|
4002
|
+
* existed — those render without a badge.
|
|
4003
|
+
*
|
|
4004
|
+
* NOTE: `sharedWithUserIds` is intentionally NOT exposed here. Only the
|
|
4005
|
+
* owner can enumerate the full ACL, via `list_agent_shared_users_request`.
|
|
4006
|
+
* The snapshot stays cheap and recipient-safe.
|
|
4007
|
+
*/
|
|
4008
|
+
ownerUserId: z11.string().nullable().optional()
|
|
3986
4009
|
});
|
|
3987
4010
|
var VoiceAudioChunkMessageSchema = z11.object({
|
|
3988
4011
|
type: z11.literal("voice_audio_chunk"),
|
|
@@ -5191,6 +5214,74 @@ var DeleteSessionUploadResponseSchema = z11.object({
|
|
|
5191
5214
|
})
|
|
5192
5215
|
])
|
|
5193
5216
|
});
|
|
5217
|
+
var ShareAgentWithUserRequestSchema = z11.object({
|
|
5218
|
+
type: z11.literal("share_agent_with_user_request"),
|
|
5219
|
+
requestId: z11.string(),
|
|
5220
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
5221
|
+
agentId: z11.string(),
|
|
5222
|
+
/** Auth-server user-id of the recipient. */
|
|
5223
|
+
userId: z11.string()
|
|
5224
|
+
});
|
|
5225
|
+
var ShareAgentWithUserResponseSchema = z11.object({
|
|
5226
|
+
type: z11.literal("share_agent_with_user_response"),
|
|
5227
|
+
payload: z11.discriminatedUnion("ok", [
|
|
5228
|
+
z11.object({
|
|
5229
|
+
requestId: z11.string(),
|
|
5230
|
+
ok: z11.literal(true),
|
|
5231
|
+
/** Updated ACL after the share applied (owner is not included). */
|
|
5232
|
+
sharedWithUserIds: z11.array(z11.string())
|
|
5233
|
+
}),
|
|
5234
|
+
z11.object({
|
|
5235
|
+
requestId: z11.string(),
|
|
5236
|
+
ok: z11.literal(false),
|
|
5237
|
+
error: z11.string()
|
|
5238
|
+
})
|
|
5239
|
+
])
|
|
5240
|
+
});
|
|
5241
|
+
var UnshareAgentWithUserRequestSchema = z11.object({
|
|
5242
|
+
type: z11.literal("unshare_agent_with_user_request"),
|
|
5243
|
+
requestId: z11.string(),
|
|
5244
|
+
agentId: z11.string(),
|
|
5245
|
+
userId: z11.string()
|
|
5246
|
+
});
|
|
5247
|
+
var UnshareAgentWithUserResponseSchema = z11.object({
|
|
5248
|
+
type: z11.literal("unshare_agent_with_user_response"),
|
|
5249
|
+
payload: z11.discriminatedUnion("ok", [
|
|
5250
|
+
z11.object({
|
|
5251
|
+
requestId: z11.string(),
|
|
5252
|
+
ok: z11.literal(true),
|
|
5253
|
+
sharedWithUserIds: z11.array(z11.string())
|
|
5254
|
+
}),
|
|
5255
|
+
z11.object({
|
|
5256
|
+
requestId: z11.string(),
|
|
5257
|
+
ok: z11.literal(false),
|
|
5258
|
+
error: z11.string()
|
|
5259
|
+
})
|
|
5260
|
+
])
|
|
5261
|
+
});
|
|
5262
|
+
var ListAgentSharedUsersRequestSchema = z11.object({
|
|
5263
|
+
type: z11.literal("list_agent_shared_users_request"),
|
|
5264
|
+
requestId: z11.string(),
|
|
5265
|
+
agentId: z11.string()
|
|
5266
|
+
});
|
|
5267
|
+
var ListAgentSharedUsersResponseSchema = z11.object({
|
|
5268
|
+
type: z11.literal("list_agent_shared_users_response"),
|
|
5269
|
+
payload: z11.discriminatedUnion("ok", [
|
|
5270
|
+
z11.object({
|
|
5271
|
+
requestId: z11.string(),
|
|
5272
|
+
ok: z11.literal(true),
|
|
5273
|
+
/** Auth-server user-id of the owner (always present when ok). */
|
|
5274
|
+
ownerUserId: z11.string().nullable(),
|
|
5275
|
+
/** Auth-server user-ids that the owner has granted access to. */
|
|
5276
|
+
sharedWithUserIds: z11.array(z11.string())
|
|
5277
|
+
}),
|
|
5278
|
+
z11.object({
|
|
5279
|
+
requestId: z11.string(),
|
|
5280
|
+
ok: z11.literal(false),
|
|
5281
|
+
error: z11.string()
|
|
5282
|
+
})
|
|
5283
|
+
])
|
|
5284
|
+
});
|
|
5194
5285
|
var SessionImageSchema = z11.object({
|
|
5195
5286
|
id: z11.string(),
|
|
5196
5287
|
fileName: z11.string(),
|
|
@@ -5433,6 +5524,9 @@ var SessionInboundMessageSchema = z11.discriminatedUnion("type", [
|
|
|
5433
5524
|
DeleteSessionUploadRequestSchema,
|
|
5434
5525
|
ListSessionImagesRequestSchema,
|
|
5435
5526
|
DeleteSessionImageRequestSchema,
|
|
5527
|
+
ShareAgentWithUserRequestSchema,
|
|
5528
|
+
UnshareAgentWithUserRequestSchema,
|
|
5529
|
+
ListAgentSharedUsersRequestSchema,
|
|
5436
5530
|
FetchAttachmentBytesRequestSchema
|
|
5437
5531
|
]);
|
|
5438
5532
|
var ActivityLogPayloadSchema = z11.object({
|
|
@@ -7016,6 +7110,9 @@ var SessionOutboundMessageSchema = z11.discriminatedUnion("type", [
|
|
|
7016
7110
|
DeleteSessionUploadResponseSchema,
|
|
7017
7111
|
ListSessionImagesResponseSchema,
|
|
7018
7112
|
DeleteSessionImageResponseSchema,
|
|
7113
|
+
ShareAgentWithUserResponseSchema,
|
|
7114
|
+
UnshareAgentWithUserResponseSchema,
|
|
7115
|
+
ListAgentSharedUsersResponseSchema,
|
|
7019
7116
|
FetchAttachmentBytesResponseSchema
|
|
7020
7117
|
]);
|
|
7021
7118
|
var WSPingMessageSchema = z11.object({
|
|
@@ -7209,7 +7306,7 @@ import { exec } from "node:child_process";
|
|
|
7209
7306
|
import { promisify as promisify3 } from "util";
|
|
7210
7307
|
import { join as join14, resolve as resolve9, sep as sep2 } from "path";
|
|
7211
7308
|
import { homedir as homedir5, hostname as osHostname } from "node:os";
|
|
7212
|
-
import { z as
|
|
7309
|
+
import { z as z38 } from "zod";
|
|
7213
7310
|
|
|
7214
7311
|
// ../server/src/server/persisted-config.ts
|
|
7215
7312
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -7723,7 +7820,9 @@ function ensurePrng() {
|
|
|
7723
7820
|
const cryptoObj = globalThis.crypto;
|
|
7724
7821
|
if (cryptoObj?.getRandomValues) {
|
|
7725
7822
|
nacl.setPRNG((x, n) => {
|
|
7726
|
-
|
|
7823
|
+
const buf = new Uint8Array(n);
|
|
7824
|
+
cryptoObj.getRandomValues(buf);
|
|
7825
|
+
x.set(buf, 0);
|
|
7727
7826
|
});
|
|
7728
7827
|
prngReady = true;
|
|
7729
7828
|
return;
|
|
@@ -7834,8 +7933,8 @@ function base64ToArrayBuffer(base64) {
|
|
|
7834
7933
|
// ../relay/dist/encrypted-channel.js
|
|
7835
7934
|
var HANDSHAKE_RETRY_MS = 1e3;
|
|
7836
7935
|
var MAX_PENDING_SENDS = 200;
|
|
7837
|
-
async function createClientChannel(transport, daemonPublicKeyB64, events = {}) {
|
|
7838
|
-
const keyPair = generateKeyPair();
|
|
7936
|
+
async function createClientChannel(transport, daemonPublicKeyB64, events = {}, staticKeyPair) {
|
|
7937
|
+
const keyPair = staticKeyPair ?? generateKeyPair();
|
|
7839
7938
|
const daemonPublicKey = importPublicKey(daemonPublicKeyB64);
|
|
7840
7939
|
const sharedKey = deriveSharedKey(keyPair.secretKey, daemonPublicKey);
|
|
7841
7940
|
const channel = new EncryptedChannel(transport, sharedKey, events);
|
|
@@ -8007,6 +8106,16 @@ var EncryptedChannel = class {
|
|
|
8007
8106
|
isOpen() {
|
|
8008
8107
|
return this.state === "open";
|
|
8009
8108
|
}
|
|
8109
|
+
/**
|
|
8110
|
+
* Peer's X25519 public key (base64) captured during the daemon-side
|
|
8111
|
+
* handshake. Returns `null` on the client side or when the channel was
|
|
8112
|
+
* built without this metadata (legacy code paths). Used by the daemon's
|
|
8113
|
+
* WS-server to resolve the connecting device → owning user.
|
|
8114
|
+
*/
|
|
8115
|
+
getPeerPublicKeyB64() {
|
|
8116
|
+
const v = this.options.peerPublicKeyB64;
|
|
8117
|
+
return v && v.length > 0 ? v : null;
|
|
8118
|
+
}
|
|
8010
8119
|
onTransitionToOpen(cb) {
|
|
8011
8120
|
this.onOpenCallbacks.push(cb);
|
|
8012
8121
|
}
|
|
@@ -9949,7 +10058,14 @@ async function ensureAgentLoaded(agentId, deps) {
|
|
|
9949
10058
|
if (!config) {
|
|
9950
10059
|
throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
|
|
9951
10060
|
}
|
|
9952
|
-
snapshot = await deps.agentManager.createAgent(config, agentId, {
|
|
10061
|
+
snapshot = await deps.agentManager.createAgent(config, agentId, {
|
|
10062
|
+
labels: record.labels,
|
|
10063
|
+
// Preserve multi-tenant ownership across the no-handle rehydrate
|
|
10064
|
+
// path (records that never landed a persistence handle, e.g. very
|
|
10065
|
+
// early agents). Without this, agents would silently drop their
|
|
10066
|
+
// owner whenever they took this branch through `ensureAgentLoaded`.
|
|
10067
|
+
ownerUserId: record.ownerUserId ?? null
|
|
10068
|
+
});
|
|
9953
10069
|
deps.logger.info({ agentId, provider: record.provider }, "Agent created from stored config");
|
|
9954
10070
|
}
|
|
9955
10071
|
await deps.agentManager.hydrateTimelineFromProvider(agentId);
|
|
@@ -31261,6 +31377,53 @@ function buildProviderRegistry(logger, options) {
|
|
|
31261
31377
|
);
|
|
31262
31378
|
}
|
|
31263
31379
|
|
|
31380
|
+
// ../server/src/server/claude-profile.ts
|
|
31381
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, rmSync as rmSync2 } from "node:fs";
|
|
31382
|
+
import path13 from "node:path";
|
|
31383
|
+
import os5 from "node:os";
|
|
31384
|
+
var SHARED_ITEMS = ["settings.json", "hooks", "agents", "skills", "plugins", "keybindings.json"];
|
|
31385
|
+
function getClaudeProfileDir(username) {
|
|
31386
|
+
return path13.join(os5.homedir(), `.claude-${username}`);
|
|
31387
|
+
}
|
|
31388
|
+
function ensureClaudeProfile(username, logger) {
|
|
31389
|
+
const profileDir = getClaudeProfileDir(username);
|
|
31390
|
+
const ownerDir = path13.join(os5.homedir(), ".claude");
|
|
31391
|
+
if (!existsSync10(ownerDir)) {
|
|
31392
|
+
throw new Error(`Owner claude config dir not found: ${ownerDir}`);
|
|
31393
|
+
}
|
|
31394
|
+
if (!existsSync10(profileDir)) {
|
|
31395
|
+
mkdirSync5(profileDir, { recursive: true });
|
|
31396
|
+
logger?.info({ profileDir, username }, "created claude profile directory");
|
|
31397
|
+
}
|
|
31398
|
+
for (const item of SHARED_ITEMS) {
|
|
31399
|
+
const target = path13.join(ownerDir, item);
|
|
31400
|
+
const link = path13.join(profileDir, item);
|
|
31401
|
+
if (!existsSync10(target)) continue;
|
|
31402
|
+
if (existsSync10(link)) continue;
|
|
31403
|
+
symlinkSync(target, link);
|
|
31404
|
+
logger?.info({ item, profileDir }, "symlinked shared config item");
|
|
31405
|
+
}
|
|
31406
|
+
return profileDir;
|
|
31407
|
+
}
|
|
31408
|
+
function hasClaudeAuth(username) {
|
|
31409
|
+
const profileDir = getClaudeProfileDir(username);
|
|
31410
|
+
return existsSync10(profileDir);
|
|
31411
|
+
}
|
|
31412
|
+
function removeClaudeProfile(username, logger) {
|
|
31413
|
+
const profileDir = getClaudeProfileDir(username);
|
|
31414
|
+
if (!existsSync10(profileDir)) return;
|
|
31415
|
+
rmSync2(profileDir, { recursive: true, force: true });
|
|
31416
|
+
logger?.info({ profileDir, username }, "removed claude profile directory");
|
|
31417
|
+
}
|
|
31418
|
+
|
|
31419
|
+
// ../server/src/server/agent/agent-manager.ts
|
|
31420
|
+
import { z as z33 } from "zod";
|
|
31421
|
+
import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
|
|
31422
|
+
|
|
31423
|
+
// ../server/src/server/agent/handoff-mcp.ts
|
|
31424
|
+
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
31425
|
+
import { z as z32 } from "zod";
|
|
31426
|
+
|
|
31264
31427
|
// ../server/src/server/agent/agent-metadata-generator.ts
|
|
31265
31428
|
import { basename as basename5 } from "path";
|
|
31266
31429
|
import { z as z31 } from "zod";
|
|
@@ -31738,6 +31901,14 @@ function scheduleAgentMetadataGeneration(options) {
|
|
|
31738
31901
|
});
|
|
31739
31902
|
}
|
|
31740
31903
|
|
|
31904
|
+
// ../server/src/server/agent/agent-manager.ts
|
|
31905
|
+
var AgentIdSchema = z33.string().uuid();
|
|
31906
|
+
function canUserAccessAgent(agent, requesterUserId) {
|
|
31907
|
+
if (agent.ownerUserId === null) return true;
|
|
31908
|
+
if (agent.ownerUserId === requesterUserId) return true;
|
|
31909
|
+
return agent.sharedWithUserIds.includes(requesterUserId);
|
|
31910
|
+
}
|
|
31911
|
+
|
|
31741
31912
|
// ../server/src/server/agent/timeline-append.ts
|
|
31742
31913
|
async function appendTimelineItemIfAgentKnown(options) {
|
|
31743
31914
|
try {
|
|
@@ -32121,25 +32292,25 @@ async function buildProjectPlacementForCwd(input) {
|
|
|
32121
32292
|
}
|
|
32122
32293
|
|
|
32123
32294
|
// ../server/src/server/workspace-registry.ts
|
|
32124
|
-
import { z as
|
|
32125
|
-
var PersistedProjectRecordSchema =
|
|
32126
|
-
projectId:
|
|
32127
|
-
rootPath:
|
|
32128
|
-
kind:
|
|
32129
|
-
displayName:
|
|
32130
|
-
createdAt:
|
|
32131
|
-
updatedAt:
|
|
32132
|
-
archivedAt:
|
|
32133
|
-
});
|
|
32134
|
-
var PersistedWorkspaceRecordSchema =
|
|
32135
|
-
workspaceId:
|
|
32136
|
-
projectId:
|
|
32137
|
-
cwd:
|
|
32138
|
-
kind:
|
|
32139
|
-
displayName:
|
|
32140
|
-
createdAt:
|
|
32141
|
-
updatedAt:
|
|
32142
|
-
archivedAt:
|
|
32295
|
+
import { z as z34 } from "zod";
|
|
32296
|
+
var PersistedProjectRecordSchema = z34.object({
|
|
32297
|
+
projectId: z34.string(),
|
|
32298
|
+
rootPath: z34.string(),
|
|
32299
|
+
kind: z34.enum(["git", "non_git"]),
|
|
32300
|
+
displayName: z34.string(),
|
|
32301
|
+
createdAt: z34.string(),
|
|
32302
|
+
updatedAt: z34.string(),
|
|
32303
|
+
archivedAt: z34.string().nullable()
|
|
32304
|
+
});
|
|
32305
|
+
var PersistedWorkspaceRecordSchema = z34.object({
|
|
32306
|
+
workspaceId: z34.string(),
|
|
32307
|
+
projectId: z34.string(),
|
|
32308
|
+
cwd: z34.string(),
|
|
32309
|
+
kind: z34.enum(["local_checkout", "worktree", "directory"]),
|
|
32310
|
+
displayName: z34.string(),
|
|
32311
|
+
createdAt: z34.string(),
|
|
32312
|
+
updatedAt: z34.string(),
|
|
32313
|
+
archivedAt: z34.string().nullable()
|
|
32143
32314
|
});
|
|
32144
32315
|
function createPersistedProjectRecord(input) {
|
|
32145
32316
|
return PersistedProjectRecordSchema.parse({
|
|
@@ -32218,7 +32389,7 @@ function isVoicePermissionAllowed(request) {
|
|
|
32218
32389
|
|
|
32219
32390
|
// ../server/src/server/file-explorer/service.ts
|
|
32220
32391
|
import { promises as fs8 } from "fs";
|
|
32221
|
-
import
|
|
32392
|
+
import path14 from "path";
|
|
32222
32393
|
|
|
32223
32394
|
// ../server/src/server/path-utils.ts
|
|
32224
32395
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -32272,7 +32443,7 @@ async function listDirectoryEntries({
|
|
|
32272
32443
|
const dirents = await fs8.readdir(directoryPath, { withFileTypes: true });
|
|
32273
32444
|
const entriesWithNulls = await Promise.all(
|
|
32274
32445
|
dirents.map(async (dirent) => {
|
|
32275
|
-
const targetPath =
|
|
32446
|
+
const targetPath = path14.join(directoryPath, dirent.name);
|
|
32276
32447
|
const kind = dirent.isDirectory() ? "directory" : "file";
|
|
32277
32448
|
try {
|
|
32278
32449
|
return await buildEntryPayload({
|
|
@@ -32311,7 +32482,7 @@ async function readExplorerFile({
|
|
|
32311
32482
|
if (!stats.isFile()) {
|
|
32312
32483
|
throw new Error("Requested path is not a file");
|
|
32313
32484
|
}
|
|
32314
|
-
const ext =
|
|
32485
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
32315
32486
|
const basePayload = {
|
|
32316
32487
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
32317
32488
|
size: stats.size,
|
|
@@ -32349,7 +32520,7 @@ async function writeTextFile({
|
|
|
32349
32520
|
relativePath,
|
|
32350
32521
|
content
|
|
32351
32522
|
}) {
|
|
32352
|
-
const ext =
|
|
32523
|
+
const ext = path14.extname(relativePath).toLowerCase();
|
|
32353
32524
|
if (ext in IMAGE_MIME_TYPES) {
|
|
32354
32525
|
throw new Error(`Refusing to write '${relativePath}': binary/image file`);
|
|
32355
32526
|
}
|
|
@@ -32359,7 +32530,7 @@ async function writeTextFile({
|
|
|
32359
32530
|
await fs8.rename(tempPath, filePath);
|
|
32360
32531
|
}
|
|
32361
32532
|
async function deleteFile({ root, relativePath }) {
|
|
32362
|
-
const ext =
|
|
32533
|
+
const ext = path14.extname(relativePath).toLowerCase();
|
|
32363
32534
|
if (ext !== ".md") {
|
|
32364
32535
|
throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
|
|
32365
32536
|
}
|
|
@@ -32393,7 +32564,7 @@ async function getDownloadableFileInfo({ root, relativePath }) {
|
|
|
32393
32564
|
if (!stats.isFile()) {
|
|
32394
32565
|
throw new Error("Requested path is not a file");
|
|
32395
32566
|
}
|
|
32396
|
-
const ext =
|
|
32567
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
32397
32568
|
let mimeType = "application/octet-stream";
|
|
32398
32569
|
if (ext in IMAGE_MIME_TYPES) {
|
|
32399
32570
|
mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
|
|
@@ -32415,23 +32586,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
|
|
|
32415
32586
|
return {
|
|
32416
32587
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
32417
32588
|
absolutePath: filePath,
|
|
32418
|
-
fileName:
|
|
32589
|
+
fileName: path14.basename(filePath),
|
|
32419
32590
|
mimeType,
|
|
32420
32591
|
size: stats.size
|
|
32421
32592
|
};
|
|
32422
32593
|
}
|
|
32423
32594
|
async function resolveScopedPath({ root, relativePath = "." }) {
|
|
32424
|
-
const normalizedRoot =
|
|
32595
|
+
const normalizedRoot = path14.resolve(root);
|
|
32425
32596
|
const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
|
|
32426
|
-
const relative =
|
|
32427
|
-
if (relative !== "" && (relative.startsWith("..") ||
|
|
32597
|
+
const relative = path14.relative(normalizedRoot, requestedPath);
|
|
32598
|
+
if (relative !== "" && (relative.startsWith("..") || path14.isAbsolute(relative))) {
|
|
32428
32599
|
throw new Error("Access outside of workspace is not allowed");
|
|
32429
32600
|
}
|
|
32430
32601
|
const realRoot = await fs8.realpath(normalizedRoot);
|
|
32431
32602
|
try {
|
|
32432
32603
|
const realPath = await fs8.realpath(requestedPath);
|
|
32433
|
-
const realRelative =
|
|
32434
|
-
if (realRelative !== "" && (realRelative.startsWith("..") ||
|
|
32604
|
+
const realRelative = path14.relative(realRoot, realPath);
|
|
32605
|
+
if (realRelative !== "" && (realRelative.startsWith("..") || path14.isAbsolute(realRelative))) {
|
|
32435
32606
|
throw new Error("Access outside of workspace is not allowed");
|
|
32436
32607
|
}
|
|
32437
32608
|
return requestedPath;
|
|
@@ -32462,10 +32633,10 @@ function isMissingEntryError(error) {
|
|
|
32462
32633
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
32463
32634
|
}
|
|
32464
32635
|
function normalizeRelativePath({ root, targetPath }) {
|
|
32465
|
-
const normalizedRoot =
|
|
32466
|
-
const normalizedTarget =
|
|
32467
|
-
const relative =
|
|
32468
|
-
return relative === "" ? "." : relative.split(
|
|
32636
|
+
const normalizedRoot = path14.resolve(root);
|
|
32637
|
+
const normalizedTarget = path14.resolve(targetPath);
|
|
32638
|
+
const relative = path14.relative(normalizedRoot, normalizedTarget);
|
|
32639
|
+
return relative === "" ? "." : relative.split(path14.sep).join("/");
|
|
32469
32640
|
}
|
|
32470
32641
|
function textMimeTypeForExtension(ext) {
|
|
32471
32642
|
return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
|
|
@@ -32818,65 +32989,65 @@ async function getProjectIcon(projectDir) {
|
|
|
32818
32989
|
}
|
|
32819
32990
|
|
|
32820
32991
|
// ../server/src/utils/path.ts
|
|
32821
|
-
import
|
|
32992
|
+
import os6 from "os";
|
|
32822
32993
|
function expandTilde(path29) {
|
|
32823
32994
|
if (path29.startsWith("~/")) {
|
|
32824
|
-
const homeDir3 = process.env.HOME ||
|
|
32995
|
+
const homeDir3 = process.env.HOME || os6.homedir();
|
|
32825
32996
|
return path29.replace("~", homeDir3);
|
|
32826
32997
|
}
|
|
32827
32998
|
if (path29 === "~") {
|
|
32828
|
-
return process.env.HOME ||
|
|
32999
|
+
return process.env.HOME || os6.homedir();
|
|
32829
33000
|
}
|
|
32830
33001
|
return path29;
|
|
32831
33002
|
}
|
|
32832
33003
|
|
|
32833
33004
|
// ../server/src/server/skills/scanner.ts
|
|
32834
33005
|
import fs9 from "node:fs/promises";
|
|
32835
|
-
import
|
|
32836
|
-
import
|
|
33006
|
+
import os7 from "node:os";
|
|
33007
|
+
import path15 from "node:path";
|
|
32837
33008
|
var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
32838
33009
|
function homeDir() {
|
|
32839
|
-
return process.env.HOME ||
|
|
33010
|
+
return process.env.HOME || os7.homedir();
|
|
32840
33011
|
}
|
|
32841
33012
|
function codexHomeDir() {
|
|
32842
|
-
return process.env.CODEX_HOME ||
|
|
33013
|
+
return process.env.CODEX_HOME || path15.join(homeDir(), ".codex");
|
|
32843
33014
|
}
|
|
32844
33015
|
function resolveScopeDir(provider, scope, workspaceRoot) {
|
|
32845
33016
|
if (scope === "codex-prompts") {
|
|
32846
33017
|
if (provider !== "codex") {
|
|
32847
33018
|
throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
|
|
32848
33019
|
}
|
|
32849
|
-
return
|
|
33020
|
+
return path15.join(codexHomeDir(), "prompts");
|
|
32850
33021
|
}
|
|
32851
33022
|
if (scope === "project") {
|
|
32852
33023
|
if (!workspaceRoot) {
|
|
32853
33024
|
throw new Error(`workspaceRoot is required for scope "project"`);
|
|
32854
33025
|
}
|
|
32855
33026
|
const dotDir = provider === "claude" ? ".claude" : ".codex";
|
|
32856
|
-
return
|
|
33027
|
+
return path15.join(workspaceRoot, dotDir, "skills");
|
|
32857
33028
|
}
|
|
32858
33029
|
if (provider === "claude") {
|
|
32859
|
-
return
|
|
33030
|
+
return path15.join(homeDir(), ".claude", "skills");
|
|
32860
33031
|
}
|
|
32861
|
-
return
|
|
33032
|
+
return path15.join(codexHomeDir(), "skills");
|
|
32862
33033
|
}
|
|
32863
33034
|
function allowedRoots(workspaceRoot) {
|
|
32864
33035
|
const roots = [
|
|
32865
|
-
|
|
32866
|
-
|
|
32867
|
-
|
|
33036
|
+
path15.join(homeDir(), ".claude", "skills"),
|
|
33037
|
+
path15.join(codexHomeDir(), "skills"),
|
|
33038
|
+
path15.join(codexHomeDir(), "prompts")
|
|
32868
33039
|
];
|
|
32869
33040
|
if (workspaceRoot) {
|
|
32870
|
-
roots.push(
|
|
32871
|
-
roots.push(
|
|
33041
|
+
roots.push(path15.join(workspaceRoot, ".claude", "skills"));
|
|
33042
|
+
roots.push(path15.join(workspaceRoot, ".codex", "skills"));
|
|
32872
33043
|
}
|
|
32873
|
-
return roots.map((r) =>
|
|
33044
|
+
return roots.map((r) => path15.resolve(r));
|
|
32874
33045
|
}
|
|
32875
33046
|
function isInsideAllowedRoot(absPath, workspaceRoot) {
|
|
32876
|
-
const resolved =
|
|
33047
|
+
const resolved = path15.resolve(absPath);
|
|
32877
33048
|
for (const root of allowedRoots(workspaceRoot)) {
|
|
32878
|
-
const rel =
|
|
32879
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
33049
|
+
const rel = path15.relative(root, resolved);
|
|
33050
|
+
if (rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
|
|
32880
33051
|
return true;
|
|
32881
33052
|
}
|
|
32882
33053
|
}
|
|
@@ -33007,7 +33178,7 @@ async function listSkills(args) {
|
|
|
33007
33178
|
if (!entry.name.endsWith(".md")) continue;
|
|
33008
33179
|
const name = entry.name.slice(0, -".md".length);
|
|
33009
33180
|
if (!name) continue;
|
|
33010
|
-
const fullPath =
|
|
33181
|
+
const fullPath = path15.join(dir, entry.name);
|
|
33011
33182
|
const stat5 = await safeStat(fullPath);
|
|
33012
33183
|
if (!stat5) continue;
|
|
33013
33184
|
const description = await readDescriptionSafely(fullPath);
|
|
@@ -33025,8 +33196,8 @@ async function listSkills(args) {
|
|
|
33025
33196
|
} else {
|
|
33026
33197
|
for (const entry of entries) {
|
|
33027
33198
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
33028
|
-
const skillDir =
|
|
33029
|
-
const skillPath =
|
|
33199
|
+
const skillDir = path15.join(dir, entry.name);
|
|
33200
|
+
const skillPath = path15.join(skillDir, "SKILL.md");
|
|
33030
33201
|
const stat5 = await safeStat(skillPath);
|
|
33031
33202
|
if (!stat5) continue;
|
|
33032
33203
|
const description = await readDescriptionSafely(skillPath);
|
|
@@ -33073,7 +33244,7 @@ async function createSkill(args) {
|
|
|
33073
33244
|
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
33074
33245
|
await fs9.mkdir(dir, { recursive: true });
|
|
33075
33246
|
if (args.scope === "codex-prompts") {
|
|
33076
|
-
const filePath2 =
|
|
33247
|
+
const filePath2 = path15.join(dir, `${args.name}.md`);
|
|
33077
33248
|
try {
|
|
33078
33249
|
await fs9.access(filePath2);
|
|
33079
33250
|
throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
|
|
@@ -33089,7 +33260,7 @@ async function createSkill(args) {
|
|
|
33089
33260
|
await fs9.writeFile(filePath2, initial2, "utf8");
|
|
33090
33261
|
return { path: filePath2 };
|
|
33091
33262
|
}
|
|
33092
|
-
const skillDir =
|
|
33263
|
+
const skillDir = path15.join(dir, args.name);
|
|
33093
33264
|
let dirExists = false;
|
|
33094
33265
|
try {
|
|
33095
33266
|
const stat5 = await fs9.stat(skillDir);
|
|
@@ -33101,7 +33272,7 @@ async function createSkill(args) {
|
|
|
33101
33272
|
throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
|
|
33102
33273
|
}
|
|
33103
33274
|
await fs9.mkdir(skillDir, { recursive: true });
|
|
33104
|
-
const filePath =
|
|
33275
|
+
const filePath = path15.join(skillDir, "SKILL.md");
|
|
33105
33276
|
const initial = buildStarterSkill(args.name);
|
|
33106
33277
|
await fs9.writeFile(filePath, initial, "utf8");
|
|
33107
33278
|
return { path: filePath };
|
|
@@ -33130,7 +33301,7 @@ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expa
|
|
|
33130
33301
|
`;
|
|
33131
33302
|
}
|
|
33132
33303
|
async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
33133
|
-
if (!
|
|
33304
|
+
if (!path15.isAbsolute(args.path)) {
|
|
33134
33305
|
throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
|
|
33135
33306
|
}
|
|
33136
33307
|
if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
|
|
@@ -33156,7 +33327,7 @@ ${original}`;
|
|
|
33156
33327
|
|
|
33157
33328
|
// ../server/src/utils/directory-suggestions.ts
|
|
33158
33329
|
import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
|
|
33159
|
-
import
|
|
33330
|
+
import path16 from "node:path";
|
|
33160
33331
|
var DEFAULT_LIMIT = 30;
|
|
33161
33332
|
var MAX_LIMIT = 100;
|
|
33162
33333
|
var DEFAULT_MAX_DEPTH = 6;
|
|
@@ -33242,7 +33413,7 @@ function normalizeLimit(limit) {
|
|
|
33242
33413
|
return Math.max(1, Math.min(MAX_LIMIT, bounded));
|
|
33243
33414
|
}
|
|
33244
33415
|
async function searchWithinParentDirectory(input) {
|
|
33245
|
-
const parentPath =
|
|
33416
|
+
const parentPath = path16.resolve(input.homeRoot, input.parentPart || ".");
|
|
33246
33417
|
const parentRoot = await resolveDirectory(parentPath);
|
|
33247
33418
|
if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
|
|
33248
33419
|
return [];
|
|
@@ -33307,7 +33478,7 @@ async function searchAcrossHomeTree(input) {
|
|
|
33307
33478
|
return dedupeAndSort(ranked).slice(0, input.limit);
|
|
33308
33479
|
}
|
|
33309
33480
|
async function searchWorkspaceWithinParentDirectory(input) {
|
|
33310
|
-
const parentPath =
|
|
33481
|
+
const parentPath = path16.resolve(input.workspaceRoot, input.parentPart || ".");
|
|
33311
33482
|
const parentRoot = await resolveDirectory(parentPath);
|
|
33312
33483
|
if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
|
|
33313
33484
|
return [];
|
|
@@ -33553,15 +33724,15 @@ function findSegmentMatchIndex(segments, predicate) {
|
|
|
33553
33724
|
return -1;
|
|
33554
33725
|
}
|
|
33555
33726
|
function normalizeRelativePath2(homeRoot, absolutePath) {
|
|
33556
|
-
const relative =
|
|
33727
|
+
const relative = path16.relative(homeRoot, absolutePath);
|
|
33557
33728
|
if (!relative) {
|
|
33558
33729
|
return ".";
|
|
33559
33730
|
}
|
|
33560
|
-
return relative.split(
|
|
33731
|
+
return relative.split(path16.sep).join("/");
|
|
33561
33732
|
}
|
|
33562
33733
|
function isPathInsideRoot(root, target) {
|
|
33563
|
-
const relative =
|
|
33564
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
33734
|
+
const relative = path16.relative(root, target);
|
|
33735
|
+
return relative === "" || !relative.startsWith("..") && !path16.isAbsolute(relative);
|
|
33565
33736
|
}
|
|
33566
33737
|
function normalizeQueryParts(query2, homeRoot) {
|
|
33567
33738
|
const typedQuery = query2.trim().replace(/\\/g, "/");
|
|
@@ -33577,9 +33748,9 @@ function normalizeQueryParts(query2, homeRoot) {
|
|
|
33577
33748
|
normalized = normalized.slice(1);
|
|
33578
33749
|
}
|
|
33579
33750
|
}
|
|
33580
|
-
if (
|
|
33751
|
+
if (path16.isAbsolute(normalized)) {
|
|
33581
33752
|
isRooted = true;
|
|
33582
|
-
const absolute =
|
|
33753
|
+
const absolute = path16.resolve(normalized);
|
|
33583
33754
|
if (!isPathInsideRoot(homeRoot, absolute)) {
|
|
33584
33755
|
return null;
|
|
33585
33756
|
}
|
|
@@ -33618,8 +33789,8 @@ function normalizeQueryParts(query2, homeRoot) {
|
|
|
33618
33789
|
}
|
|
33619
33790
|
function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
|
|
33620
33791
|
let normalized = query2.trim().replace(/\\/g, "/");
|
|
33621
|
-
if (
|
|
33622
|
-
const absolute =
|
|
33792
|
+
if (path16.isAbsolute(normalized)) {
|
|
33793
|
+
const absolute = path16.resolve(normalized);
|
|
33623
33794
|
if (!isPathInsideRoot(workspaceRoot, absolute)) {
|
|
33624
33795
|
return null;
|
|
33625
33796
|
}
|
|
@@ -33645,7 +33816,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
|
|
|
33645
33816
|
}
|
|
33646
33817
|
async function resolveDirectory(inputPath) {
|
|
33647
33818
|
try {
|
|
33648
|
-
const resolved = await realpath(
|
|
33819
|
+
const resolved = await realpath(path16.resolve(inputPath));
|
|
33649
33820
|
const stats = await stat3(resolved);
|
|
33650
33821
|
if (!stats.isDirectory()) {
|
|
33651
33822
|
return null;
|
|
@@ -33672,7 +33843,7 @@ async function listChildDirectories(input) {
|
|
|
33672
33843
|
if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
|
|
33673
33844
|
continue;
|
|
33674
33845
|
}
|
|
33675
|
-
const candidatePath =
|
|
33846
|
+
const candidatePath = path16.join(input.directory, dirent.name);
|
|
33676
33847
|
const absolutePath = await resolveDirectoryCandidate({
|
|
33677
33848
|
candidatePath,
|
|
33678
33849
|
dirent,
|
|
@@ -33709,7 +33880,7 @@ async function listWorkspaceChildEntries(input) {
|
|
|
33709
33880
|
if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
|
|
33710
33881
|
continue;
|
|
33711
33882
|
}
|
|
33712
|
-
const candidatePath =
|
|
33883
|
+
const candidatePath = path16.join(input.directory, dirent.name);
|
|
33713
33884
|
const entry = await resolveWorkspaceCandidate({
|
|
33714
33885
|
candidatePath,
|
|
33715
33886
|
dirent,
|
|
@@ -33732,7 +33903,7 @@ async function listWorkspaceChildEntries(input) {
|
|
|
33732
33903
|
}
|
|
33733
33904
|
async function resolveDirectoryCandidate(input) {
|
|
33734
33905
|
if (input.dirent.isDirectory()) {
|
|
33735
|
-
const resolved2 =
|
|
33906
|
+
const resolved2 = path16.resolve(input.candidatePath);
|
|
33736
33907
|
return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
|
|
33737
33908
|
}
|
|
33738
33909
|
const resolved = await resolveDirectory(input.candidatePath);
|
|
@@ -33743,14 +33914,14 @@ async function resolveDirectoryCandidate(input) {
|
|
|
33743
33914
|
}
|
|
33744
33915
|
async function resolveWorkspaceCandidate(input) {
|
|
33745
33916
|
if (input.dirent.isDirectory()) {
|
|
33746
|
-
const resolved =
|
|
33917
|
+
const resolved = path16.resolve(input.candidatePath);
|
|
33747
33918
|
if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
|
|
33748
33919
|
return null;
|
|
33749
33920
|
}
|
|
33750
33921
|
return { absolutePath: resolved, kind: "directory" };
|
|
33751
33922
|
}
|
|
33752
33923
|
if (input.dirent.isFile()) {
|
|
33753
|
-
const resolved =
|
|
33924
|
+
const resolved = path16.resolve(input.candidatePath);
|
|
33754
33925
|
if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
|
|
33755
33926
|
return null;
|
|
33756
33927
|
}
|
|
@@ -33830,7 +34001,7 @@ function pruneWorkspaceEntryListCache() {
|
|
|
33830
34001
|
// ../server/src/utils/directory-listing.ts
|
|
33831
34002
|
import { readdir as readdir3, stat as stat4, realpath as realpath2 } from "node:fs/promises";
|
|
33832
34003
|
import { homedir as homedir3 } from "node:os";
|
|
33833
|
-
import
|
|
34004
|
+
import path17 from "node:path";
|
|
33834
34005
|
var DEFAULT_LIMIT2 = 500;
|
|
33835
34006
|
async function listDirectoryContents(options) {
|
|
33836
34007
|
const includeFiles = options.includeFiles ?? false;
|
|
@@ -33841,7 +34012,7 @@ async function listDirectoryContents(options) {
|
|
|
33841
34012
|
const collected = [];
|
|
33842
34013
|
for (const dirent of dirents) {
|
|
33843
34014
|
if (!includeHidden && dirent.name.startsWith(".")) continue;
|
|
33844
|
-
const childPath =
|
|
34015
|
+
const childPath = path17.join(resolvedPath, dirent.name);
|
|
33845
34016
|
const kind = await classifyEntry(dirent, childPath);
|
|
33846
34017
|
if (!kind) continue;
|
|
33847
34018
|
if (kind === "file" && !includeFiles) continue;
|
|
@@ -33849,7 +34020,7 @@ async function listDirectoryContents(options) {
|
|
|
33849
34020
|
if (collected.length >= limit) break;
|
|
33850
34021
|
}
|
|
33851
34022
|
collected.sort(compareEntries);
|
|
33852
|
-
const parent =
|
|
34023
|
+
const parent = path17.dirname(resolvedPath);
|
|
33853
34024
|
return {
|
|
33854
34025
|
path: resolvedPath,
|
|
33855
34026
|
parent: parent === resolvedPath ? null : parent,
|
|
@@ -33860,12 +34031,12 @@ async function resolveAbsolutePath(rawPath) {
|
|
|
33860
34031
|
const home = process.env.HOME ?? homedir3();
|
|
33861
34032
|
const trimmed = rawPath.trim();
|
|
33862
34033
|
if (trimmed === "" || trimmed === "~") {
|
|
33863
|
-
return
|
|
34034
|
+
return path17.resolve(home);
|
|
33864
34035
|
}
|
|
33865
34036
|
if (trimmed.startsWith("~/")) {
|
|
33866
|
-
return
|
|
34037
|
+
return path17.resolve(home, trimmed.slice(2));
|
|
33867
34038
|
}
|
|
33868
|
-
if (!
|
|
34039
|
+
if (!path17.isAbsolute(trimmed)) {
|
|
33869
34040
|
throw new Error(
|
|
33870
34041
|
`list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
|
|
33871
34042
|
);
|
|
@@ -33873,7 +34044,7 @@ async function resolveAbsolutePath(rawPath) {
|
|
|
33873
34044
|
try {
|
|
33874
34045
|
return await realpath2(trimmed);
|
|
33875
34046
|
} catch {
|
|
33876
|
-
return
|
|
34047
|
+
return path17.resolve(trimmed);
|
|
33877
34048
|
}
|
|
33878
34049
|
}
|
|
33879
34050
|
async function classifyEntry(dirent, fullPath) {
|
|
@@ -33913,10 +34084,10 @@ function resolveClientMessageId(clientMessageId, generateId = uuidv45) {
|
|
|
33913
34084
|
}
|
|
33914
34085
|
|
|
33915
34086
|
// ../server/src/server/chat/chat-service.ts
|
|
33916
|
-
import { z as
|
|
33917
|
-
var ChatStorePayloadSchema =
|
|
33918
|
-
rooms:
|
|
33919
|
-
messages:
|
|
34087
|
+
import { z as z35 } from "zod";
|
|
34088
|
+
var ChatStorePayloadSchema = z35.object({
|
|
34089
|
+
rooms: z35.array(ChatRoomSchema),
|
|
34090
|
+
messages: z35.array(ChatMessageSchema)
|
|
33920
34091
|
});
|
|
33921
34092
|
var ChatServiceError = class extends Error {
|
|
33922
34093
|
constructor(code, message) {
|
|
@@ -34007,33 +34178,33 @@ function buildChatMentionNotification(input) {
|
|
|
34007
34178
|
|
|
34008
34179
|
// ../server/src/server/roles/scanner.ts
|
|
34009
34180
|
import fs10 from "node:fs/promises";
|
|
34010
|
-
import
|
|
34011
|
-
import
|
|
34181
|
+
import os8 from "node:os";
|
|
34182
|
+
import path18 from "node:path";
|
|
34012
34183
|
var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
34013
34184
|
function homeDir2() {
|
|
34014
|
-
return process.env.HOME ||
|
|
34185
|
+
return process.env.HOME || os8.homedir();
|
|
34015
34186
|
}
|
|
34016
34187
|
function resolveScopeDir2(scope, workspaceRoot) {
|
|
34017
34188
|
if (scope === "project") {
|
|
34018
34189
|
if (!workspaceRoot) {
|
|
34019
34190
|
throw new Error('workspaceRoot is required for scope "project"');
|
|
34020
34191
|
}
|
|
34021
|
-
return
|
|
34192
|
+
return path18.join(workspaceRoot, ".roles");
|
|
34022
34193
|
}
|
|
34023
|
-
return
|
|
34194
|
+
return path18.join(homeDir2(), ".appostle", ".roles");
|
|
34024
34195
|
}
|
|
34025
34196
|
function allowedRoots2(workspaceRoot) {
|
|
34026
|
-
const roots = [
|
|
34197
|
+
const roots = [path18.join(homeDir2(), ".appostle", ".roles")];
|
|
34027
34198
|
if (workspaceRoot) {
|
|
34028
|
-
roots.push(
|
|
34199
|
+
roots.push(path18.join(workspaceRoot, ".roles"));
|
|
34029
34200
|
}
|
|
34030
|
-
return roots.map((r) =>
|
|
34201
|
+
return roots.map((r) => path18.resolve(r));
|
|
34031
34202
|
}
|
|
34032
34203
|
function isInsideAllowedRoot2(absPath, workspaceRoot) {
|
|
34033
|
-
const resolved =
|
|
34204
|
+
const resolved = path18.resolve(absPath);
|
|
34034
34205
|
for (const root of allowedRoots2(workspaceRoot)) {
|
|
34035
|
-
const rel =
|
|
34036
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
34206
|
+
const rel = path18.relative(root, resolved);
|
|
34207
|
+
if (rel === "" || !rel.startsWith("..") && !path18.isAbsolute(rel)) {
|
|
34037
34208
|
return true;
|
|
34038
34209
|
}
|
|
34039
34210
|
}
|
|
@@ -34231,7 +34402,7 @@ async function readRolesFromDir(scope, dir, category) {
|
|
|
34231
34402
|
if (!entry.name.endsWith(".md")) continue;
|
|
34232
34403
|
const name = entry.name.slice(0, -".md".length);
|
|
34233
34404
|
if (!name || !NAME_REGEX2.test(name)) continue;
|
|
34234
|
-
const fullPath =
|
|
34405
|
+
const fullPath = path18.join(dir, entry.name);
|
|
34235
34406
|
let stat5;
|
|
34236
34407
|
try {
|
|
34237
34408
|
const s = await fs10.stat(fullPath);
|
|
@@ -34281,7 +34452,7 @@ async function readRolesFromScopeDir(scope, scopeDir) {
|
|
|
34281
34452
|
}
|
|
34282
34453
|
const flat = await readRolesFromDir(scope, scopeDir, null);
|
|
34283
34454
|
const categoryResults = await Promise.all(
|
|
34284
|
-
topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope,
|
|
34455
|
+
topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path18.join(scopeDir, e.name), e.name))
|
|
34285
34456
|
);
|
|
34286
34457
|
const all = [...flat, ...categoryResults.flat()];
|
|
34287
34458
|
all.sort((a, b) => {
|
|
@@ -34321,9 +34492,9 @@ async function createRole(args) {
|
|
|
34321
34492
|
throw new Error(`Role name must not contain path separators or "..".`);
|
|
34322
34493
|
}
|
|
34323
34494
|
const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
|
|
34324
|
-
const dir = args.category ?
|
|
34495
|
+
const dir = args.category ? path18.join(scopeDir, args.category) : scopeDir;
|
|
34325
34496
|
await fs10.mkdir(dir, { recursive: true });
|
|
34326
|
-
const filePath =
|
|
34497
|
+
const filePath = path18.join(dir, `${args.name}.md`);
|
|
34327
34498
|
try {
|
|
34328
34499
|
await fs10.access(filePath);
|
|
34329
34500
|
throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
|
|
@@ -34357,7 +34528,7 @@ the role is invoked.
|
|
|
34357
34528
|
`;
|
|
34358
34529
|
}
|
|
34359
34530
|
async function writeRoleFrontmatter(args, workspaceRoot) {
|
|
34360
|
-
if (!
|
|
34531
|
+
if (!path18.isAbsolute(args.path)) {
|
|
34361
34532
|
throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
|
|
34362
34533
|
}
|
|
34363
34534
|
if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
|
|
@@ -34381,20 +34552,20 @@ ${original}`;
|
|
|
34381
34552
|
await fs10.writeFile(args.path, nextContent, "utf8");
|
|
34382
34553
|
}
|
|
34383
34554
|
async function moveRole(args, workspaceRoot) {
|
|
34384
|
-
if (!
|
|
34555
|
+
if (!path18.isAbsolute(args.path)) {
|
|
34385
34556
|
throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
|
|
34386
34557
|
}
|
|
34387
34558
|
if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
|
|
34388
34559
|
throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
|
|
34389
34560
|
}
|
|
34390
|
-
const oldDir =
|
|
34391
|
-
const oldFilename =
|
|
34561
|
+
const oldDir = path18.dirname(args.path);
|
|
34562
|
+
const oldFilename = path18.basename(args.path, ".md");
|
|
34392
34563
|
const roots = allowedRoots2(workspaceRoot);
|
|
34393
|
-
const rolesRoot = roots.find((r) =>
|
|
34564
|
+
const rolesRoot = roots.find((r) => path18.resolve(args.path).startsWith(r));
|
|
34394
34565
|
if (!rolesRoot) {
|
|
34395
34566
|
throw new Error(`Cannot determine roles root for "${args.path}"`);
|
|
34396
34567
|
}
|
|
34397
|
-
const relFromRoot =
|
|
34568
|
+
const relFromRoot = path18.relative(rolesRoot, path18.dirname(args.path));
|
|
34398
34569
|
const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
|
|
34399
34570
|
const newName = args.newName ?? oldFilename;
|
|
34400
34571
|
const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
|
|
@@ -34404,9 +34575,9 @@ async function moveRole(args, workspaceRoot) {
|
|
|
34404
34575
|
if (newCategory && !NAME_REGEX2.test(newCategory)) {
|
|
34405
34576
|
throw new Error(`Invalid category name: "${newCategory}"`);
|
|
34406
34577
|
}
|
|
34407
|
-
const newDir = newCategory ?
|
|
34408
|
-
const newPath =
|
|
34409
|
-
if (
|
|
34578
|
+
const newDir = newCategory ? path18.join(rolesRoot, newCategory) : rolesRoot;
|
|
34579
|
+
const newPath = path18.join(newDir, `${newName}.md`);
|
|
34580
|
+
if (path18.resolve(newPath) === path18.resolve(args.path)) {
|
|
34410
34581
|
return { path: args.path };
|
|
34411
34582
|
}
|
|
34412
34583
|
await fs10.mkdir(newDir, { recursive: true });
|
|
@@ -34444,17 +34615,17 @@ async function moveRole(args, workspaceRoot) {
|
|
|
34444
34615
|
|
|
34445
34616
|
// ../server/src/server/brands/scanner.ts
|
|
34446
34617
|
import fs11 from "node:fs/promises";
|
|
34447
|
-
import
|
|
34618
|
+
import path19 from "node:path";
|
|
34448
34619
|
var CATEGORY_REGEX = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
34449
34620
|
function resolveAssetsDir(workspaceRoot, category) {
|
|
34450
|
-
const base =
|
|
34621
|
+
const base = path19.join(workspaceRoot, ".appostle", "brand", "assets");
|
|
34451
34622
|
if (!category) return base;
|
|
34452
34623
|
if (!CATEGORY_REGEX.test(category) || category.includes("..")) {
|
|
34453
34624
|
throw new Error(
|
|
34454
34625
|
`Invalid asset category "${category}". Use letters, digits, dot, underscore, dash.`
|
|
34455
34626
|
);
|
|
34456
34627
|
}
|
|
34457
|
-
return
|
|
34628
|
+
return path19.join(base, category);
|
|
34458
34629
|
}
|
|
34459
34630
|
function relativePathFor(category, fileName) {
|
|
34460
34631
|
return category ? `assets/${category}/${fileName}` : `assets/${fileName}`;
|
|
@@ -34470,9 +34641,9 @@ async function removeSiblingExtensions(dir, targetName, keepFileName) {
|
|
|
34470
34641
|
if (entry === keepFileName) continue;
|
|
34471
34642
|
if (!entry.startsWith(`${targetName}.`)) continue;
|
|
34472
34643
|
try {
|
|
34473
|
-
const stat5 = await fs11.stat(
|
|
34644
|
+
const stat5 = await fs11.stat(path19.join(dir, entry));
|
|
34474
34645
|
if (!stat5.isFile()) continue;
|
|
34475
|
-
await fs11.unlink(
|
|
34646
|
+
await fs11.unlink(path19.join(dir, entry));
|
|
34476
34647
|
} catch {
|
|
34477
34648
|
}
|
|
34478
34649
|
}
|
|
@@ -34483,7 +34654,7 @@ function convertSvgToMono(svg, color) {
|
|
|
34483
34654
|
async function runDerivations(options) {
|
|
34484
34655
|
const { primaryAbsolutePath, assetsDir, category, derive } = options;
|
|
34485
34656
|
if (derive.length === 0) return [];
|
|
34486
|
-
const ext =
|
|
34657
|
+
const ext = path19.extname(primaryAbsolutePath).toLowerCase();
|
|
34487
34658
|
const isSvg = ext === ".svg";
|
|
34488
34659
|
const results = [];
|
|
34489
34660
|
for (const spec of derive) {
|
|
@@ -34518,9 +34689,9 @@ async function runDerivations(options) {
|
|
|
34518
34689
|
const sourceText = await fs11.readFile(primaryAbsolutePath, "utf8");
|
|
34519
34690
|
const monoText = convertSvgToMono(sourceText, spec.color);
|
|
34520
34691
|
const fileName = `${spec.targetName}${ext}`;
|
|
34521
|
-
const destAbs =
|
|
34522
|
-
const rel =
|
|
34523
|
-
if (rel.startsWith("..") ||
|
|
34692
|
+
const destAbs = path19.resolve(assetsDir, fileName);
|
|
34693
|
+
const rel = path19.relative(path19.resolve(assetsDir), destAbs);
|
|
34694
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) {
|
|
34524
34695
|
results.push({
|
|
34525
34696
|
targetName: spec.targetName,
|
|
34526
34697
|
relativePath: "",
|
|
@@ -34554,22 +34725,22 @@ function resolveScopeDir3(scope, workspaceRoot) {
|
|
|
34554
34725
|
if (!workspaceRoot) {
|
|
34555
34726
|
throw new Error('workspaceRoot is required for scope "project"');
|
|
34556
34727
|
}
|
|
34557
|
-
return
|
|
34728
|
+
return path19.join(workspaceRoot, ".appostle", "brand");
|
|
34558
34729
|
}
|
|
34559
34730
|
throw new Error(`Unknown scope: ${scope}`);
|
|
34560
34731
|
}
|
|
34561
34732
|
function allowedRoots3(workspaceRoot) {
|
|
34562
34733
|
const roots = [];
|
|
34563
34734
|
if (workspaceRoot) {
|
|
34564
|
-
roots.push(
|
|
34735
|
+
roots.push(path19.join(workspaceRoot, ".appostle", "brand"));
|
|
34565
34736
|
}
|
|
34566
|
-
return roots.map((r) =>
|
|
34737
|
+
return roots.map((r) => path19.resolve(r));
|
|
34567
34738
|
}
|
|
34568
34739
|
function isInsideAllowedRoot3(absPath, workspaceRoot) {
|
|
34569
|
-
const resolved =
|
|
34740
|
+
const resolved = path19.resolve(absPath);
|
|
34570
34741
|
for (const root of allowedRoots3(workspaceRoot)) {
|
|
34571
|
-
const rel =
|
|
34572
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
34742
|
+
const rel = path19.relative(root, resolved);
|
|
34743
|
+
if (rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel)) {
|
|
34573
34744
|
return true;
|
|
34574
34745
|
}
|
|
34575
34746
|
}
|
|
@@ -34783,7 +34954,7 @@ async function readBrandsFromDir(scope, dir) {
|
|
|
34783
34954
|
if (!entry.name.endsWith(".md")) continue;
|
|
34784
34955
|
const name = entry.name.slice(0, -".md".length);
|
|
34785
34956
|
if (!name || !NAME_REGEX3.test(name)) continue;
|
|
34786
|
-
const fullPath =
|
|
34957
|
+
const fullPath = path19.join(dir, entry.name);
|
|
34787
34958
|
let stat5;
|
|
34788
34959
|
try {
|
|
34789
34960
|
const s = await fs11.stat(fullPath);
|
|
@@ -34827,7 +34998,7 @@ async function createBrand(args) {
|
|
|
34827
34998
|
}
|
|
34828
34999
|
const dir = resolveScopeDir3("project", args.workspaceRoot);
|
|
34829
35000
|
await fs11.mkdir(dir, { recursive: true });
|
|
34830
|
-
const filePath =
|
|
35001
|
+
const filePath = path19.join(dir, `${args.name}.md`);
|
|
34831
35002
|
try {
|
|
34832
35003
|
await fs11.access(filePath);
|
|
34833
35004
|
throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
|
|
@@ -34883,7 +35054,7 @@ async function copyBrandAsset(args) {
|
|
|
34883
35054
|
if (!args.workspaceRoot) {
|
|
34884
35055
|
throw new Error("workspaceRoot is required to copy a brand asset");
|
|
34885
35056
|
}
|
|
34886
|
-
if (!args.sourcePath || !
|
|
35057
|
+
if (!args.sourcePath || !path19.isAbsolute(args.sourcePath)) {
|
|
34887
35058
|
throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
|
|
34888
35059
|
}
|
|
34889
35060
|
if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
|
|
@@ -34895,12 +35066,12 @@ async function copyBrandAsset(args) {
|
|
|
34895
35066
|
if (!stats.isFile()) {
|
|
34896
35067
|
throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
|
|
34897
35068
|
}
|
|
34898
|
-
const ext =
|
|
35069
|
+
const ext = path19.extname(args.sourcePath).toLowerCase();
|
|
34899
35070
|
const fileName = `${args.targetName}${ext}`;
|
|
34900
35071
|
const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
|
|
34901
|
-
const destAbs =
|
|
34902
|
-
const rel =
|
|
34903
|
-
if (rel.startsWith("..") ||
|
|
35072
|
+
const destAbs = path19.resolve(assetsDir, fileName);
|
|
35073
|
+
const rel = path19.relative(path19.resolve(assetsDir), destAbs);
|
|
35074
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) {
|
|
34904
35075
|
throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
|
|
34905
35076
|
}
|
|
34906
35077
|
await fs11.mkdir(assetsDir, { recursive: true });
|
|
@@ -34930,13 +35101,13 @@ async function uploadBrandAsset(args) {
|
|
|
34930
35101
|
if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
|
|
34931
35102
|
throw new Error("No file data provided for brand asset upload");
|
|
34932
35103
|
}
|
|
34933
|
-
const extFromSource = args.sourceName ?
|
|
35104
|
+
const extFromSource = args.sourceName ? path19.extname(args.sourceName).toLowerCase() : "";
|
|
34934
35105
|
const ext = extFromSource || ".png";
|
|
34935
35106
|
const fileName = `${args.targetName}${ext}`;
|
|
34936
35107
|
const assetsDir = resolveAssetsDir(args.workspaceRoot, args.category);
|
|
34937
|
-
const destAbs =
|
|
34938
|
-
const rel =
|
|
34939
|
-
if (rel.startsWith("..") ||
|
|
35108
|
+
const destAbs = path19.resolve(assetsDir, fileName);
|
|
35109
|
+
const rel = path19.relative(path19.resolve(assetsDir), destAbs);
|
|
35110
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) {
|
|
34940
35111
|
throw new Error(`Refusing to write outside of .appostle/brand/assets: ${destAbs}`);
|
|
34941
35112
|
}
|
|
34942
35113
|
let data;
|
|
@@ -34964,7 +35135,7 @@ async function uploadBrandAsset(args) {
|
|
|
34964
35135
|
};
|
|
34965
35136
|
}
|
|
34966
35137
|
async function writeBrandFrontmatter(args, workspaceRoot) {
|
|
34967
|
-
if (!
|
|
35138
|
+
if (!path19.isAbsolute(args.path)) {
|
|
34968
35139
|
throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
|
|
34969
35140
|
}
|
|
34970
35141
|
if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
|
|
@@ -34989,10 +35160,10 @@ ${original}`;
|
|
|
34989
35160
|
}
|
|
34990
35161
|
|
|
34991
35162
|
// ../server/src/server/brand/token-generator.ts
|
|
34992
|
-
import { z as
|
|
35163
|
+
import { z as z36 } from "zod";
|
|
34993
35164
|
var HEX6 = /^#[0-9a-f]{6}$/i;
|
|
34994
|
-
var TokensResponseSchema =
|
|
34995
|
-
tokens:
|
|
35165
|
+
var TokensResponseSchema = z36.object({
|
|
35166
|
+
tokens: z36.record(z36.string(), z36.string())
|
|
34996
35167
|
});
|
|
34997
35168
|
function buildPrompt2(args) {
|
|
34998
35169
|
const baseColours = args.paletteVars.filter((v) => v.type === "color");
|
|
@@ -35210,21 +35381,21 @@ async function generateAndApplyBrandTokens(options) {
|
|
|
35210
35381
|
|
|
35211
35382
|
// ../server/src/server/brand/art-direction-generator.ts
|
|
35212
35383
|
import { promises as fs12 } from "node:fs";
|
|
35213
|
-
import
|
|
35384
|
+
import path20 from "node:path";
|
|
35214
35385
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
35215
|
-
import { z as
|
|
35386
|
+
import { z as z37 } from "zod";
|
|
35216
35387
|
var PROMPT_FILENAME = "art-direction-prompt.md";
|
|
35217
35388
|
var MAX_LOOKUP_LEVELS = 10;
|
|
35218
35389
|
async function findPromptFile() {
|
|
35219
|
-
let dir =
|
|
35390
|
+
let dir = path20.dirname(fileURLToPath2(import.meta.url));
|
|
35220
35391
|
for (let i = 0; i < MAX_LOOKUP_LEVELS; i++) {
|
|
35221
|
-
const candidate =
|
|
35392
|
+
const candidate = path20.join(dir, PROMPT_FILENAME);
|
|
35222
35393
|
try {
|
|
35223
35394
|
await fs12.access(candidate);
|
|
35224
35395
|
return candidate;
|
|
35225
35396
|
} catch {
|
|
35226
35397
|
}
|
|
35227
|
-
const parent =
|
|
35398
|
+
const parent = path20.dirname(dir);
|
|
35228
35399
|
if (parent === dir) break;
|
|
35229
35400
|
dir = parent;
|
|
35230
35401
|
}
|
|
@@ -35257,8 +35428,8 @@ var EMBEDDED_PROMPT_FALLBACK = [
|
|
|
35257
35428
|
"",
|
|
35258
35429
|
'Return ONLY JSON: { "fields": { "art-direction.intent": "...", ... } }'
|
|
35259
35430
|
].join("\n");
|
|
35260
|
-
var ArtDirectionResponseSchema =
|
|
35261
|
-
fields:
|
|
35431
|
+
var ArtDirectionResponseSchema = z37.object({
|
|
35432
|
+
fields: z37.record(z37.string(), z37.string())
|
|
35262
35433
|
});
|
|
35263
35434
|
var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
35264
35435
|
"art-direction.intent",
|
|
@@ -35413,45 +35584,6 @@ async function generateAndApplyArtDirection(options) {
|
|
|
35413
35584
|
return { generatedCount: acceptedUpdates.size };
|
|
35414
35585
|
}
|
|
35415
35586
|
|
|
35416
|
-
// ../server/src/server/claude-profile.ts
|
|
35417
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, rmSync as rmSync2 } from "node:fs";
|
|
35418
|
-
import path20 from "node:path";
|
|
35419
|
-
import os8 from "node:os";
|
|
35420
|
-
var SHARED_ITEMS = ["settings.json", "hooks", "agents", "skills", "plugins", "keybindings.json"];
|
|
35421
|
-
function getClaudeProfileDir(username) {
|
|
35422
|
-
return path20.join(os8.homedir(), `.claude-${username}`);
|
|
35423
|
-
}
|
|
35424
|
-
function ensureClaudeProfile(username, logger) {
|
|
35425
|
-
const profileDir = getClaudeProfileDir(username);
|
|
35426
|
-
const ownerDir = path20.join(os8.homedir(), ".claude");
|
|
35427
|
-
if (!existsSync10(ownerDir)) {
|
|
35428
|
-
throw new Error(`Owner claude config dir not found: ${ownerDir}`);
|
|
35429
|
-
}
|
|
35430
|
-
if (!existsSync10(profileDir)) {
|
|
35431
|
-
mkdirSync5(profileDir, { recursive: true });
|
|
35432
|
-
logger?.info({ profileDir, username }, "created claude profile directory");
|
|
35433
|
-
}
|
|
35434
|
-
for (const item of SHARED_ITEMS) {
|
|
35435
|
-
const target = path20.join(ownerDir, item);
|
|
35436
|
-
const link = path20.join(profileDir, item);
|
|
35437
|
-
if (!existsSync10(target)) continue;
|
|
35438
|
-
if (existsSync10(link)) continue;
|
|
35439
|
-
symlinkSync(target, link);
|
|
35440
|
-
logger?.info({ item, profileDir }, "symlinked shared config item");
|
|
35441
|
-
}
|
|
35442
|
-
return profileDir;
|
|
35443
|
-
}
|
|
35444
|
-
function hasClaudeAuth(username) {
|
|
35445
|
-
const profileDir = getClaudeProfileDir(username);
|
|
35446
|
-
return existsSync10(profileDir);
|
|
35447
|
-
}
|
|
35448
|
-
function removeClaudeProfile(username, logger) {
|
|
35449
|
-
const profileDir = getClaudeProfileDir(username);
|
|
35450
|
-
if (!existsSync10(profileDir)) return;
|
|
35451
|
-
rmSync2(profileDir, { recursive: true, force: true });
|
|
35452
|
-
logger?.info({ profileDir, username }, "removed claude profile directory");
|
|
35453
|
-
}
|
|
35454
|
-
|
|
35455
35587
|
// ../server/src/services/oauth-service.ts
|
|
35456
35588
|
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
35457
35589
|
import { mkdir as mkdir4, readFile as readFile3, rename, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
@@ -36843,7 +36975,7 @@ var MIN_STREAMING_SEGMENT_DURATION_MS = 1e3;
|
|
|
36843
36975
|
var MIN_STREAMING_SEGMENT_BYTES = Math.round(
|
|
36844
36976
|
PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS
|
|
36845
36977
|
);
|
|
36846
|
-
var
|
|
36978
|
+
var AgentIdSchema2 = z38.string().uuid();
|
|
36847
36979
|
var VOICE_INTERRUPT_CONFIRMATION_MS = 500;
|
|
36848
36980
|
var VoiceFeatureUnavailableError = class extends Error {
|
|
36849
36981
|
constructor(context) {
|
|
@@ -36949,6 +37081,8 @@ var Session = class _Session {
|
|
|
36949
37081
|
const {
|
|
36950
37082
|
clientId,
|
|
36951
37083
|
appVersion,
|
|
37084
|
+
peerPublicKeyB64,
|
|
37085
|
+
ownerUserId,
|
|
36952
37086
|
onMessage,
|
|
36953
37087
|
onBinaryMessage,
|
|
36954
37088
|
onLifecycleIntent,
|
|
@@ -37011,6 +37145,8 @@ var Session = class _Session {
|
|
|
37011
37145
|
});
|
|
37012
37146
|
this.agentManager = agentManager;
|
|
37013
37147
|
this.agentStorage = agentStorage;
|
|
37148
|
+
this.peerPublicKeyB64 = peerPublicKeyB64 ?? null;
|
|
37149
|
+
this.ownerUserId = ownerUserId ?? null;
|
|
37014
37150
|
this.uploadStore = new SessionUploadStore({ logger: this.sessionLogger });
|
|
37015
37151
|
this.imageStore = new SessionImageStore({ logger: this.sessionLogger });
|
|
37016
37152
|
this.projectRegistry = projectRegistry;
|
|
@@ -37085,7 +37221,43 @@ var Session = class _Session {
|
|
|
37085
37221
|
});
|
|
37086
37222
|
void this.initializeAgentMcp();
|
|
37087
37223
|
this.subscribeToAgentEvents();
|
|
37088
|
-
this.sessionLogger.trace(
|
|
37224
|
+
this.sessionLogger.trace(
|
|
37225
|
+
{
|
|
37226
|
+
hasPeerPubkey: this.peerPublicKeyB64 !== null,
|
|
37227
|
+
// Log a fingerprint, not the full key, so the audit log stays useful
|
|
37228
|
+
// without bloating every entry with 44-char base64. First 8 chars is
|
|
37229
|
+
// unique enough for human cross-reference.
|
|
37230
|
+
peerPubkeyPrefix: this.peerPublicKeyB64 ? this.peerPublicKeyB64.slice(0, 8) : null,
|
|
37231
|
+
ownerUserId: this.ownerUserId
|
|
37232
|
+
},
|
|
37233
|
+
"Session created"
|
|
37234
|
+
);
|
|
37235
|
+
}
|
|
37236
|
+
/**
|
|
37237
|
+
* Peer device's e2ee pubkey (base64). Surfaced so the upcoming user-scope
|
|
37238
|
+
* lookup can resolve it via the auth-server allow-list. Null when unknown
|
|
37239
|
+
* (local TCP, legacy paths).
|
|
37240
|
+
*/
|
|
37241
|
+
getPeerPublicKeyB64() {
|
|
37242
|
+
return this.peerPublicKeyB64;
|
|
37243
|
+
}
|
|
37244
|
+
/**
|
|
37245
|
+
* Auth-server user-id that owns the connecting device. Null when the
|
|
37246
|
+
* daemon has no auth-server linkage, when the peer pubkey is unknown
|
|
37247
|
+
* (local TCP), or when the resolver returned no match (allow-list entry
|
|
37248
|
+
* for this pubkey hasn't been seen yet). Callers use this for per-user
|
|
37249
|
+
* agent filtering; null means "show everything" (single-tenant fallback).
|
|
37250
|
+
*/
|
|
37251
|
+
getOwnerUserId() {
|
|
37252
|
+
return this.ownerUserId;
|
|
37253
|
+
}
|
|
37254
|
+
/**
|
|
37255
|
+
* Whether this session is bound to a specific account. Equivalent to
|
|
37256
|
+
* `getOwnerUserId() !== null` but reads cleaner at call sites that gate
|
|
37257
|
+
* on "should we apply per-user filtering at all?".
|
|
37258
|
+
*/
|
|
37259
|
+
hasOwnerUserId() {
|
|
37260
|
+
return this.ownerUserId !== null;
|
|
37089
37261
|
}
|
|
37090
37262
|
updateAppVersion(appVersion) {
|
|
37091
37263
|
if (appVersion && appVersion !== this.appVersion) {
|
|
@@ -37330,6 +37502,9 @@ var Session = class _Session {
|
|
|
37330
37502
|
);
|
|
37331
37503
|
});
|
|
37332
37504
|
}
|
|
37505
|
+
if (this.ownerUserId !== null && !this.agentManager.canUserAccessAgentById(event.agentId, this.ownerUserId)) {
|
|
37506
|
+
return;
|
|
37507
|
+
}
|
|
37333
37508
|
const activity = this.clientActivity;
|
|
37334
37509
|
if (activity?.deviceType === "mobile") {
|
|
37335
37510
|
if (!activity.focusedAgentId) {
|
|
@@ -37557,6 +37732,9 @@ var Session = class _Session {
|
|
|
37557
37732
|
}
|
|
37558
37733
|
async forwardAgentUpdate(agent) {
|
|
37559
37734
|
try {
|
|
37735
|
+
if (this.ownerUserId !== null && !this.agentManager.canUserAccessAgentById(agent.id, this.ownerUserId)) {
|
|
37736
|
+
return;
|
|
37737
|
+
}
|
|
37560
37738
|
const subscription = this.agentUpdatesSubscription;
|
|
37561
37739
|
const payload = await this.buildAgentPayload(agent);
|
|
37562
37740
|
if (subscription) {
|
|
@@ -37671,6 +37849,15 @@ var Session = class _Session {
|
|
|
37671
37849
|
case "delete_session_upload_request":
|
|
37672
37850
|
await this.handleDeleteSessionUploadRequest(msg);
|
|
37673
37851
|
break;
|
|
37852
|
+
case "share_agent_with_user_request":
|
|
37853
|
+
await this.handleShareAgentWithUserRequest(msg);
|
|
37854
|
+
break;
|
|
37855
|
+
case "unshare_agent_with_user_request":
|
|
37856
|
+
await this.handleUnshareAgentWithUserRequest(msg);
|
|
37857
|
+
break;
|
|
37858
|
+
case "list_agent_shared_users_request":
|
|
37859
|
+
await this.handleListAgentSharedUsersRequest(msg);
|
|
37860
|
+
break;
|
|
37674
37861
|
case "list_session_images_request":
|
|
37675
37862
|
await this.handleListSessionImagesRequest(msg);
|
|
37676
37863
|
break;
|
|
@@ -38969,7 +39156,7 @@ var Session = class _Session {
|
|
|
38969
39156
|
}
|
|
38970
39157
|
}
|
|
38971
39158
|
parseVoiceTargetAgentId(rawId, source) {
|
|
38972
|
-
const parsed =
|
|
39159
|
+
const parsed = AgentIdSchema2.safeParse(rawId.trim());
|
|
38973
39160
|
if (!parsed.success) {
|
|
38974
39161
|
throw new Error(`${source}: agentId must be a UUID`);
|
|
38975
39162
|
}
|
|
@@ -39298,6 +39485,12 @@ var Session = class _Session {
|
|
|
39298
39485
|
labels,
|
|
39299
39486
|
workspaceId: resolvedWorkspace.workspaceId,
|
|
39300
39487
|
initialPrompt: trimmedPrompt,
|
|
39488
|
+
// Stamp the agent with the user that created it. Null on single-
|
|
39489
|
+
// tenant daemons (no auth-server linkage) — agent stays globally
|
|
39490
|
+
// visible, preserving legacy behavior. This is the authoritative
|
|
39491
|
+
// owner (pubkey-derived), used for `canAccessAgent`.
|
|
39492
|
+
ownerUserId: this.ownerUserId,
|
|
39493
|
+
// Self-declared identity, used for Claude profile dir naming etc.
|
|
39301
39494
|
userId: this.userId ?? void 0,
|
|
39302
39495
|
username: this.username ?? void 0
|
|
39303
39496
|
}
|
|
@@ -39778,8 +39971,8 @@ var Session = class _Session {
|
|
|
39778
39971
|
}
|
|
39779
39972
|
async generateCommitMessage(cwd) {
|
|
39780
39973
|
const files = await listUncommittedFiles(cwd);
|
|
39781
|
-
const schema =
|
|
39782
|
-
message:
|
|
39974
|
+
const schema = z38.object({
|
|
39975
|
+
message: z38.string().min(1).max(100).describe(
|
|
39783
39976
|
"Short feature-level summary, lowercase, imperative mood, no trailing period, no filename references."
|
|
39784
39977
|
)
|
|
39785
39978
|
});
|
|
@@ -39824,9 +40017,9 @@ var Session = class _Session {
|
|
|
39824
40017
|
},
|
|
39825
40018
|
{ appostleHome: this.appostleHome }
|
|
39826
40019
|
);
|
|
39827
|
-
const schema =
|
|
39828
|
-
title:
|
|
39829
|
-
body:
|
|
40020
|
+
const schema = z38.object({
|
|
40021
|
+
title: z38.string().min(1).max(72),
|
|
40022
|
+
body: z38.string().min(1)
|
|
39830
40023
|
});
|
|
39831
40024
|
const fileList = diff.structured && diff.structured.length > 0 ? [
|
|
39832
40025
|
"Files changed:",
|
|
@@ -41866,13 +42059,22 @@ ${details}`.trim());
|
|
|
41866
42059
|
* Build the current agent list payload (live + persisted), optionally filtered by labels.
|
|
41867
42060
|
*/
|
|
41868
42061
|
async listAgentPayloads(filter) {
|
|
41869
|
-
const agentSnapshots = this.agentManager.
|
|
42062
|
+
const agentSnapshots = this.agentManager.listAgentsForUser(this.ownerUserId);
|
|
41870
42063
|
const liveAgents = await Promise.all(
|
|
41871
42064
|
agentSnapshots.map((agent) => this.buildAgentPayload(agent))
|
|
41872
42065
|
);
|
|
41873
42066
|
const registryRecords = await this.agentStorage.list();
|
|
42067
|
+
const requesterId = this.ownerUserId;
|
|
41874
42068
|
const liveIds = new Set(agentSnapshots.map((a) => a.id));
|
|
41875
|
-
const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id)).
|
|
42069
|
+
const persistedAgents = registryRecords.filter((record) => !liveIds.has(record.id)).filter(
|
|
42070
|
+
(record) => requesterId === null || canUserAccessAgent(
|
|
42071
|
+
{
|
|
42072
|
+
ownerUserId: record.ownerUserId ?? null,
|
|
42073
|
+
sharedWithUserIds: record.sharedWithUserIds
|
|
42074
|
+
},
|
|
42075
|
+
requesterId
|
|
42076
|
+
)
|
|
42077
|
+
).map((record) => this.buildStoredAgentPayload(record));
|
|
41876
42078
|
let agents = [...liveAgents, ...persistedAgents];
|
|
41877
42079
|
agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
|
|
41878
42080
|
if (filter?.labels) {
|
|
@@ -41889,6 +42091,9 @@ ${details}`.trim());
|
|
|
41889
42091
|
return { ok: false, error: "Agent identifier cannot be empty" };
|
|
41890
42092
|
}
|
|
41891
42093
|
if (this.agentManager.getAgent(trimmed)) {
|
|
42094
|
+
if (!this.agentManager.canUserAccessAgentById(trimmed, this.ownerUserId)) {
|
|
42095
|
+
return { ok: false, error: "Agent not found" };
|
|
42096
|
+
}
|
|
41892
42097
|
return { ok: true, agentId: trimmed };
|
|
41893
42098
|
}
|
|
41894
42099
|
const exactStored = await this.agentStorage.get(trimmed);
|
|
@@ -43492,6 +43697,139 @@ ${details}`.trim());
|
|
|
43492
43697
|
}
|
|
43493
43698
|
}
|
|
43494
43699
|
// ──────────────────────────────────────────────────────────────────────
|
|
43700
|
+
// Phase 4: agent sharing — owner-only mutations of the access ACL.
|
|
43701
|
+
// The visibility predicate (Phase 2c) already honors `sharedWithUserIds`;
|
|
43702
|
+
// these handlers surface a typed user-facing path so an owner can grant /
|
|
43703
|
+
// revoke / inspect access from the app instead of editing JSON on disk.
|
|
43704
|
+
//
|
|
43705
|
+
// Authorization:
|
|
43706
|
+
// - All three handlers require the session's `ownerUserId` to equal the
|
|
43707
|
+
// agent's `ownerUserId`. Anyone else gets `unauthorized` (including
|
|
43708
|
+
// existing share-recipients — only the owner can re-share).
|
|
43709
|
+
// - `resolveAgentIdentifier` already applies `canUserAccessAgent`, so a
|
|
43710
|
+
// non-recipient even sees the agent as "not found". The owner check
|
|
43711
|
+
// here is the strictly-tighter follow-up gate for mutations.
|
|
43712
|
+
// - On a single-tenant daemon (session.ownerUserId == null) the gate
|
|
43713
|
+
// opens — no isolation to enforce, sharing is a no-op for unscoped
|
|
43714
|
+
// agents (their ownerUserId is also null).
|
|
43715
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
43716
|
+
async handleShareAgentWithUserRequest(msg) {
|
|
43717
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
43718
|
+
if (!resolved.ok) {
|
|
43719
|
+
this.emit({
|
|
43720
|
+
type: "share_agent_with_user_response",
|
|
43721
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
43722
|
+
});
|
|
43723
|
+
return;
|
|
43724
|
+
}
|
|
43725
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
43726
|
+
if (!agent) {
|
|
43727
|
+
this.emit({
|
|
43728
|
+
type: "share_agent_with_user_response",
|
|
43729
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
43730
|
+
});
|
|
43731
|
+
return;
|
|
43732
|
+
}
|
|
43733
|
+
if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
|
|
43734
|
+
this.emit({
|
|
43735
|
+
type: "share_agent_with_user_response",
|
|
43736
|
+
payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
|
|
43737
|
+
});
|
|
43738
|
+
return;
|
|
43739
|
+
}
|
|
43740
|
+
try {
|
|
43741
|
+
const sharedWithUserIds = await this.agentManager.shareAgentWithUser(
|
|
43742
|
+
resolved.agentId,
|
|
43743
|
+
msg.userId
|
|
43744
|
+
);
|
|
43745
|
+
this.emit({
|
|
43746
|
+
type: "share_agent_with_user_response",
|
|
43747
|
+
payload: { requestId: msg.requestId, ok: true, sharedWithUserIds }
|
|
43748
|
+
});
|
|
43749
|
+
} catch (error) {
|
|
43750
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43751
|
+
this.emit({
|
|
43752
|
+
type: "share_agent_with_user_response",
|
|
43753
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
43754
|
+
});
|
|
43755
|
+
}
|
|
43756
|
+
}
|
|
43757
|
+
async handleUnshareAgentWithUserRequest(msg) {
|
|
43758
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
43759
|
+
if (!resolved.ok) {
|
|
43760
|
+
this.emit({
|
|
43761
|
+
type: "unshare_agent_with_user_response",
|
|
43762
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
43763
|
+
});
|
|
43764
|
+
return;
|
|
43765
|
+
}
|
|
43766
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
43767
|
+
if (!agent) {
|
|
43768
|
+
this.emit({
|
|
43769
|
+
type: "unshare_agent_with_user_response",
|
|
43770
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
43771
|
+
});
|
|
43772
|
+
return;
|
|
43773
|
+
}
|
|
43774
|
+
if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
|
|
43775
|
+
this.emit({
|
|
43776
|
+
type: "unshare_agent_with_user_response",
|
|
43777
|
+
payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
|
|
43778
|
+
});
|
|
43779
|
+
return;
|
|
43780
|
+
}
|
|
43781
|
+
try {
|
|
43782
|
+
const sharedWithUserIds = await this.agentManager.unshareAgentWithUser(
|
|
43783
|
+
resolved.agentId,
|
|
43784
|
+
msg.userId
|
|
43785
|
+
);
|
|
43786
|
+
this.emit({
|
|
43787
|
+
type: "unshare_agent_with_user_response",
|
|
43788
|
+
payload: { requestId: msg.requestId, ok: true, sharedWithUserIds }
|
|
43789
|
+
});
|
|
43790
|
+
} catch (error) {
|
|
43791
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43792
|
+
this.emit({
|
|
43793
|
+
type: "unshare_agent_with_user_response",
|
|
43794
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
43795
|
+
});
|
|
43796
|
+
}
|
|
43797
|
+
}
|
|
43798
|
+
async handleListAgentSharedUsersRequest(msg) {
|
|
43799
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
43800
|
+
if (!resolved.ok) {
|
|
43801
|
+
this.emit({
|
|
43802
|
+
type: "list_agent_shared_users_response",
|
|
43803
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
43804
|
+
});
|
|
43805
|
+
return;
|
|
43806
|
+
}
|
|
43807
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
43808
|
+
if (!agent) {
|
|
43809
|
+
this.emit({
|
|
43810
|
+
type: "list_agent_shared_users_response",
|
|
43811
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
43812
|
+
});
|
|
43813
|
+
return;
|
|
43814
|
+
}
|
|
43815
|
+
if (this.ownerUserId !== null && agent.ownerUserId !== null && agent.ownerUserId !== this.ownerUserId) {
|
|
43816
|
+
this.emit({
|
|
43817
|
+
type: "list_agent_shared_users_response",
|
|
43818
|
+
payload: { requestId: msg.requestId, ok: false, error: "unauthorized" }
|
|
43819
|
+
});
|
|
43820
|
+
return;
|
|
43821
|
+
}
|
|
43822
|
+
this.emit({
|
|
43823
|
+
type: "list_agent_shared_users_response",
|
|
43824
|
+
payload: {
|
|
43825
|
+
requestId: msg.requestId,
|
|
43826
|
+
ok: true,
|
|
43827
|
+
ownerUserId: agent.ownerUserId,
|
|
43828
|
+
sharedWithUserIds: [...agent.sharedWithUserIds]
|
|
43829
|
+
}
|
|
43830
|
+
});
|
|
43831
|
+
}
|
|
43832
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
43495
43833
|
// Session images — backs the "Images" tab inside the uploads modal. Same
|
|
43496
43834
|
// shape as the file handlers above; the store has no manifest, so listing
|
|
43497
43835
|
// is a directory scan and delete is `unlink` (traversal-guarded).
|
|
@@ -45666,7 +46004,7 @@ import webpush from "web-push";
|
|
|
45666
46004
|
import webpush2 from "web-push";
|
|
45667
46005
|
|
|
45668
46006
|
// ../server/src/server/speech/providers/local/sherpa/model-catalog.ts
|
|
45669
|
-
import { z as
|
|
46007
|
+
import { z as z39 } from "zod";
|
|
45670
46008
|
var SHERPA_ONNX_MODEL_CATALOG = {
|
|
45671
46009
|
"zipformer-bilingual-zh-en-2023-02-20": {
|
|
45672
46010
|
kind: "stt-online",
|
|
@@ -45759,7 +46097,7 @@ function buildAliasMap(modelIds) {
|
|
|
45759
46097
|
}
|
|
45760
46098
|
function createAliasedModelIdSchema(params) {
|
|
45761
46099
|
const validIds = new Set(params.modelIds);
|
|
45762
|
-
return
|
|
46100
|
+
return z39.string().trim().toLowerCase().refine(
|
|
45763
46101
|
(value) => validIds.has(value) || Object.prototype.hasOwnProperty.call(params.aliases, value),
|
|
45764
46102
|
{
|
|
45765
46103
|
message: "Invalid model id"
|
|
@@ -45790,20 +46128,20 @@ import { v4 as uuidv410 } from "uuid";
|
|
|
45790
46128
|
import { v4 as uuidv411 } from "uuid";
|
|
45791
46129
|
|
|
45792
46130
|
// ../server/src/server/speech/providers/openai/config.ts
|
|
45793
|
-
import { z as
|
|
46131
|
+
import { z as z40 } from "zod";
|
|
45794
46132
|
var DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL = "gpt-4o-transcribe";
|
|
45795
46133
|
var DEFAULT_OPENAI_TTS_MODEL = "tts-1";
|
|
45796
|
-
var OpenAiTtsVoiceSchema =
|
|
45797
|
-
var OpenAiTtsModelSchema =
|
|
45798
|
-
var NumberLikeSchema =
|
|
45799
|
-
var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(
|
|
45800
|
-
var OptionalTrimmedStringSchema =
|
|
45801
|
-
var OpenAiSpeechResolutionSchema =
|
|
46134
|
+
var OpenAiTtsVoiceSchema = z40.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
|
|
46135
|
+
var OpenAiTtsModelSchema = z40.enum(["tts-1", "tts-1-hd"]);
|
|
46136
|
+
var NumberLikeSchema = z40.union([z40.number(), z40.string().trim().min(1)]);
|
|
46137
|
+
var OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z40.coerce.number().finite()).optional();
|
|
46138
|
+
var OptionalTrimmedStringSchema = z40.string().trim().optional().transform((value) => value && value.length > 0 ? value : void 0);
|
|
46139
|
+
var OpenAiSpeechResolutionSchema = z40.object({
|
|
45802
46140
|
apiKey: OptionalTrimmedStringSchema,
|
|
45803
46141
|
sttConfidenceThreshold: OptionalFiniteNumberSchema,
|
|
45804
46142
|
sttModel: OptionalTrimmedStringSchema,
|
|
45805
|
-
ttsVoice:
|
|
45806
|
-
ttsModel:
|
|
46143
|
+
ttsVoice: z40.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
|
|
46144
|
+
ttsModel: z40.string().trim().toLowerCase().pipe(OpenAiTtsModelSchema).default(DEFAULT_OPENAI_TTS_MODEL),
|
|
45807
46145
|
realtimeTranscriptionModel: OptionalTrimmedStringSchema.default(
|
|
45808
46146
|
DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL
|
|
45809
46147
|
)
|
|
@@ -45847,17 +46185,6 @@ import { v4 } from "uuid";
|
|
|
45847
46185
|
// ../server/src/server/speech/providers/openai/tts.ts
|
|
45848
46186
|
import OpenAI2 from "openai";
|
|
45849
46187
|
|
|
45850
|
-
// ../server/src/server/agent/agent-manager.ts
|
|
45851
|
-
import { z as z40 } from "zod";
|
|
45852
|
-
import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
|
|
45853
|
-
|
|
45854
|
-
// ../server/src/server/agent/handoff-mcp.ts
|
|
45855
|
-
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
45856
|
-
import { z as z39 } from "zod";
|
|
45857
|
-
|
|
45858
|
-
// ../server/src/server/agent/agent-manager.ts
|
|
45859
|
-
var AgentIdSchema2 = z40.string().uuid();
|
|
45860
|
-
|
|
45861
46188
|
// ../server/src/server/agent/agent-storage.ts
|
|
45862
46189
|
import { z as z41 } from "zod";
|
|
45863
46190
|
var SERIALIZABLE_CONFIG_SCHEMA = z41.object({
|
|
@@ -45907,7 +46234,22 @@ var STORED_AGENT_SCHEMA = z41.object({
|
|
|
45907
46234
|
archivedAt: z41.string().nullable().optional(),
|
|
45908
46235
|
// Fork lineage (optional for backward compat with pre-fork records).
|
|
45909
46236
|
parentAgentId: z41.string().optional(),
|
|
45910
|
-
forkedFromMessageUuid: z41.string().optional()
|
|
46237
|
+
forkedFromMessageUuid: z41.string().optional(),
|
|
46238
|
+
// Multi-tenant session isolation: the auth-server user-id that created
|
|
46239
|
+
// (and therefore owns) this agent + the additive ACL of other users
|
|
46240
|
+
// granted access. Both optional/null-default for backward compatibility
|
|
46241
|
+
// with pre-Phase-2c records — those load with `null` owner and stay
|
|
46242
|
+
// visible to every connecting user (matches today's single-tenant
|
|
46243
|
+
// behavior). Set on new agents at create time via Session.ownerUserId.
|
|
46244
|
+
ownerUserId: z41.string().nullable().optional(),
|
|
46245
|
+
sharedWithUserIds: z41.array(z41.string()).default([]),
|
|
46246
|
+
// Owner's display username — needed on resume to route to the correct
|
|
46247
|
+
// `CLAUDE_CONFIG_DIR` via `ensureClaudeProfile(username)` for agents
|
|
46248
|
+
// created by a shared (non-owner) user. Without this, a resumed shared-
|
|
46249
|
+
// user agent would silently fall back to the daemon owner's Claude
|
|
46250
|
+
// profile/subscription. Optional — pre-Phase-3 records rehydrate without
|
|
46251
|
+
// per-user profile routing.
|
|
46252
|
+
ownerUsername: z41.string().optional()
|
|
45911
46253
|
});
|
|
45912
46254
|
|
|
45913
46255
|
// ../server/src/server/agent/mcp-server.ts
|
|
@@ -46591,10 +46933,10 @@ function encodeUtf8String(value) {
|
|
|
46591
46933
|
function createRelayE2eeTransportFactory(args) {
|
|
46592
46934
|
return ({ url, headers }) => {
|
|
46593
46935
|
const base = args.baseFactory({ url, headers });
|
|
46594
|
-
return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger);
|
|
46936
|
+
return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger, args.staticKeyPair);
|
|
46595
46937
|
};
|
|
46596
46938
|
}
|
|
46597
|
-
function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
46939
|
+
function createEncryptedTransport(base, daemonPublicKeyB64, logger, staticKeyPair) {
|
|
46598
46940
|
let channel = null;
|
|
46599
46941
|
let opened = false;
|
|
46600
46942
|
let closed = false;
|
|
@@ -46651,12 +46993,17 @@ function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
|
46651
46993
|
};
|
|
46652
46994
|
const startHandshake = async () => {
|
|
46653
46995
|
try {
|
|
46654
|
-
channel = await createClientChannel(
|
|
46655
|
-
|
|
46656
|
-
|
|
46657
|
-
|
|
46658
|
-
|
|
46659
|
-
|
|
46996
|
+
channel = await createClientChannel(
|
|
46997
|
+
relayTransport,
|
|
46998
|
+
daemonPublicKeyB64,
|
|
46999
|
+
{
|
|
47000
|
+
onopen: emitOpen,
|
|
47001
|
+
onmessage: (data) => emitMessage(data),
|
|
47002
|
+
onclose: (code, reason) => emitClose({ code, reason }),
|
|
47003
|
+
onerror: (error) => emitError(error)
|
|
47004
|
+
},
|
|
47005
|
+
staticKeyPair
|
|
47006
|
+
);
|
|
46660
47007
|
} catch (error) {
|
|
46661
47008
|
logger.warn({ err: normalizeTransportError(error) }, "relay_e2ee_handshake_failed");
|
|
46662
47009
|
emitError(error);
|
|
@@ -47019,7 +47366,8 @@ var DaemonClient = class {
|
|
|
47019
47366
|
transportFactory = createRelayE2eeTransportFactory({
|
|
47020
47367
|
baseFactory: baseTransportFactory,
|
|
47021
47368
|
daemonPublicKeyB64,
|
|
47022
|
-
logger: this.logger
|
|
47369
|
+
logger: this.logger,
|
|
47370
|
+
staticKeyPair: this.config.e2ee?.staticKeyPair
|
|
47023
47371
|
});
|
|
47024
47372
|
}
|
|
47025
47373
|
const transportUrl = this.resolveTransportUrlForAttempt();
|
|
@@ -48093,6 +48441,99 @@ var DaemonClient = class {
|
|
|
48093
48441
|
throw new Error(payload.error || "Failed to delete session upload");
|
|
48094
48442
|
}
|
|
48095
48443
|
}
|
|
48444
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
48445
|
+
// Phase 4 agent sharing — owner-only mutations of the per-agent ACL.
|
|
48446
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
48447
|
+
/**
|
|
48448
|
+
* Grant a user access to this agent. Owner-only. Idempotent — sharing
|
|
48449
|
+
* with a user already on the ACL is a no-op. The recipient does not
|
|
48450
|
+
* need to be currently paired; access takes effect at their next
|
|
48451
|
+
* connection.
|
|
48452
|
+
*/
|
|
48453
|
+
async shareAgentWithUser(agentId, userId) {
|
|
48454
|
+
const requestId = this.createRequestId();
|
|
48455
|
+
const message = SessionInboundMessageSchema.parse({
|
|
48456
|
+
type: "share_agent_with_user_request",
|
|
48457
|
+
requestId,
|
|
48458
|
+
agentId,
|
|
48459
|
+
userId
|
|
48460
|
+
});
|
|
48461
|
+
const payload = await this.sendRequest({
|
|
48462
|
+
requestId,
|
|
48463
|
+
message,
|
|
48464
|
+
timeout: 15e3,
|
|
48465
|
+
options: { skipQueue: true },
|
|
48466
|
+
select: (msg) => {
|
|
48467
|
+
if (msg.type !== "share_agent_with_user_response") return null;
|
|
48468
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
48469
|
+
return msg.payload;
|
|
48470
|
+
}
|
|
48471
|
+
});
|
|
48472
|
+
if (!payload.ok) {
|
|
48473
|
+
throw new Error(payload.error || "Failed to share agent");
|
|
48474
|
+
}
|
|
48475
|
+
return payload.sharedWithUserIds;
|
|
48476
|
+
}
|
|
48477
|
+
/**
|
|
48478
|
+
* Revoke a user's access to this agent. Owner-only. Effects are
|
|
48479
|
+
* immediate — once the auth-server allow-list propagates (and the
|
|
48480
|
+
* daemon's in-memory state updates here), the removed user's resolver
|
|
48481
|
+
* answers "Agent not found" for any further per-agent RPC.
|
|
48482
|
+
*/
|
|
48483
|
+
async unshareAgentWithUser(agentId, userId) {
|
|
48484
|
+
const requestId = this.createRequestId();
|
|
48485
|
+
const message = SessionInboundMessageSchema.parse({
|
|
48486
|
+
type: "unshare_agent_with_user_request",
|
|
48487
|
+
requestId,
|
|
48488
|
+
agentId,
|
|
48489
|
+
userId
|
|
48490
|
+
});
|
|
48491
|
+
const payload = await this.sendRequest({
|
|
48492
|
+
requestId,
|
|
48493
|
+
message,
|
|
48494
|
+
timeout: 15e3,
|
|
48495
|
+
options: { skipQueue: true },
|
|
48496
|
+
select: (msg) => {
|
|
48497
|
+
if (msg.type !== "unshare_agent_with_user_response") return null;
|
|
48498
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
48499
|
+
return msg.payload;
|
|
48500
|
+
}
|
|
48501
|
+
});
|
|
48502
|
+
if (!payload.ok) {
|
|
48503
|
+
throw new Error(payload.error || "Failed to unshare agent");
|
|
48504
|
+
}
|
|
48505
|
+
return payload.sharedWithUserIds;
|
|
48506
|
+
}
|
|
48507
|
+
/**
|
|
48508
|
+
* Return the ACL — owner + the user-ids the owner has granted access
|
|
48509
|
+
* to. Owner-only: recipients can't enumerate who else has access.
|
|
48510
|
+
*/
|
|
48511
|
+
async listAgentSharedUsers(agentId) {
|
|
48512
|
+
const requestId = this.createRequestId();
|
|
48513
|
+
const message = SessionInboundMessageSchema.parse({
|
|
48514
|
+
type: "list_agent_shared_users_request",
|
|
48515
|
+
requestId,
|
|
48516
|
+
agentId
|
|
48517
|
+
});
|
|
48518
|
+
const payload = await this.sendRequest({
|
|
48519
|
+
requestId,
|
|
48520
|
+
message,
|
|
48521
|
+
timeout: 15e3,
|
|
48522
|
+
options: { skipQueue: true },
|
|
48523
|
+
select: (msg) => {
|
|
48524
|
+
if (msg.type !== "list_agent_shared_users_response") return null;
|
|
48525
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
48526
|
+
return msg.payload;
|
|
48527
|
+
}
|
|
48528
|
+
});
|
|
48529
|
+
if (!payload.ok) {
|
|
48530
|
+
throw new Error(payload.error || "Failed to list agent shared users");
|
|
48531
|
+
}
|
|
48532
|
+
return {
|
|
48533
|
+
ownerUserId: payload.ownerUserId,
|
|
48534
|
+
sharedWithUserIds: payload.sharedWithUserIds
|
|
48535
|
+
};
|
|
48536
|
+
}
|
|
48096
48537
|
/**
|
|
48097
48538
|
* List the images the user has attached to this agent's chat (the "Images"
|
|
48098
48539
|
* tab in the uploads modal). Returns newest first; the daemon scans
|