appostle-installer 0.0.9 → 0.0.10
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 +1916 -505
- package/dist/appostle.js.map +4 -4
- package/dist/worker.js +1856 -595
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/appostle.js
CHANGED
|
@@ -454,18 +454,18 @@ function resolveNodePtyPackageRoot() {
|
|
|
454
454
|
return null;
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
|
-
function ensureExecutableBit(
|
|
458
|
-
if (!existsSync2(
|
|
457
|
+
function ensureExecutableBit(path26) {
|
|
458
|
+
if (!existsSync2(path26)) {
|
|
459
459
|
return;
|
|
460
460
|
}
|
|
461
|
-
const stat5 = statSync(
|
|
461
|
+
const stat5 = statSync(path26);
|
|
462
462
|
if (!stat5.isFile()) {
|
|
463
463
|
return;
|
|
464
464
|
}
|
|
465
465
|
if ((stat5.mode & 73) === 73) {
|
|
466
466
|
return;
|
|
467
467
|
}
|
|
468
|
-
chmodSync(
|
|
468
|
+
chmodSync(path26, stat5.mode | 73);
|
|
469
469
|
}
|
|
470
470
|
function ensureNodePtySpawnHelperExecutableForCurrentPlatform(options = {}) {
|
|
471
471
|
const platform2 = options.platform ?? process.platform;
|
|
@@ -547,11 +547,11 @@ function parseGitRevParsePath(stdout) {
|
|
|
547
547
|
if (lines.length !== 1) {
|
|
548
548
|
return null;
|
|
549
549
|
}
|
|
550
|
-
const
|
|
551
|
-
if (!
|
|
550
|
+
const path26 = lines[0]?.trim() ?? "";
|
|
551
|
+
if (!path26 || path26.startsWith("--")) {
|
|
552
552
|
return null;
|
|
553
553
|
}
|
|
554
|
-
return
|
|
554
|
+
return path26;
|
|
555
555
|
}
|
|
556
556
|
function resolveGitRevParsePath(cwd, stdout) {
|
|
557
557
|
const parsed = parseGitRevParsePath(stdout);
|
|
@@ -1324,9 +1324,9 @@ async function deleteAppostleWorktree({
|
|
|
1324
1324
|
}
|
|
1325
1325
|
}
|
|
1326
1326
|
}
|
|
1327
|
-
async function pathExists(
|
|
1327
|
+
async function pathExists(path26) {
|
|
1328
1328
|
try {
|
|
1329
|
-
await stat(
|
|
1329
|
+
await stat(path26);
|
|
1330
1330
|
return true;
|
|
1331
1331
|
} catch (error) {
|
|
1332
1332
|
if (error.code === "ENOENT") {
|
|
@@ -1335,8 +1335,8 @@ async function pathExists(path24) {
|
|
|
1335
1335
|
throw error;
|
|
1336
1336
|
}
|
|
1337
1337
|
}
|
|
1338
|
-
async function removeDirectoryWithRetries(
|
|
1339
|
-
if (!await pathExists(
|
|
1338
|
+
async function removeDirectoryWithRetries(path26) {
|
|
1339
|
+
if (!await pathExists(path26)) {
|
|
1340
1340
|
return;
|
|
1341
1341
|
}
|
|
1342
1342
|
const delaysMs = [0, 100, 300, 700, 1500];
|
|
@@ -1346,17 +1346,17 @@ async function removeDirectoryWithRetries(path24) {
|
|
|
1346
1346
|
await new Promise((resolve12) => setTimeout(resolve12, delay));
|
|
1347
1347
|
}
|
|
1348
1348
|
try {
|
|
1349
|
-
await rm(
|
|
1350
|
-
if (!await pathExists(
|
|
1349
|
+
await rm(path26, { recursive: true, force: true });
|
|
1350
|
+
if (!await pathExists(path26)) {
|
|
1351
1351
|
return;
|
|
1352
1352
|
}
|
|
1353
|
-
lastError = new Error(`Directory still present after rm: ${
|
|
1353
|
+
lastError = new Error(`Directory still present after rm: ${path26}`);
|
|
1354
1354
|
} catch (error) {
|
|
1355
1355
|
lastError = error;
|
|
1356
1356
|
}
|
|
1357
1357
|
}
|
|
1358
|
-
if (await pathExists(
|
|
1359
|
-
throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${
|
|
1358
|
+
if (await pathExists(path26)) {
|
|
1359
|
+
throw lastError instanceof Error ? lastError : new Error(`Failed to remove worktree directory: ${path26}`);
|
|
1360
1360
|
}
|
|
1361
1361
|
}
|
|
1362
1362
|
var createWorktree = async ({
|
|
@@ -3373,7 +3373,46 @@ var AgentTimelineItemPayloadSchema = z10.union([
|
|
|
3373
3373
|
z10.object({
|
|
3374
3374
|
type: z10.literal("user_message"),
|
|
3375
3375
|
text: z10.string(),
|
|
3376
|
-
messageId: z10.string().optional()
|
|
3376
|
+
messageId: z10.string().optional(),
|
|
3377
|
+
// Bytes-stripped attachment metadata so a reload re-renders the chip
|
|
3378
|
+
// without round-tripping image/file bytes through the timeline. Both
|
|
3379
|
+
// optional → backward-compatible with old daemons (silently strip) and
|
|
3380
|
+
// old clients (silently ignore). Inlined (not factored to a const)
|
|
3381
|
+
// because they're only used in this one place and forward-declaring
|
|
3382
|
+
// them would force a top-level reorder.
|
|
3383
|
+
images: z10.array(
|
|
3384
|
+
z10.object({
|
|
3385
|
+
id: z10.string(),
|
|
3386
|
+
mimeType: z10.string(),
|
|
3387
|
+
fileName: z10.string().optional(),
|
|
3388
|
+
byteSize: z10.number().int().nonnegative().optional(),
|
|
3389
|
+
// Daemon-side pointer for fetch-by-id. Optional → old daemons
|
|
3390
|
+
// (which only emit local-cache-friendly metadata) keep working;
|
|
3391
|
+
// old clients see the field and ignore it.
|
|
3392
|
+
daemonRef: z10.object({
|
|
3393
|
+
kind: z10.enum(["image", "file"]),
|
|
3394
|
+
attachmentId: z10.string(),
|
|
3395
|
+
relativePath: z10.string()
|
|
3396
|
+
}).optional()
|
|
3397
|
+
})
|
|
3398
|
+
).optional(),
|
|
3399
|
+
files: z10.array(
|
|
3400
|
+
z10.object({
|
|
3401
|
+
id: z10.string(),
|
|
3402
|
+
fileName: z10.string(),
|
|
3403
|
+
mimeType: z10.string(),
|
|
3404
|
+
size: z10.number().int().nonnegative(),
|
|
3405
|
+
daemonRef: z10.object({
|
|
3406
|
+
kind: z10.enum(["image", "file"]),
|
|
3407
|
+
attachmentId: z10.string(),
|
|
3408
|
+
relativePath: z10.string()
|
|
3409
|
+
}).optional()
|
|
3410
|
+
})
|
|
3411
|
+
).optional(),
|
|
3412
|
+
// Marker for daemon-synthesized text (e.g. "Sent 1 image" when the
|
|
3413
|
+
// user typed nothing). The chat bubble hides the text when this is
|
|
3414
|
+
// true; the LLM still sees it for cache_control compliance.
|
|
3415
|
+
textIsSynthesized: z10.boolean().optional()
|
|
3377
3416
|
}),
|
|
3378
3417
|
z10.object({
|
|
3379
3418
|
type: z10.literal("assistant_message"),
|
|
@@ -3653,8 +3692,34 @@ var AgentAttachmentsSchema = z10.unknown().transform(normalizeAgentAttachments).
|
|
|
3653
3692
|
var ImageAttachmentSchema = z10.object({
|
|
3654
3693
|
data: z10.string(),
|
|
3655
3694
|
// base64 encoded image
|
|
3656
|
-
mimeType: z10.string()
|
|
3695
|
+
mimeType: z10.string(),
|
|
3657
3696
|
// e.g., "image/jpeg", "image/png"
|
|
3697
|
+
// Optional client-side identity. When provided, the daemon echoes these
|
|
3698
|
+
// (without bytes) on the `user_message` timeline event so a reload can
|
|
3699
|
+
// re-render the chip. All optional → backward-compatible: old clients
|
|
3700
|
+
// omit them, old daemons strip them.
|
|
3701
|
+
id: z10.string().optional(),
|
|
3702
|
+
fileName: z10.string().optional(),
|
|
3703
|
+
byteSize: z10.number().int().nonnegative().optional()
|
|
3704
|
+
});
|
|
3705
|
+
var FileAttachmentSchema = z10.object({
|
|
3706
|
+
fileName: z10.string().min(1).max(255),
|
|
3707
|
+
mimeType: z10.string().min(1).max(255),
|
|
3708
|
+
size: z10.number().int().nonnegative(),
|
|
3709
|
+
data: z10.string()
|
|
3710
|
+
// base64-encoded bytes
|
|
3711
|
+
});
|
|
3712
|
+
var SessionUploadSchema = z10.object({
|
|
3713
|
+
id: z10.string(),
|
|
3714
|
+
fileName: z10.string(),
|
|
3715
|
+
mimeType: z10.string(),
|
|
3716
|
+
size: z10.number().int().nonnegative(),
|
|
3717
|
+
createdAt: z10.string(),
|
|
3718
|
+
// ISO-8601 UTC timestamp
|
|
3719
|
+
messageId: z10.string().nullable(),
|
|
3720
|
+
// Path relative to the agent's workspace root, e.g.
|
|
3721
|
+
// ".appostle/uploaded_files/<id>__report.sql". Always uses POSIX separators.
|
|
3722
|
+
relativePath: z10.string()
|
|
3658
3723
|
});
|
|
3659
3724
|
var SendAgentMessageSchema = z10.object({
|
|
3660
3725
|
type: z10.literal("send_agent_message"),
|
|
@@ -3663,6 +3728,7 @@ var SendAgentMessageSchema = z10.object({
|
|
|
3663
3728
|
messageId: z10.string().optional(),
|
|
3664
3729
|
// Client-provided ID for deduplication
|
|
3665
3730
|
images: z10.array(ImageAttachmentSchema).optional(),
|
|
3731
|
+
files: z10.array(FileAttachmentSchema).optional(),
|
|
3666
3732
|
attachments: AgentAttachmentsSchema
|
|
3667
3733
|
});
|
|
3668
3734
|
var FetchAgentsRequestMessageSchema = z10.object({
|
|
@@ -3727,6 +3793,7 @@ var SendAgentMessageRequestSchema = z10.object({
|
|
|
3727
3793
|
messageId: z10.string().optional(),
|
|
3728
3794
|
// Client-provided ID for deduplication
|
|
3729
3795
|
images: z10.array(ImageAttachmentSchema).optional(),
|
|
3796
|
+
files: z10.array(FileAttachmentSchema).optional(),
|
|
3730
3797
|
attachments: AgentAttachmentsSchema
|
|
3731
3798
|
});
|
|
3732
3799
|
var WaitForFinishRequestSchema = z10.object({
|
|
@@ -3788,6 +3855,7 @@ var CreateAgentRequestMessageSchema = z10.object({
|
|
|
3788
3855
|
clientMessageId: z10.string().optional(),
|
|
3789
3856
|
outputSchema: z10.record(z10.unknown()).optional(),
|
|
3790
3857
|
images: z10.array(ImageAttachmentSchema).optional(),
|
|
3858
|
+
files: z10.array(FileAttachmentSchema).optional(),
|
|
3791
3859
|
attachments: AgentAttachmentsSchema,
|
|
3792
3860
|
git: GitSetupOptionsSchema.optional(),
|
|
3793
3861
|
labels: z10.record(z10.string()).default({}),
|
|
@@ -4584,6 +4652,131 @@ var LinkAccountResponseMessageSchema = z10.object({
|
|
|
4584
4652
|
})
|
|
4585
4653
|
])
|
|
4586
4654
|
});
|
|
4655
|
+
var ListSessionUploadsRequestSchema = z10.object({
|
|
4656
|
+
type: z10.literal("list_session_uploads_request"),
|
|
4657
|
+
requestId: z10.string(),
|
|
4658
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
4659
|
+
agentId: z10.string()
|
|
4660
|
+
});
|
|
4661
|
+
var ListSessionUploadsResponseSchema = z10.object({
|
|
4662
|
+
type: z10.literal("list_session_uploads_response"),
|
|
4663
|
+
payload: z10.discriminatedUnion("ok", [
|
|
4664
|
+
z10.object({
|
|
4665
|
+
requestId: z10.string(),
|
|
4666
|
+
ok: z10.literal(true),
|
|
4667
|
+
uploads: z10.array(SessionUploadSchema)
|
|
4668
|
+
}),
|
|
4669
|
+
z10.object({
|
|
4670
|
+
requestId: z10.string(),
|
|
4671
|
+
ok: z10.literal(false),
|
|
4672
|
+
error: z10.string()
|
|
4673
|
+
})
|
|
4674
|
+
])
|
|
4675
|
+
});
|
|
4676
|
+
var DeleteSessionUploadRequestSchema = z10.object({
|
|
4677
|
+
type: z10.literal("delete_session_upload_request"),
|
|
4678
|
+
requestId: z10.string(),
|
|
4679
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
4680
|
+
agentId: z10.string(),
|
|
4681
|
+
uploadId: z10.string()
|
|
4682
|
+
});
|
|
4683
|
+
var DeleteSessionUploadResponseSchema = z10.object({
|
|
4684
|
+
type: z10.literal("delete_session_upload_response"),
|
|
4685
|
+
payload: z10.discriminatedUnion("ok", [
|
|
4686
|
+
z10.object({
|
|
4687
|
+
requestId: z10.string(),
|
|
4688
|
+
ok: z10.literal(true)
|
|
4689
|
+
}),
|
|
4690
|
+
z10.object({
|
|
4691
|
+
requestId: z10.string(),
|
|
4692
|
+
ok: z10.literal(false),
|
|
4693
|
+
error: z10.string()
|
|
4694
|
+
})
|
|
4695
|
+
])
|
|
4696
|
+
});
|
|
4697
|
+
var SessionImageSchema = z10.object({
|
|
4698
|
+
id: z10.string(),
|
|
4699
|
+
fileName: z10.string(),
|
|
4700
|
+
mimeType: z10.string(),
|
|
4701
|
+
size: z10.number().int().nonnegative(),
|
|
4702
|
+
createdAt: z10.string(),
|
|
4703
|
+
// ISO-8601 UTC timestamp
|
|
4704
|
+
relativePath: z10.string()
|
|
4705
|
+
});
|
|
4706
|
+
var ListSessionImagesRequestSchema = z10.object({
|
|
4707
|
+
type: z10.literal("list_session_images_request"),
|
|
4708
|
+
requestId: z10.string(),
|
|
4709
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
4710
|
+
agentId: z10.string()
|
|
4711
|
+
});
|
|
4712
|
+
var ListSessionImagesResponseSchema = z10.object({
|
|
4713
|
+
type: z10.literal("list_session_images_response"),
|
|
4714
|
+
payload: z10.discriminatedUnion("ok", [
|
|
4715
|
+
z10.object({
|
|
4716
|
+
requestId: z10.string(),
|
|
4717
|
+
ok: z10.literal(true),
|
|
4718
|
+
images: z10.array(SessionImageSchema)
|
|
4719
|
+
}),
|
|
4720
|
+
z10.object({
|
|
4721
|
+
requestId: z10.string(),
|
|
4722
|
+
ok: z10.literal(false),
|
|
4723
|
+
error: z10.string()
|
|
4724
|
+
})
|
|
4725
|
+
])
|
|
4726
|
+
});
|
|
4727
|
+
var DeleteSessionImageRequestSchema = z10.object({
|
|
4728
|
+
type: z10.literal("delete_session_image_request"),
|
|
4729
|
+
requestId: z10.string(),
|
|
4730
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
4731
|
+
agentId: z10.string(),
|
|
4732
|
+
/** Workspace-relative POSIX path; traversal-guarded by the daemon. */
|
|
4733
|
+
relativePath: z10.string()
|
|
4734
|
+
});
|
|
4735
|
+
var DeleteSessionImageResponseSchema = z10.object({
|
|
4736
|
+
type: z10.literal("delete_session_image_response"),
|
|
4737
|
+
payload: z10.discriminatedUnion("ok", [
|
|
4738
|
+
z10.object({
|
|
4739
|
+
requestId: z10.string(),
|
|
4740
|
+
ok: z10.literal(true)
|
|
4741
|
+
}),
|
|
4742
|
+
z10.object({
|
|
4743
|
+
requestId: z10.string(),
|
|
4744
|
+
ok: z10.literal(false),
|
|
4745
|
+
error: z10.string()
|
|
4746
|
+
})
|
|
4747
|
+
])
|
|
4748
|
+
});
|
|
4749
|
+
var FetchAttachmentBytesRequestSchema = z10.object({
|
|
4750
|
+
type: z10.literal("fetch_attachment_bytes_request"),
|
|
4751
|
+
requestId: z10.string(),
|
|
4752
|
+
/** Accepts full ID, unique prefix, or exact full title (server resolves). */
|
|
4753
|
+
agentId: z10.string(),
|
|
4754
|
+
kind: z10.enum(["image", "file"]),
|
|
4755
|
+
attachmentId: z10.string(),
|
|
4756
|
+
/** Workspace-relative POSIX path to the byte source on the daemon's disk. */
|
|
4757
|
+
relativePath: z10.string()
|
|
4758
|
+
});
|
|
4759
|
+
var FetchAttachmentBytesResponseSchema = z10.object({
|
|
4760
|
+
type: z10.literal("fetch_attachment_bytes_response"),
|
|
4761
|
+
payload: z10.discriminatedUnion("ok", [
|
|
4762
|
+
z10.object({
|
|
4763
|
+
requestId: z10.string(),
|
|
4764
|
+
ok: z10.literal(true),
|
|
4765
|
+
attachmentId: z10.string(),
|
|
4766
|
+
kind: z10.enum(["image", "file"]),
|
|
4767
|
+
data: z10.string(),
|
|
4768
|
+
// base64
|
|
4769
|
+
mimeType: z10.string(),
|
|
4770
|
+
byteSize: z10.number().int().nonnegative(),
|
|
4771
|
+
fileName: z10.string().optional()
|
|
4772
|
+
}),
|
|
4773
|
+
z10.object({
|
|
4774
|
+
requestId: z10.string(),
|
|
4775
|
+
ok: z10.literal(false),
|
|
4776
|
+
error: z10.string()
|
|
4777
|
+
})
|
|
4778
|
+
])
|
|
4779
|
+
});
|
|
4587
4780
|
var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
|
|
4588
4781
|
VoiceAudioChunkMessageSchema,
|
|
4589
4782
|
AbortRequestMessageSchema,
|
|
@@ -4725,7 +4918,12 @@ var SessionInboundMessageSchema = z10.discriminatedUnion("type", [
|
|
|
4725
4918
|
QuestListRequestSchema,
|
|
4726
4919
|
QuestInspectRequestSchema,
|
|
4727
4920
|
QuestStopRequestSchema,
|
|
4728
|
-
RolesListRequestSchema
|
|
4921
|
+
RolesListRequestSchema,
|
|
4922
|
+
ListSessionUploadsRequestSchema,
|
|
4923
|
+
DeleteSessionUploadRequestSchema,
|
|
4924
|
+
ListSessionImagesRequestSchema,
|
|
4925
|
+
DeleteSessionImageRequestSchema,
|
|
4926
|
+
FetchAttachmentBytesRequestSchema
|
|
4729
4927
|
]);
|
|
4730
4928
|
var ActivityLogPayloadSchema = z10.object({
|
|
4731
4929
|
id: z10.string(),
|
|
@@ -6269,7 +6467,12 @@ var SessionOutboundMessageSchema = z10.discriminatedUnion("type", [
|
|
|
6269
6467
|
BrandAssetCopyResponseSchema,
|
|
6270
6468
|
BrandAssetUploadResponseSchema,
|
|
6271
6469
|
GetVapidPublicKeyResponseSchema,
|
|
6272
|
-
LinkAccountResponseMessageSchema
|
|
6470
|
+
LinkAccountResponseMessageSchema,
|
|
6471
|
+
ListSessionUploadsResponseSchema,
|
|
6472
|
+
DeleteSessionUploadResponseSchema,
|
|
6473
|
+
ListSessionImagesResponseSchema,
|
|
6474
|
+
DeleteSessionImageResponseSchema,
|
|
6475
|
+
FetchAttachmentBytesResponseSchema
|
|
6273
6476
|
]);
|
|
6274
6477
|
var WSPingMessageSchema = z10.object({
|
|
6275
6478
|
type: z10.literal("ping")
|
|
@@ -8671,14 +8874,14 @@ var DictationStreamManager = class {
|
|
|
8671
8874
|
PCM_CHANNELS,
|
|
8672
8875
|
PCM_BITS_PER_SAMPLE
|
|
8673
8876
|
);
|
|
8674
|
-
const
|
|
8877
|
+
const path26 = await maybePersistDictationDebugAudio(
|
|
8675
8878
|
wavBuffer,
|
|
8676
8879
|
{ sessionId: state.sessionId, dictationId: state.dictationId, format: "audio/wav" },
|
|
8677
8880
|
this.logger,
|
|
8678
8881
|
state.debugChunkWriter?.folder
|
|
8679
8882
|
);
|
|
8680
|
-
state.debugRecordingPath =
|
|
8681
|
-
return
|
|
8883
|
+
state.debugRecordingPath = path26;
|
|
8884
|
+
return path26;
|
|
8682
8885
|
}
|
|
8683
8886
|
failDictationStream(dictationId, error, retryable) {
|
|
8684
8887
|
this.emit({
|
|
@@ -9707,6 +9910,9 @@ async function sendPromptToAgent(params) {
|
|
|
9707
9910
|
messageId,
|
|
9708
9911
|
runOptions,
|
|
9709
9912
|
sessionMode,
|
|
9913
|
+
userMessageImages,
|
|
9914
|
+
userMessageFiles,
|
|
9915
|
+
userMessageTextIsSynthesized,
|
|
9710
9916
|
logger
|
|
9711
9917
|
} = params;
|
|
9712
9918
|
await unarchiveAgentState(agentStorage, agentManager, agentId);
|
|
@@ -9721,7 +9927,10 @@ async function sendPromptToAgent(params) {
|
|
|
9721
9927
|
try {
|
|
9722
9928
|
agentManager.recordUserMessage(agentId, userMessageText, {
|
|
9723
9929
|
messageId,
|
|
9724
|
-
emitState: false
|
|
9930
|
+
emitState: false,
|
|
9931
|
+
...userMessageImages && userMessageImages.length > 0 ? { images: userMessageImages } : {},
|
|
9932
|
+
...userMessageFiles && userMessageFiles.length > 0 ? { files: userMessageFiles } : {},
|
|
9933
|
+
...userMessageTextIsSynthesized ? { textIsSynthesized: true } : {}
|
|
9725
9934
|
});
|
|
9726
9935
|
} catch (error) {
|
|
9727
9936
|
logger.error({ err: error, agentId }, "Failed to record user message");
|
|
@@ -9732,6 +9941,544 @@ async function sendPromptToAgent(params) {
|
|
|
9732
9941
|
});
|
|
9733
9942
|
}
|
|
9734
9943
|
|
|
9944
|
+
// ../server/src/server/agent/prompt-attachments.ts
|
|
9945
|
+
function renderPromptAttachmentAsText(attachment) {
|
|
9946
|
+
switch (attachment.type) {
|
|
9947
|
+
case "github_pr": {
|
|
9948
|
+
const lines = [`GitHub PR #${attachment.number}: ${attachment.title}`, attachment.url];
|
|
9949
|
+
if (attachment.baseRefName) {
|
|
9950
|
+
lines.push(`Base: ${attachment.baseRefName}`);
|
|
9951
|
+
}
|
|
9952
|
+
if (attachment.headRefName) {
|
|
9953
|
+
lines.push(`Head: ${attachment.headRefName}`);
|
|
9954
|
+
}
|
|
9955
|
+
if (attachment.body) {
|
|
9956
|
+
lines.push("", attachment.body);
|
|
9957
|
+
}
|
|
9958
|
+
return lines.join("\n");
|
|
9959
|
+
}
|
|
9960
|
+
case "github_issue": {
|
|
9961
|
+
const lines = [`GitHub Issue #${attachment.number}: ${attachment.title}`, attachment.url];
|
|
9962
|
+
if (attachment.body) {
|
|
9963
|
+
lines.push("", attachment.body);
|
|
9964
|
+
}
|
|
9965
|
+
return lines.join("\n");
|
|
9966
|
+
}
|
|
9967
|
+
case "gitlab_mr": {
|
|
9968
|
+
const lines = [`GitLab MR !${attachment.iid}: ${attachment.title}`, attachment.url];
|
|
9969
|
+
if (attachment.targetBranch) {
|
|
9970
|
+
lines.push(`Target: ${attachment.targetBranch}`);
|
|
9971
|
+
}
|
|
9972
|
+
if (attachment.sourceBranch) {
|
|
9973
|
+
lines.push(`Source: ${attachment.sourceBranch}`);
|
|
9974
|
+
}
|
|
9975
|
+
if (attachment.body) {
|
|
9976
|
+
lines.push("", attachment.body);
|
|
9977
|
+
}
|
|
9978
|
+
return lines.join("\n");
|
|
9979
|
+
}
|
|
9980
|
+
case "gitlab_issue": {
|
|
9981
|
+
const lines = [`GitLab Issue #${attachment.iid}: ${attachment.title}`, attachment.url];
|
|
9982
|
+
if (attachment.body) {
|
|
9983
|
+
lines.push("", attachment.body);
|
|
9984
|
+
}
|
|
9985
|
+
return lines.join("\n");
|
|
9986
|
+
}
|
|
9987
|
+
}
|
|
9988
|
+
}
|
|
9989
|
+
function renderFileAttachmentFooter(uploads) {
|
|
9990
|
+
if (uploads.length === 0) {
|
|
9991
|
+
return "";
|
|
9992
|
+
}
|
|
9993
|
+
const lines = [
|
|
9994
|
+
"",
|
|
9995
|
+
"The user attached the following file(s) to this message. Read them with your file-read tool when relevant; the paths are relative to the workspace root:"
|
|
9996
|
+
];
|
|
9997
|
+
for (const upload of uploads) {
|
|
9998
|
+
const size = formatBytes(upload.size);
|
|
9999
|
+
lines.push(`- ${upload.relativePath} (${upload.mimeType}, ${size})`);
|
|
10000
|
+
}
|
|
10001
|
+
return lines.join("\n");
|
|
10002
|
+
}
|
|
10003
|
+
function formatBytes(bytes) {
|
|
10004
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
10005
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
10006
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
10007
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
10008
|
+
}
|
|
10009
|
+
function findGitHubPrAttachment(attachments) {
|
|
10010
|
+
if (!attachments) {
|
|
10011
|
+
return null;
|
|
10012
|
+
}
|
|
10013
|
+
return attachments.find(
|
|
10014
|
+
(attachment) => attachment.type === "github_pr"
|
|
10015
|
+
) ?? null;
|
|
10016
|
+
}
|
|
10017
|
+
|
|
10018
|
+
// ../server/src/server/uploads/session-upload-store.ts
|
|
10019
|
+
import { promises as fs } from "node:fs";
|
|
10020
|
+
import * as path5 from "node:path";
|
|
10021
|
+
var MAX_UPLOAD_BYTES_PER_MESSAGE = 50 * 1024 * 1024;
|
|
10022
|
+
var MAX_UPLOAD_BYTES_PER_FILE = 50 * 1024 * 1024;
|
|
10023
|
+
var UPLOADS_DIR_REL = path5.posix.join(".appostle", "uploaded_files");
|
|
10024
|
+
var MANIFEST_BASENAME = "uploads.json";
|
|
10025
|
+
function originalUploadFileName(relativePath) {
|
|
10026
|
+
const safe = path5.posix.normalize(relativePath);
|
|
10027
|
+
if (!safe.startsWith(`${UPLOADS_DIR_REL}/`)) return null;
|
|
10028
|
+
const baseName = path5.posix.basename(safe);
|
|
10029
|
+
const sep4 = baseName.indexOf("__");
|
|
10030
|
+
if (sep4 < 0) return null;
|
|
10031
|
+
return baseName.slice(sep4 + 2);
|
|
10032
|
+
}
|
|
10033
|
+
var GITIGNORE_MARKER_LINE = "# Added by Appostle: uploaded chat attachments";
|
|
10034
|
+
var GITIGNORE_BLOCK = `${GITIGNORE_MARKER_LINE}
|
|
10035
|
+
.appostle/
|
|
10036
|
+
`;
|
|
10037
|
+
function emptyManifest() {
|
|
10038
|
+
return { version: 1, uploads: [] };
|
|
10039
|
+
}
|
|
10040
|
+
function sanitizeFileName(input) {
|
|
10041
|
+
const stripped = input.replace(/[\\/\x00-\x1F\x7F]/g, "_").trim();
|
|
10042
|
+
const noTraversal = stripped.replace(/\.{2,}/g, ".");
|
|
10043
|
+
const trimmed = noTraversal.replace(/^[.\s]+|[\s.]+$/g, "");
|
|
10044
|
+
if (trimmed.length === 0) {
|
|
10045
|
+
return "upload.bin";
|
|
10046
|
+
}
|
|
10047
|
+
return trimmed.slice(0, 200);
|
|
10048
|
+
}
|
|
10049
|
+
function manifestDirFor(cwd) {
|
|
10050
|
+
return path5.join(cwd, UPLOADS_DIR_REL);
|
|
10051
|
+
}
|
|
10052
|
+
function manifestPathFor(cwd) {
|
|
10053
|
+
return path5.join(manifestDirFor(cwd), MANIFEST_BASENAME);
|
|
10054
|
+
}
|
|
10055
|
+
function relativeUploadPath(id, sanitizedName) {
|
|
10056
|
+
return path5.posix.join(UPLOADS_DIR_REL, `${id}__${sanitizedName}`);
|
|
10057
|
+
}
|
|
10058
|
+
var UploadSizeError = class extends Error {
|
|
10059
|
+
constructor(totalBytes, cap) {
|
|
10060
|
+
super(`Upload payload exceeds ${cap} bytes (got ${totalBytes})`);
|
|
10061
|
+
this.totalBytes = totalBytes;
|
|
10062
|
+
this.cap = cap;
|
|
10063
|
+
this.name = "UploadSizeError";
|
|
10064
|
+
}
|
|
10065
|
+
};
|
|
10066
|
+
var SessionUploadStore = class {
|
|
10067
|
+
constructor(opts = {}) {
|
|
10068
|
+
this.logger = opts.logger;
|
|
10069
|
+
}
|
|
10070
|
+
/**
|
|
10071
|
+
* Decode and write each file. Throws `UploadSizeError` if either a single
|
|
10072
|
+
* file exceeds the per-file cap or the message total exceeds the per-message
|
|
10073
|
+
* cap. On success, returns the manifest entries (in the same order as input).
|
|
10074
|
+
*/
|
|
10075
|
+
async persistFiles(params) {
|
|
10076
|
+
const { cwd, agentId, files, messageId } = params;
|
|
10077
|
+
if (files.length === 0) {
|
|
10078
|
+
return [];
|
|
10079
|
+
}
|
|
10080
|
+
const decoded = [];
|
|
10081
|
+
let totalBytes = 0;
|
|
10082
|
+
for (const file of files) {
|
|
10083
|
+
let bytes;
|
|
10084
|
+
try {
|
|
10085
|
+
bytes = Buffer.from(file.data, "base64");
|
|
10086
|
+
} catch (error) {
|
|
10087
|
+
throw new Error(
|
|
10088
|
+
`Failed to decode base64 payload for "${file.fileName}": ${error instanceof Error ? error.message : String(error)}`
|
|
10089
|
+
);
|
|
10090
|
+
}
|
|
10091
|
+
if (bytes.length > MAX_UPLOAD_BYTES_PER_FILE) {
|
|
10092
|
+
throw new UploadSizeError(bytes.length, MAX_UPLOAD_BYTES_PER_FILE);
|
|
10093
|
+
}
|
|
10094
|
+
totalBytes += bytes.length;
|
|
10095
|
+
if (totalBytes > MAX_UPLOAD_BYTES_PER_MESSAGE) {
|
|
10096
|
+
throw new UploadSizeError(totalBytes, MAX_UPLOAD_BYTES_PER_MESSAGE);
|
|
10097
|
+
}
|
|
10098
|
+
decoded.push({ file, bytes });
|
|
10099
|
+
}
|
|
10100
|
+
await fs.mkdir(manifestDirFor(cwd), { recursive: true });
|
|
10101
|
+
await this.ensureGitignoreEntry(cwd);
|
|
10102
|
+
const manifest = await this.readManifest(cwd);
|
|
10103
|
+
const created = [];
|
|
10104
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10105
|
+
for (const { file, bytes } of decoded) {
|
|
10106
|
+
const id = crypto.randomUUID();
|
|
10107
|
+
const sanitizedName = sanitizeFileName(file.fileName);
|
|
10108
|
+
const relPath = relativeUploadPath(id, sanitizedName);
|
|
10109
|
+
const absPath = path5.join(cwd, relPath);
|
|
10110
|
+
await fs.writeFile(absPath, bytes);
|
|
10111
|
+
const entry = {
|
|
10112
|
+
id,
|
|
10113
|
+
fileName: sanitizedName,
|
|
10114
|
+
mimeType: file.mimeType,
|
|
10115
|
+
size: bytes.length,
|
|
10116
|
+
createdAt,
|
|
10117
|
+
messageId,
|
|
10118
|
+
relativePath: relPath
|
|
10119
|
+
};
|
|
10120
|
+
manifest.uploads.push({ ...entry, agentId });
|
|
10121
|
+
created.push(entry);
|
|
10122
|
+
}
|
|
10123
|
+
await this.writeManifest(cwd, manifest);
|
|
10124
|
+
this.logger?.info(
|
|
10125
|
+
{ agentId, count: created.length, totalBytes },
|
|
10126
|
+
"Persisted chat file uploads"
|
|
10127
|
+
);
|
|
10128
|
+
return created;
|
|
10129
|
+
}
|
|
10130
|
+
/**
|
|
10131
|
+
* Read bytes back from disk by workspace-relative path. Symmetric with
|
|
10132
|
+
* `SessionImageStore.readImageBytes` so the fetch-bytes RPC handler can
|
|
10133
|
+
* route uniformly. Traversal-guarded: refuses anything that escapes
|
|
10134
|
+
* `.appostle/uploaded_files/` or contains `..`.
|
|
10135
|
+
*/
|
|
10136
|
+
async readUploadBytes(params) {
|
|
10137
|
+
const safe = path5.posix.normalize(params.relativePath);
|
|
10138
|
+
if (!safe.startsWith(`${UPLOADS_DIR_REL}/`) || safe.includes("..")) {
|
|
10139
|
+
throw new Error(`Refused to read upload outside ${UPLOADS_DIR_REL}: ${params.relativePath}`);
|
|
10140
|
+
}
|
|
10141
|
+
const abs = path5.join(params.cwd, safe);
|
|
10142
|
+
const data = await fs.readFile(abs);
|
|
10143
|
+
const baseName = path5.posix.basename(safe);
|
|
10144
|
+
const sep4 = baseName.indexOf("__");
|
|
10145
|
+
const fileName = sep4 >= 0 ? baseName.slice(sep4 + 2) : baseName;
|
|
10146
|
+
let mimeType = "application/octet-stream";
|
|
10147
|
+
try {
|
|
10148
|
+
const manifest = await this.readManifest(params.cwd);
|
|
10149
|
+
const match = manifest.uploads.find((entry) => entry.relativePath === safe);
|
|
10150
|
+
if (match) {
|
|
10151
|
+
mimeType = match.mimeType;
|
|
10152
|
+
}
|
|
10153
|
+
} catch {
|
|
10154
|
+
}
|
|
10155
|
+
return { data, mimeType, byteSize: data.length, fileName };
|
|
10156
|
+
}
|
|
10157
|
+
/**
|
|
10158
|
+
* List every file stored for this workspace. Workspace-scoped, NOT
|
|
10159
|
+
* agent-scoped — uploads live under `<workspace>/.appostle/uploaded_files/`
|
|
10160
|
+
* and persist across agent lifecycles, so filtering by `agentId` would hide
|
|
10161
|
+
* files attached by older or sibling agents in the same workspace. (Mirrors
|
|
10162
|
+
* the image store's directory-scan behaviour.) The `agentId` parameter is
|
|
10163
|
+
* preserved on the API for backward-compat but ignored.
|
|
10164
|
+
*
|
|
10165
|
+
* Newest first.
|
|
10166
|
+
*/
|
|
10167
|
+
async listUploads(params) {
|
|
10168
|
+
const manifest = await this.readManifest(params.cwd);
|
|
10169
|
+
return manifest.uploads.map(({ agentId: _agentId, ...rest }) => rest).sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
10170
|
+
}
|
|
10171
|
+
/**
|
|
10172
|
+
* Delete a file by upload id. Workspace-scoped to match `listUploads` — any
|
|
10173
|
+
* agent in the workspace can delete any file. The `agentId` parameter is
|
|
10174
|
+
* preserved on the API for backward-compat but ignored.
|
|
10175
|
+
*/
|
|
10176
|
+
async deleteUpload(params) {
|
|
10177
|
+
const manifest = await this.readManifest(params.cwd);
|
|
10178
|
+
const idx = manifest.uploads.findIndex((entry) => entry.id === params.uploadId);
|
|
10179
|
+
if (idx === -1) {
|
|
10180
|
+
return { deleted: false };
|
|
10181
|
+
}
|
|
10182
|
+
const [removed] = manifest.uploads.splice(idx, 1);
|
|
10183
|
+
if (removed) {
|
|
10184
|
+
const absPath = path5.join(params.cwd, removed.relativePath);
|
|
10185
|
+
try {
|
|
10186
|
+
await fs.unlink(absPath);
|
|
10187
|
+
} catch (error) {
|
|
10188
|
+
this.logger?.warn(
|
|
10189
|
+
{ uploadId: params.uploadId, error: error.message },
|
|
10190
|
+
"deleteUpload: file already missing, removing manifest entry"
|
|
10191
|
+
);
|
|
10192
|
+
}
|
|
10193
|
+
}
|
|
10194
|
+
await this.writeManifest(params.cwd, manifest);
|
|
10195
|
+
return { deleted: true };
|
|
10196
|
+
}
|
|
10197
|
+
async readManifest(cwd) {
|
|
10198
|
+
const file = manifestPathFor(cwd);
|
|
10199
|
+
let raw;
|
|
10200
|
+
try {
|
|
10201
|
+
raw = await fs.readFile(file, "utf8");
|
|
10202
|
+
} catch (error) {
|
|
10203
|
+
if (error.code === "ENOENT") {
|
|
10204
|
+
return emptyManifest();
|
|
10205
|
+
}
|
|
10206
|
+
throw error;
|
|
10207
|
+
}
|
|
10208
|
+
let parsed;
|
|
10209
|
+
try {
|
|
10210
|
+
parsed = JSON.parse(raw);
|
|
10211
|
+
} catch {
|
|
10212
|
+
this.logger?.warn({ file }, "Manifest is not valid JSON; treating as empty");
|
|
10213
|
+
return emptyManifest();
|
|
10214
|
+
}
|
|
10215
|
+
if (typeof parsed === "object" && parsed !== null && "version" in parsed && parsed.version === 1 && Array.isArray(parsed.uploads)) {
|
|
10216
|
+
return parsed;
|
|
10217
|
+
}
|
|
10218
|
+
return emptyManifest();
|
|
10219
|
+
}
|
|
10220
|
+
async writeManifest(cwd, manifest) {
|
|
10221
|
+
const file = manifestPathFor(cwd);
|
|
10222
|
+
const tmp = `${file}.tmp`;
|
|
10223
|
+
await fs.writeFile(tmp, JSON.stringify(manifest, null, 2), "utf8");
|
|
10224
|
+
await fs.rename(tmp, file);
|
|
10225
|
+
}
|
|
10226
|
+
/** Append a `.appostle/` ignore line to the workspace `.gitignore`, idempotently. */
|
|
10227
|
+
async ensureGitignoreEntry(cwd) {
|
|
10228
|
+
const file = path5.join(cwd, ".gitignore");
|
|
10229
|
+
let existing = "";
|
|
10230
|
+
try {
|
|
10231
|
+
existing = await fs.readFile(file, "utf8");
|
|
10232
|
+
} catch (error) {
|
|
10233
|
+
if (error.code !== "ENOENT") {
|
|
10234
|
+
throw error;
|
|
10235
|
+
}
|
|
10236
|
+
}
|
|
10237
|
+
if (existing.includes(GITIGNORE_MARKER_LINE)) {
|
|
10238
|
+
return;
|
|
10239
|
+
}
|
|
10240
|
+
const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
10241
|
+
await fs.writeFile(file, `${existing}${separator}${GITIGNORE_BLOCK}`, "utf8");
|
|
10242
|
+
}
|
|
10243
|
+
};
|
|
10244
|
+
|
|
10245
|
+
// ../server/src/server/uploads/session-image-store.ts
|
|
10246
|
+
import { promises as fs2 } from "node:fs";
|
|
10247
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
10248
|
+
import * as path6 from "node:path";
|
|
10249
|
+
var IMAGES_DIR_REL = path6.posix.join(".appostle", "uploaded_images");
|
|
10250
|
+
var GITIGNORE_MARKER_LINE2 = "# Added by Appostle: uploaded chat attachments";
|
|
10251
|
+
var GITIGNORE_BLOCK2 = `${GITIGNORE_MARKER_LINE2}
|
|
10252
|
+
.appostle/
|
|
10253
|
+
`;
|
|
10254
|
+
var EXTENSION_BY_MIME = {
|
|
10255
|
+
"image/png": "png",
|
|
10256
|
+
"image/jpeg": "jpg",
|
|
10257
|
+
"image/jpg": "jpg",
|
|
10258
|
+
"image/gif": "gif",
|
|
10259
|
+
"image/webp": "webp",
|
|
10260
|
+
"image/avif": "avif",
|
|
10261
|
+
"image/heic": "heic",
|
|
10262
|
+
"image/heif": "heif",
|
|
10263
|
+
"image/tiff": "tiff",
|
|
10264
|
+
"image/bmp": "bmp",
|
|
10265
|
+
"image/svg+xml": "svg"
|
|
10266
|
+
};
|
|
10267
|
+
function extensionForImage(input) {
|
|
10268
|
+
if (input.fileName) {
|
|
10269
|
+
const dot = input.fileName.lastIndexOf(".");
|
|
10270
|
+
if (dot >= 0 && dot < input.fileName.length - 1) {
|
|
10271
|
+
const ext = input.fileName.slice(dot + 1).toLowerCase();
|
|
10272
|
+
if (/^[a-z0-9]{1,8}$/.test(ext)) {
|
|
10273
|
+
return ext;
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
return EXTENSION_BY_MIME[input.mimeType] ?? "img";
|
|
10278
|
+
}
|
|
10279
|
+
function mimeTypeForExtension(ext) {
|
|
10280
|
+
switch (ext) {
|
|
10281
|
+
case "png":
|
|
10282
|
+
return "image/png";
|
|
10283
|
+
case "jpg":
|
|
10284
|
+
case "jpeg":
|
|
10285
|
+
return "image/jpeg";
|
|
10286
|
+
case "gif":
|
|
10287
|
+
return "image/gif";
|
|
10288
|
+
case "webp":
|
|
10289
|
+
return "image/webp";
|
|
10290
|
+
case "avif":
|
|
10291
|
+
return "image/avif";
|
|
10292
|
+
case "heic":
|
|
10293
|
+
return "image/heic";
|
|
10294
|
+
case "heif":
|
|
10295
|
+
return "image/heif";
|
|
10296
|
+
case "tiff":
|
|
10297
|
+
return "image/tiff";
|
|
10298
|
+
case "bmp":
|
|
10299
|
+
return "image/bmp";
|
|
10300
|
+
case "svg":
|
|
10301
|
+
return "image/svg+xml";
|
|
10302
|
+
default:
|
|
10303
|
+
return "application/octet-stream";
|
|
10304
|
+
}
|
|
10305
|
+
}
|
|
10306
|
+
var SessionImageStore = class {
|
|
10307
|
+
constructor(opts = {}) {
|
|
10308
|
+
this.logger = opts.logger;
|
|
10309
|
+
}
|
|
10310
|
+
/**
|
|
10311
|
+
* Decode and write each image. Throws `UploadSizeError` on cap violations.
|
|
10312
|
+
* On success returns one entry per input image (in order).
|
|
10313
|
+
*/
|
|
10314
|
+
async persistImages(params) {
|
|
10315
|
+
const { cwd, agentId, messageId, images } = params;
|
|
10316
|
+
if (images.length === 0) {
|
|
10317
|
+
return [];
|
|
10318
|
+
}
|
|
10319
|
+
const decoded = [];
|
|
10320
|
+
let totalBytes = 0;
|
|
10321
|
+
for (const image of images) {
|
|
10322
|
+
let bytes;
|
|
10323
|
+
try {
|
|
10324
|
+
bytes = Buffer.from(image.data, "base64");
|
|
10325
|
+
} catch (error) {
|
|
10326
|
+
throw new Error(
|
|
10327
|
+
`Failed to decode base64 image: ${error instanceof Error ? error.message : String(error)}`
|
|
10328
|
+
);
|
|
10329
|
+
}
|
|
10330
|
+
if (bytes.length > MAX_UPLOAD_BYTES_PER_FILE) {
|
|
10331
|
+
throw new UploadSizeError(bytes.length, MAX_UPLOAD_BYTES_PER_FILE);
|
|
10332
|
+
}
|
|
10333
|
+
totalBytes += bytes.length;
|
|
10334
|
+
if (totalBytes > MAX_UPLOAD_BYTES_PER_MESSAGE) {
|
|
10335
|
+
throw new UploadSizeError(totalBytes, MAX_UPLOAD_BYTES_PER_MESSAGE);
|
|
10336
|
+
}
|
|
10337
|
+
const hash = createHash2("sha256").update(bytes).digest("hex").slice(0, 16);
|
|
10338
|
+
decoded.push({ image, bytes, hash });
|
|
10339
|
+
}
|
|
10340
|
+
const dir = path6.join(cwd, IMAGES_DIR_REL);
|
|
10341
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
10342
|
+
await this.ensureGitignoreEntry(cwd);
|
|
10343
|
+
const created = [];
|
|
10344
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10345
|
+
const timestampMs = Date.now();
|
|
10346
|
+
for (const entry of decoded) {
|
|
10347
|
+
const ext = extensionForImage({
|
|
10348
|
+
mimeType: entry.image.mimeType,
|
|
10349
|
+
fileName: entry.image.fileName
|
|
10350
|
+
});
|
|
10351
|
+
const baseName = `${timestampMs}-${entry.hash}.${ext}`;
|
|
10352
|
+
const relPath = path6.posix.join(IMAGES_DIR_REL, baseName);
|
|
10353
|
+
const absPath = path6.join(cwd, relPath);
|
|
10354
|
+
await fs2.writeFile(absPath, entry.bytes);
|
|
10355
|
+
created.push({
|
|
10356
|
+
id: entry.image.id ?? randomUUID(),
|
|
10357
|
+
fileName: entry.image.fileName ?? null,
|
|
10358
|
+
mimeType: entry.image.mimeType,
|
|
10359
|
+
size: entry.bytes.length,
|
|
10360
|
+
createdAt,
|
|
10361
|
+
messageId,
|
|
10362
|
+
relativePath: relPath,
|
|
10363
|
+
hash: entry.hash
|
|
10364
|
+
});
|
|
10365
|
+
}
|
|
10366
|
+
this.logger?.info(
|
|
10367
|
+
{ agentId, count: created.length, totalBytes },
|
|
10368
|
+
"Persisted chat image uploads"
|
|
10369
|
+
);
|
|
10370
|
+
return created;
|
|
10371
|
+
}
|
|
10372
|
+
/**
|
|
10373
|
+
* Read bytes back from disk by workspace-relative path. Used by the (future
|
|
10374
|
+
* Phase 4) fetch-bytes RPC and by Phase 3's prompt-build path.
|
|
10375
|
+
*
|
|
10376
|
+
* Defense in depth: refuses anything outside `.appostle/uploaded_images/`
|
|
10377
|
+
* or anything containing `..`. The caller is the trust boundary, but
|
|
10378
|
+
* costing nothing to double-check at the I/O edge.
|
|
10379
|
+
*/
|
|
10380
|
+
async readImageBytes(params) {
|
|
10381
|
+
const safe = path6.posix.normalize(params.relativePath);
|
|
10382
|
+
if (!safe.startsWith(`${IMAGES_DIR_REL}/`) || safe.includes("..")) {
|
|
10383
|
+
throw new Error(`Refused to read image outside ${IMAGES_DIR_REL}: ${params.relativePath}`);
|
|
10384
|
+
}
|
|
10385
|
+
const abs = path6.join(params.cwd, safe);
|
|
10386
|
+
const data = await fs2.readFile(abs);
|
|
10387
|
+
const ext = path6.extname(safe).slice(1).toLowerCase();
|
|
10388
|
+
return { data, mimeType: mimeTypeForExtension(ext), byteSize: data.length };
|
|
10389
|
+
}
|
|
10390
|
+
/**
|
|
10391
|
+
* List every image stored for this workspace. There is no manifest — the
|
|
10392
|
+
* directory listing IS the source of truth. Filenames carry the timestamp
|
|
10393
|
+
* (`<unix_ms>-<hash>.<ext>`); we parse that for `createdAt` and fall back to
|
|
10394
|
+
* file mtime if the prefix is missing.
|
|
10395
|
+
*
|
|
10396
|
+
* Returns newest-first to match the upload-store ordering expected by the UI.
|
|
10397
|
+
*/
|
|
10398
|
+
async listImages(params) {
|
|
10399
|
+
const dir = path6.join(params.cwd, IMAGES_DIR_REL);
|
|
10400
|
+
let names;
|
|
10401
|
+
try {
|
|
10402
|
+
names = await fs2.readdir(dir);
|
|
10403
|
+
} catch (error) {
|
|
10404
|
+
if (error.code === "ENOENT") {
|
|
10405
|
+
return [];
|
|
10406
|
+
}
|
|
10407
|
+
throw error;
|
|
10408
|
+
}
|
|
10409
|
+
const entries = [];
|
|
10410
|
+
for (const name of names) {
|
|
10411
|
+
if (name.startsWith(".")) continue;
|
|
10412
|
+
const abs = path6.join(dir, name);
|
|
10413
|
+
let stat5;
|
|
10414
|
+
try {
|
|
10415
|
+
stat5 = await fs2.stat(abs);
|
|
10416
|
+
} catch {
|
|
10417
|
+
continue;
|
|
10418
|
+
}
|
|
10419
|
+
if (!stat5.isFile()) continue;
|
|
10420
|
+
const ext = path6.extname(name).slice(1).toLowerCase();
|
|
10421
|
+
const mimeType = mimeTypeForExtension(ext);
|
|
10422
|
+
let createdAt;
|
|
10423
|
+
const dash = name.indexOf("-");
|
|
10424
|
+
const msCandidate = dash > 0 ? Number(name.slice(0, dash)) : Number.NaN;
|
|
10425
|
+
if (Number.isFinite(msCandidate) && msCandidate > 0) {
|
|
10426
|
+
createdAt = new Date(msCandidate).toISOString();
|
|
10427
|
+
} else {
|
|
10428
|
+
createdAt = stat5.mtime.toISOString();
|
|
10429
|
+
}
|
|
10430
|
+
const relPath = path6.posix.join(IMAGES_DIR_REL, name);
|
|
10431
|
+
entries.push({
|
|
10432
|
+
id: relPath,
|
|
10433
|
+
fileName: name,
|
|
10434
|
+
mimeType,
|
|
10435
|
+
size: stat5.size,
|
|
10436
|
+
createdAt,
|
|
10437
|
+
relativePath: relPath
|
|
10438
|
+
});
|
|
10439
|
+
}
|
|
10440
|
+
entries.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
10441
|
+
return entries;
|
|
10442
|
+
}
|
|
10443
|
+
/**
|
|
10444
|
+
* Delete a single stored image by workspace-relative path. Same traversal
|
|
10445
|
+
* guard as `readImageBytes`. Idempotent: returns `{ deleted: false }` if
|
|
10446
|
+
* the file is already gone (so the caller can refresh its listing).
|
|
10447
|
+
*/
|
|
10448
|
+
async deleteImage(params) {
|
|
10449
|
+
const safe = path6.posix.normalize(params.relativePath);
|
|
10450
|
+
if (!safe.startsWith(`${IMAGES_DIR_REL}/`) || safe.includes("..")) {
|
|
10451
|
+
throw new Error(`Refused to delete image outside ${IMAGES_DIR_REL}: ${params.relativePath}`);
|
|
10452
|
+
}
|
|
10453
|
+
const abs = path6.join(params.cwd, safe);
|
|
10454
|
+
try {
|
|
10455
|
+
await fs2.unlink(abs);
|
|
10456
|
+
return { deleted: true };
|
|
10457
|
+
} catch (error) {
|
|
10458
|
+
if (error.code === "ENOENT") {
|
|
10459
|
+
return { deleted: false };
|
|
10460
|
+
}
|
|
10461
|
+
throw error;
|
|
10462
|
+
}
|
|
10463
|
+
}
|
|
10464
|
+
async ensureGitignoreEntry(cwd) {
|
|
10465
|
+
const file = path6.join(cwd, ".gitignore");
|
|
10466
|
+
let existing = "";
|
|
10467
|
+
try {
|
|
10468
|
+
existing = await fs2.readFile(file, "utf8");
|
|
10469
|
+
} catch (error) {
|
|
10470
|
+
if (error.code !== "ENOENT") {
|
|
10471
|
+
throw error;
|
|
10472
|
+
}
|
|
10473
|
+
}
|
|
10474
|
+
if (existing.includes(GITIGNORE_MARKER_LINE2)) {
|
|
10475
|
+
return;
|
|
10476
|
+
}
|
|
10477
|
+
const sep4 = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
10478
|
+
await fs2.writeFile(file, `${existing}${sep4}${GITIGNORE_BLOCK2}`, "utf8");
|
|
10479
|
+
}
|
|
10480
|
+
};
|
|
10481
|
+
|
|
9735
10482
|
// ../server/src/server/session.ts
|
|
9736
10483
|
import { experimental_createMCPClient } from "ai";
|
|
9737
10484
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -9924,14 +10671,14 @@ function parseDiff(diffText) {
|
|
|
9924
10671
|
const firstLine = lines[0];
|
|
9925
10672
|
const isNew = section.includes("new file mode") || section.includes("--- /dev/null");
|
|
9926
10673
|
const isDeleted = section.includes("deleted file mode") || section.includes("+++ /dev/null");
|
|
9927
|
-
let
|
|
10674
|
+
let path26 = "unknown";
|
|
9928
10675
|
const pathMatch = firstLine.match(/a\/(.*?) b\//);
|
|
9929
10676
|
if (pathMatch) {
|
|
9930
|
-
|
|
10677
|
+
path26 = pathMatch[1];
|
|
9931
10678
|
} else {
|
|
9932
10679
|
const newFileMatch = firstLine.match(/b\/(.+)$/);
|
|
9933
10680
|
if (newFileMatch) {
|
|
9934
|
-
|
|
10681
|
+
path26 = newFileMatch[1];
|
|
9935
10682
|
}
|
|
9936
10683
|
}
|
|
9937
10684
|
const hunks = [];
|
|
@@ -9975,7 +10722,7 @@ function parseDiff(diffText) {
|
|
|
9975
10722
|
if (currentHunk) {
|
|
9976
10723
|
hunks.push(currentHunk);
|
|
9977
10724
|
}
|
|
9978
|
-
files.push({ path:
|
|
10725
|
+
files.push({ path: path26, isNew, isDeleted, additions, deletions, hunks });
|
|
9979
10726
|
}
|
|
9980
10727
|
return files;
|
|
9981
10728
|
}
|
|
@@ -10031,9 +10778,9 @@ function buildTokenLookup(lineMap, highlighted) {
|
|
|
10031
10778
|
}
|
|
10032
10779
|
return lookup2;
|
|
10033
10780
|
}
|
|
10034
|
-
function buildFullFileTokenLookup(fileContent,
|
|
10781
|
+
function buildFullFileTokenLookup(fileContent, path26) {
|
|
10035
10782
|
const lookup2 = /* @__PURE__ */ new Map();
|
|
10036
|
-
const highlighted = highlightCode(fileContent,
|
|
10783
|
+
const highlighted = highlightCode(fileContent, path26);
|
|
10037
10784
|
for (let i = 0; i < highlighted.length; i++) {
|
|
10038
10785
|
lookup2.set(i + 1, highlighted[i]);
|
|
10039
10786
|
}
|
|
@@ -11734,11 +12481,11 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
|
|
|
11734
12481
|
}
|
|
11735
12482
|
continue;
|
|
11736
12483
|
}
|
|
11737
|
-
const
|
|
11738
|
-
if (!
|
|
12484
|
+
const path26 = tabParts[1];
|
|
12485
|
+
if (!path26) continue;
|
|
11739
12486
|
const code = rawStatus[0];
|
|
11740
12487
|
changes.push({
|
|
11741
|
-
path:
|
|
12488
|
+
path: path26,
|
|
11742
12489
|
status: rawStatus,
|
|
11743
12490
|
isNew: code === "A",
|
|
11744
12491
|
isDeleted: code === "D"
|
|
@@ -11773,9 +12520,9 @@ async function listCheckoutFileChanges(cwd, ref, ignoreWhitespace = false) {
|
|
|
11773
12520
|
}
|
|
11774
12521
|
return Array.from(byPath.values());
|
|
11775
12522
|
}
|
|
11776
|
-
async function readGitFileContentAtRef(cwd, ref,
|
|
12523
|
+
async function readGitFileContentAtRef(cwd, ref, path26) {
|
|
11777
12524
|
try {
|
|
11778
|
-
const { stdout } = await runGitCommand(["show", `${ref}:${
|
|
12525
|
+
const { stdout } = await runGitCommand(["show", `${ref}:${path26}`], {
|
|
11779
12526
|
cwd,
|
|
11780
12527
|
env: READ_ONLY_GIT_ENV2
|
|
11781
12528
|
});
|
|
@@ -11836,21 +12583,21 @@ async function getTrackedNumstatByPath(cwd, ref, ignoreWhitespace = false) {
|
|
|
11836
12583
|
const additionsField = parts[0] ?? "";
|
|
11837
12584
|
const deletionsField = parts[1] ?? "";
|
|
11838
12585
|
const rawPath = parts.slice(2).join(" ");
|
|
11839
|
-
const
|
|
11840
|
-
if (!
|
|
12586
|
+
const path26 = normalizeNumstatPath(rawPath);
|
|
12587
|
+
if (!path26) {
|
|
11841
12588
|
continue;
|
|
11842
12589
|
}
|
|
11843
12590
|
if (additionsField === "-" || deletionsField === "-") {
|
|
11844
|
-
stats.set(
|
|
12591
|
+
stats.set(path26, { additions: 0, deletions: 0, isBinary: true });
|
|
11845
12592
|
continue;
|
|
11846
12593
|
}
|
|
11847
12594
|
const additions = Number.parseInt(additionsField, 10);
|
|
11848
12595
|
const deletions = Number.parseInt(deletionsField, 10);
|
|
11849
12596
|
if (Number.isNaN(additions) || Number.isNaN(deletions)) {
|
|
11850
|
-
stats.set(
|
|
12597
|
+
stats.set(path26, null);
|
|
11851
12598
|
continue;
|
|
11852
12599
|
}
|
|
11853
|
-
stats.set(
|
|
12600
|
+
stats.set(path26, { additions, deletions, isBinary: false });
|
|
11854
12601
|
}
|
|
11855
12602
|
return stats;
|
|
11856
12603
|
}
|
|
@@ -12435,10 +13182,10 @@ async function listUncommittedFiles(cwd) {
|
|
|
12435
13182
|
if (dest) results.push({ path: dest, changeType: code });
|
|
12436
13183
|
continue;
|
|
12437
13184
|
}
|
|
12438
|
-
const
|
|
12439
|
-
if (!
|
|
13185
|
+
const path26 = parts[1];
|
|
13186
|
+
if (!path26) continue;
|
|
12440
13187
|
if (code === "A" || code === "M" || code === "D") {
|
|
12441
|
-
results.push({ path:
|
|
13188
|
+
results.push({ path: path26, changeType: code });
|
|
12442
13189
|
}
|
|
12443
13190
|
}
|
|
12444
13191
|
} catch {
|
|
@@ -14136,11 +14883,11 @@ async function spawnWorktreeScripts(options) {
|
|
|
14136
14883
|
}
|
|
14137
14884
|
|
|
14138
14885
|
// ../server/src/server/agent/providers/claude-agent.ts
|
|
14139
|
-
import { randomUUID } from "node:crypto";
|
|
14140
|
-
import
|
|
14886
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14887
|
+
import fs3 from "node:fs";
|
|
14141
14888
|
import { promises } from "node:fs";
|
|
14142
14889
|
import os2 from "node:os";
|
|
14143
|
-
import
|
|
14890
|
+
import path8 from "node:path";
|
|
14144
14891
|
import {
|
|
14145
14892
|
query
|
|
14146
14893
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -15215,7 +15962,7 @@ function mapClaudeCanceledToolCall(params) {
|
|
|
15215
15962
|
}
|
|
15216
15963
|
|
|
15217
15964
|
// ../server/src/server/agent/providers/claude/task-notification-tool-call.ts
|
|
15218
|
-
import { createHash as
|
|
15965
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
15219
15966
|
import { z as z21 } from "zod";
|
|
15220
15967
|
var TASK_NOTIFICATION_MARKER = "<task-notification>";
|
|
15221
15968
|
var TAG_NAME_PATTERN = /[.*+?^${}()|[\]\\]/g;
|
|
@@ -15352,7 +16099,7 @@ function buildTaskNotificationCallId(envelope) {
|
|
|
15352
16099
|
return `task_notification_${taskSegment}`;
|
|
15353
16100
|
}
|
|
15354
16101
|
const seed = [envelope.status, envelope.summary, envelope.outputFile, envelope.rawText].filter((value) => typeof value === "string").join("|") || "task_notification";
|
|
15355
|
-
const digest =
|
|
16102
|
+
const digest = createHash3("sha1").update(seed).digest("hex").slice(0, 12);
|
|
15356
16103
|
return `task_notification_${digest}`;
|
|
15357
16104
|
}
|
|
15358
16105
|
function buildTaskNotificationLabel(envelope) {
|
|
@@ -15531,6 +16278,40 @@ function normalizeClaudeRuntimeModelId(value) {
|
|
|
15531
16278
|
return `claude-${family}-${major}-${minor}${suffix}`;
|
|
15532
16279
|
}
|
|
15533
16280
|
|
|
16281
|
+
// ../server/src/server/agent/providers/claude-feature-definitions.ts
|
|
16282
|
+
var CLAUDE_FAST_MODE_SUPPORTED_MODEL_PREFIXES = ["claude-opus-"];
|
|
16283
|
+
var CLAUDE_FAST_MODE_FEATURE = {
|
|
16284
|
+
type: "toggle",
|
|
16285
|
+
id: "fast_mode",
|
|
16286
|
+
label: "Fast",
|
|
16287
|
+
description: "Priority inference for faster responses",
|
|
16288
|
+
tooltip: "Toggle fast mode",
|
|
16289
|
+
icon: "zap"
|
|
16290
|
+
};
|
|
16291
|
+
function normalizeClaudeModelId(modelId) {
|
|
16292
|
+
const normalized = typeof modelId === "string" ? modelId.trim() : "";
|
|
16293
|
+
return normalized.length > 0 ? normalized : null;
|
|
16294
|
+
}
|
|
16295
|
+
function claudeModelSupportsFastMode(modelId) {
|
|
16296
|
+
const normalizedModelId = normalizeClaudeModelId(modelId);
|
|
16297
|
+
if (!normalizedModelId) {
|
|
16298
|
+
return false;
|
|
16299
|
+
}
|
|
16300
|
+
return CLAUDE_FAST_MODE_SUPPORTED_MODEL_PREFIXES.some(
|
|
16301
|
+
(prefix) => normalizedModelId.startsWith(prefix)
|
|
16302
|
+
);
|
|
16303
|
+
}
|
|
16304
|
+
function buildClaudeFeatures(input) {
|
|
16305
|
+
const features = [];
|
|
16306
|
+
if (claudeModelSupportsFastMode(input.modelId)) {
|
|
16307
|
+
features.push({
|
|
16308
|
+
...CLAUDE_FAST_MODE_FEATURE,
|
|
16309
|
+
value: input.fastModeEnabled
|
|
16310
|
+
});
|
|
16311
|
+
}
|
|
16312
|
+
return features;
|
|
16313
|
+
}
|
|
16314
|
+
|
|
15534
16315
|
// ../server/src/server/agent/providers/claude/partial-json.ts
|
|
15535
16316
|
function skipWhitespace(input, index) {
|
|
15536
16317
|
let currentIndex = index;
|
|
@@ -16098,62 +16879,8 @@ async function resolveBinaryVersion(binaryPath) {
|
|
|
16098
16879
|
}
|
|
16099
16880
|
}
|
|
16100
16881
|
|
|
16101
|
-
// ../server/src/server/agent/prompt-attachments.ts
|
|
16102
|
-
function renderPromptAttachmentAsText(attachment) {
|
|
16103
|
-
switch (attachment.type) {
|
|
16104
|
-
case "github_pr": {
|
|
16105
|
-
const lines = [`GitHub PR #${attachment.number}: ${attachment.title}`, attachment.url];
|
|
16106
|
-
if (attachment.baseRefName) {
|
|
16107
|
-
lines.push(`Base: ${attachment.baseRefName}`);
|
|
16108
|
-
}
|
|
16109
|
-
if (attachment.headRefName) {
|
|
16110
|
-
lines.push(`Head: ${attachment.headRefName}`);
|
|
16111
|
-
}
|
|
16112
|
-
if (attachment.body) {
|
|
16113
|
-
lines.push("", attachment.body);
|
|
16114
|
-
}
|
|
16115
|
-
return lines.join("\n");
|
|
16116
|
-
}
|
|
16117
|
-
case "github_issue": {
|
|
16118
|
-
const lines = [`GitHub Issue #${attachment.number}: ${attachment.title}`, attachment.url];
|
|
16119
|
-
if (attachment.body) {
|
|
16120
|
-
lines.push("", attachment.body);
|
|
16121
|
-
}
|
|
16122
|
-
return lines.join("\n");
|
|
16123
|
-
}
|
|
16124
|
-
case "gitlab_mr": {
|
|
16125
|
-
const lines = [`GitLab MR !${attachment.iid}: ${attachment.title}`, attachment.url];
|
|
16126
|
-
if (attachment.targetBranch) {
|
|
16127
|
-
lines.push(`Target: ${attachment.targetBranch}`);
|
|
16128
|
-
}
|
|
16129
|
-
if (attachment.sourceBranch) {
|
|
16130
|
-
lines.push(`Source: ${attachment.sourceBranch}`);
|
|
16131
|
-
}
|
|
16132
|
-
if (attachment.body) {
|
|
16133
|
-
lines.push("", attachment.body);
|
|
16134
|
-
}
|
|
16135
|
-
return lines.join("\n");
|
|
16136
|
-
}
|
|
16137
|
-
case "gitlab_issue": {
|
|
16138
|
-
const lines = [`GitLab Issue #${attachment.iid}: ${attachment.title}`, attachment.url];
|
|
16139
|
-
if (attachment.body) {
|
|
16140
|
-
lines.push("", attachment.body);
|
|
16141
|
-
}
|
|
16142
|
-
return lines.join("\n");
|
|
16143
|
-
}
|
|
16144
|
-
}
|
|
16145
|
-
}
|
|
16146
|
-
function findGitHubPrAttachment(attachments) {
|
|
16147
|
-
if (!attachments) {
|
|
16148
|
-
return null;
|
|
16149
|
-
}
|
|
16150
|
-
return attachments.find(
|
|
16151
|
-
(attachment) => attachment.type === "github_pr"
|
|
16152
|
-
) ?? null;
|
|
16153
|
-
}
|
|
16154
|
-
|
|
16155
16882
|
// ../server/src/server/agent/plan-filename.ts
|
|
16156
|
-
import
|
|
16883
|
+
import path7 from "node:path";
|
|
16157
16884
|
var MAX_SLUG_LENGTH2 = 40;
|
|
16158
16885
|
function slugifyPlanName(name) {
|
|
16159
16886
|
if (typeof name !== "string") {
|
|
@@ -16202,12 +16929,12 @@ function extractPlanNameFromFrontmatter(content) {
|
|
|
16202
16929
|
}
|
|
16203
16930
|
function resolvePlanFilename(options) {
|
|
16204
16931
|
const { originalPath, newSlug, existsSync: existsSync14 } = options;
|
|
16205
|
-
const dir =
|
|
16932
|
+
const dir = path7.dirname(originalPath);
|
|
16206
16933
|
const base = `${newSlug}.md`;
|
|
16207
|
-
let candidate =
|
|
16934
|
+
let candidate = path7.join(dir, base);
|
|
16208
16935
|
let counter = 2;
|
|
16209
16936
|
while (candidate !== originalPath && existsSync14(candidate)) {
|
|
16210
|
-
candidate =
|
|
16937
|
+
candidate = path7.join(dir, `${newSlug}-${counter}.md`);
|
|
16211
16938
|
counter += 1;
|
|
16212
16939
|
}
|
|
16213
16940
|
return candidate;
|
|
@@ -16766,12 +17493,12 @@ var homeMcpServersCache = null;
|
|
|
16766
17493
|
function loadHomeMcpServers(logger) {
|
|
16767
17494
|
try {
|
|
16768
17495
|
const home = process.env.HOME ?? os2.homedir();
|
|
16769
|
-
const filePath =
|
|
16770
|
-
const stat5 =
|
|
17496
|
+
const filePath = path8.join(home, ".claude.json");
|
|
17497
|
+
const stat5 = fs3.statSync(filePath);
|
|
16771
17498
|
if (homeMcpServersCache && homeMcpServersCache.mtimeMs === stat5.mtimeMs) {
|
|
16772
17499
|
return homeMcpServersCache.servers;
|
|
16773
17500
|
}
|
|
16774
|
-
const raw =
|
|
17501
|
+
const raw = fs3.readFileSync(filePath, "utf8");
|
|
16775
17502
|
const parsed = JSON.parse(raw);
|
|
16776
17503
|
if (!isMetadata(parsed)) return null;
|
|
16777
17504
|
const rawServers = parsed.mcpServers;
|
|
@@ -17118,8 +17845,8 @@ var ClaudeAgentClient = class {
|
|
|
17118
17845
|
return getClaudeModels();
|
|
17119
17846
|
}
|
|
17120
17847
|
async listPersistedAgents(options) {
|
|
17121
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
17122
|
-
const projectsRoot =
|
|
17848
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path8.join(os2.homedir(), ".claude");
|
|
17849
|
+
const projectsRoot = path8.join(configDir, "projects");
|
|
17123
17850
|
if (!await pathExists2(projectsRoot)) {
|
|
17124
17851
|
return [];
|
|
17125
17852
|
}
|
|
@@ -17340,9 +18067,10 @@ var ClaudeAgentSession = class {
|
|
|
17340
18067
|
this.userMessageIds = [];
|
|
17341
18068
|
this.recentStderr = "";
|
|
17342
18069
|
this.closed = false;
|
|
18070
|
+
this.fastModeEnabled = false;
|
|
17343
18071
|
this.handlePermissionRequest = async (toolName, input, options) => {
|
|
17344
18072
|
this.maybeRewritePlanFilename(toolName, input);
|
|
17345
|
-
const requestId = `permission-${
|
|
18073
|
+
const requestId = `permission-${randomUUID2()}`;
|
|
17346
18074
|
const kind = resolvePermissionKind(toolName, input);
|
|
17347
18075
|
if (kind === "tool") {
|
|
17348
18076
|
if (this.currentMode === "bypassPermissions") {
|
|
@@ -17438,10 +18166,17 @@ var ClaudeAgentSession = class {
|
|
|
17438
18166
|
if (this.currentMode !== "plan") {
|
|
17439
18167
|
this.planResumeMode = this.currentMode;
|
|
17440
18168
|
}
|
|
18169
|
+
this.fastModeEnabled = Boolean(config.featureValues?.fast_mode);
|
|
17441
18170
|
}
|
|
17442
18171
|
get id() {
|
|
17443
18172
|
return this.claudeSessionId;
|
|
17444
18173
|
}
|
|
18174
|
+
get features() {
|
|
18175
|
+
return buildClaudeFeatures({
|
|
18176
|
+
modelId: this.config.model,
|
|
18177
|
+
fastModeEnabled: this.fastModeEnabled
|
|
18178
|
+
});
|
|
18179
|
+
}
|
|
17445
18180
|
async getRuntimeInfo() {
|
|
17446
18181
|
if (this.cachedRuntimeInfo) {
|
|
17447
18182
|
return { ...this.cachedRuntimeInfo };
|
|
@@ -17680,6 +18415,41 @@ var ClaudeAgentSession = class {
|
|
|
17680
18415
|
}
|
|
17681
18416
|
this.queryRestartNeeded = true;
|
|
17682
18417
|
}
|
|
18418
|
+
async setFeature(featureId, value) {
|
|
18419
|
+
if (featureId === "fast_mode") {
|
|
18420
|
+
const next = Boolean(value);
|
|
18421
|
+
if (next === this.fastModeEnabled) {
|
|
18422
|
+
return;
|
|
18423
|
+
}
|
|
18424
|
+
this.fastModeEnabled = next;
|
|
18425
|
+
this.config.featureValues = {
|
|
18426
|
+
...this.config.featureValues ?? {},
|
|
18427
|
+
fast_mode: next
|
|
18428
|
+
};
|
|
18429
|
+
this.cachedRuntimeInfo = null;
|
|
18430
|
+
if (this.query?.applyFlagSettings) {
|
|
18431
|
+
await this.query.applyFlagSettings({ fastMode: next });
|
|
18432
|
+
const queryWithGetSettings = this.query;
|
|
18433
|
+
if (typeof queryWithGetSettings.getSettings === "function") {
|
|
18434
|
+
try {
|
|
18435
|
+
const effective = await queryWithGetSettings.getSettings();
|
|
18436
|
+
const observedFastMode = effective && typeof effective === "object" && "fastMode" in effective && typeof effective.fastMode === "boolean" ? effective.fastMode : null;
|
|
18437
|
+
this.logger.info(
|
|
18438
|
+
{ featureId, requested: next, observed: observedFastMode },
|
|
18439
|
+
"claude: fast_mode read-back from SDK"
|
|
18440
|
+
);
|
|
18441
|
+
} catch (error) {
|
|
18442
|
+
this.logger.warn(
|
|
18443
|
+
{ err: error, featureId },
|
|
18444
|
+
"claude: fast_mode getSettings read-back failed"
|
|
18445
|
+
);
|
|
18446
|
+
}
|
|
18447
|
+
}
|
|
18448
|
+
}
|
|
18449
|
+
return;
|
|
18450
|
+
}
|
|
18451
|
+
throw new Error(`Unknown feature: ${featureId}`);
|
|
18452
|
+
}
|
|
17683
18453
|
getPendingPermissions() {
|
|
17684
18454
|
return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
|
|
17685
18455
|
}
|
|
@@ -17931,12 +18701,12 @@ var ClaudeAgentSession = class {
|
|
|
17931
18701
|
return [];
|
|
17932
18702
|
}
|
|
17933
18703
|
const historyPath = this.resolveHistoryPath(this.claudeSessionId);
|
|
17934
|
-
if (!historyPath || !
|
|
18704
|
+
if (!historyPath || !fs3.existsSync(historyPath)) {
|
|
17935
18705
|
return [];
|
|
17936
18706
|
}
|
|
17937
18707
|
try {
|
|
17938
18708
|
const ids = [];
|
|
17939
|
-
const content =
|
|
18709
|
+
const content = fs3.readFileSync(historyPath, "utf8");
|
|
17940
18710
|
for (const line of content.split(/\n+/)) {
|
|
17941
18711
|
const trimmed = line.trim();
|
|
17942
18712
|
if (!trimmed) continue;
|
|
@@ -18082,6 +18852,10 @@ var ClaudeAgentSession = class {
|
|
|
18082
18852
|
...this.claudeSessionId ? { resume: this.claudeSessionId } : {},
|
|
18083
18853
|
...thinking ? { thinking } : {},
|
|
18084
18854
|
...effort ? { effort } : {},
|
|
18855
|
+
// Initial-spawn path for fast mode: bake the flag into the new
|
|
18856
|
+
// process's flag-settings layer. Mid-session toggles go through
|
|
18857
|
+
// setFeature → query.applyFlagSettings instead (no restart needed).
|
|
18858
|
+
...this.fastModeEnabled ? { settings: { fastMode: true } } : {},
|
|
18085
18859
|
...this.config.extra?.claude
|
|
18086
18860
|
};
|
|
18087
18861
|
if (this.config.mcpServers) {
|
|
@@ -18129,7 +18903,9 @@ var ClaudeAgentSession = class {
|
|
|
18129
18903
|
if (Array.isArray(prompt)) {
|
|
18130
18904
|
for (const chunk of prompt) {
|
|
18131
18905
|
if (chunk.type === "text") {
|
|
18132
|
-
|
|
18906
|
+
if (chunk.text.length > 0) {
|
|
18907
|
+
content.push({ type: "text", text: chunk.text });
|
|
18908
|
+
}
|
|
18133
18909
|
} else if (chunk.type === "image") {
|
|
18134
18910
|
content.push({
|
|
18135
18911
|
type: "image",
|
|
@@ -18140,13 +18916,19 @@ var ClaudeAgentSession = class {
|
|
|
18140
18916
|
}
|
|
18141
18917
|
});
|
|
18142
18918
|
} else if (chunk.type === "github_pr" || chunk.type === "github_issue" || chunk.type === "gitlab_mr" || chunk.type === "gitlab_issue") {
|
|
18143
|
-
|
|
18919
|
+
const rendered = renderPromptAttachmentAsText(chunk);
|
|
18920
|
+
if (rendered.length > 0) {
|
|
18921
|
+
content.push({ type: "text", text: rendered });
|
|
18922
|
+
}
|
|
18144
18923
|
}
|
|
18145
18924
|
}
|
|
18146
|
-
} else {
|
|
18925
|
+
} else if (prompt.length > 0) {
|
|
18147
18926
|
content.push({ type: "text", text: prompt });
|
|
18148
18927
|
}
|
|
18149
|
-
|
|
18928
|
+
if (content.length === 0) {
|
|
18929
|
+
content.push({ type: "text", text: "(empty message)" });
|
|
18930
|
+
}
|
|
18931
|
+
const messageId = randomUUID2();
|
|
18150
18932
|
this.rememberUserMessageId(messageId);
|
|
18151
18933
|
return {
|
|
18152
18934
|
type: "user",
|
|
@@ -18899,9 +19681,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
18899
19681
|
if (typeof filePath !== "string" || typeof content !== "string") {
|
|
18900
19682
|
return;
|
|
18901
19683
|
}
|
|
18902
|
-
const dir =
|
|
18903
|
-
const dirName =
|
|
18904
|
-
const baseName =
|
|
19684
|
+
const dir = path8.dirname(filePath);
|
|
19685
|
+
const dirName = path8.basename(dir);
|
|
19686
|
+
const baseName = path8.basename(filePath);
|
|
18905
19687
|
if (dirName !== ".plans" || !baseName.endsWith(".md")) {
|
|
18906
19688
|
return;
|
|
18907
19689
|
}
|
|
@@ -18920,7 +19702,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
18920
19702
|
const newPath = resolvePlanFilename({
|
|
18921
19703
|
originalPath: filePath,
|
|
18922
19704
|
newSlug: slug,
|
|
18923
|
-
existsSync:
|
|
19705
|
+
existsSync: fs3.existsSync
|
|
18924
19706
|
});
|
|
18925
19707
|
if (newPath === filePath) {
|
|
18926
19708
|
return;
|
|
@@ -19008,10 +19790,10 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
19008
19790
|
loadPersistedHistory(sessionId) {
|
|
19009
19791
|
try {
|
|
19010
19792
|
const historyPath = this.resolveHistoryPath(sessionId);
|
|
19011
|
-
if (!historyPath || !
|
|
19793
|
+
if (!historyPath || !fs3.existsSync(historyPath)) {
|
|
19012
19794
|
return;
|
|
19013
19795
|
}
|
|
19014
|
-
this.ingestPersistedHistory(
|
|
19796
|
+
this.ingestPersistedHistory(fs3.readFileSync(historyPath, "utf8"));
|
|
19015
19797
|
} catch (error) {
|
|
19016
19798
|
}
|
|
19017
19799
|
}
|
|
@@ -19054,9 +19836,9 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
19054
19836
|
const cwd = this.config.cwd;
|
|
19055
19837
|
if (!cwd) return null;
|
|
19056
19838
|
const sanitized = sanitizeClaudeProjectPath(cwd);
|
|
19057
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
19058
|
-
const dir =
|
|
19059
|
-
return
|
|
19839
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path8.join(os2.homedir(), ".claude");
|
|
19840
|
+
const dir = path8.join(configDir, "projects", sanitized);
|
|
19841
|
+
return path8.join(dir, `${sessionId}.jsonl`);
|
|
19060
19842
|
}
|
|
19061
19843
|
convertHistoryEntry(entry) {
|
|
19062
19844
|
return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
|
|
@@ -19518,7 +20300,7 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
19518
20300
|
}
|
|
19519
20301
|
detectFileKind(filePath) {
|
|
19520
20302
|
try {
|
|
19521
|
-
return
|
|
20303
|
+
return fs3.existsSync(filePath) ? "update" : "add";
|
|
19522
20304
|
} catch {
|
|
19523
20305
|
return "update";
|
|
19524
20306
|
}
|
|
@@ -19529,8 +20311,8 @@ ${error.stack ?? ""}` : JSON.stringify(error);
|
|
|
19529
20311
|
}
|
|
19530
20312
|
const cwd = this.config.cwd;
|
|
19531
20313
|
if (cwd && target.startsWith(cwd)) {
|
|
19532
|
-
const relative =
|
|
19533
|
-
return relative.length > 0 ? relative :
|
|
20314
|
+
const relative = path8.relative(cwd, target);
|
|
20315
|
+
return relative.length > 0 ? relative : path8.basename(target);
|
|
19534
20316
|
}
|
|
19535
20317
|
return target;
|
|
19536
20318
|
}
|
|
@@ -19719,7 +20501,7 @@ async function collectRecentClaudeSessions(root, limit) {
|
|
|
19719
20501
|
}
|
|
19720
20502
|
const candidates = [];
|
|
19721
20503
|
for (const dirName of projectDirs) {
|
|
19722
|
-
const projectPath =
|
|
20504
|
+
const projectPath = path8.join(root, dirName);
|
|
19723
20505
|
let stats;
|
|
19724
20506
|
try {
|
|
19725
20507
|
stats = await fsPromises.stat(projectPath);
|
|
@@ -19739,7 +20521,7 @@ async function collectRecentClaudeSessions(root, limit) {
|
|
|
19739
20521
|
if (!file.endsWith(".jsonl")) {
|
|
19740
20522
|
continue;
|
|
19741
20523
|
}
|
|
19742
|
-
const fullPath =
|
|
20524
|
+
const fullPath = path8.join(projectPath, file);
|
|
19743
20525
|
try {
|
|
19744
20526
|
const fileStats = await fsPromises.stat(fullPath);
|
|
19745
20527
|
candidates.push({ path: fullPath, mtime: fileStats.mtime });
|
|
@@ -19914,17 +20696,17 @@ function unescapePartialJsonString(buffer, start) {
|
|
|
19914
20696
|
|
|
19915
20697
|
// ../server/src/server/agent/providers/codex-app-server-agent.ts
|
|
19916
20698
|
import { execSync as execSync2 } from "node:child_process";
|
|
19917
|
-
import { randomUUID as
|
|
19918
|
-
import
|
|
20699
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
20700
|
+
import fs5 from "node:fs/promises";
|
|
19919
20701
|
import os4 from "node:os";
|
|
19920
|
-
import
|
|
20702
|
+
import path10 from "node:path";
|
|
19921
20703
|
import readline from "node:readline";
|
|
19922
20704
|
import { z as z25 } from "zod";
|
|
19923
20705
|
|
|
19924
20706
|
// ../server/src/server/agent/providers/codex-rollout-timeline.ts
|
|
19925
|
-
import { promises as
|
|
20707
|
+
import { promises as fs4 } from "node:fs";
|
|
19926
20708
|
import os3 from "node:os";
|
|
19927
|
-
import
|
|
20709
|
+
import path9 from "node:path";
|
|
19928
20710
|
import { z as z24 } from "zod";
|
|
19929
20711
|
|
|
19930
20712
|
// ../server/src/server/agent/providers/codex/tool-call-mapper.ts
|
|
@@ -20376,14 +21158,14 @@ function codexApplyPatchToUnifiedDiff(text) {
|
|
|
20376
21158
|
for (const line of lines) {
|
|
20377
21159
|
const directive = parseCodexApplyPatchDirective(line);
|
|
20378
21160
|
if (directive) {
|
|
20379
|
-
const
|
|
20380
|
-
if (
|
|
21161
|
+
const path26 = normalizeDiffHeaderPath(directive.path);
|
|
21162
|
+
if (path26.length > 0) {
|
|
20381
21163
|
if (output.length > 0 && output[output.length - 1] !== "") {
|
|
20382
21164
|
output.push("");
|
|
20383
21165
|
}
|
|
20384
|
-
const left = directive.kind === "add" ? "/dev/null" : `a/${
|
|
20385
|
-
const right = directive.kind === "delete" ? "/dev/null" : `b/${
|
|
20386
|
-
output.push(`diff --git a/${
|
|
21166
|
+
const left = directive.kind === "add" ? "/dev/null" : `a/${path26}`;
|
|
21167
|
+
const right = directive.kind === "delete" ? "/dev/null" : `b/${path26}`;
|
|
21168
|
+
output.push(`diff --git a/${path26} b/${path26}`);
|
|
20387
21169
|
output.push(`--- ${left}`);
|
|
20388
21170
|
output.push(`+++ ${right}`);
|
|
20389
21171
|
sawDiffContent = true;
|
|
@@ -20453,9 +21235,9 @@ function asEditTextFields(text) {
|
|
|
20453
21235
|
function normalizeRolloutEditInput(input) {
|
|
20454
21236
|
if (typeof input === "string") {
|
|
20455
21237
|
const textFields2 = asEditTextFields(input);
|
|
20456
|
-
const
|
|
21238
|
+
const path26 = extractPatchPrimaryFilePath(input);
|
|
20457
21239
|
return {
|
|
20458
|
-
...
|
|
21240
|
+
...path26 ? { path: path26 } : {},
|
|
20459
21241
|
...textFields2.unifiedDiff ? { patch: textFields2.unifiedDiff } : {},
|
|
20460
21242
|
...textFields2.newString ? { content: textFields2.newString } : {}
|
|
20461
21243
|
};
|
|
@@ -20603,12 +21385,12 @@ function parseFileChangeDiff(entry) {
|
|
|
20603
21385
|
]);
|
|
20604
21386
|
}
|
|
20605
21387
|
function toFileChangeEntry(entry, options, fallbackPath) {
|
|
20606
|
-
const
|
|
20607
|
-
if (!
|
|
21388
|
+
const path26 = parseFileChangePath(entry, options, fallbackPath);
|
|
21389
|
+
if (!path26) {
|
|
20608
21390
|
return null;
|
|
20609
21391
|
}
|
|
20610
21392
|
return {
|
|
20611
|
-
path:
|
|
21393
|
+
path: path26,
|
|
20612
21394
|
kind: parseFileChangeKind(entry),
|
|
20613
21395
|
diff: parseFileChangeDiff(entry)
|
|
20614
21396
|
};
|
|
@@ -20630,12 +21412,12 @@ function parseFileChangeEntries(changes, options) {
|
|
|
20630
21412
|
if (singleEntry) {
|
|
20631
21413
|
return [singleEntry];
|
|
20632
21414
|
}
|
|
20633
|
-
return Object.entries(changes).map(([
|
|
21415
|
+
return Object.entries(changes).map(([path26, value]) => {
|
|
20634
21416
|
if (isRecord2(value)) {
|
|
20635
|
-
return toFileChangeEntry(value, options,
|
|
21417
|
+
return toFileChangeEntry(value, options, path26);
|
|
20636
21418
|
}
|
|
20637
21419
|
if (typeof value === "string") {
|
|
20638
|
-
const normalizedPath = normalizeCodexFilePath(
|
|
21420
|
+
const normalizedPath = normalizeCodexFilePath(path26.trim(), options?.cwd);
|
|
20639
21421
|
if (!normalizedPath) {
|
|
20640
21422
|
return null;
|
|
20641
21423
|
}
|
|
@@ -20797,8 +21579,8 @@ function resolveCodexSessionRoot() {
|
|
|
20797
21579
|
if (process.env.CODEX_SESSION_DIR) {
|
|
20798
21580
|
return process.env.CODEX_SESSION_DIR;
|
|
20799
21581
|
}
|
|
20800
|
-
const codexHome = process.env.CODEX_HOME ??
|
|
20801
|
-
return
|
|
21582
|
+
const codexHome = process.env.CODEX_HOME ?? path9.join(os3.homedir(), ".codex");
|
|
21583
|
+
return path9.join(codexHome, "sessions");
|
|
20802
21584
|
}
|
|
20803
21585
|
async function findRolloutFile(threadId, root) {
|
|
20804
21586
|
const stack = [{ dir: root, depth: 0 }];
|
|
@@ -20806,12 +21588,12 @@ async function findRolloutFile(threadId, root) {
|
|
|
20806
21588
|
const { dir, depth } = stack.pop();
|
|
20807
21589
|
let entries;
|
|
20808
21590
|
try {
|
|
20809
|
-
entries = await
|
|
21591
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
20810
21592
|
} catch {
|
|
20811
21593
|
continue;
|
|
20812
21594
|
}
|
|
20813
21595
|
for (const entry of entries) {
|
|
20814
|
-
const entryPath =
|
|
21596
|
+
const entryPath = path9.join(dir, entry.name);
|
|
20815
21597
|
if (entry.isFile()) {
|
|
20816
21598
|
const matchesThread = entry.name.includes(threadId);
|
|
20817
21599
|
const matchesPrefix = entry.name.startsWith("rollout-");
|
|
@@ -21171,7 +21953,7 @@ function dedupeMirroredTextTimelineItems(timeline) {
|
|
|
21171
21953
|
return deduped;
|
|
21172
21954
|
}
|
|
21173
21955
|
async function parseRolloutFile(filePath) {
|
|
21174
|
-
const content = await
|
|
21956
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
21175
21957
|
const trimmed = content.trim();
|
|
21176
21958
|
if (!trimmed) {
|
|
21177
21959
|
return [];
|
|
@@ -21224,7 +22006,7 @@ async function loadCodexPersistedTimeline(sessionId, options, logger) {
|
|
|
21224
22006
|
const rolloutPath = options?.rolloutPath ?? null;
|
|
21225
22007
|
if (rolloutPath) {
|
|
21226
22008
|
try {
|
|
21227
|
-
const stat5 = await
|
|
22009
|
+
const stat5 = await fs4.stat(rolloutPath);
|
|
21228
22010
|
if (stat5.isFile()) {
|
|
21229
22011
|
const timeline = await parseRolloutFile(rolloutPath);
|
|
21230
22012
|
if (timeline.length > 0) {
|
|
@@ -21380,16 +22162,16 @@ function isObjectSchemaNode(schema) {
|
|
|
21380
22162
|
const type = schema.type;
|
|
21381
22163
|
return isSchemaRecord(schema.properties) || type === "object" || Array.isArray(type) && type.includes("object");
|
|
21382
22164
|
}
|
|
21383
|
-
function normalizeCodexOutputSchemaNode(schema,
|
|
22165
|
+
function normalizeCodexOutputSchemaNode(schema, path26) {
|
|
21384
22166
|
if (Array.isArray(schema)) {
|
|
21385
|
-
return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${
|
|
22167
|
+
return schema.map((entry, index) => normalizeCodexOutputSchemaNode(entry, `${path26}[${index}]`));
|
|
21386
22168
|
}
|
|
21387
22169
|
if (!isSchemaRecord(schema)) {
|
|
21388
22170
|
return schema;
|
|
21389
22171
|
}
|
|
21390
22172
|
const normalized = {};
|
|
21391
22173
|
for (const [key, value] of Object.entries(schema)) {
|
|
21392
|
-
normalized[key] = normalizeCodexOutputSchemaNode(value, `${
|
|
22174
|
+
normalized[key] = normalizeCodexOutputSchemaNode(value, `${path26}.${key}`);
|
|
21393
22175
|
}
|
|
21394
22176
|
if (!isObjectSchemaNode(normalized)) {
|
|
21395
22177
|
return normalized;
|
|
@@ -21398,7 +22180,7 @@ function normalizeCodexOutputSchemaNode(schema, path24) {
|
|
|
21398
22180
|
normalized.additionalProperties = false;
|
|
21399
22181
|
} else if (normalized.additionalProperties !== false) {
|
|
21400
22182
|
throw new Error(
|
|
21401
|
-
`Codex structured outputs require ${
|
|
22183
|
+
`Codex structured outputs require ${path26} to set additionalProperties to false for object schemas.`
|
|
21402
22184
|
);
|
|
21403
22185
|
}
|
|
21404
22186
|
const properties = isSchemaRecord(normalized.properties) ? normalized.properties : null;
|
|
@@ -21439,7 +22221,7 @@ async function resolveCodexLaunchPrefix(runtimeSettings) {
|
|
|
21439
22221
|
return resolveProviderCommandPrefix(runtimeSettings?.command, resolveCodexBinary);
|
|
21440
22222
|
}
|
|
21441
22223
|
function resolveCodexHomeDir() {
|
|
21442
|
-
return process.env.CODEX_HOME ??
|
|
22224
|
+
return process.env.CODEX_HOME ?? path10.join(os4.homedir(), ".codex");
|
|
21443
22225
|
}
|
|
21444
22226
|
function tokenizeCommandArgs(args) {
|
|
21445
22227
|
const tokens = [];
|
|
@@ -21519,10 +22301,10 @@ function parseFrontMatter(markdown) {
|
|
|
21519
22301
|
}
|
|
21520
22302
|
async function listCodexCustomPrompts() {
|
|
21521
22303
|
const codexHome = resolveCodexHomeDir();
|
|
21522
|
-
const promptsDir =
|
|
22304
|
+
const promptsDir = path10.join(codexHome, "prompts");
|
|
21523
22305
|
let entries;
|
|
21524
22306
|
try {
|
|
21525
|
-
entries = await
|
|
22307
|
+
entries = await fs5.readdir(promptsDir, { withFileTypes: true });
|
|
21526
22308
|
} catch {
|
|
21527
22309
|
return [];
|
|
21528
22310
|
}
|
|
@@ -21538,10 +22320,10 @@ async function listCodexCustomPrompts() {
|
|
|
21538
22320
|
if (!name) {
|
|
21539
22321
|
continue;
|
|
21540
22322
|
}
|
|
21541
|
-
const fullPath =
|
|
22323
|
+
const fullPath = path10.join(promptsDir, entry.name);
|
|
21542
22324
|
let content;
|
|
21543
22325
|
try {
|
|
21544
|
-
content = await
|
|
22326
|
+
content = await fs5.readFile(fullPath, "utf8");
|
|
21545
22327
|
} catch {
|
|
21546
22328
|
continue;
|
|
21547
22329
|
}
|
|
@@ -21558,7 +22340,7 @@ async function listCodexCustomPrompts() {
|
|
|
21558
22340
|
}
|
|
21559
22341
|
async function listCodexSkills(cwd) {
|
|
21560
22342
|
const candidates = [];
|
|
21561
|
-
candidates.push(
|
|
22343
|
+
candidates.push(path10.join(cwd, ".codex", "skills"));
|
|
21562
22344
|
const repoRoot = (() => {
|
|
21563
22345
|
try {
|
|
21564
22346
|
const output = execSync2("git rev-parse --show-toplevel", {
|
|
@@ -21573,15 +22355,15 @@ async function listCodexSkills(cwd) {
|
|
|
21573
22355
|
}
|
|
21574
22356
|
})();
|
|
21575
22357
|
if (repoRoot) {
|
|
21576
|
-
candidates.push(
|
|
21577
|
-
candidates.push(
|
|
22358
|
+
candidates.push(path10.join(path10.dirname(cwd), ".codex", "skills"));
|
|
22359
|
+
candidates.push(path10.join(repoRoot, ".codex", "skills"));
|
|
21578
22360
|
}
|
|
21579
|
-
candidates.push(
|
|
22361
|
+
candidates.push(path10.join(resolveCodexHomeDir(), "skills"));
|
|
21580
22362
|
const commandsByName = /* @__PURE__ */ new Map();
|
|
21581
22363
|
for (const dir of candidates) {
|
|
21582
22364
|
let entries;
|
|
21583
22365
|
try {
|
|
21584
|
-
entries = await
|
|
22366
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
21585
22367
|
} catch {
|
|
21586
22368
|
continue;
|
|
21587
22369
|
}
|
|
@@ -21589,11 +22371,11 @@ async function listCodexSkills(cwd) {
|
|
|
21589
22371
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
21590
22372
|
continue;
|
|
21591
22373
|
}
|
|
21592
|
-
const skillDir =
|
|
21593
|
-
const skillPath =
|
|
22374
|
+
const skillDir = path10.join(dir, entry.name);
|
|
22375
|
+
const skillPath = path10.join(skillDir, "SKILL.md");
|
|
21594
22376
|
let content;
|
|
21595
22377
|
try {
|
|
21596
|
-
content = await
|
|
22378
|
+
content = await fs5.readFile(skillPath, "utf8");
|
|
21597
22379
|
} catch {
|
|
21598
22380
|
continue;
|
|
21599
22381
|
}
|
|
@@ -22162,8 +22944,8 @@ function parseCodexPatchChanges(changes) {
|
|
|
22162
22944
|
}
|
|
22163
22945
|
];
|
|
22164
22946
|
}
|
|
22165
|
-
return Object.entries(recordChanges).map(([
|
|
22166
|
-
const normalizedPath =
|
|
22947
|
+
return Object.entries(recordChanges).map(([path26, value]) => {
|
|
22948
|
+
const normalizedPath = path26.trim();
|
|
22167
22949
|
if (!normalizedPath) {
|
|
22168
22950
|
return null;
|
|
22169
22951
|
}
|
|
@@ -22901,13 +23683,13 @@ var CodexNotificationSchema = z25.union([
|
|
|
22901
23683
|
)
|
|
22902
23684
|
]);
|
|
22903
23685
|
async function writeImageAttachment(mimeType, data) {
|
|
22904
|
-
const attachmentsDir =
|
|
22905
|
-
await
|
|
23686
|
+
const attachmentsDir = path10.join(os4.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
|
|
23687
|
+
await fs5.mkdir(attachmentsDir, { recursive: true });
|
|
22906
23688
|
const normalized = normalizeImageData(mimeType, data);
|
|
22907
23689
|
const extension = getImageExtension(normalized.mimeType);
|
|
22908
|
-
const filename = `${
|
|
22909
|
-
const filePath =
|
|
22910
|
-
await
|
|
23690
|
+
const filename = `${randomUUID3()}.${extension}`;
|
|
23691
|
+
const filePath = path10.join(attachmentsDir, filename);
|
|
23692
|
+
await fs5.writeFile(filePath, Buffer.from(normalized.data, "base64"));
|
|
22911
23693
|
return filePath;
|
|
22912
23694
|
}
|
|
22913
23695
|
async function readCodexConfiguredDefaults(client, logger) {
|
|
@@ -23187,7 +23969,7 @@ var CodexAppServerAgentSession = class {
|
|
|
23187
23969
|
};
|
|
23188
23970
|
}
|
|
23189
23971
|
emitSyntheticPlanApprovalRequest(planText) {
|
|
23190
|
-
const requestId = `permission-${
|
|
23972
|
+
const requestId = `permission-${randomUUID3()}`;
|
|
23191
23973
|
const request = {
|
|
23192
23974
|
id: requestId,
|
|
23193
23975
|
provider: CODEX_PROVIDER,
|
|
@@ -23344,8 +24126,8 @@ var CodexAppServerAgentSession = class {
|
|
|
23344
24126
|
if (commandName.startsWith("prompts:")) {
|
|
23345
24127
|
const promptName = commandName.slice("prompts:".length);
|
|
23346
24128
|
const codexHome = resolveCodexHomeDir();
|
|
23347
|
-
const promptPath =
|
|
23348
|
-
const raw = await
|
|
24129
|
+
const promptPath = path10.join(codexHome, "prompts", `${promptName}.md`);
|
|
24130
|
+
const raw = await fs5.readFile(promptPath, "utf8");
|
|
23349
24131
|
const parsed = parseFrontMatter(raw);
|
|
23350
24132
|
return expandCodexCustomPrompt(parsed.body, args);
|
|
23351
24133
|
}
|
|
@@ -24577,9 +25359,9 @@ var CodexAppServerAgentClient = class {
|
|
|
24577
25359
|
};
|
|
24578
25360
|
|
|
24579
25361
|
// ../server/src/server/agent/providers/acp-agent.ts
|
|
24580
|
-
import { randomUUID as
|
|
24581
|
-
import
|
|
24582
|
-
import
|
|
25362
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
25363
|
+
import fs6 from "node:fs/promises";
|
|
25364
|
+
import path11 from "node:path";
|
|
24583
25365
|
import { Readable, Writable } from "node:stream";
|
|
24584
25366
|
import {
|
|
24585
25367
|
ClientSideConnection,
|
|
@@ -24876,12 +25658,12 @@ ${stderr}` : String(error)));
|
|
|
24876
25658
|
async sessionUpdate() {
|
|
24877
25659
|
},
|
|
24878
25660
|
async readTextFile(params) {
|
|
24879
|
-
const content = await
|
|
25661
|
+
const content = await fs6.readFile(params.path, "utf8");
|
|
24880
25662
|
return { content };
|
|
24881
25663
|
},
|
|
24882
25664
|
async writeTextFile(params) {
|
|
24883
|
-
await
|
|
24884
|
-
await
|
|
25665
|
+
await fs6.mkdir(path11.dirname(params.path), { recursive: true });
|
|
25666
|
+
await fs6.writeFile(params.path, params.content, "utf8");
|
|
24885
25667
|
return {};
|
|
24886
25668
|
},
|
|
24887
25669
|
async createTerminal() {
|
|
@@ -25103,8 +25885,8 @@ var ACPAgentSession = class {
|
|
|
25103
25885
|
if (this.activeForegroundTurnId) {
|
|
25104
25886
|
throw new Error("A foreground turn is already active");
|
|
25105
25887
|
}
|
|
25106
|
-
const turnId =
|
|
25107
|
-
const messageId =
|
|
25888
|
+
const turnId = randomUUID4();
|
|
25889
|
+
const messageId = randomUUID4();
|
|
25108
25890
|
this.activeForegroundTurnId = turnId;
|
|
25109
25891
|
this.suppressUserEchoMessageId = messageId;
|
|
25110
25892
|
this.suppressUserEchoText = extractPromptText(prompt);
|
|
@@ -25405,7 +26187,7 @@ var ACPAgentSession = class {
|
|
|
25405
26187
|
}
|
|
25406
26188
|
} : { outcome: { outcome: "cancelled" } };
|
|
25407
26189
|
}
|
|
25408
|
-
const requestId =
|
|
26190
|
+
const requestId = randomUUID4();
|
|
25409
26191
|
let toolSnapshot = this.toolCalls.get(params.toolCall.toolCallId) ?? mergeToolSnapshot(params.toolCall.toolCallId, params.toolCall);
|
|
25410
26192
|
if (this.toolSnapshotTransformer) {
|
|
25411
26193
|
toolSnapshot = this.toolSnapshotTransformer(toolSnapshot);
|
|
@@ -25446,7 +26228,7 @@ var ACPAgentSession = class {
|
|
|
25446
26228
|
}
|
|
25447
26229
|
}
|
|
25448
26230
|
async readTextFile(params) {
|
|
25449
|
-
const raw = await
|
|
26231
|
+
const raw = await fs6.readFile(params.path, "utf8");
|
|
25450
26232
|
if (!params.line && !params.limit) {
|
|
25451
26233
|
return { content: raw };
|
|
25452
26234
|
}
|
|
@@ -25456,12 +26238,12 @@ var ACPAgentSession = class {
|
|
|
25456
26238
|
return { content: lines.slice(start, end).join("\n") };
|
|
25457
26239
|
}
|
|
25458
26240
|
async writeTextFile(params) {
|
|
25459
|
-
await
|
|
25460
|
-
await
|
|
26241
|
+
await fs6.mkdir(path11.dirname(params.path), { recursive: true });
|
|
26242
|
+
await fs6.writeFile(params.path, params.content, "utf8");
|
|
25461
26243
|
return {};
|
|
25462
26244
|
}
|
|
25463
26245
|
async createTerminal(params) {
|
|
25464
|
-
const terminalId =
|
|
26246
|
+
const terminalId = randomUUID4();
|
|
25465
26247
|
const env = Object.fromEntries(
|
|
25466
26248
|
(params.env ?? []).map((entry2) => [entry2.name, entry2.value])
|
|
25467
26249
|
);
|
|
@@ -28515,9 +29297,9 @@ var OpenCodeAgentSession = class {
|
|
|
28515
29297
|
};
|
|
28516
29298
|
|
|
28517
29299
|
// ../server/src/server/agent/providers/pi-direct-agent.ts
|
|
28518
|
-
import { randomUUID as
|
|
29300
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
28519
29301
|
import { existsSync as existsSync9 } from "node:fs";
|
|
28520
|
-
import { join as
|
|
29302
|
+
import { join as join9 } from "node:path";
|
|
28521
29303
|
import { homedir } from "node:os";
|
|
28522
29304
|
import {
|
|
28523
29305
|
AuthStorage,
|
|
@@ -29288,7 +30070,7 @@ var PiDirectAgentSession = class {
|
|
|
29288
30070
|
throw new Error("A Pi turn is already active");
|
|
29289
30071
|
}
|
|
29290
30072
|
const payload = convertPromptInput(prompt);
|
|
29291
|
-
const turnId =
|
|
30073
|
+
const turnId = randomUUID5();
|
|
29292
30074
|
this.activeTurnId = turnId;
|
|
29293
30075
|
void this.session.prompt(payload.text, payload.images ? { images: payload.images } : void 0).catch((error) => {
|
|
29294
30076
|
const failedTurnId = this.activeTurnId ?? turnId;
|
|
@@ -29523,7 +30305,7 @@ var PiDirectAgentClient = class {
|
|
|
29523
30305
|
} else if (!await isCommandAvailable(PI_BINARY_COMMAND)) {
|
|
29524
30306
|
return false;
|
|
29525
30307
|
}
|
|
29526
|
-
return Boolean(process.env.OPENAI_API_KEY) || Boolean(process.env.ANTHROPIC_API_KEY) || Boolean(process.env.OPENROUTER_API_KEY) || existsSync9(
|
|
30308
|
+
return Boolean(process.env.OPENAI_API_KEY) || Boolean(process.env.ANTHROPIC_API_KEY) || Boolean(process.env.OPENROUTER_API_KEY) || existsSync9(join9(homedir(), ".pi", "agent", "auth.json"));
|
|
29527
30309
|
}
|
|
29528
30310
|
async getDiagnostic() {
|
|
29529
30311
|
try {
|
|
@@ -29531,7 +30313,7 @@ var PiDirectAgentClient = class {
|
|
|
29531
30313
|
const binaryOverride = this.runtimeSettings?.command;
|
|
29532
30314
|
const binary = binaryOverride?.mode === "replace" && binaryOverride.argv[0] ? binaryOverride.argv[0] : await findExecutable(PI_BINARY_COMMAND);
|
|
29533
30315
|
const version = binary ? await resolveBinaryVersion(binary) : "unknown";
|
|
29534
|
-
const authConfigPath =
|
|
30316
|
+
const authConfigPath = join9(homedir(), ".pi", "agent", "auth.json");
|
|
29535
30317
|
let modelsValue = "Not checked";
|
|
29536
30318
|
let status = formatDiagnosticStatus(available);
|
|
29537
30319
|
if (available) {
|
|
@@ -29924,8 +30706,8 @@ function buildZodValidator(schema, schemaName) {
|
|
|
29924
30706
|
return { ok: true, value: result.data };
|
|
29925
30707
|
}
|
|
29926
30708
|
const errors = result.error.issues.map((issue) => {
|
|
29927
|
-
const
|
|
29928
|
-
return `${
|
|
30709
|
+
const path26 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
30710
|
+
return `${path26}: ${issue.message}`;
|
|
29929
30711
|
});
|
|
29930
30712
|
return { ok: false, errors };
|
|
29931
30713
|
}
|
|
@@ -29943,9 +30725,9 @@ function buildJsonSchemaValidator(schema) {
|
|
|
29943
30725
|
return { ok: true, value };
|
|
29944
30726
|
}
|
|
29945
30727
|
const errors = (validate.errors ?? []).map((error) => {
|
|
29946
|
-
const
|
|
30728
|
+
const path26 = error.instancePath && error.instancePath.length > 0 ? error.instancePath : "(root)";
|
|
29947
30729
|
const message = error.message ?? "is invalid";
|
|
29948
|
-
return `${
|
|
30730
|
+
return `${path26}: ${message}`;
|
|
29949
30731
|
});
|
|
29950
30732
|
return { ok: false, errors };
|
|
29951
30733
|
}
|
|
@@ -30830,8 +31612,8 @@ function isVoicePermissionAllowed(request) {
|
|
|
30830
31612
|
}
|
|
30831
31613
|
|
|
30832
31614
|
// ../server/src/server/file-explorer/service.ts
|
|
30833
|
-
import { promises as
|
|
30834
|
-
import
|
|
31615
|
+
import { promises as fs7 } from "fs";
|
|
31616
|
+
import path12 from "path";
|
|
30835
31617
|
|
|
30836
31618
|
// ../server/src/server/path-utils.ts
|
|
30837
31619
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -30878,14 +31660,14 @@ async function listDirectoryEntries({
|
|
|
30878
31660
|
relativePath = "."
|
|
30879
31661
|
}) {
|
|
30880
31662
|
const directoryPath = await resolveScopedPath({ root, relativePath });
|
|
30881
|
-
const stats = await
|
|
31663
|
+
const stats = await fs7.stat(directoryPath);
|
|
30882
31664
|
if (!stats.isDirectory()) {
|
|
30883
31665
|
throw new Error("Requested path is not a directory");
|
|
30884
31666
|
}
|
|
30885
|
-
const dirents = await
|
|
31667
|
+
const dirents = await fs7.readdir(directoryPath, { withFileTypes: true });
|
|
30886
31668
|
const entriesWithNulls = await Promise.all(
|
|
30887
31669
|
dirents.map(async (dirent) => {
|
|
30888
|
-
const targetPath =
|
|
31670
|
+
const targetPath = path12.join(directoryPath, dirent.name);
|
|
30889
31671
|
const kind = dirent.isDirectory() ? "directory" : "file";
|
|
30890
31672
|
try {
|
|
30891
31673
|
return await buildEntryPayload({
|
|
@@ -30920,18 +31702,18 @@ async function readExplorerFile({
|
|
|
30920
31702
|
relativePath
|
|
30921
31703
|
}) {
|
|
30922
31704
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
30923
|
-
const stats = await
|
|
31705
|
+
const stats = await fs7.stat(filePath);
|
|
30924
31706
|
if (!stats.isFile()) {
|
|
30925
31707
|
throw new Error("Requested path is not a file");
|
|
30926
31708
|
}
|
|
30927
|
-
const ext =
|
|
31709
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
30928
31710
|
const basePayload = {
|
|
30929
31711
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
30930
31712
|
size: stats.size,
|
|
30931
31713
|
modifiedAt: stats.mtime.toISOString()
|
|
30932
31714
|
};
|
|
30933
31715
|
if (ext in IMAGE_MIME_TYPES) {
|
|
30934
|
-
const buffer2 = await
|
|
31716
|
+
const buffer2 = await fs7.readFile(filePath);
|
|
30935
31717
|
return {
|
|
30936
31718
|
...basePayload,
|
|
30937
31719
|
kind: "image",
|
|
@@ -30940,7 +31722,7 @@ async function readExplorerFile({
|
|
|
30940
31722
|
mimeType: IMAGE_MIME_TYPES[ext]
|
|
30941
31723
|
};
|
|
30942
31724
|
}
|
|
30943
|
-
const buffer = await
|
|
31725
|
+
const buffer = await fs7.readFile(filePath);
|
|
30944
31726
|
if (isLikelyBinary(buffer)) {
|
|
30945
31727
|
return {
|
|
30946
31728
|
...basePayload,
|
|
@@ -30962,34 +31744,34 @@ async function writeTextFile({
|
|
|
30962
31744
|
relativePath,
|
|
30963
31745
|
content
|
|
30964
31746
|
}) {
|
|
30965
|
-
const ext =
|
|
31747
|
+
const ext = path12.extname(relativePath).toLowerCase();
|
|
30966
31748
|
if (ext in IMAGE_MIME_TYPES) {
|
|
30967
31749
|
throw new Error(`Refusing to write '${relativePath}': binary/image file`);
|
|
30968
31750
|
}
|
|
30969
31751
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
30970
31752
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
30971
|
-
await
|
|
30972
|
-
await
|
|
31753
|
+
await fs7.writeFile(tempPath, content, "utf8");
|
|
31754
|
+
await fs7.rename(tempPath, filePath);
|
|
30973
31755
|
}
|
|
30974
31756
|
async function deleteFile({ root, relativePath }) {
|
|
30975
|
-
const ext =
|
|
31757
|
+
const ext = path12.extname(relativePath).toLowerCase();
|
|
30976
31758
|
if (ext !== ".md") {
|
|
30977
31759
|
throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
|
|
30978
31760
|
}
|
|
30979
31761
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
30980
|
-
const stats = await
|
|
31762
|
+
const stats = await fs7.stat(filePath);
|
|
30981
31763
|
if (!stats.isFile()) {
|
|
30982
31764
|
throw new Error("Requested path is not a file");
|
|
30983
31765
|
}
|
|
30984
|
-
await
|
|
31766
|
+
await fs7.unlink(filePath);
|
|
30985
31767
|
}
|
|
30986
31768
|
async function deleteEntry({ root, relativePath }) {
|
|
30987
31769
|
const entryPath = await resolveScopedPath({ root, relativePath });
|
|
30988
|
-
await
|
|
31770
|
+
await fs7.rm(entryPath, { recursive: true, force: false });
|
|
30989
31771
|
}
|
|
30990
31772
|
async function createDirectory({ root, relativePath }) {
|
|
30991
31773
|
const dirPath = await resolveScopedPath({ root, relativePath });
|
|
30992
|
-
await
|
|
31774
|
+
await fs7.mkdir(dirPath, { recursive: true });
|
|
30993
31775
|
}
|
|
30994
31776
|
async function moveEntry({
|
|
30995
31777
|
root,
|
|
@@ -30998,22 +31780,22 @@ async function moveEntry({
|
|
|
30998
31780
|
}) {
|
|
30999
31781
|
const src = await resolveScopedPath({ root, relativePath: sourcePath });
|
|
31000
31782
|
const dest = await resolveScopedPath({ root, relativePath: destinationPath });
|
|
31001
|
-
await
|
|
31783
|
+
await fs7.rename(src, dest);
|
|
31002
31784
|
}
|
|
31003
31785
|
async function getDownloadableFileInfo({ root, relativePath }) {
|
|
31004
31786
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
31005
|
-
const stats = await
|
|
31787
|
+
const stats = await fs7.stat(filePath);
|
|
31006
31788
|
if (!stats.isFile()) {
|
|
31007
31789
|
throw new Error("Requested path is not a file");
|
|
31008
31790
|
}
|
|
31009
|
-
const ext =
|
|
31791
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
31010
31792
|
let mimeType = "application/octet-stream";
|
|
31011
31793
|
if (ext in IMAGE_MIME_TYPES) {
|
|
31012
31794
|
mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
|
|
31013
31795
|
} else if (ext in FONT_MIME_TYPES) {
|
|
31014
31796
|
mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
|
|
31015
31797
|
} else {
|
|
31016
|
-
const handle = await
|
|
31798
|
+
const handle = await fs7.open(filePath, "r");
|
|
31017
31799
|
const sample = Buffer.alloc(8192);
|
|
31018
31800
|
try {
|
|
31019
31801
|
const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
|
|
@@ -31028,23 +31810,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
|
|
|
31028
31810
|
return {
|
|
31029
31811
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
31030
31812
|
absolutePath: filePath,
|
|
31031
|
-
fileName:
|
|
31813
|
+
fileName: path12.basename(filePath),
|
|
31032
31814
|
mimeType,
|
|
31033
31815
|
size: stats.size
|
|
31034
31816
|
};
|
|
31035
31817
|
}
|
|
31036
31818
|
async function resolveScopedPath({ root, relativePath = "." }) {
|
|
31037
|
-
const normalizedRoot =
|
|
31819
|
+
const normalizedRoot = path12.resolve(root);
|
|
31038
31820
|
const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
|
|
31039
|
-
const relative =
|
|
31040
|
-
if (relative !== "" && (relative.startsWith("..") ||
|
|
31821
|
+
const relative = path12.relative(normalizedRoot, requestedPath);
|
|
31822
|
+
if (relative !== "" && (relative.startsWith("..") || path12.isAbsolute(relative))) {
|
|
31041
31823
|
throw new Error("Access outside of workspace is not allowed");
|
|
31042
31824
|
}
|
|
31043
|
-
const realRoot = await
|
|
31825
|
+
const realRoot = await fs7.realpath(normalizedRoot);
|
|
31044
31826
|
try {
|
|
31045
|
-
const realPath = await
|
|
31046
|
-
const realRelative =
|
|
31047
|
-
if (realRelative !== "" && (realRelative.startsWith("..") ||
|
|
31827
|
+
const realPath = await fs7.realpath(requestedPath);
|
|
31828
|
+
const realRelative = path12.relative(realRoot, realPath);
|
|
31829
|
+
if (realRelative !== "" && (realRelative.startsWith("..") || path12.isAbsolute(realRelative))) {
|
|
31048
31830
|
throw new Error("Access outside of workspace is not allowed");
|
|
31049
31831
|
}
|
|
31050
31832
|
return requestedPath;
|
|
@@ -31061,7 +31843,7 @@ async function buildEntryPayload({
|
|
|
31061
31843
|
name,
|
|
31062
31844
|
kind
|
|
31063
31845
|
}) {
|
|
31064
|
-
const stats = await
|
|
31846
|
+
const stats = await fs7.stat(targetPath);
|
|
31065
31847
|
return {
|
|
31066
31848
|
name,
|
|
31067
31849
|
path: normalizeRelativePath({ root, targetPath }),
|
|
@@ -31075,10 +31857,10 @@ function isMissingEntryError(error) {
|
|
|
31075
31857
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
31076
31858
|
}
|
|
31077
31859
|
function normalizeRelativePath({ root, targetPath }) {
|
|
31078
|
-
const normalizedRoot =
|
|
31079
|
-
const normalizedTarget =
|
|
31080
|
-
const relative =
|
|
31081
|
-
return relative === "" ? "." : relative.split(
|
|
31860
|
+
const normalizedRoot = path12.resolve(root);
|
|
31861
|
+
const normalizedTarget = path12.resolve(targetPath);
|
|
31862
|
+
const relative = path12.relative(normalizedRoot, normalizedTarget);
|
|
31863
|
+
return relative === "" ? "." : relative.split(path12.sep).join("/");
|
|
31082
31864
|
}
|
|
31083
31865
|
function textMimeTypeForExtension(ext) {
|
|
31084
31866
|
return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
|
|
@@ -31105,7 +31887,7 @@ function isLikelyBinary(buffer) {
|
|
|
31105
31887
|
|
|
31106
31888
|
// ../server/src/utils/project-icon.ts
|
|
31107
31889
|
import { readdir, readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
31108
|
-
import { extname as
|
|
31890
|
+
import { extname as extname3, join as join10 } from "path";
|
|
31109
31891
|
var ICON_PATTERNS = [
|
|
31110
31892
|
"favicon.ico",
|
|
31111
31893
|
"favicon.png",
|
|
@@ -31219,7 +32001,7 @@ function isSquareImage(buffer, mimeType) {
|
|
|
31219
32001
|
return dimensions.width === dimensions.height;
|
|
31220
32002
|
}
|
|
31221
32003
|
function getMimeType(filename) {
|
|
31222
|
-
const ext =
|
|
32004
|
+
const ext = extname3(filename).toLowerCase();
|
|
31223
32005
|
switch (ext) {
|
|
31224
32006
|
case ".ico":
|
|
31225
32007
|
return "image/x-icon";
|
|
@@ -31255,7 +32037,7 @@ async function findIconInDir(dir, patterns) {
|
|
|
31255
32037
|
for (const pattern of patterns) {
|
|
31256
32038
|
for (const entry of entries) {
|
|
31257
32039
|
if (matchesPattern(entry, pattern)) {
|
|
31258
|
-
const fullPath =
|
|
32040
|
+
const fullPath = join10(dir, entry);
|
|
31259
32041
|
try {
|
|
31260
32042
|
const stats = await stat2(fullPath);
|
|
31261
32043
|
if (stats.isFile()) {
|
|
@@ -31286,7 +32068,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
|
|
|
31286
32068
|
if (ignoredDirs.has(entry)) {
|
|
31287
32069
|
continue;
|
|
31288
32070
|
}
|
|
31289
|
-
const fullPath =
|
|
32071
|
+
const fullPath = join10(dir, entry);
|
|
31290
32072
|
try {
|
|
31291
32073
|
const stats = await stat2(fullPath);
|
|
31292
32074
|
if (stats.isDirectory()) {
|
|
@@ -31309,7 +32091,7 @@ async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, curren
|
|
|
31309
32091
|
async function findProjectIcon(projectDir, maxDepth = 3) {
|
|
31310
32092
|
const ignoredDirsSet = new Set(IGNORED_DIRS);
|
|
31311
32093
|
for (const priorityDir of PRIORITY_DIRS) {
|
|
31312
|
-
const priorityPath =
|
|
32094
|
+
const priorityPath = join10(projectDir, priorityDir);
|
|
31313
32095
|
try {
|
|
31314
32096
|
const stats = await stat2(priorityPath);
|
|
31315
32097
|
if (stats.isDirectory()) {
|
|
@@ -31327,7 +32109,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
|
|
|
31327
32109
|
}
|
|
31328
32110
|
}
|
|
31329
32111
|
for (const monoDir of MONOREPO_PACKAGE_DIRS) {
|
|
31330
|
-
const monoPath =
|
|
32112
|
+
const monoPath = join10(projectDir, monoDir);
|
|
31331
32113
|
let packageEntries;
|
|
31332
32114
|
try {
|
|
31333
32115
|
packageEntries = await readdir(monoPath);
|
|
@@ -31335,7 +32117,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
|
|
|
31335
32117
|
continue;
|
|
31336
32118
|
}
|
|
31337
32119
|
for (const packageName of packageEntries) {
|
|
31338
|
-
const packagePath =
|
|
32120
|
+
const packagePath = join10(monoPath, packageName);
|
|
31339
32121
|
try {
|
|
31340
32122
|
const packageStats = await stat2(packagePath);
|
|
31341
32123
|
if (!packageStats.isDirectory()) continue;
|
|
@@ -31343,7 +32125,7 @@ async function findProjectIcon(projectDir, maxDepth = 3) {
|
|
|
31343
32125
|
continue;
|
|
31344
32126
|
}
|
|
31345
32127
|
for (const priorityDir of PRIORITY_DIRS) {
|
|
31346
|
-
const priorityPath =
|
|
32128
|
+
const priorityPath = join10(packagePath, priorityDir);
|
|
31347
32129
|
try {
|
|
31348
32130
|
const priorityStats = await stat2(priorityPath);
|
|
31349
32131
|
if (priorityStats.isDirectory()) {
|
|
@@ -31393,7 +32175,7 @@ async function findDirRecursively(dir, maxDepth = 2, currentDepth = 0) {
|
|
|
31393
32175
|
if (ignoredDirsSet.has(entry) || priorityDirsSet.has(entry)) {
|
|
31394
32176
|
continue;
|
|
31395
32177
|
}
|
|
31396
|
-
const fullPath =
|
|
32178
|
+
const fullPath = join10(dir, entry);
|
|
31397
32179
|
try {
|
|
31398
32180
|
const stats = await stat2(fullPath);
|
|
31399
32181
|
if (stats.isDirectory()) {
|
|
@@ -31432,64 +32214,64 @@ async function getProjectIcon(projectDir) {
|
|
|
31432
32214
|
|
|
31433
32215
|
// ../server/src/utils/path.ts
|
|
31434
32216
|
import os5 from "os";
|
|
31435
|
-
function expandTilde(
|
|
31436
|
-
if (
|
|
32217
|
+
function expandTilde(path26) {
|
|
32218
|
+
if (path26.startsWith("~/")) {
|
|
31437
32219
|
const homeDir3 = process.env.HOME || os5.homedir();
|
|
31438
|
-
return
|
|
32220
|
+
return path26.replace("~", homeDir3);
|
|
31439
32221
|
}
|
|
31440
|
-
if (
|
|
32222
|
+
if (path26 === "~") {
|
|
31441
32223
|
return process.env.HOME || os5.homedir();
|
|
31442
32224
|
}
|
|
31443
|
-
return
|
|
32225
|
+
return path26;
|
|
31444
32226
|
}
|
|
31445
32227
|
|
|
31446
32228
|
// ../server/src/server/skills/scanner.ts
|
|
31447
|
-
import
|
|
32229
|
+
import fs8 from "node:fs/promises";
|
|
31448
32230
|
import os6 from "node:os";
|
|
31449
|
-
import
|
|
32231
|
+
import path13 from "node:path";
|
|
31450
32232
|
var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
31451
32233
|
function homeDir() {
|
|
31452
32234
|
return process.env.HOME || os6.homedir();
|
|
31453
32235
|
}
|
|
31454
32236
|
function codexHomeDir() {
|
|
31455
|
-
return process.env.CODEX_HOME ||
|
|
32237
|
+
return process.env.CODEX_HOME || path13.join(homeDir(), ".codex");
|
|
31456
32238
|
}
|
|
31457
32239
|
function resolveScopeDir(provider, scope, workspaceRoot) {
|
|
31458
32240
|
if (scope === "codex-prompts") {
|
|
31459
32241
|
if (provider !== "codex") {
|
|
31460
32242
|
throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
|
|
31461
32243
|
}
|
|
31462
|
-
return
|
|
32244
|
+
return path13.join(codexHomeDir(), "prompts");
|
|
31463
32245
|
}
|
|
31464
32246
|
if (scope === "project") {
|
|
31465
32247
|
if (!workspaceRoot) {
|
|
31466
32248
|
throw new Error(`workspaceRoot is required for scope "project"`);
|
|
31467
32249
|
}
|
|
31468
32250
|
const dotDir = provider === "claude" ? ".claude" : ".codex";
|
|
31469
|
-
return
|
|
32251
|
+
return path13.join(workspaceRoot, dotDir, "skills");
|
|
31470
32252
|
}
|
|
31471
32253
|
if (provider === "claude") {
|
|
31472
|
-
return
|
|
32254
|
+
return path13.join(homeDir(), ".claude", "skills");
|
|
31473
32255
|
}
|
|
31474
|
-
return
|
|
32256
|
+
return path13.join(codexHomeDir(), "skills");
|
|
31475
32257
|
}
|
|
31476
32258
|
function allowedRoots(workspaceRoot) {
|
|
31477
32259
|
const roots = [
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
32260
|
+
path13.join(homeDir(), ".claude", "skills"),
|
|
32261
|
+
path13.join(codexHomeDir(), "skills"),
|
|
32262
|
+
path13.join(codexHomeDir(), "prompts")
|
|
31481
32263
|
];
|
|
31482
32264
|
if (workspaceRoot) {
|
|
31483
|
-
roots.push(
|
|
31484
|
-
roots.push(
|
|
32265
|
+
roots.push(path13.join(workspaceRoot, ".claude", "skills"));
|
|
32266
|
+
roots.push(path13.join(workspaceRoot, ".codex", "skills"));
|
|
31485
32267
|
}
|
|
31486
|
-
return roots.map((r) =>
|
|
32268
|
+
return roots.map((r) => path13.resolve(r));
|
|
31487
32269
|
}
|
|
31488
32270
|
function isInsideAllowedRoot(absPath, workspaceRoot) {
|
|
31489
|
-
const resolved =
|
|
32271
|
+
const resolved = path13.resolve(absPath);
|
|
31490
32272
|
for (const root of allowedRoots(workspaceRoot)) {
|
|
31491
|
-
const rel =
|
|
31492
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
32273
|
+
const rel = path13.relative(root, resolved);
|
|
32274
|
+
if (rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
|
|
31493
32275
|
return true;
|
|
31494
32276
|
}
|
|
31495
32277
|
}
|
|
@@ -31609,7 +32391,7 @@ async function listSkills(args) {
|
|
|
31609
32391
|
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
31610
32392
|
let entries;
|
|
31611
32393
|
try {
|
|
31612
|
-
entries = await
|
|
32394
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
31613
32395
|
} catch {
|
|
31614
32396
|
return [];
|
|
31615
32397
|
}
|
|
@@ -31620,7 +32402,7 @@ async function listSkills(args) {
|
|
|
31620
32402
|
if (!entry.name.endsWith(".md")) continue;
|
|
31621
32403
|
const name = entry.name.slice(0, -".md".length);
|
|
31622
32404
|
if (!name) continue;
|
|
31623
|
-
const fullPath =
|
|
32405
|
+
const fullPath = path13.join(dir, entry.name);
|
|
31624
32406
|
const stat5 = await safeStat(fullPath);
|
|
31625
32407
|
if (!stat5) continue;
|
|
31626
32408
|
const description = await readDescriptionSafely(fullPath);
|
|
@@ -31638,8 +32420,8 @@ async function listSkills(args) {
|
|
|
31638
32420
|
} else {
|
|
31639
32421
|
for (const entry of entries) {
|
|
31640
32422
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
31641
|
-
const skillDir =
|
|
31642
|
-
const skillPath =
|
|
32423
|
+
const skillDir = path13.join(dir, entry.name);
|
|
32424
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
31643
32425
|
const stat5 = await safeStat(skillPath);
|
|
31644
32426
|
if (!stat5) continue;
|
|
31645
32427
|
const description = await readDescriptionSafely(skillPath);
|
|
@@ -31660,7 +32442,7 @@ async function listSkills(args) {
|
|
|
31660
32442
|
}
|
|
31661
32443
|
async function safeStat(filePath) {
|
|
31662
32444
|
try {
|
|
31663
|
-
const s = await
|
|
32445
|
+
const s = await fs8.stat(filePath);
|
|
31664
32446
|
return { mtime: s.mtime, size: s.size };
|
|
31665
32447
|
} catch {
|
|
31666
32448
|
return null;
|
|
@@ -31668,7 +32450,7 @@ async function safeStat(filePath) {
|
|
|
31668
32450
|
}
|
|
31669
32451
|
async function readDescriptionSafely(filePath) {
|
|
31670
32452
|
try {
|
|
31671
|
-
const text = await
|
|
32453
|
+
const text = await fs8.readFile(filePath, "utf8");
|
|
31672
32454
|
return readDescription(text);
|
|
31673
32455
|
} catch {
|
|
31674
32456
|
return "";
|
|
@@ -31684,11 +32466,11 @@ async function createSkill(args) {
|
|
|
31684
32466
|
throw new Error(`Skill name must not contain path separators or "..".`);
|
|
31685
32467
|
}
|
|
31686
32468
|
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
31687
|
-
await
|
|
32469
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
31688
32470
|
if (args.scope === "codex-prompts") {
|
|
31689
|
-
const filePath2 =
|
|
32471
|
+
const filePath2 = path13.join(dir, `${args.name}.md`);
|
|
31690
32472
|
try {
|
|
31691
|
-
await
|
|
32473
|
+
await fs8.access(filePath2);
|
|
31692
32474
|
throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
|
|
31693
32475
|
} catch (err) {
|
|
31694
32476
|
if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
|
|
@@ -31699,13 +32481,13 @@ async function createSkill(args) {
|
|
|
31699
32481
|
}
|
|
31700
32482
|
}
|
|
31701
32483
|
const initial2 = buildStarterPrompt(args.name);
|
|
31702
|
-
await
|
|
32484
|
+
await fs8.writeFile(filePath2, initial2, "utf8");
|
|
31703
32485
|
return { path: filePath2 };
|
|
31704
32486
|
}
|
|
31705
|
-
const skillDir =
|
|
32487
|
+
const skillDir = path13.join(dir, args.name);
|
|
31706
32488
|
let dirExists = false;
|
|
31707
32489
|
try {
|
|
31708
|
-
const stat5 = await
|
|
32490
|
+
const stat5 = await fs8.stat(skillDir);
|
|
31709
32491
|
dirExists = stat5.isDirectory();
|
|
31710
32492
|
} catch {
|
|
31711
32493
|
dirExists = false;
|
|
@@ -31713,10 +32495,10 @@ async function createSkill(args) {
|
|
|
31713
32495
|
if (dirExists) {
|
|
31714
32496
|
throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
|
|
31715
32497
|
}
|
|
31716
|
-
await
|
|
31717
|
-
const filePath =
|
|
32498
|
+
await fs8.mkdir(skillDir, { recursive: true });
|
|
32499
|
+
const filePath = path13.join(skillDir, "SKILL.md");
|
|
31718
32500
|
const initial = buildStarterSkill(args.name);
|
|
31719
|
-
await
|
|
32501
|
+
await fs8.writeFile(filePath, initial, "utf8");
|
|
31720
32502
|
return { path: filePath };
|
|
31721
32503
|
}
|
|
31722
32504
|
function buildStarterSkill(name) {
|
|
@@ -31743,7 +32525,7 @@ Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expa
|
|
|
31743
32525
|
`;
|
|
31744
32526
|
}
|
|
31745
32527
|
async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
31746
|
-
if (!
|
|
32528
|
+
if (!path13.isAbsolute(args.path)) {
|
|
31747
32529
|
throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
|
|
31748
32530
|
}
|
|
31749
32531
|
if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
|
|
@@ -31751,7 +32533,7 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
|
31751
32533
|
}
|
|
31752
32534
|
let original;
|
|
31753
32535
|
try {
|
|
31754
|
-
original = await
|
|
32536
|
+
original = await fs8.readFile(args.path, "utf8");
|
|
31755
32537
|
} catch (err) {
|
|
31756
32538
|
throw new Error(
|
|
31757
32539
|
`Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -31764,12 +32546,12 @@ async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
|
31764
32546
|
${parsed.body}` : `${nextFrontmatter}
|
|
31765
32547
|
|
|
31766
32548
|
${original}`;
|
|
31767
|
-
await
|
|
32549
|
+
await fs8.writeFile(args.path, nextContent, "utf8");
|
|
31768
32550
|
}
|
|
31769
32551
|
|
|
31770
32552
|
// ../server/src/utils/directory-suggestions.ts
|
|
31771
32553
|
import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
|
|
31772
|
-
import
|
|
32554
|
+
import path14 from "node:path";
|
|
31773
32555
|
var DEFAULT_LIMIT = 30;
|
|
31774
32556
|
var MAX_LIMIT = 100;
|
|
31775
32557
|
var DEFAULT_MAX_DEPTH = 6;
|
|
@@ -31855,7 +32637,7 @@ function normalizeLimit(limit) {
|
|
|
31855
32637
|
return Math.max(1, Math.min(MAX_LIMIT, bounded));
|
|
31856
32638
|
}
|
|
31857
32639
|
async function searchWithinParentDirectory(input) {
|
|
31858
|
-
const parentPath =
|
|
32640
|
+
const parentPath = path14.resolve(input.homeRoot, input.parentPart || ".");
|
|
31859
32641
|
const parentRoot = await resolveDirectory(parentPath);
|
|
31860
32642
|
if (!parentRoot || !isPathInsideRoot(input.homeRoot, parentRoot)) {
|
|
31861
32643
|
return [];
|
|
@@ -31920,7 +32702,7 @@ async function searchAcrossHomeTree(input) {
|
|
|
31920
32702
|
return dedupeAndSort(ranked).slice(0, input.limit);
|
|
31921
32703
|
}
|
|
31922
32704
|
async function searchWorkspaceWithinParentDirectory(input) {
|
|
31923
|
-
const parentPath =
|
|
32705
|
+
const parentPath = path14.resolve(input.workspaceRoot, input.parentPart || ".");
|
|
31924
32706
|
const parentRoot = await resolveDirectory(parentPath);
|
|
31925
32707
|
if (!parentRoot || !isPathInsideRoot(input.workspaceRoot, parentRoot)) {
|
|
31926
32708
|
return [];
|
|
@@ -32166,15 +32948,15 @@ function findSegmentMatchIndex(segments, predicate) {
|
|
|
32166
32948
|
return -1;
|
|
32167
32949
|
}
|
|
32168
32950
|
function normalizeRelativePath2(homeRoot, absolutePath) {
|
|
32169
|
-
const relative =
|
|
32951
|
+
const relative = path14.relative(homeRoot, absolutePath);
|
|
32170
32952
|
if (!relative) {
|
|
32171
32953
|
return ".";
|
|
32172
32954
|
}
|
|
32173
|
-
return relative.split(
|
|
32955
|
+
return relative.split(path14.sep).join("/");
|
|
32174
32956
|
}
|
|
32175
32957
|
function isPathInsideRoot(root, target) {
|
|
32176
|
-
const relative =
|
|
32177
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
32958
|
+
const relative = path14.relative(root, target);
|
|
32959
|
+
return relative === "" || !relative.startsWith("..") && !path14.isAbsolute(relative);
|
|
32178
32960
|
}
|
|
32179
32961
|
function normalizeQueryParts(query2, homeRoot) {
|
|
32180
32962
|
const typedQuery = query2.trim().replace(/\\/g, "/");
|
|
@@ -32190,9 +32972,9 @@ function normalizeQueryParts(query2, homeRoot) {
|
|
|
32190
32972
|
normalized = normalized.slice(1);
|
|
32191
32973
|
}
|
|
32192
32974
|
}
|
|
32193
|
-
if (
|
|
32975
|
+
if (path14.isAbsolute(normalized)) {
|
|
32194
32976
|
isRooted = true;
|
|
32195
|
-
const absolute =
|
|
32977
|
+
const absolute = path14.resolve(normalized);
|
|
32196
32978
|
if (!isPathInsideRoot(homeRoot, absolute)) {
|
|
32197
32979
|
return null;
|
|
32198
32980
|
}
|
|
@@ -32231,8 +33013,8 @@ function normalizeQueryParts(query2, homeRoot) {
|
|
|
32231
33013
|
}
|
|
32232
33014
|
function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
|
|
32233
33015
|
let normalized = query2.trim().replace(/\\/g, "/");
|
|
32234
|
-
if (
|
|
32235
|
-
const absolute =
|
|
33016
|
+
if (path14.isAbsolute(normalized)) {
|
|
33017
|
+
const absolute = path14.resolve(normalized);
|
|
32236
33018
|
if (!isPathInsideRoot(workspaceRoot, absolute)) {
|
|
32237
33019
|
return null;
|
|
32238
33020
|
}
|
|
@@ -32258,7 +33040,7 @@ function normalizeWorkspaceQueryParts(query2, workspaceRoot) {
|
|
|
32258
33040
|
}
|
|
32259
33041
|
async function resolveDirectory(inputPath) {
|
|
32260
33042
|
try {
|
|
32261
|
-
const resolved = await realpath(
|
|
33043
|
+
const resolved = await realpath(path14.resolve(inputPath));
|
|
32262
33044
|
const stats = await stat3(resolved);
|
|
32263
33045
|
if (!stats.isDirectory()) {
|
|
32264
33046
|
return null;
|
|
@@ -32285,7 +33067,7 @@ async function listChildDirectories(input) {
|
|
|
32285
33067
|
if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
|
|
32286
33068
|
continue;
|
|
32287
33069
|
}
|
|
32288
|
-
const candidatePath =
|
|
33070
|
+
const candidatePath = path14.join(input.directory, dirent.name);
|
|
32289
33071
|
const absolutePath = await resolveDirectoryCandidate({
|
|
32290
33072
|
candidatePath,
|
|
32291
33073
|
dirent,
|
|
@@ -32322,7 +33104,7 @@ async function listWorkspaceChildEntries(input) {
|
|
|
32322
33104
|
if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
|
|
32323
33105
|
continue;
|
|
32324
33106
|
}
|
|
32325
|
-
const candidatePath =
|
|
33107
|
+
const candidatePath = path14.join(input.directory, dirent.name);
|
|
32326
33108
|
const entry = await resolveWorkspaceCandidate({
|
|
32327
33109
|
candidatePath,
|
|
32328
33110
|
dirent,
|
|
@@ -32345,7 +33127,7 @@ async function listWorkspaceChildEntries(input) {
|
|
|
32345
33127
|
}
|
|
32346
33128
|
async function resolveDirectoryCandidate(input) {
|
|
32347
33129
|
if (input.dirent.isDirectory()) {
|
|
32348
|
-
const resolved2 =
|
|
33130
|
+
const resolved2 = path14.resolve(input.candidatePath);
|
|
32349
33131
|
return isPathInsideRoot(input.homeRoot, resolved2) ? resolved2 : null;
|
|
32350
33132
|
}
|
|
32351
33133
|
const resolved = await resolveDirectory(input.candidatePath);
|
|
@@ -32356,14 +33138,14 @@ async function resolveDirectoryCandidate(input) {
|
|
|
32356
33138
|
}
|
|
32357
33139
|
async function resolveWorkspaceCandidate(input) {
|
|
32358
33140
|
if (input.dirent.isDirectory()) {
|
|
32359
|
-
const resolved =
|
|
33141
|
+
const resolved = path14.resolve(input.candidatePath);
|
|
32360
33142
|
if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
|
|
32361
33143
|
return null;
|
|
32362
33144
|
}
|
|
32363
33145
|
return { absolutePath: resolved, kind: "directory" };
|
|
32364
33146
|
}
|
|
32365
33147
|
if (input.dirent.isFile()) {
|
|
32366
|
-
const resolved =
|
|
33148
|
+
const resolved = path14.resolve(input.candidatePath);
|
|
32367
33149
|
if (!isPathInsideRoot(input.workspaceRoot, resolved)) {
|
|
32368
33150
|
return null;
|
|
32369
33151
|
}
|
|
@@ -32443,7 +33225,7 @@ function pruneWorkspaceEntryListCache() {
|
|
|
32443
33225
|
// ../server/src/utils/directory-listing.ts
|
|
32444
33226
|
import { readdir as readdir3, stat as stat4, realpath as realpath2 } from "node:fs/promises";
|
|
32445
33227
|
import { homedir as homedir3 } from "node:os";
|
|
32446
|
-
import
|
|
33228
|
+
import path15 from "node:path";
|
|
32447
33229
|
var DEFAULT_LIMIT2 = 500;
|
|
32448
33230
|
async function listDirectoryContents(options) {
|
|
32449
33231
|
const includeFiles = options.includeFiles ?? false;
|
|
@@ -32454,7 +33236,7 @@ async function listDirectoryContents(options) {
|
|
|
32454
33236
|
const collected = [];
|
|
32455
33237
|
for (const dirent of dirents) {
|
|
32456
33238
|
if (!includeHidden && dirent.name.startsWith(".")) continue;
|
|
32457
|
-
const childPath =
|
|
33239
|
+
const childPath = path15.join(resolvedPath, dirent.name);
|
|
32458
33240
|
const kind = await classifyEntry(dirent, childPath);
|
|
32459
33241
|
if (!kind) continue;
|
|
32460
33242
|
if (kind === "file" && !includeFiles) continue;
|
|
@@ -32462,7 +33244,7 @@ async function listDirectoryContents(options) {
|
|
|
32462
33244
|
if (collected.length >= limit) break;
|
|
32463
33245
|
}
|
|
32464
33246
|
collected.sort(compareEntries);
|
|
32465
|
-
const parent =
|
|
33247
|
+
const parent = path15.dirname(resolvedPath);
|
|
32466
33248
|
return {
|
|
32467
33249
|
path: resolvedPath,
|
|
32468
33250
|
parent: parent === resolvedPath ? null : parent,
|
|
@@ -32473,12 +33255,12 @@ async function resolveAbsolutePath(rawPath) {
|
|
|
32473
33255
|
const home = process.env.HOME ?? homedir3();
|
|
32474
33256
|
const trimmed = rawPath.trim();
|
|
32475
33257
|
if (trimmed === "" || trimmed === "~") {
|
|
32476
|
-
return
|
|
33258
|
+
return path15.resolve(home);
|
|
32477
33259
|
}
|
|
32478
33260
|
if (trimmed.startsWith("~/")) {
|
|
32479
|
-
return
|
|
33261
|
+
return path15.resolve(home, trimmed.slice(2));
|
|
32480
33262
|
}
|
|
32481
|
-
if (!
|
|
33263
|
+
if (!path15.isAbsolute(trimmed)) {
|
|
32482
33264
|
throw new Error(
|
|
32483
33265
|
`list_directory requires an absolute path, an empty string, or a "~"-prefixed path; got ${JSON.stringify(rawPath)}`
|
|
32484
33266
|
);
|
|
@@ -32486,7 +33268,7 @@ async function resolveAbsolutePath(rawPath) {
|
|
|
32486
33268
|
try {
|
|
32487
33269
|
return await realpath2(trimmed);
|
|
32488
33270
|
} catch {
|
|
32489
|
-
return
|
|
33271
|
+
return path15.resolve(trimmed);
|
|
32490
33272
|
}
|
|
32491
33273
|
}
|
|
32492
33274
|
async function classifyEntry(dirent, fullPath) {
|
|
@@ -32619,9 +33401,9 @@ function buildChatMentionNotification(input) {
|
|
|
32619
33401
|
}
|
|
32620
33402
|
|
|
32621
33403
|
// ../server/src/server/roles/scanner.ts
|
|
32622
|
-
import
|
|
33404
|
+
import fs9 from "node:fs/promises";
|
|
32623
33405
|
import os7 from "node:os";
|
|
32624
|
-
import
|
|
33406
|
+
import path16 from "node:path";
|
|
32625
33407
|
var NAME_REGEX2 = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
32626
33408
|
function homeDir2() {
|
|
32627
33409
|
return process.env.HOME || os7.homedir();
|
|
@@ -32631,22 +33413,22 @@ function resolveScopeDir2(scope, workspaceRoot) {
|
|
|
32631
33413
|
if (!workspaceRoot) {
|
|
32632
33414
|
throw new Error('workspaceRoot is required for scope "project"');
|
|
32633
33415
|
}
|
|
32634
|
-
return
|
|
33416
|
+
return path16.join(workspaceRoot, ".roles");
|
|
32635
33417
|
}
|
|
32636
|
-
return
|
|
33418
|
+
return path16.join(homeDir2(), ".appostle", ".roles");
|
|
32637
33419
|
}
|
|
32638
33420
|
function allowedRoots2(workspaceRoot) {
|
|
32639
|
-
const roots = [
|
|
33421
|
+
const roots = [path16.join(homeDir2(), ".appostle", ".roles")];
|
|
32640
33422
|
if (workspaceRoot) {
|
|
32641
|
-
roots.push(
|
|
33423
|
+
roots.push(path16.join(workspaceRoot, ".roles"));
|
|
32642
33424
|
}
|
|
32643
|
-
return roots.map((r) =>
|
|
33425
|
+
return roots.map((r) => path16.resolve(r));
|
|
32644
33426
|
}
|
|
32645
33427
|
function isInsideAllowedRoot2(absPath, workspaceRoot) {
|
|
32646
|
-
const resolved =
|
|
33428
|
+
const resolved = path16.resolve(absPath);
|
|
32647
33429
|
for (const root of allowedRoots2(workspaceRoot)) {
|
|
32648
|
-
const rel =
|
|
32649
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
33430
|
+
const rel = path16.relative(root, resolved);
|
|
33431
|
+
if (rel === "" || !rel.startsWith("..") && !path16.isAbsolute(rel)) {
|
|
32650
33432
|
return true;
|
|
32651
33433
|
}
|
|
32652
33434
|
}
|
|
@@ -32834,7 +33616,7 @@ function rewriteFrontmatter2(originalLines, next) {
|
|
|
32834
33616
|
async function readRolesFromDir(scope, dir, category) {
|
|
32835
33617
|
let entries;
|
|
32836
33618
|
try {
|
|
32837
|
-
entries = await
|
|
33619
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
32838
33620
|
} catch {
|
|
32839
33621
|
return [];
|
|
32840
33622
|
}
|
|
@@ -32844,17 +33626,17 @@ async function readRolesFromDir(scope, dir, category) {
|
|
|
32844
33626
|
if (!entry.name.endsWith(".md")) continue;
|
|
32845
33627
|
const name = entry.name.slice(0, -".md".length);
|
|
32846
33628
|
if (!name || !NAME_REGEX2.test(name)) continue;
|
|
32847
|
-
const fullPath =
|
|
33629
|
+
const fullPath = path16.join(dir, entry.name);
|
|
32848
33630
|
let stat5;
|
|
32849
33631
|
try {
|
|
32850
|
-
const s = await
|
|
33632
|
+
const s = await fs9.stat(fullPath);
|
|
32851
33633
|
stat5 = { mtime: s.mtime, size: s.size };
|
|
32852
33634
|
} catch {
|
|
32853
33635
|
continue;
|
|
32854
33636
|
}
|
|
32855
33637
|
let parsed;
|
|
32856
33638
|
try {
|
|
32857
|
-
const text = await
|
|
33639
|
+
const text = await fs9.readFile(fullPath, "utf8");
|
|
32858
33640
|
parsed = parseFrontmatter(text);
|
|
32859
33641
|
} catch {
|
|
32860
33642
|
parsed = {
|
|
@@ -32888,13 +33670,13 @@ async function readRolesFromDir(scope, dir, category) {
|
|
|
32888
33670
|
async function readRolesFromScopeDir(scope, scopeDir) {
|
|
32889
33671
|
let topEntries;
|
|
32890
33672
|
try {
|
|
32891
|
-
topEntries = await
|
|
33673
|
+
topEntries = await fs9.readdir(scopeDir, { withFileTypes: true });
|
|
32892
33674
|
} catch {
|
|
32893
33675
|
return [];
|
|
32894
33676
|
}
|
|
32895
33677
|
const flat = await readRolesFromDir(scope, scopeDir, null);
|
|
32896
33678
|
const categoryResults = await Promise.all(
|
|
32897
|
-
topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope,
|
|
33679
|
+
topEntries.filter((e) => e.isDirectory() && NAME_REGEX2.test(e.name)).map((e) => readRolesFromDir(scope, path16.join(scopeDir, e.name), e.name))
|
|
32898
33680
|
);
|
|
32899
33681
|
const all = [...flat, ...categoryResults.flat()];
|
|
32900
33682
|
all.sort((a, b) => {
|
|
@@ -32934,11 +33716,11 @@ async function createRole(args) {
|
|
|
32934
33716
|
throw new Error(`Role name must not contain path separators or "..".`);
|
|
32935
33717
|
}
|
|
32936
33718
|
const scopeDir = resolveScopeDir2(args.scope, args.workspaceRoot);
|
|
32937
|
-
const dir = args.category ?
|
|
32938
|
-
await
|
|
32939
|
-
const filePath =
|
|
33719
|
+
const dir = args.category ? path16.join(scopeDir, args.category) : scopeDir;
|
|
33720
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
33721
|
+
const filePath = path16.join(dir, `${args.name}.md`);
|
|
32940
33722
|
try {
|
|
32941
|
-
await
|
|
33723
|
+
await fs9.access(filePath);
|
|
32942
33724
|
throw new Error(`A role named "${args.name}" already exists at ${filePath}`);
|
|
32943
33725
|
} catch (err) {
|
|
32944
33726
|
if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
|
|
@@ -32949,7 +33731,7 @@ async function createRole(args) {
|
|
|
32949
33731
|
}
|
|
32950
33732
|
}
|
|
32951
33733
|
const initial = buildStarterRole(args.name);
|
|
32952
|
-
await
|
|
33734
|
+
await fs9.writeFile(filePath, initial, "utf8");
|
|
32953
33735
|
return { path: filePath };
|
|
32954
33736
|
}
|
|
32955
33737
|
function buildStarterRole(name) {
|
|
@@ -32970,7 +33752,7 @@ the role is invoked.
|
|
|
32970
33752
|
`;
|
|
32971
33753
|
}
|
|
32972
33754
|
async function writeRoleFrontmatter(args, workspaceRoot) {
|
|
32973
|
-
if (!
|
|
33755
|
+
if (!path16.isAbsolute(args.path)) {
|
|
32974
33756
|
throw new Error(`writeRoleFrontmatter expects an absolute path; got "${args.path}"`);
|
|
32975
33757
|
}
|
|
32976
33758
|
if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
|
|
@@ -32978,7 +33760,7 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
|
|
|
32978
33760
|
}
|
|
32979
33761
|
let original;
|
|
32980
33762
|
try {
|
|
32981
|
-
original = await
|
|
33763
|
+
original = await fs9.readFile(args.path, "utf8");
|
|
32982
33764
|
} catch (err) {
|
|
32983
33765
|
throw new Error(
|
|
32984
33766
|
`Failed to read role file: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -32991,23 +33773,23 @@ async function writeRoleFrontmatter(args, workspaceRoot) {
|
|
|
32991
33773
|
${parsed.body}` : `${nextFrontmatter}
|
|
32992
33774
|
|
|
32993
33775
|
${original}`;
|
|
32994
|
-
await
|
|
33776
|
+
await fs9.writeFile(args.path, nextContent, "utf8");
|
|
32995
33777
|
}
|
|
32996
33778
|
async function moveRole(args, workspaceRoot) {
|
|
32997
|
-
if (!
|
|
33779
|
+
if (!path16.isAbsolute(args.path)) {
|
|
32998
33780
|
throw new Error(`moveRole expects an absolute path; got "${args.path}"`);
|
|
32999
33781
|
}
|
|
33000
33782
|
if (!isInsideAllowedRoot2(args.path, workspaceRoot)) {
|
|
33001
33783
|
throw new Error(`Path "${args.path}" is not inside an allowlisted role root`);
|
|
33002
33784
|
}
|
|
33003
|
-
const oldDir =
|
|
33004
|
-
const oldFilename =
|
|
33785
|
+
const oldDir = path16.dirname(args.path);
|
|
33786
|
+
const oldFilename = path16.basename(args.path, ".md");
|
|
33005
33787
|
const roots = allowedRoots2(workspaceRoot);
|
|
33006
|
-
const rolesRoot = roots.find((r) =>
|
|
33788
|
+
const rolesRoot = roots.find((r) => path16.resolve(args.path).startsWith(r));
|
|
33007
33789
|
if (!rolesRoot) {
|
|
33008
33790
|
throw new Error(`Cannot determine roles root for "${args.path}"`);
|
|
33009
33791
|
}
|
|
33010
|
-
const relFromRoot =
|
|
33792
|
+
const relFromRoot = path16.relative(rolesRoot, path16.dirname(args.path));
|
|
33011
33793
|
const currentCategory = relFromRoot && relFromRoot !== "." ? relFromRoot : "";
|
|
33012
33794
|
const newName = args.newName ?? oldFilename;
|
|
33013
33795
|
const newCategory = args.newCategory !== void 0 ? args.newCategory : currentCategory;
|
|
@@ -33017,19 +33799,19 @@ async function moveRole(args, workspaceRoot) {
|
|
|
33017
33799
|
if (newCategory && !NAME_REGEX2.test(newCategory)) {
|
|
33018
33800
|
throw new Error(`Invalid category name: "${newCategory}"`);
|
|
33019
33801
|
}
|
|
33020
|
-
const newDir = newCategory ?
|
|
33021
|
-
const newPath =
|
|
33022
|
-
if (
|
|
33802
|
+
const newDir = newCategory ? path16.join(rolesRoot, newCategory) : rolesRoot;
|
|
33803
|
+
const newPath = path16.join(newDir, `${newName}.md`);
|
|
33804
|
+
if (path16.resolve(newPath) === path16.resolve(args.path)) {
|
|
33023
33805
|
return { path: args.path };
|
|
33024
33806
|
}
|
|
33025
|
-
await
|
|
33807
|
+
await fs9.mkdir(newDir, { recursive: true });
|
|
33026
33808
|
try {
|
|
33027
|
-
await
|
|
33809
|
+
await fs9.access(newPath);
|
|
33028
33810
|
throw new Error(`A role already exists at "${newPath}"`);
|
|
33029
33811
|
} catch (err) {
|
|
33030
33812
|
if (err.code !== "ENOENT") throw err;
|
|
33031
33813
|
}
|
|
33032
|
-
await
|
|
33814
|
+
await fs9.rename(args.path, newPath);
|
|
33033
33815
|
const frontmatterPatch = {};
|
|
33034
33816
|
if (newName !== oldFilename) {
|
|
33035
33817
|
frontmatterPatch.description = void 0;
|
|
@@ -33045,9 +33827,9 @@ async function moveRole(args, workspaceRoot) {
|
|
|
33045
33827
|
);
|
|
33046
33828
|
if (oldDir !== rolesRoot) {
|
|
33047
33829
|
try {
|
|
33048
|
-
const remaining = await
|
|
33830
|
+
const remaining = await fs9.readdir(oldDir);
|
|
33049
33831
|
if (remaining.length === 0) {
|
|
33050
|
-
await
|
|
33832
|
+
await fs9.rmdir(oldDir);
|
|
33051
33833
|
}
|
|
33052
33834
|
} catch {
|
|
33053
33835
|
}
|
|
@@ -33056,30 +33838,30 @@ async function moveRole(args, workspaceRoot) {
|
|
|
33056
33838
|
}
|
|
33057
33839
|
|
|
33058
33840
|
// ../server/src/server/brands/scanner.ts
|
|
33059
|
-
import
|
|
33060
|
-
import
|
|
33841
|
+
import fs10 from "node:fs/promises";
|
|
33842
|
+
import path17 from "node:path";
|
|
33061
33843
|
var NAME_REGEX3 = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
33062
33844
|
function resolveScopeDir3(scope, workspaceRoot) {
|
|
33063
33845
|
if (scope === "project") {
|
|
33064
33846
|
if (!workspaceRoot) {
|
|
33065
33847
|
throw new Error('workspaceRoot is required for scope "project"');
|
|
33066
33848
|
}
|
|
33067
|
-
return
|
|
33849
|
+
return path17.join(workspaceRoot, ".brand");
|
|
33068
33850
|
}
|
|
33069
33851
|
throw new Error(`Unknown scope: ${scope}`);
|
|
33070
33852
|
}
|
|
33071
33853
|
function allowedRoots3(workspaceRoot) {
|
|
33072
33854
|
const roots = [];
|
|
33073
33855
|
if (workspaceRoot) {
|
|
33074
|
-
roots.push(
|
|
33856
|
+
roots.push(path17.join(workspaceRoot, ".brand"));
|
|
33075
33857
|
}
|
|
33076
|
-
return roots.map((r) =>
|
|
33858
|
+
return roots.map((r) => path17.resolve(r));
|
|
33077
33859
|
}
|
|
33078
33860
|
function isInsideAllowedRoot3(absPath, workspaceRoot) {
|
|
33079
|
-
const resolved =
|
|
33861
|
+
const resolved = path17.resolve(absPath);
|
|
33080
33862
|
for (const root of allowedRoots3(workspaceRoot)) {
|
|
33081
|
-
const rel =
|
|
33082
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
33863
|
+
const rel = path17.relative(root, resolved);
|
|
33864
|
+
if (rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel)) {
|
|
33083
33865
|
return true;
|
|
33084
33866
|
}
|
|
33085
33867
|
}
|
|
@@ -33296,7 +34078,7 @@ function rewriteFrontmatter3(originalLines, next) {
|
|
|
33296
34078
|
async function readBrandsFromDir(scope, dir) {
|
|
33297
34079
|
let entries;
|
|
33298
34080
|
try {
|
|
33299
|
-
entries = await
|
|
34081
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
33300
34082
|
} catch {
|
|
33301
34083
|
return [];
|
|
33302
34084
|
}
|
|
@@ -33306,17 +34088,17 @@ async function readBrandsFromDir(scope, dir) {
|
|
|
33306
34088
|
if (!entry.name.endsWith(".md")) continue;
|
|
33307
34089
|
const name = entry.name.slice(0, -".md".length);
|
|
33308
34090
|
if (!name || !NAME_REGEX3.test(name)) continue;
|
|
33309
|
-
const fullPath =
|
|
34091
|
+
const fullPath = path17.join(dir, entry.name);
|
|
33310
34092
|
let stat5;
|
|
33311
34093
|
try {
|
|
33312
|
-
const s = await
|
|
34094
|
+
const s = await fs10.stat(fullPath);
|
|
33313
34095
|
stat5 = { mtime: s.mtime, size: s.size };
|
|
33314
34096
|
} catch {
|
|
33315
34097
|
continue;
|
|
33316
34098
|
}
|
|
33317
34099
|
let parsed;
|
|
33318
34100
|
try {
|
|
33319
|
-
const text = await
|
|
34101
|
+
const text = await fs10.readFile(fullPath, "utf8");
|
|
33320
34102
|
const { rawFrontmatterLines, hadFrontmatter } = parseBrandFile(text);
|
|
33321
34103
|
parsed = hadFrontmatter ? parseFrontmatter2(rawFrontmatterLines) : { description: "", tags: [], variables: [] };
|
|
33322
34104
|
} catch {
|
|
@@ -33350,10 +34132,10 @@ async function createBrand(args) {
|
|
|
33350
34132
|
throw new Error(`Brand name must not contain path separators or "..".`);
|
|
33351
34133
|
}
|
|
33352
34134
|
const dir = resolveScopeDir3("project", args.workspaceRoot);
|
|
33353
|
-
await
|
|
33354
|
-
const filePath =
|
|
34135
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
34136
|
+
const filePath = path17.join(dir, `${args.name}.md`);
|
|
33355
34137
|
try {
|
|
33356
|
-
await
|
|
34138
|
+
await fs10.access(filePath);
|
|
33357
34139
|
throw new Error(`A brand named "${args.name}" already exists at ${filePath}`);
|
|
33358
34140
|
} catch (err) {
|
|
33359
34141
|
if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
|
|
@@ -33363,7 +34145,7 @@ async function createBrand(args) {
|
|
|
33363
34145
|
throw err;
|
|
33364
34146
|
}
|
|
33365
34147
|
}
|
|
33366
|
-
await
|
|
34148
|
+
await fs10.writeFile(filePath, buildStarterBrand(args.name), "utf8");
|
|
33367
34149
|
return { path: filePath };
|
|
33368
34150
|
}
|
|
33369
34151
|
function buildStarterBrand(name) {
|
|
@@ -33408,7 +34190,7 @@ async function copyBrandAsset(args) {
|
|
|
33408
34190
|
if (!args.workspaceRoot) {
|
|
33409
34191
|
throw new Error("workspaceRoot is required to copy a brand asset");
|
|
33410
34192
|
}
|
|
33411
|
-
if (!args.sourcePath || !
|
|
34193
|
+
if (!args.sourcePath || !path17.isAbsolute(args.sourcePath)) {
|
|
33412
34194
|
throw new Error(`copyBrandAsset expects an absolute sourcePath; got "${args.sourcePath}"`);
|
|
33413
34195
|
}
|
|
33414
34196
|
if (!TARGET_NAME_REGEX.test(args.targetName) || args.targetName.includes("..")) {
|
|
@@ -33416,20 +34198,20 @@ async function copyBrandAsset(args) {
|
|
|
33416
34198
|
`Invalid targetName "${args.targetName}". Use letters, digits, dot, underscore, dash.`
|
|
33417
34199
|
);
|
|
33418
34200
|
}
|
|
33419
|
-
const stats = await
|
|
34201
|
+
const stats = await fs10.stat(args.sourcePath);
|
|
33420
34202
|
if (!stats.isFile()) {
|
|
33421
34203
|
throw new Error(`Source path is not a regular file: ${args.sourcePath}`);
|
|
33422
34204
|
}
|
|
33423
|
-
const ext =
|
|
34205
|
+
const ext = path17.extname(args.sourcePath).toLowerCase();
|
|
33424
34206
|
const fileName = `${args.targetName}${ext}`;
|
|
33425
|
-
const assetsDir =
|
|
33426
|
-
const destAbs =
|
|
33427
|
-
const rel =
|
|
33428
|
-
if (rel.startsWith("..") ||
|
|
34207
|
+
const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
|
|
34208
|
+
const destAbs = path17.resolve(assetsDir, fileName);
|
|
34209
|
+
const rel = path17.relative(path17.resolve(assetsDir), destAbs);
|
|
34210
|
+
if (rel.startsWith("..") || path17.isAbsolute(rel)) {
|
|
33429
34211
|
throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
|
|
33430
34212
|
}
|
|
33431
|
-
await
|
|
33432
|
-
await
|
|
34213
|
+
await fs10.mkdir(assetsDir, { recursive: true });
|
|
34214
|
+
await fs10.copyFile(args.sourcePath, destAbs);
|
|
33433
34215
|
return {
|
|
33434
34216
|
relativePath: `assets/${fileName}`,
|
|
33435
34217
|
absolutePath: destAbs
|
|
@@ -33447,13 +34229,13 @@ async function uploadBrandAsset(args) {
|
|
|
33447
34229
|
if (!args.dataBase64 || args.dataBase64.trim().length === 0) {
|
|
33448
34230
|
throw new Error("No file data provided for brand asset upload");
|
|
33449
34231
|
}
|
|
33450
|
-
const extFromSource = args.sourceName ?
|
|
34232
|
+
const extFromSource = args.sourceName ? path17.extname(args.sourceName).toLowerCase() : "";
|
|
33451
34233
|
const ext = extFromSource || ".png";
|
|
33452
34234
|
const fileName = `${args.targetName}${ext}`;
|
|
33453
|
-
const assetsDir =
|
|
33454
|
-
const destAbs =
|
|
33455
|
-
const rel =
|
|
33456
|
-
if (rel.startsWith("..") ||
|
|
34235
|
+
const assetsDir = path17.join(args.workspaceRoot, ".brand", "assets");
|
|
34236
|
+
const destAbs = path17.resolve(assetsDir, fileName);
|
|
34237
|
+
const rel = path17.relative(path17.resolve(assetsDir), destAbs);
|
|
34238
|
+
if (rel.startsWith("..") || path17.isAbsolute(rel)) {
|
|
33457
34239
|
throw new Error(`Refusing to write outside of .brand/assets: ${destAbs}`);
|
|
33458
34240
|
}
|
|
33459
34241
|
let data;
|
|
@@ -33465,15 +34247,15 @@ async function uploadBrandAsset(args) {
|
|
|
33465
34247
|
if (data.length === 0) {
|
|
33466
34248
|
throw new Error("Uploaded brand asset is empty");
|
|
33467
34249
|
}
|
|
33468
|
-
await
|
|
33469
|
-
await
|
|
34250
|
+
await fs10.mkdir(assetsDir, { recursive: true });
|
|
34251
|
+
await fs10.writeFile(destAbs, data);
|
|
33470
34252
|
return {
|
|
33471
34253
|
relativePath: `assets/${fileName}`,
|
|
33472
34254
|
absolutePath: destAbs
|
|
33473
34255
|
};
|
|
33474
34256
|
}
|
|
33475
34257
|
async function writeBrandFrontmatter(args, workspaceRoot) {
|
|
33476
|
-
if (!
|
|
34258
|
+
if (!path17.isAbsolute(args.path)) {
|
|
33477
34259
|
throw new Error(`writeBrandFrontmatter expects an absolute path; got "${args.path}"`);
|
|
33478
34260
|
}
|
|
33479
34261
|
if (!isInsideAllowedRoot3(args.path, workspaceRoot)) {
|
|
@@ -33481,7 +34263,7 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
|
|
|
33481
34263
|
}
|
|
33482
34264
|
let original;
|
|
33483
34265
|
try {
|
|
33484
|
-
original = await
|
|
34266
|
+
original = await fs10.readFile(args.path, "utf8");
|
|
33485
34267
|
} catch (err) {
|
|
33486
34268
|
throw new Error(
|
|
33487
34269
|
`Failed to read brand file: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -33494,14 +34276,14 @@ async function writeBrandFrontmatter(args, workspaceRoot) {
|
|
|
33494
34276
|
${parsed.body}` : `${nextFrontmatter}
|
|
33495
34277
|
|
|
33496
34278
|
${original}`;
|
|
33497
|
-
await
|
|
34279
|
+
await fs10.writeFile(args.path, nextContent, "utf8");
|
|
33498
34280
|
}
|
|
33499
34281
|
|
|
33500
34282
|
// ../server/src/services/oauth-service.ts
|
|
33501
|
-
import { createHash as
|
|
34283
|
+
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
33502
34284
|
import { mkdir as mkdir4, readFile as readFile3, rename, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
33503
34285
|
import { homedir as homedir4 } from "node:os";
|
|
33504
|
-
import { dirname as dirname4, join as
|
|
34286
|
+
import { dirname as dirname4, join as join11 } from "node:path";
|
|
33505
34287
|
import * as YAML from "yaml";
|
|
33506
34288
|
var GITLAB_CLIENT_ID = "7783dfb277ccf3dbce35a9d54c3c0aec24ed2006cb2d171e0749fd8444ed4f7c";
|
|
33507
34289
|
var GITLAB_AUTHORIZE_URL = "https://gitlab.com/oauth/authorize";
|
|
@@ -33523,7 +34305,7 @@ var OAuthError = class extends Error {
|
|
|
33523
34305
|
function createOAuthService(options) {
|
|
33524
34306
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
33525
34307
|
const now = options.now ?? Date.now;
|
|
33526
|
-
const credentialsPath =
|
|
34308
|
+
const credentialsPath = join11(options.appostleHome, "credentials.json");
|
|
33527
34309
|
const glabConfigPath = options.glabConfigPath ?? defaultGlabConfigPath();
|
|
33528
34310
|
const oauthLogger = options.logger.child({ module: "oauth-service" });
|
|
33529
34311
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -33645,7 +34427,7 @@ function generateCodeVerifier() {
|
|
|
33645
34427
|
return base64url(randomBytes2(32));
|
|
33646
34428
|
}
|
|
33647
34429
|
function computeCodeChallenge(verifier) {
|
|
33648
|
-
return base64url(
|
|
34430
|
+
return base64url(createHash4("sha256").update(verifier).digest());
|
|
33649
34431
|
}
|
|
33650
34432
|
function generateState() {
|
|
33651
34433
|
return base64url(randomBytes2(24));
|
|
@@ -33742,80 +34524,80 @@ async function fetchGitLabUsername(fetchImpl, accessToken) {
|
|
|
33742
34524
|
return null;
|
|
33743
34525
|
}
|
|
33744
34526
|
}
|
|
33745
|
-
async function readCredential(
|
|
34527
|
+
async function readCredential(path26, log2) {
|
|
33746
34528
|
try {
|
|
33747
|
-
const raw = await readFile3(
|
|
34529
|
+
const raw = await readFile3(path26, "utf8");
|
|
33748
34530
|
const parsed = JSON.parse(raw);
|
|
33749
34531
|
return parsed.gitlab ?? null;
|
|
33750
34532
|
} catch (error) {
|
|
33751
34533
|
if (isNotFound(error)) return null;
|
|
33752
|
-
log2.warn({ err: error, path:
|
|
34534
|
+
log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed");
|
|
33753
34535
|
return null;
|
|
33754
34536
|
}
|
|
33755
34537
|
}
|
|
33756
|
-
async function persistCredential(
|
|
33757
|
-
await mkdir4(dirname4(
|
|
34538
|
+
async function persistCredential(path26, credential, log2) {
|
|
34539
|
+
await mkdir4(dirname4(path26), { recursive: true });
|
|
33758
34540
|
let current = {};
|
|
33759
34541
|
try {
|
|
33760
|
-
const raw = await readFile3(
|
|
34542
|
+
const raw = await readFile3(path26, "utf8");
|
|
33761
34543
|
current = JSON.parse(raw);
|
|
33762
34544
|
} catch (error) {
|
|
33763
34545
|
if (!isNotFound(error)) {
|
|
33764
|
-
log2.warn({ err: error, path:
|
|
34546
|
+
log2.warn({ err: error, path: path26 }, "oauth.credentials.read_failed_overwriting");
|
|
33765
34547
|
}
|
|
33766
34548
|
}
|
|
33767
34549
|
const next = { ...current, gitlab: credential };
|
|
33768
|
-
const tmpPath = `${
|
|
34550
|
+
const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
|
|
33769
34551
|
await writeFile4(tmpPath, JSON.stringify(next, null, 2), { mode: 384 });
|
|
33770
|
-
await rename(tmpPath,
|
|
34552
|
+
await rename(tmpPath, path26);
|
|
33771
34553
|
}
|
|
33772
|
-
async function deleteCredential(
|
|
34554
|
+
async function deleteCredential(path26, log2) {
|
|
33773
34555
|
let current = {};
|
|
33774
34556
|
try {
|
|
33775
|
-
const raw = await readFile3(
|
|
34557
|
+
const raw = await readFile3(path26, "utf8");
|
|
33776
34558
|
current = JSON.parse(raw);
|
|
33777
34559
|
} catch (error) {
|
|
33778
34560
|
if (isNotFound(error)) return;
|
|
33779
|
-
log2.warn({ err: error, path:
|
|
34561
|
+
log2.warn({ err: error, path: path26 }, "oauth.credentials.delete_read_failed");
|
|
33780
34562
|
return;
|
|
33781
34563
|
}
|
|
33782
34564
|
delete current.gitlab;
|
|
33783
34565
|
if (Object.keys(current).length === 0) {
|
|
33784
34566
|
try {
|
|
33785
|
-
await unlink(
|
|
34567
|
+
await unlink(path26);
|
|
33786
34568
|
} catch (error) {
|
|
33787
34569
|
if (!isNotFound(error)) {
|
|
33788
|
-
log2.warn({ err: error, path:
|
|
34570
|
+
log2.warn({ err: error, path: path26 }, "oauth.credentials.unlink_failed");
|
|
33789
34571
|
}
|
|
33790
34572
|
}
|
|
33791
34573
|
return;
|
|
33792
34574
|
}
|
|
33793
|
-
const tmpPath = `${
|
|
34575
|
+
const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
|
|
33794
34576
|
await writeFile4(tmpPath, JSON.stringify(current, null, 2), { mode: 384 });
|
|
33795
|
-
await rename(tmpPath,
|
|
34577
|
+
await rename(tmpPath, path26);
|
|
33796
34578
|
}
|
|
33797
34579
|
function defaultGlabConfigPath() {
|
|
33798
34580
|
const home = homedir4();
|
|
33799
34581
|
if (process.platform === "darwin") {
|
|
33800
|
-
return
|
|
34582
|
+
return join11(home, "Library", "Application Support", "glab-cli", "config.yml");
|
|
33801
34583
|
}
|
|
33802
34584
|
if (process.platform === "win32") {
|
|
33803
34585
|
const appData = process.env.APPDATA;
|
|
33804
|
-
if (appData) return
|
|
33805
|
-
return
|
|
34586
|
+
if (appData) return join11(appData, "glab-cli", "config.yml");
|
|
34587
|
+
return join11(home, "AppData", "Roaming", "glab-cli", "config.yml");
|
|
33806
34588
|
}
|
|
33807
34589
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
33808
|
-
return
|
|
34590
|
+
return join11(xdg && xdg.length > 0 ? xdg : join11(home, ".config"), "glab-cli", "config.yml");
|
|
33809
34591
|
}
|
|
33810
|
-
async function writeGlabConfig(
|
|
33811
|
-
await mkdir4(dirname4(
|
|
34592
|
+
async function writeGlabConfig(path26, credential, log2) {
|
|
34593
|
+
await mkdir4(dirname4(path26), { recursive: true });
|
|
33812
34594
|
let doc;
|
|
33813
34595
|
try {
|
|
33814
|
-
const raw = await readFile3(
|
|
34596
|
+
const raw = await readFile3(path26, "utf8");
|
|
33815
34597
|
doc = YAML.parseDocument(raw);
|
|
33816
34598
|
if (doc.errors.length > 0) {
|
|
33817
34599
|
log2.warn(
|
|
33818
|
-
{ errors: doc.errors.map((e) => e.message), path:
|
|
34600
|
+
{ errors: doc.errors.map((e) => e.message), path: path26 },
|
|
33819
34601
|
"oauth.glab.parse_errors_replacing"
|
|
33820
34602
|
);
|
|
33821
34603
|
doc = YAML.parseDocument("{}");
|
|
@@ -33824,7 +34606,7 @@ async function writeGlabConfig(path24, credential, log2) {
|
|
|
33824
34606
|
if (isNotFound(error)) {
|
|
33825
34607
|
doc = YAML.parseDocument("{}");
|
|
33826
34608
|
} else {
|
|
33827
|
-
log2.warn({ err: error, path:
|
|
34609
|
+
log2.warn({ err: error, path: path26 }, "oauth.glab.read_failed_replacing");
|
|
33828
34610
|
doc = YAML.parseDocument("{}");
|
|
33829
34611
|
}
|
|
33830
34612
|
}
|
|
@@ -33837,18 +34619,18 @@ async function writeGlabConfig(path24, credential, log2) {
|
|
|
33837
34619
|
if (credential.username) {
|
|
33838
34620
|
hostEntry.set("user", credential.username);
|
|
33839
34621
|
}
|
|
33840
|
-
const tmpPath = `${
|
|
34622
|
+
const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
|
|
33841
34623
|
await writeFile4(tmpPath, doc.toString(), { mode: 384 });
|
|
33842
|
-
await rename(tmpPath,
|
|
34624
|
+
await rename(tmpPath, path26);
|
|
33843
34625
|
}
|
|
33844
|
-
async function removeGlabHost(
|
|
34626
|
+
async function removeGlabHost(path26, log2) {
|
|
33845
34627
|
let doc;
|
|
33846
34628
|
try {
|
|
33847
|
-
const raw = await readFile3(
|
|
34629
|
+
const raw = await readFile3(path26, "utf8");
|
|
33848
34630
|
doc = YAML.parseDocument(raw);
|
|
33849
34631
|
} catch (error) {
|
|
33850
34632
|
if (isNotFound(error)) return;
|
|
33851
|
-
log2.warn({ err: error, path:
|
|
34633
|
+
log2.warn({ err: error, path: path26 }, "oauth.glab.remove_read_failed");
|
|
33852
34634
|
return;
|
|
33853
34635
|
}
|
|
33854
34636
|
const hosts = doc.get("hosts");
|
|
@@ -33857,9 +34639,9 @@ async function removeGlabHost(path24, log2) {
|
|
|
33857
34639
|
if (hosts.items.length === 0) {
|
|
33858
34640
|
doc.delete("hosts");
|
|
33859
34641
|
}
|
|
33860
|
-
const tmpPath = `${
|
|
34642
|
+
const tmpPath = `${path26}.tmp-${process.pid}-${Date.now()}`;
|
|
33861
34643
|
await writeFile4(tmpPath, doc.toString(), { mode: 384 });
|
|
33862
|
-
await rename(tmpPath,
|
|
34644
|
+
await rename(tmpPath, path26);
|
|
33863
34645
|
}
|
|
33864
34646
|
function ensureMap(doc, key) {
|
|
33865
34647
|
const existing = doc.get(key);
|
|
@@ -34825,6 +35607,50 @@ function summarizeFetchWorkspacesEntries(entries) {
|
|
|
34825
35607
|
workspaces
|
|
34826
35608
|
};
|
|
34827
35609
|
}
|
|
35610
|
+
function synthesizeRecordedUserText(rawText, files, imageCount = 0) {
|
|
35611
|
+
if (rawText.trim().length > 0) {
|
|
35612
|
+
return rawText;
|
|
35613
|
+
}
|
|
35614
|
+
if (files.length === 0 && imageCount === 0) {
|
|
35615
|
+
return rawText;
|
|
35616
|
+
}
|
|
35617
|
+
const parts = [];
|
|
35618
|
+
if (imageCount > 0) {
|
|
35619
|
+
parts.push(imageCount === 1 ? "Sent 1 image" : `Sent ${imageCount} images`);
|
|
35620
|
+
}
|
|
35621
|
+
if (files.length > 0) {
|
|
35622
|
+
const names = files.map((f) => f.fileName).join(", ");
|
|
35623
|
+
parts.push(
|
|
35624
|
+
files.length === 1 ? `uploaded ${names}` : `uploaded ${files.length} files: ${names}`
|
|
35625
|
+
);
|
|
35626
|
+
}
|
|
35627
|
+
return parts.join(" and ");
|
|
35628
|
+
}
|
|
35629
|
+
function buildImageEcho(incoming, persisted) {
|
|
35630
|
+
const out = [];
|
|
35631
|
+
for (let i = 0; i < incoming.length; i++) {
|
|
35632
|
+
const img = incoming[i];
|
|
35633
|
+
const stored = persisted[i];
|
|
35634
|
+
const id = stored?.id ?? img?.id;
|
|
35635
|
+
if (!img || !id) continue;
|
|
35636
|
+
const fileName = img.fileName ?? stored?.fileName ?? void 0;
|
|
35637
|
+
const byteSize = typeof img.byteSize === "number" ? img.byteSize : typeof stored?.size === "number" ? stored.size : void 0;
|
|
35638
|
+
out.push({
|
|
35639
|
+
id,
|
|
35640
|
+
mimeType: img.mimeType,
|
|
35641
|
+
...fileName ? { fileName } : {},
|
|
35642
|
+
...typeof byteSize === "number" ? { byteSize } : {},
|
|
35643
|
+
...stored ? {
|
|
35644
|
+
daemonRef: {
|
|
35645
|
+
kind: "image",
|
|
35646
|
+
attachmentId: stored.id,
|
|
35647
|
+
relativePath: stored.relativePath
|
|
35648
|
+
}
|
|
35649
|
+
} : {}
|
|
35650
|
+
});
|
|
35651
|
+
}
|
|
35652
|
+
return out;
|
|
35653
|
+
}
|
|
34828
35654
|
var SessionRequestError = class extends Error {
|
|
34829
35655
|
constructor(code, message) {
|
|
34830
35656
|
super(message);
|
|
@@ -35002,6 +35828,8 @@ var Session = class _Session {
|
|
|
35002
35828
|
});
|
|
35003
35829
|
this.agentManager = agentManager;
|
|
35004
35830
|
this.agentStorage = agentStorage;
|
|
35831
|
+
this.uploadStore = new SessionUploadStore({ logger: this.sessionLogger });
|
|
35832
|
+
this.imageStore = new SessionImageStore({ logger: this.sessionLogger });
|
|
35005
35833
|
this.projectRegistry = projectRegistry;
|
|
35006
35834
|
this.workspaceRegistry = workspaceRegistry;
|
|
35007
35835
|
this.chatService = chatService;
|
|
@@ -35113,11 +35941,12 @@ var Session = class _Session {
|
|
|
35113
35941
|
/**
|
|
35114
35942
|
* Normalize a user prompt (with optional image metadata) for AgentManager
|
|
35115
35943
|
*/
|
|
35116
|
-
buildAgentPrompt(text, images, attachments) {
|
|
35944
|
+
buildAgentPrompt(text, images, attachments, files) {
|
|
35117
35945
|
const normalized = text?.trim() ?? "";
|
|
35118
35946
|
const hasImages = Boolean(images && images.length > 0);
|
|
35119
35947
|
const hasAttachments = Boolean(attachments && attachments.length > 0);
|
|
35120
|
-
|
|
35948
|
+
const hasFiles = Boolean(files && files.length > 0);
|
|
35949
|
+
if (!hasImages && !hasAttachments && !hasFiles) {
|
|
35121
35950
|
return normalized;
|
|
35122
35951
|
}
|
|
35123
35952
|
const blocks = [];
|
|
@@ -35130,8 +35959,46 @@ var Session = class _Session {
|
|
|
35130
35959
|
for (const attachment of attachments ?? []) {
|
|
35131
35960
|
blocks.push(attachment);
|
|
35132
35961
|
}
|
|
35962
|
+
if (hasFiles && files) {
|
|
35963
|
+
blocks.push({ type: "text", text: renderFileAttachmentFooter(files) });
|
|
35964
|
+
}
|
|
35133
35965
|
return blocks;
|
|
35134
35966
|
}
|
|
35967
|
+
/**
|
|
35968
|
+
* Read persisted image bytes off disk for the LLM prompt. The daemon is
|
|
35969
|
+
* the canonical source of truth for image bytes (Clay pattern); the wire
|
|
35970
|
+
* payload is just an upload conduit. Reads through the agent's cwd via
|
|
35971
|
+
* `imageStore.readImageBytes`, which traversal-guards the relative path.
|
|
35972
|
+
*
|
|
35973
|
+
* Falls back to skipping any image whose disk read fails (logged) — better
|
|
35974
|
+
* to send a partial prompt than to crash the whole turn.
|
|
35975
|
+
*/
|
|
35976
|
+
async loadPromptImagesFromDisk(persistedImages, agentId) {
|
|
35977
|
+
const agent = this.agentManager.getAgent(agentId);
|
|
35978
|
+
if (!agent) {
|
|
35979
|
+
this.sessionLogger.warn(
|
|
35980
|
+
{ agentId },
|
|
35981
|
+
"loadPromptImagesFromDisk: agent vanished between persist and read"
|
|
35982
|
+
);
|
|
35983
|
+
return [];
|
|
35984
|
+
}
|
|
35985
|
+
const out = [];
|
|
35986
|
+
for (const persisted of persistedImages) {
|
|
35987
|
+
try {
|
|
35988
|
+
const { data, mimeType } = await this.imageStore.readImageBytes({
|
|
35989
|
+
cwd: agent.cwd,
|
|
35990
|
+
relativePath: persisted.relativePath
|
|
35991
|
+
});
|
|
35992
|
+
out.push({ data: data.toString("base64"), mimeType });
|
|
35993
|
+
} catch (error) {
|
|
35994
|
+
this.sessionLogger.warn(
|
|
35995
|
+
{ agentId, attachmentId: persisted.id, err: error },
|
|
35996
|
+
"loadPromptImagesFromDisk: failed to read image; skipping for this turn"
|
|
35997
|
+
);
|
|
35998
|
+
}
|
|
35999
|
+
}
|
|
36000
|
+
return out;
|
|
36001
|
+
}
|
|
35135
36002
|
/**
|
|
35136
36003
|
* Interrupt the agent's active run so the next prompt starts a fresh turn.
|
|
35137
36004
|
* Returns once the manager confirms the stream has been cancelled.
|
|
@@ -35600,6 +36467,21 @@ var Session = class _Session {
|
|
|
35600
36467
|
case "send_agent_message_request":
|
|
35601
36468
|
await this.handleSendAgentMessageRequest(msg);
|
|
35602
36469
|
break;
|
|
36470
|
+
case "list_session_uploads_request":
|
|
36471
|
+
await this.handleListSessionUploadsRequest(msg);
|
|
36472
|
+
break;
|
|
36473
|
+
case "delete_session_upload_request":
|
|
36474
|
+
await this.handleDeleteSessionUploadRequest(msg);
|
|
36475
|
+
break;
|
|
36476
|
+
case "list_session_images_request":
|
|
36477
|
+
await this.handleListSessionImagesRequest(msg);
|
|
36478
|
+
break;
|
|
36479
|
+
case "delete_session_image_request":
|
|
36480
|
+
await this.handleDeleteSessionImageRequest(msg);
|
|
36481
|
+
break;
|
|
36482
|
+
case "fetch_attachment_bytes_request":
|
|
36483
|
+
await this.handleFetchAttachmentBytesRequest(msg);
|
|
36484
|
+
break;
|
|
35603
36485
|
case "wait_for_finish_request":
|
|
35604
36486
|
await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
|
|
35605
36487
|
break;
|
|
@@ -36870,26 +37752,86 @@ var Session = class _Session {
|
|
|
36870
37752
|
* Handle text message to agent (with optional image attachments)
|
|
36871
37753
|
*/
|
|
36872
37754
|
async handleSendAgentMessage(agentId, text, messageId, images, attachments, runOptions, options) {
|
|
37755
|
+
const files = options?.files ?? [];
|
|
36873
37756
|
this.sessionLogger.info(
|
|
36874
37757
|
{
|
|
36875
37758
|
agentId,
|
|
36876
37759
|
textPreview: text.substring(0, 50),
|
|
36877
37760
|
imageCount: images?.length ?? 0,
|
|
36878
|
-
attachmentCount: attachments?.length ?? 0
|
|
37761
|
+
attachmentCount: attachments?.length ?? 0,
|
|
37762
|
+
fileCount: files.length
|
|
36879
37763
|
},
|
|
36880
|
-
`Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${attachments && attachments.length > 0 ? ` and ${attachments.length} structured attachment(s)` : ""}`
|
|
37764
|
+
`Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${attachments && attachments.length > 0 ? ` and ${attachments.length} structured attachment(s)` : ""}${files.length > 0 ? ` and ${files.length} file attachment(s)` : ""}`
|
|
36881
37765
|
);
|
|
37766
|
+
let persistedFiles = [];
|
|
37767
|
+
let persistedImages = [];
|
|
37768
|
+
const hasFiles = files.length > 0;
|
|
37769
|
+
const hasImages = (images?.length ?? 0) > 0;
|
|
37770
|
+
if (hasFiles || hasImages) {
|
|
37771
|
+
const agent = this.agentManager.getAgent(agentId);
|
|
37772
|
+
if (!agent) {
|
|
37773
|
+
return {
|
|
37774
|
+
ok: false,
|
|
37775
|
+
error: `Agent ${agentId} not found; cannot persist attachments`
|
|
37776
|
+
};
|
|
37777
|
+
}
|
|
37778
|
+
try {
|
|
37779
|
+
if (hasFiles) {
|
|
37780
|
+
persistedFiles = await this.uploadStore.persistFiles({
|
|
37781
|
+
cwd: agent.cwd,
|
|
37782
|
+
agentId,
|
|
37783
|
+
messageId: messageId ?? null,
|
|
37784
|
+
files
|
|
37785
|
+
});
|
|
37786
|
+
}
|
|
37787
|
+
if (hasImages && images) {
|
|
37788
|
+
persistedImages = await this.imageStore.persistImages({
|
|
37789
|
+
cwd: agent.cwd,
|
|
37790
|
+
agentId,
|
|
37791
|
+
messageId: messageId ?? null,
|
|
37792
|
+
images
|
|
37793
|
+
});
|
|
37794
|
+
}
|
|
37795
|
+
} catch (error) {
|
|
37796
|
+
if (error instanceof UploadSizeError) {
|
|
37797
|
+
return {
|
|
37798
|
+
ok: false,
|
|
37799
|
+
error: `Attachments exceed the ${MAX_UPLOAD_BYTES_PER_MESSAGE} byte limit per message`
|
|
37800
|
+
};
|
|
37801
|
+
}
|
|
37802
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37803
|
+
return { ok: false, error: `Failed to persist attachments: ${message}` };
|
|
37804
|
+
}
|
|
37805
|
+
}
|
|
36882
37806
|
const promptText = options?.spokenInput ? wrapSpokenInput(text) : text;
|
|
36883
|
-
const
|
|
37807
|
+
const promptImages = persistedImages.length > 0 ? await this.loadPromptImagesFromDisk(persistedImages, agentId) : images;
|
|
37808
|
+
const prompt = this.buildAgentPrompt(promptText, promptImages, attachments, persistedFiles);
|
|
37809
|
+
const recordedText = synthesizeRecordedUserText(text, persistedFiles, images?.length ?? 0);
|
|
37810
|
+
const userMessageImages = buildImageEcho(images ?? [], persistedImages);
|
|
37811
|
+
const userMessageFiles = persistedFiles.map((f) => ({
|
|
37812
|
+
id: f.id,
|
|
37813
|
+
fileName: f.fileName,
|
|
37814
|
+
mimeType: f.mimeType,
|
|
37815
|
+
size: f.size,
|
|
37816
|
+
daemonRef: {
|
|
37817
|
+
kind: "file",
|
|
37818
|
+
attachmentId: f.id,
|
|
37819
|
+
relativePath: f.relativePath
|
|
37820
|
+
}
|
|
37821
|
+
}));
|
|
37822
|
+
const recordedTextIsSynthesized = text.trim().length === 0 && (userMessageImages.length > 0 || userMessageFiles.length > 0);
|
|
36884
37823
|
try {
|
|
36885
37824
|
await sendPromptToAgent({
|
|
36886
37825
|
agentManager: this.agentManager,
|
|
36887
37826
|
agentStorage: this.agentStorage,
|
|
36888
37827
|
agentId,
|
|
36889
|
-
userMessageText:
|
|
37828
|
+
userMessageText: recordedText,
|
|
36890
37829
|
prompt,
|
|
36891
37830
|
messageId,
|
|
36892
37831
|
runOptions,
|
|
37832
|
+
...userMessageImages.length > 0 ? { userMessageImages } : {},
|
|
37833
|
+
...userMessageFiles.length > 0 ? { userMessageFiles } : {},
|
|
37834
|
+
...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
|
|
36893
37835
|
logger: this.sessionLogger
|
|
36894
37836
|
});
|
|
36895
37837
|
return { ok: true };
|
|
@@ -36914,6 +37856,7 @@ var Session = class _Session {
|
|
|
36914
37856
|
outputSchema,
|
|
36915
37857
|
git,
|
|
36916
37858
|
images,
|
|
37859
|
+
files,
|
|
36917
37860
|
attachments,
|
|
36918
37861
|
labels
|
|
36919
37862
|
} = msg;
|
|
@@ -36954,7 +37897,7 @@ var Session = class _Session {
|
|
|
36954
37897
|
}
|
|
36955
37898
|
);
|
|
36956
37899
|
await this.forwardAgentUpdate(snapshot);
|
|
36957
|
-
if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0) {
|
|
37900
|
+
if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0 || (files?.length ?? 0) > 0) {
|
|
36958
37901
|
scheduleAgentMetadataGeneration({
|
|
36959
37902
|
agentManager: this.agentManager,
|
|
36960
37903
|
agentId: snapshot.id,
|
|
@@ -36970,7 +37913,8 @@ var Session = class _Session {
|
|
|
36970
37913
|
resolveClientMessageId(clientMessageId),
|
|
36971
37914
|
images,
|
|
36972
37915
|
attachments,
|
|
36973
|
-
outputSchema ? { outputSchema } : void 0
|
|
37916
|
+
outputSchema ? { outputSchema } : void 0,
|
|
37917
|
+
files && files.length > 0 ? { files } : void 0
|
|
36974
37918
|
);
|
|
36975
37919
|
if (!started.ok) {
|
|
36976
37920
|
throw new Error(started.error);
|
|
@@ -38387,7 +39331,7 @@ var Session = class _Session {
|
|
|
38387
39331
|
homeDir: process.env.HOME ?? homedir5(),
|
|
38388
39332
|
query: query2,
|
|
38389
39333
|
limit
|
|
38390
|
-
})).map((
|
|
39334
|
+
})).map((path26) => ({ path: path26, kind: "directory" }));
|
|
38391
39335
|
const directories = entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
|
|
38392
39336
|
this.emit({
|
|
38393
39337
|
type: "directory_suggestions_response",
|
|
@@ -39441,10 +40385,11 @@ ${details}`.trim());
|
|
|
39441
40385
|
root: cwd,
|
|
39442
40386
|
relativePath: requestedPath
|
|
39443
40387
|
});
|
|
40388
|
+
const recoveredFileName = originalUploadFileName(info.path) ?? info.fileName;
|
|
39444
40389
|
const entry = this.downloadTokenStore.issueToken({
|
|
39445
40390
|
path: info.path,
|
|
39446
40391
|
absolutePath: info.absolutePath,
|
|
39447
|
-
fileName:
|
|
40392
|
+
fileName: recoveredFileName,
|
|
39448
40393
|
mimeType: info.mimeType,
|
|
39449
40394
|
size: info.size
|
|
39450
40395
|
});
|
|
@@ -40878,19 +41823,96 @@ ${details}`.trim());
|
|
|
40878
41823
|
}
|
|
40879
41824
|
try {
|
|
40880
41825
|
const agentId = resolved.agentId;
|
|
40881
|
-
|
|
41826
|
+
let persistedFiles = [];
|
|
41827
|
+
let persistedImages = [];
|
|
41828
|
+
const hasFiles = (msg.files?.length ?? 0) > 0;
|
|
41829
|
+
const hasImages = (msg.images?.length ?? 0) > 0;
|
|
41830
|
+
if (hasFiles || hasImages) {
|
|
41831
|
+
const agent = this.agentManager.getAgent(agentId);
|
|
41832
|
+
if (!agent) {
|
|
41833
|
+
this.emit({
|
|
41834
|
+
type: "send_agent_message_response",
|
|
41835
|
+
payload: {
|
|
41836
|
+
requestId: msg.requestId,
|
|
41837
|
+
agentId,
|
|
41838
|
+
accepted: false,
|
|
41839
|
+
error: `Agent ${agentId} not found; cannot persist attachments`
|
|
41840
|
+
}
|
|
41841
|
+
});
|
|
41842
|
+
return;
|
|
41843
|
+
}
|
|
41844
|
+
try {
|
|
41845
|
+
if (hasFiles && msg.files) {
|
|
41846
|
+
persistedFiles = await this.uploadStore.persistFiles({
|
|
41847
|
+
cwd: agent.cwd,
|
|
41848
|
+
agentId,
|
|
41849
|
+
messageId: msg.messageId ?? null,
|
|
41850
|
+
files: msg.files
|
|
41851
|
+
});
|
|
41852
|
+
}
|
|
41853
|
+
if (hasImages && msg.images) {
|
|
41854
|
+
persistedImages = await this.imageStore.persistImages({
|
|
41855
|
+
cwd: agent.cwd,
|
|
41856
|
+
agentId,
|
|
41857
|
+
messageId: msg.messageId ?? null,
|
|
41858
|
+
images: msg.images
|
|
41859
|
+
});
|
|
41860
|
+
}
|
|
41861
|
+
} catch (error) {
|
|
41862
|
+
const message = error instanceof UploadSizeError ? `Attachments exceed the ${MAX_UPLOAD_BYTES_PER_MESSAGE} byte limit per message` : error instanceof Error ? `Failed to persist attachments: ${error.message}` : "Failed to persist attachments";
|
|
41863
|
+
this.emit({
|
|
41864
|
+
type: "send_agent_message_response",
|
|
41865
|
+
payload: {
|
|
41866
|
+
requestId: msg.requestId,
|
|
41867
|
+
agentId,
|
|
41868
|
+
accepted: false,
|
|
41869
|
+
error: message
|
|
41870
|
+
}
|
|
41871
|
+
});
|
|
41872
|
+
return;
|
|
41873
|
+
}
|
|
41874
|
+
}
|
|
41875
|
+
const promptImages = persistedImages.length > 0 ? await this.loadPromptImagesFromDisk(persistedImages, agentId) : msg.images;
|
|
41876
|
+
const prompt = this.buildAgentPrompt(msg.text, promptImages, msg.attachments, persistedFiles);
|
|
41877
|
+
const recordedText = synthesizeRecordedUserText(
|
|
41878
|
+
msg.text,
|
|
41879
|
+
persistedFiles,
|
|
41880
|
+
msg.images?.length ?? 0
|
|
41881
|
+
);
|
|
41882
|
+
const userMessageImages = buildImageEcho(msg.images ?? [], persistedImages);
|
|
41883
|
+
const userMessageFiles = persistedFiles.map((f) => ({
|
|
41884
|
+
id: f.id,
|
|
41885
|
+
fileName: f.fileName,
|
|
41886
|
+
mimeType: f.mimeType,
|
|
41887
|
+
size: f.size,
|
|
41888
|
+
daemonRef: {
|
|
41889
|
+
kind: "file",
|
|
41890
|
+
attachmentId: f.id,
|
|
41891
|
+
relativePath: f.relativePath
|
|
41892
|
+
}
|
|
41893
|
+
}));
|
|
40882
41894
|
this.sessionLogger.trace(
|
|
40883
|
-
{
|
|
41895
|
+
{
|
|
41896
|
+
agentId,
|
|
41897
|
+
messageId: msg.messageId,
|
|
41898
|
+
textPrefix: msg.text.slice(0, 80),
|
|
41899
|
+
fileCount: persistedFiles.length,
|
|
41900
|
+
echoImageCount: userMessageImages.length
|
|
41901
|
+
},
|
|
40884
41902
|
"send_agent_message_request: dispatching shared sendPromptToAgent"
|
|
40885
41903
|
);
|
|
41904
|
+
const recordedTextIsSynthesized = msg.text.trim().length === 0 && (userMessageImages.length > 0 || userMessageFiles.length > 0);
|
|
40886
41905
|
try {
|
|
40887
41906
|
await sendPromptToAgent({
|
|
40888
41907
|
agentManager: this.agentManager,
|
|
40889
41908
|
agentStorage: this.agentStorage,
|
|
40890
41909
|
agentId,
|
|
40891
|
-
userMessageText:
|
|
41910
|
+
userMessageText: recordedText,
|
|
40892
41911
|
prompt,
|
|
40893
41912
|
messageId: msg.messageId,
|
|
41913
|
+
...userMessageImages.length > 0 ? { userMessageImages } : {},
|
|
41914
|
+
...userMessageFiles.length > 0 ? { userMessageFiles } : {},
|
|
41915
|
+
...recordedTextIsSynthesized ? { userMessageTextIsSynthesized: true } : {},
|
|
40894
41916
|
logger: this.sessionLogger
|
|
40895
41917
|
});
|
|
40896
41918
|
} catch (error) {
|
|
@@ -40949,6 +41971,242 @@ ${details}`.trim());
|
|
|
40949
41971
|
});
|
|
40950
41972
|
}
|
|
40951
41973
|
}
|
|
41974
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
41975
|
+
// Session uploads — backs the in-app "Files" tab inside an active session.
|
|
41976
|
+
// Both handlers resolve `agentId` against the agent registry first so we
|
|
41977
|
+
// get the same accept-prefix-or-title behaviour as send_agent_message, and
|
|
41978
|
+
// both fail closed on a missing agent (no implicit "list everything" that
|
|
41979
|
+
// could leak across sessions).
|
|
41980
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
41981
|
+
async handleListSessionUploadsRequest(msg) {
|
|
41982
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
41983
|
+
if (!resolved.ok) {
|
|
41984
|
+
this.emit({
|
|
41985
|
+
type: "list_session_uploads_response",
|
|
41986
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
41987
|
+
});
|
|
41988
|
+
return;
|
|
41989
|
+
}
|
|
41990
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
41991
|
+
if (!agent) {
|
|
41992
|
+
this.emit({
|
|
41993
|
+
type: "list_session_uploads_response",
|
|
41994
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
41995
|
+
});
|
|
41996
|
+
return;
|
|
41997
|
+
}
|
|
41998
|
+
try {
|
|
41999
|
+
const uploads = await this.uploadStore.listUploads({
|
|
42000
|
+
cwd: agent.cwd,
|
|
42001
|
+
agentId: resolved.agentId
|
|
42002
|
+
});
|
|
42003
|
+
this.emit({
|
|
42004
|
+
type: "list_session_uploads_response",
|
|
42005
|
+
payload: { requestId: msg.requestId, ok: true, uploads }
|
|
42006
|
+
});
|
|
42007
|
+
} catch (error) {
|
|
42008
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42009
|
+
this.emit({
|
|
42010
|
+
type: "list_session_uploads_response",
|
|
42011
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
42012
|
+
});
|
|
42013
|
+
}
|
|
42014
|
+
}
|
|
42015
|
+
async handleDeleteSessionUploadRequest(msg) {
|
|
42016
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
42017
|
+
if (!resolved.ok) {
|
|
42018
|
+
this.emit({
|
|
42019
|
+
type: "delete_session_upload_response",
|
|
42020
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
42021
|
+
});
|
|
42022
|
+
return;
|
|
42023
|
+
}
|
|
42024
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
42025
|
+
if (!agent) {
|
|
42026
|
+
this.emit({
|
|
42027
|
+
type: "delete_session_upload_response",
|
|
42028
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
42029
|
+
});
|
|
42030
|
+
return;
|
|
42031
|
+
}
|
|
42032
|
+
try {
|
|
42033
|
+
const result = await this.uploadStore.deleteUpload({
|
|
42034
|
+
cwd: agent.cwd,
|
|
42035
|
+
agentId: resolved.agentId,
|
|
42036
|
+
uploadId: msg.uploadId
|
|
42037
|
+
});
|
|
42038
|
+
if (!result.deleted) {
|
|
42039
|
+
this.emit({
|
|
42040
|
+
type: "delete_session_upload_response",
|
|
42041
|
+
payload: { requestId: msg.requestId, ok: false, error: "Upload not found" }
|
|
42042
|
+
});
|
|
42043
|
+
return;
|
|
42044
|
+
}
|
|
42045
|
+
this.emit({
|
|
42046
|
+
type: "delete_session_upload_response",
|
|
42047
|
+
payload: { requestId: msg.requestId, ok: true }
|
|
42048
|
+
});
|
|
42049
|
+
} catch (error) {
|
|
42050
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42051
|
+
this.emit({
|
|
42052
|
+
type: "delete_session_upload_response",
|
|
42053
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
42054
|
+
});
|
|
42055
|
+
}
|
|
42056
|
+
}
|
|
42057
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
42058
|
+
// Session images — backs the "Images" tab inside the uploads modal. Same
|
|
42059
|
+
// shape as the file handlers above; the store has no manifest, so listing
|
|
42060
|
+
// is a directory scan and delete is `unlink` (traversal-guarded).
|
|
42061
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
42062
|
+
async handleListSessionImagesRequest(msg) {
|
|
42063
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
42064
|
+
if (!resolved.ok) {
|
|
42065
|
+
this.emit({
|
|
42066
|
+
type: "list_session_images_response",
|
|
42067
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
42068
|
+
});
|
|
42069
|
+
return;
|
|
42070
|
+
}
|
|
42071
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
42072
|
+
if (!agent) {
|
|
42073
|
+
this.emit({
|
|
42074
|
+
type: "list_session_images_response",
|
|
42075
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
42076
|
+
});
|
|
42077
|
+
return;
|
|
42078
|
+
}
|
|
42079
|
+
try {
|
|
42080
|
+
const images = await this.imageStore.listImages({ cwd: agent.cwd });
|
|
42081
|
+
this.emit({
|
|
42082
|
+
type: "list_session_images_response",
|
|
42083
|
+
payload: { requestId: msg.requestId, ok: true, images }
|
|
42084
|
+
});
|
|
42085
|
+
} catch (error) {
|
|
42086
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42087
|
+
this.emit({
|
|
42088
|
+
type: "list_session_images_response",
|
|
42089
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
42090
|
+
});
|
|
42091
|
+
}
|
|
42092
|
+
}
|
|
42093
|
+
async handleDeleteSessionImageRequest(msg) {
|
|
42094
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
42095
|
+
if (!resolved.ok) {
|
|
42096
|
+
this.emit({
|
|
42097
|
+
type: "delete_session_image_response",
|
|
42098
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
42099
|
+
});
|
|
42100
|
+
return;
|
|
42101
|
+
}
|
|
42102
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
42103
|
+
if (!agent) {
|
|
42104
|
+
this.emit({
|
|
42105
|
+
type: "delete_session_image_response",
|
|
42106
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
42107
|
+
});
|
|
42108
|
+
return;
|
|
42109
|
+
}
|
|
42110
|
+
try {
|
|
42111
|
+
const result = await this.imageStore.deleteImage({
|
|
42112
|
+
cwd: agent.cwd,
|
|
42113
|
+
relativePath: msg.relativePath
|
|
42114
|
+
});
|
|
42115
|
+
if (!result.deleted) {
|
|
42116
|
+
this.emit({
|
|
42117
|
+
type: "delete_session_image_response",
|
|
42118
|
+
payload: { requestId: msg.requestId, ok: false, error: "Image not found" }
|
|
42119
|
+
});
|
|
42120
|
+
return;
|
|
42121
|
+
}
|
|
42122
|
+
this.emit({
|
|
42123
|
+
type: "delete_session_image_response",
|
|
42124
|
+
payload: { requestId: msg.requestId, ok: true }
|
|
42125
|
+
});
|
|
42126
|
+
} catch (error) {
|
|
42127
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42128
|
+
this.emit({
|
|
42129
|
+
type: "delete_session_image_response",
|
|
42130
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
42131
|
+
});
|
|
42132
|
+
}
|
|
42133
|
+
}
|
|
42134
|
+
/**
|
|
42135
|
+
* Serve attachment bytes by id. The daemon is the canonical source — every
|
|
42136
|
+
* paired client (Electron after Cmd-R, phone, web) hits this RPC to render
|
|
42137
|
+
* thumbnails or download files. Bytes never round-trip through the chat
|
|
42138
|
+
* timeline; they're fetched on demand.
|
|
42139
|
+
*
|
|
42140
|
+
* `relativePath` comes from the user_message timeline echo's `daemonRef`;
|
|
42141
|
+
* the store's read methods traversal-guard it so it can't escape the
|
|
42142
|
+
* dedicated subdirectories.
|
|
42143
|
+
*/
|
|
42144
|
+
async handleFetchAttachmentBytesRequest(msg) {
|
|
42145
|
+
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
42146
|
+
if (!resolved.ok) {
|
|
42147
|
+
this.emit({
|
|
42148
|
+
type: "fetch_attachment_bytes_response",
|
|
42149
|
+
payload: { requestId: msg.requestId, ok: false, error: resolved.error }
|
|
42150
|
+
});
|
|
42151
|
+
return;
|
|
42152
|
+
}
|
|
42153
|
+
const agent = this.agentManager.getAgent(resolved.agentId);
|
|
42154
|
+
if (!agent) {
|
|
42155
|
+
this.emit({
|
|
42156
|
+
type: "fetch_attachment_bytes_response",
|
|
42157
|
+
payload: { requestId: msg.requestId, ok: false, error: "Agent not found" }
|
|
42158
|
+
});
|
|
42159
|
+
return;
|
|
42160
|
+
}
|
|
42161
|
+
try {
|
|
42162
|
+
if (msg.kind === "image") {
|
|
42163
|
+
const { data, mimeType, byteSize } = await this.imageStore.readImageBytes({
|
|
42164
|
+
cwd: agent.cwd,
|
|
42165
|
+
relativePath: msg.relativePath
|
|
42166
|
+
});
|
|
42167
|
+
this.emit({
|
|
42168
|
+
type: "fetch_attachment_bytes_response",
|
|
42169
|
+
payload: {
|
|
42170
|
+
requestId: msg.requestId,
|
|
42171
|
+
ok: true,
|
|
42172
|
+
attachmentId: msg.attachmentId,
|
|
42173
|
+
kind: "image",
|
|
42174
|
+
data: data.toString("base64"),
|
|
42175
|
+
mimeType,
|
|
42176
|
+
byteSize
|
|
42177
|
+
}
|
|
42178
|
+
});
|
|
42179
|
+
} else {
|
|
42180
|
+
const { data, mimeType, byteSize, fileName } = await this.uploadStore.readUploadBytes({
|
|
42181
|
+
cwd: agent.cwd,
|
|
42182
|
+
relativePath: msg.relativePath
|
|
42183
|
+
});
|
|
42184
|
+
this.emit({
|
|
42185
|
+
type: "fetch_attachment_bytes_response",
|
|
42186
|
+
payload: {
|
|
42187
|
+
requestId: msg.requestId,
|
|
42188
|
+
ok: true,
|
|
42189
|
+
attachmentId: msg.attachmentId,
|
|
42190
|
+
kind: "file",
|
|
42191
|
+
data: data.toString("base64"),
|
|
42192
|
+
mimeType,
|
|
42193
|
+
byteSize,
|
|
42194
|
+
fileName
|
|
42195
|
+
}
|
|
42196
|
+
});
|
|
42197
|
+
}
|
|
42198
|
+
} catch (error) {
|
|
42199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42200
|
+
this.sessionLogger.warn(
|
|
42201
|
+
{ agentId: resolved.agentId, attachmentId: msg.attachmentId, kind: msg.kind, err: error },
|
|
42202
|
+
"fetch_attachment_bytes_request: failed to read"
|
|
42203
|
+
);
|
|
42204
|
+
this.emit({
|
|
42205
|
+
type: "fetch_attachment_bytes_response",
|
|
42206
|
+
payload: { requestId: msg.requestId, ok: false, error: message }
|
|
42207
|
+
});
|
|
42208
|
+
}
|
|
42209
|
+
}
|
|
40952
42210
|
async handleWaitForFinish(agentIdOrIdentifier, requestId, timeoutMs) {
|
|
40953
42211
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
40954
42212
|
if (!resolved.ok) {
|
|
@@ -43272,16 +44530,16 @@ function isRelayClientWebSocketUrl(url) {
|
|
|
43272
44530
|
}
|
|
43273
44531
|
|
|
43274
44532
|
// ../server/src/server/config.ts
|
|
43275
|
-
import
|
|
44533
|
+
import path19 from "node:path";
|
|
43276
44534
|
import { z as z47 } from "zod";
|
|
43277
44535
|
|
|
43278
44536
|
// ../server/src/server/speech/speech-config-resolver.ts
|
|
43279
44537
|
import { z as z46 } from "zod";
|
|
43280
44538
|
|
|
43281
44539
|
// ../server/src/server/speech/providers/local/config.ts
|
|
43282
|
-
import
|
|
44540
|
+
import path18 from "node:path";
|
|
43283
44541
|
import { z as z44 } from "zod";
|
|
43284
|
-
var DEFAULT_LOCAL_MODELS_SUBDIR =
|
|
44542
|
+
var DEFAULT_LOCAL_MODELS_SUBDIR = path18.join("models", "local-speech");
|
|
43285
44543
|
var NumberLikeSchema2 = z44.union([z44.number(), z44.string().trim().min(1)]);
|
|
43286
44544
|
var OptionalFiniteNumberSchema2 = NumberLikeSchema2.pipe(z44.coerce.number().finite()).optional();
|
|
43287
44545
|
var OptionalIntegerSchema = NumberLikeSchema2.pipe(z44.coerce.number().int()).optional();
|
|
@@ -43308,7 +44566,7 @@ function resolveLocalSpeechConfig(params) {
|
|
|
43308
44566
|
const includeProviderConfig = shouldIncludeLocalProviderConfig(params);
|
|
43309
44567
|
const parsed = LocalSpeechResolutionSchema.parse({
|
|
43310
44568
|
includeProviderConfig,
|
|
43311
|
-
modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ??
|
|
44569
|
+
modelsDir: params.env.APPOSTLE_LOCAL_MODELS_DIR ?? params.persisted.providers?.local?.modelsDir ?? path18.join(params.appostleHome, DEFAULT_LOCAL_MODELS_SUBDIR),
|
|
43312
44570
|
dictationLocalSttModel: params.env.APPOSTLE_DICTATION_LOCAL_STT_MODEL ?? persistedLocalFeatureModel(
|
|
43313
44571
|
params.providers.dictationStt.provider,
|
|
43314
44572
|
params.providers.dictationStt.enabled,
|
|
@@ -43564,7 +44822,7 @@ function loadConfig(appostleHome, options) {
|
|
|
43564
44822
|
chromeEnabled,
|
|
43565
44823
|
mcpDebug: env.MCP_DEBUG === "1",
|
|
43566
44824
|
daemonIcon,
|
|
43567
|
-
agentStoragePath:
|
|
44825
|
+
agentStoragePath: path19.join(appostleHome, "agents"),
|
|
43568
44826
|
staticDir: "public",
|
|
43569
44827
|
agentClients: {},
|
|
43570
44828
|
relayEnabled,
|
|
@@ -44782,12 +46040,12 @@ var DaemonClient = class {
|
|
|
44782
46040
|
timeout: 1e4
|
|
44783
46041
|
});
|
|
44784
46042
|
}
|
|
44785
|
-
async openInEditor(
|
|
46043
|
+
async openInEditor(path26, editorId, requestId) {
|
|
44786
46044
|
return this.sendCorrelatedSessionRequest({
|
|
44787
46045
|
requestId,
|
|
44788
46046
|
message: {
|
|
44789
46047
|
type: "open_in_editor_request",
|
|
44790
|
-
path:
|
|
46048
|
+
path: path26,
|
|
44791
46049
|
editorId
|
|
44792
46050
|
},
|
|
44793
46051
|
responseType: "open_in_editor_response",
|
|
@@ -44887,6 +46145,7 @@ var DaemonClient = class {
|
|
|
44887
46145
|
...options.clientMessageId ? { clientMessageId: options.clientMessageId } : {},
|
|
44888
46146
|
...options.outputSchema ? { outputSchema: options.outputSchema } : {},
|
|
44889
46147
|
...options.images && options.images.length > 0 ? { images: options.images } : {},
|
|
46148
|
+
...options.files && options.files.length > 0 ? { files: options.files } : {},
|
|
44890
46149
|
...options.attachments && options.attachments.length > 0 ? { attachments: options.attachments } : {},
|
|
44891
46150
|
...options.git ? { git: options.git } : {},
|
|
44892
46151
|
...options.worktreeName ? { worktreeName: options.worktreeName } : {},
|
|
@@ -45112,6 +46371,7 @@ var DaemonClient = class {
|
|
|
45112
46371
|
text,
|
|
45113
46372
|
...messageId ? { messageId } : {},
|
|
45114
46373
|
...options?.images ? { images: options.images } : {},
|
|
46374
|
+
...options?.files ? { files: options.files } : {},
|
|
45115
46375
|
...options?.attachments ? { attachments: options.attachments } : {}
|
|
45116
46376
|
});
|
|
45117
46377
|
const payload = await this.sendRequest({
|
|
@@ -45136,6 +46396,157 @@ var DaemonClient = class {
|
|
|
45136
46396
|
async sendMessage(agentId, text, options) {
|
|
45137
46397
|
await this.sendAgentMessage(agentId, text, options);
|
|
45138
46398
|
}
|
|
46399
|
+
/**
|
|
46400
|
+
* List the files the user has attached to this agent's chat (the "Files"
|
|
46401
|
+
* tab in an active session). Returns newest first; the daemon serves the
|
|
46402
|
+
* manifest stored under `<workspace>/.appostle/uploaded_files/`.
|
|
46403
|
+
*/
|
|
46404
|
+
async listSessionUploads(agentId) {
|
|
46405
|
+
const requestId = this.createRequestId();
|
|
46406
|
+
const message = SessionInboundMessageSchema.parse({
|
|
46407
|
+
type: "list_session_uploads_request",
|
|
46408
|
+
requestId,
|
|
46409
|
+
agentId
|
|
46410
|
+
});
|
|
46411
|
+
const payload = await this.sendRequest({
|
|
46412
|
+
requestId,
|
|
46413
|
+
message,
|
|
46414
|
+
timeout: 15e3,
|
|
46415
|
+
options: { skipQueue: true },
|
|
46416
|
+
select: (msg) => {
|
|
46417
|
+
if (msg.type !== "list_session_uploads_response") return null;
|
|
46418
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
46419
|
+
return msg.payload;
|
|
46420
|
+
}
|
|
46421
|
+
});
|
|
46422
|
+
if (!payload.ok) {
|
|
46423
|
+
throw new Error(payload.error || "Failed to list session uploads");
|
|
46424
|
+
}
|
|
46425
|
+
return payload.uploads;
|
|
46426
|
+
}
|
|
46427
|
+
/**
|
|
46428
|
+
* Delete a single uploaded file. Removes both the manifest entry and the
|
|
46429
|
+
* file on disk; the operation is idempotent — deleting a no-longer-existent
|
|
46430
|
+
* upload throws so the caller can refresh the listing.
|
|
46431
|
+
*/
|
|
46432
|
+
async deleteSessionUpload(agentId, uploadId) {
|
|
46433
|
+
const requestId = this.createRequestId();
|
|
46434
|
+
const message = SessionInboundMessageSchema.parse({
|
|
46435
|
+
type: "delete_session_upload_request",
|
|
46436
|
+
requestId,
|
|
46437
|
+
agentId,
|
|
46438
|
+
uploadId
|
|
46439
|
+
});
|
|
46440
|
+
const payload = await this.sendRequest({
|
|
46441
|
+
requestId,
|
|
46442
|
+
message,
|
|
46443
|
+
timeout: 15e3,
|
|
46444
|
+
options: { skipQueue: true },
|
|
46445
|
+
select: (msg) => {
|
|
46446
|
+
if (msg.type !== "delete_session_upload_response") return null;
|
|
46447
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
46448
|
+
return msg.payload;
|
|
46449
|
+
}
|
|
46450
|
+
});
|
|
46451
|
+
if (!payload.ok) {
|
|
46452
|
+
throw new Error(payload.error || "Failed to delete session upload");
|
|
46453
|
+
}
|
|
46454
|
+
}
|
|
46455
|
+
/**
|
|
46456
|
+
* List the images the user has attached to this agent's chat (the "Images"
|
|
46457
|
+
* tab in the uploads modal). Returns newest first; the daemon scans
|
|
46458
|
+
* `<workspace>/.appostle/uploaded_images/` directly — there's no manifest.
|
|
46459
|
+
*/
|
|
46460
|
+
async listSessionImages(agentId) {
|
|
46461
|
+
const requestId = this.createRequestId();
|
|
46462
|
+
const message = SessionInboundMessageSchema.parse({
|
|
46463
|
+
type: "list_session_images_request",
|
|
46464
|
+
requestId,
|
|
46465
|
+
agentId
|
|
46466
|
+
});
|
|
46467
|
+
const payload = await this.sendRequest({
|
|
46468
|
+
requestId,
|
|
46469
|
+
message,
|
|
46470
|
+
timeout: 15e3,
|
|
46471
|
+
options: { skipQueue: true },
|
|
46472
|
+
select: (msg) => {
|
|
46473
|
+
if (msg.type !== "list_session_images_response") return null;
|
|
46474
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
46475
|
+
return msg.payload;
|
|
46476
|
+
}
|
|
46477
|
+
});
|
|
46478
|
+
if (!payload.ok) {
|
|
46479
|
+
throw new Error(payload.error || "Failed to list session images");
|
|
46480
|
+
}
|
|
46481
|
+
return payload.images;
|
|
46482
|
+
}
|
|
46483
|
+
/**
|
|
46484
|
+
* Delete a single uploaded image by workspace-relative path. The daemon
|
|
46485
|
+
* traversal-guards the path so it cannot escape `.appostle/uploaded_images/`.
|
|
46486
|
+
*/
|
|
46487
|
+
async deleteSessionImage(agentId, relativePath) {
|
|
46488
|
+
const requestId = this.createRequestId();
|
|
46489
|
+
const message = SessionInboundMessageSchema.parse({
|
|
46490
|
+
type: "delete_session_image_request",
|
|
46491
|
+
requestId,
|
|
46492
|
+
agentId,
|
|
46493
|
+
relativePath
|
|
46494
|
+
});
|
|
46495
|
+
const payload = await this.sendRequest({
|
|
46496
|
+
requestId,
|
|
46497
|
+
message,
|
|
46498
|
+
timeout: 15e3,
|
|
46499
|
+
options: { skipQueue: true },
|
|
46500
|
+
select: (msg) => {
|
|
46501
|
+
if (msg.type !== "delete_session_image_response") return null;
|
|
46502
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
46503
|
+
return msg.payload;
|
|
46504
|
+
}
|
|
46505
|
+
});
|
|
46506
|
+
if (!payload.ok) {
|
|
46507
|
+
throw new Error(payload.error || "Failed to delete session image");
|
|
46508
|
+
}
|
|
46509
|
+
}
|
|
46510
|
+
/**
|
|
46511
|
+
* Fetch attachment bytes by id from the daemon. The daemon is the canonical
|
|
46512
|
+
* source for image and file bytes — every paired client (Electron after
|
|
46513
|
+
* Cmd-R, phone, web) hits this RPC to render thumbnails and previews.
|
|
46514
|
+
*
|
|
46515
|
+
* `relativePath` comes from the user_message timeline echo's `daemonRef`.
|
|
46516
|
+
* Returns base64-encoded bytes plus mime/size.
|
|
46517
|
+
*/
|
|
46518
|
+
async fetchAttachmentBytes(input) {
|
|
46519
|
+
const requestId = this.createRequestId();
|
|
46520
|
+
const message = SessionInboundMessageSchema.parse({
|
|
46521
|
+
type: "fetch_attachment_bytes_request",
|
|
46522
|
+
requestId,
|
|
46523
|
+
agentId: input.agentId,
|
|
46524
|
+
kind: input.kind,
|
|
46525
|
+
attachmentId: input.attachmentId,
|
|
46526
|
+
relativePath: input.relativePath
|
|
46527
|
+
});
|
|
46528
|
+
const payload = await this.sendRequest({
|
|
46529
|
+
requestId,
|
|
46530
|
+
message,
|
|
46531
|
+
// Image bytes can be a couple MB; allow more time than a typical RPC.
|
|
46532
|
+
timeout: 3e4,
|
|
46533
|
+
options: { skipQueue: true },
|
|
46534
|
+
select: (msg) => {
|
|
46535
|
+
if (msg.type !== "fetch_attachment_bytes_response") return null;
|
|
46536
|
+
if (msg.payload.requestId !== requestId) return null;
|
|
46537
|
+
return msg.payload;
|
|
46538
|
+
}
|
|
46539
|
+
});
|
|
46540
|
+
if (!payload.ok) {
|
|
46541
|
+
throw new Error(payload.error || "Failed to fetch attachment bytes");
|
|
46542
|
+
}
|
|
46543
|
+
return {
|
|
46544
|
+
data: payload.data,
|
|
46545
|
+
mimeType: payload.mimeType,
|
|
46546
|
+
byteSize: payload.byteSize,
|
|
46547
|
+
...payload.fileName !== void 0 ? { fileName: payload.fileName } : {}
|
|
46548
|
+
};
|
|
46549
|
+
}
|
|
45139
46550
|
async cancelAgent(agentId) {
|
|
45140
46551
|
const requestId = this.createRequestId();
|
|
45141
46552
|
const message = SessionInboundMessageSchema.parse({
|
|
@@ -46049,13 +47460,13 @@ var DaemonClient = class {
|
|
|
46049
47460
|
// ============================================================================
|
|
46050
47461
|
// File Explorer
|
|
46051
47462
|
// ============================================================================
|
|
46052
|
-
async exploreFileSystem(cwd,
|
|
47463
|
+
async exploreFileSystem(cwd, path26, mode = "list", requestId) {
|
|
46053
47464
|
return this.sendCorrelatedSessionRequest({
|
|
46054
47465
|
requestId,
|
|
46055
47466
|
message: {
|
|
46056
47467
|
type: "file_explorer_request",
|
|
46057
47468
|
cwd,
|
|
46058
|
-
path:
|
|
47469
|
+
path: path26,
|
|
46059
47470
|
mode
|
|
46060
47471
|
},
|
|
46061
47472
|
responseType: "file_explorer_response",
|
|
@@ -46067,13 +47478,13 @@ var DaemonClient = class {
|
|
|
46067
47478
|
* allowlists extensions (currently `.md` only) — callers don't need to
|
|
46068
47479
|
* re-check. Used by the plan-todos UI to rewrite plan-file frontmatter.
|
|
46069
47480
|
*/
|
|
46070
|
-
async writeFile(cwd,
|
|
47481
|
+
async writeFile(cwd, path26, content, requestId) {
|
|
46071
47482
|
return this.sendCorrelatedSessionRequest({
|
|
46072
47483
|
requestId,
|
|
46073
47484
|
message: {
|
|
46074
47485
|
type: "file_write_request",
|
|
46075
47486
|
cwd,
|
|
46076
|
-
path:
|
|
47487
|
+
path: path26,
|
|
46077
47488
|
content
|
|
46078
47489
|
},
|
|
46079
47490
|
responseType: "file_write_response",
|
|
@@ -46086,42 +47497,42 @@ var DaemonClient = class {
|
|
|
46086
47497
|
* action is the only consumer). Returns the daemon's structured response
|
|
46087
47498
|
* so callers can surface the error in the UI.
|
|
46088
47499
|
*/
|
|
46089
|
-
async deleteFile(cwd,
|
|
47500
|
+
async deleteFile(cwd, path26, requestId) {
|
|
46090
47501
|
return this.sendCorrelatedSessionRequest({
|
|
46091
47502
|
requestId,
|
|
46092
47503
|
message: {
|
|
46093
47504
|
type: "file_delete_request",
|
|
46094
47505
|
cwd,
|
|
46095
|
-
path:
|
|
47506
|
+
path: path26
|
|
46096
47507
|
},
|
|
46097
47508
|
responseType: "file_delete_response",
|
|
46098
47509
|
timeout: 1e4
|
|
46099
47510
|
});
|
|
46100
47511
|
}
|
|
46101
|
-
async requestDownloadToken(cwd,
|
|
47512
|
+
async requestDownloadToken(cwd, path26, requestId) {
|
|
46102
47513
|
return this.sendCorrelatedSessionRequest({
|
|
46103
47514
|
requestId,
|
|
46104
47515
|
message: {
|
|
46105
47516
|
type: "file_download_token_request",
|
|
46106
47517
|
cwd,
|
|
46107
|
-
path:
|
|
47518
|
+
path: path26
|
|
46108
47519
|
},
|
|
46109
47520
|
responseType: "file_download_token_response",
|
|
46110
47521
|
timeout: 1e4
|
|
46111
47522
|
});
|
|
46112
47523
|
}
|
|
46113
|
-
async explorerDeleteEntry(cwd,
|
|
47524
|
+
async explorerDeleteEntry(cwd, path26, requestId) {
|
|
46114
47525
|
return this.sendCorrelatedSessionRequest({
|
|
46115
47526
|
requestId,
|
|
46116
|
-
message: { type: "file_explorer_delete_request", cwd, path:
|
|
47527
|
+
message: { type: "file_explorer_delete_request", cwd, path: path26 },
|
|
46117
47528
|
responseType: "file_explorer_delete_response",
|
|
46118
47529
|
timeout: 1e4
|
|
46119
47530
|
});
|
|
46120
47531
|
}
|
|
46121
|
-
async explorerMkdir(cwd,
|
|
47532
|
+
async explorerMkdir(cwd, path26, requestId) {
|
|
46122
47533
|
return this.sendCorrelatedSessionRequest({
|
|
46123
47534
|
requestId,
|
|
46124
|
-
message: { type: "file_mkdir_request", cwd, path:
|
|
47535
|
+
message: { type: "file_mkdir_request", cwd, path: path26 },
|
|
46125
47536
|
responseType: "file_mkdir_response",
|
|
46126
47537
|
timeout: 1e4
|
|
46127
47538
|
});
|
|
@@ -47511,16 +48922,16 @@ function resolveAgentConfig(options) {
|
|
|
47511
48922
|
}
|
|
47512
48923
|
|
|
47513
48924
|
// ../cli/src/utils/client.ts
|
|
47514
|
-
import
|
|
48925
|
+
import path20 from "node:path";
|
|
47515
48926
|
import WebSocket3 from "ws";
|
|
47516
48927
|
|
|
47517
48928
|
// ../cli/src/utils/client-id.ts
|
|
47518
48929
|
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
|
|
47519
|
-
import { randomUUID as
|
|
47520
|
-
import { dirname as dirname5, join as
|
|
48930
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
48931
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
47521
48932
|
import { homedir as homedir6 } from "node:os";
|
|
47522
|
-
var CLIENT_SESSION_KEY_FILE =
|
|
47523
|
-
process.env.APPOSTLE_HOME ??
|
|
48933
|
+
var CLIENT_SESSION_KEY_FILE = join12(
|
|
48934
|
+
process.env.APPOSTLE_HOME ?? join12(homedir6(), ".appostle"),
|
|
47524
48935
|
"cli-client-id"
|
|
47525
48936
|
);
|
|
47526
48937
|
var cachedClientId = null;
|
|
@@ -47529,7 +48940,7 @@ function normalizeClientId2(value) {
|
|
|
47529
48940
|
return trimmed.length > 0 ? trimmed : null;
|
|
47530
48941
|
}
|
|
47531
48942
|
function generateClientId() {
|
|
47532
|
-
return `cid_${
|
|
48943
|
+
return `cid_${randomUUID6().replace(/-/g, "")}`;
|
|
47533
48944
|
}
|
|
47534
48945
|
async function getOrCreateCliClientId() {
|
|
47535
48946
|
if (cachedClientId) {
|
|
@@ -47587,7 +48998,7 @@ function isTcpDaemonHost(host) {
|
|
|
47587
48998
|
return host !== null && !isIpcDaemonHost(host);
|
|
47588
48999
|
}
|
|
47589
49000
|
function readPidSocketTarget(appostleHome) {
|
|
47590
|
-
const pidPath =
|
|
49001
|
+
const pidPath = path20.join(appostleHome, PID_FILENAME);
|
|
47591
49002
|
if (!existsSync10(pidPath)) {
|
|
47592
49003
|
return null;
|
|
47593
49004
|
}
|
|
@@ -48056,12 +49467,12 @@ function relativeTime(date) {
|
|
|
48056
49467
|
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
48057
49468
|
return `${Math.floor(seconds / 86400)} days ago`;
|
|
48058
49469
|
}
|
|
48059
|
-
function shortenPath(
|
|
49470
|
+
function shortenPath(path26) {
|
|
48060
49471
|
const home = process.env.HOME;
|
|
48061
|
-
if (home &&
|
|
48062
|
-
return "~" +
|
|
49472
|
+
if (home && path26.startsWith(home)) {
|
|
49473
|
+
return "~" + path26.slice(home.length);
|
|
48063
49474
|
}
|
|
48064
|
-
return
|
|
49475
|
+
return path26;
|
|
48065
49476
|
}
|
|
48066
49477
|
function normalizeModelId(modelId) {
|
|
48067
49478
|
if (typeof modelId !== "string") return null;
|
|
@@ -48904,7 +50315,7 @@ async function runStopCommand(id, options, _command) {
|
|
|
48904
50315
|
|
|
48905
50316
|
// ../cli/src/commands/agent/send.ts
|
|
48906
50317
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
48907
|
-
import { extname as
|
|
50318
|
+
import { extname as extname4, resolve as resolve11 } from "node:path";
|
|
48908
50319
|
var agentSendSchema = {
|
|
48909
50320
|
idField: "agentId",
|
|
48910
50321
|
columns: [
|
|
@@ -48918,10 +50329,10 @@ function addSendOptions(cmd) {
|
|
|
48918
50329
|
}
|
|
48919
50330
|
async function readImageFiles(imagePaths) {
|
|
48920
50331
|
const images = [];
|
|
48921
|
-
for (const
|
|
50332
|
+
for (const path26 of imagePaths) {
|
|
48922
50333
|
try {
|
|
48923
|
-
const buffer = await readFile5(
|
|
48924
|
-
const ext =
|
|
50334
|
+
const buffer = await readFile5(path26);
|
|
50335
|
+
const ext = extname4(path26).toLowerCase();
|
|
48925
50336
|
let mimeType = "image/jpeg";
|
|
48926
50337
|
switch (ext) {
|
|
48927
50338
|
case ".png":
|
|
@@ -48949,7 +50360,7 @@ async function readImageFiles(imagePaths) {
|
|
|
48949
50360
|
const message = err instanceof Error ? err.message : String(err);
|
|
48950
50361
|
const error = {
|
|
48951
50362
|
code: "IMAGE_READ_ERROR",
|
|
48952
|
-
message: `Failed to read image file: ${
|
|
50363
|
+
message: `Failed to read image file: ${path26}`,
|
|
48953
50364
|
details: message
|
|
48954
50365
|
};
|
|
48955
50366
|
throw error;
|
|
@@ -49122,12 +50533,12 @@ function createInspectSchema(agent) {
|
|
|
49122
50533
|
serialize: (_item) => agent
|
|
49123
50534
|
};
|
|
49124
50535
|
}
|
|
49125
|
-
function shortenPath2(
|
|
50536
|
+
function shortenPath2(path26) {
|
|
49126
50537
|
const home = process.env.HOME;
|
|
49127
|
-
if (home &&
|
|
49128
|
-
return "~" +
|
|
50538
|
+
if (home && path26.startsWith(home)) {
|
|
50539
|
+
return "~" + path26.slice(home.length);
|
|
49129
50540
|
}
|
|
49130
|
-
return
|
|
50541
|
+
return path26;
|
|
49131
50542
|
}
|
|
49132
50543
|
function formatCost(costUsd) {
|
|
49133
50544
|
if (costUsd === 0) return "$0.00";
|
|
@@ -50095,7 +51506,7 @@ import chalk3 from "chalk";
|
|
|
50095
51506
|
import { spawn as spawn7, spawnSync } from "node:child_process";
|
|
50096
51507
|
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
50097
51508
|
import { createRequire as createRequire3 } from "node:module";
|
|
50098
|
-
import
|
|
51509
|
+
import path21 from "node:path";
|
|
50099
51510
|
import { fileURLToPath } from "node:url";
|
|
50100
51511
|
var DETACHED_STARTUP_GRACE_MS = 1200;
|
|
50101
51512
|
var PID_POLL_INTERVAL_MS = 100;
|
|
@@ -50146,7 +51557,7 @@ function buildChildEnv(options) {
|
|
|
50146
51557
|
function resolveDaemonRunnerEntry() {
|
|
50147
51558
|
try {
|
|
50148
51559
|
const here = fileURLToPath(import.meta.url);
|
|
50149
|
-
const sibling =
|
|
51560
|
+
const sibling = path21.join(path21.dirname(here), "supervisor-entrypoint.js");
|
|
50150
51561
|
if (existsSync11(sibling)) {
|
|
50151
51562
|
return sibling;
|
|
50152
51563
|
}
|
|
@@ -50160,23 +51571,23 @@ function resolveDaemonRunnerEntry() {
|
|
|
50160
51571
|
"Unable to resolve @appostle/server package root for daemon runner (and no sibling supervisor-entrypoint.js was bundled)"
|
|
50161
51572
|
);
|
|
50162
51573
|
}
|
|
50163
|
-
let currentDir =
|
|
51574
|
+
let currentDir = path21.dirname(serverExportPath);
|
|
50164
51575
|
while (true) {
|
|
50165
|
-
const packageJsonPath =
|
|
51576
|
+
const packageJsonPath = path21.join(currentDir, "package.json");
|
|
50166
51577
|
if (existsSync11(packageJsonPath)) {
|
|
50167
51578
|
try {
|
|
50168
51579
|
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
50169
51580
|
if (packageJson.name === "@appostle/server") {
|
|
50170
|
-
const distRunner =
|
|
51581
|
+
const distRunner = path21.join(currentDir, "dist", "scripts", "supervisor-entrypoint.js");
|
|
50171
51582
|
if (existsSync11(distRunner)) {
|
|
50172
51583
|
return distRunner;
|
|
50173
51584
|
}
|
|
50174
|
-
return
|
|
51585
|
+
return path21.join(currentDir, "scripts", "supervisor-entrypoint.ts");
|
|
50175
51586
|
}
|
|
50176
51587
|
} catch {
|
|
50177
51588
|
}
|
|
50178
51589
|
}
|
|
50179
|
-
const parentDir =
|
|
51590
|
+
const parentDir = path21.dirname(currentDir);
|
|
50180
51591
|
if (parentDir === currentDir) {
|
|
50181
51592
|
break;
|
|
50182
51593
|
}
|
|
@@ -50185,7 +51596,7 @@ function resolveDaemonRunnerEntry() {
|
|
|
50185
51596
|
throw new Error("Unable to resolve @appostle/server package root for daemon runner");
|
|
50186
51597
|
}
|
|
50187
51598
|
function pidFilePath(appostleHome) {
|
|
50188
|
-
return
|
|
51599
|
+
return path21.join(appostleHome, DAEMON_PID_FILENAME);
|
|
50189
51600
|
}
|
|
50190
51601
|
function readPidFile(pidPath) {
|
|
50191
51602
|
try {
|
|
@@ -50327,7 +51738,7 @@ function resolveLocalDaemonState(options = {}) {
|
|
|
50327
51738
|
const home = resolveAppostleHome(env);
|
|
50328
51739
|
const config = loadConfig(home, { env });
|
|
50329
51740
|
const pidPath = pidFilePath(home);
|
|
50330
|
-
const logPath =
|
|
51741
|
+
const logPath = path21.join(home, DAEMON_LOG_FILENAME);
|
|
50331
51742
|
const pidInfo = existsSync11(pidPath) ? readPidFile(pidPath) : null;
|
|
50332
51743
|
const running = pidInfo ? isProcessRunning(pidInfo.pid) : false;
|
|
50333
51744
|
const listen = pidInfo?.listen ?? config.listen;
|
|
@@ -50342,7 +51753,7 @@ function resolveLocalDaemonState(options = {}) {
|
|
|
50342
51753
|
};
|
|
50343
51754
|
}
|
|
50344
51755
|
function tailDaemonLog(home, lines = 30) {
|
|
50345
|
-
const logPath =
|
|
51756
|
+
const logPath = path21.join(resolveLocalAppostleHome(home), DAEMON_LOG_FILENAME);
|
|
50346
51757
|
return tailFile(logPath, lines);
|
|
50347
51758
|
}
|
|
50348
51759
|
async function startLocalDaemonDetached(options) {
|
|
@@ -50351,7 +51762,7 @@ async function startLocalDaemonDetached(options) {
|
|
|
50351
51762
|
}
|
|
50352
51763
|
const childEnv = buildChildEnv(options);
|
|
50353
51764
|
const appostleHome = resolveAppostleHome(childEnv);
|
|
50354
|
-
const logPath =
|
|
51765
|
+
const logPath = path21.join(appostleHome, DAEMON_LOG_FILENAME);
|
|
50355
51766
|
const daemonRunnerEntry = resolveDaemonRunnerEntry();
|
|
50356
51767
|
const child = spawn7(
|
|
50357
51768
|
process.execPath,
|
|
@@ -53076,22 +54487,22 @@ import { Command as Command13 } from "commander";
|
|
|
53076
54487
|
|
|
53077
54488
|
// ../cli/src/commands/worktree/ls.ts
|
|
53078
54489
|
import { homedir as homedir7 } from "node:os";
|
|
53079
|
-
import { basename as basename7, join as
|
|
53080
|
-
function shortenPath3(
|
|
54490
|
+
import { basename as basename7, join as join13, sep as sep3 } from "node:path";
|
|
54491
|
+
function shortenPath3(path26) {
|
|
53081
54492
|
const home = process.env.HOME;
|
|
53082
|
-
if (home &&
|
|
53083
|
-
return "~" +
|
|
54493
|
+
if (home && path26.startsWith(home)) {
|
|
54494
|
+
return "~" + path26.slice(home.length);
|
|
53084
54495
|
}
|
|
53085
|
-
return
|
|
54496
|
+
return path26;
|
|
53086
54497
|
}
|
|
53087
|
-
function extractWorktreeName(
|
|
53088
|
-
return basename7(
|
|
54498
|
+
function extractWorktreeName(path26) {
|
|
54499
|
+
return basename7(path26);
|
|
53089
54500
|
}
|
|
53090
54501
|
function resolveAppostleHomePath() {
|
|
53091
|
-
return process.env.APPOSTLE_HOME ??
|
|
54502
|
+
return process.env.APPOSTLE_HOME ?? join13(homedir7(), ".appostle");
|
|
53092
54503
|
}
|
|
53093
54504
|
function resolveAppostleWorktreesDir() {
|
|
53094
|
-
return
|
|
54505
|
+
return join13(resolveAppostleHomePath(), "worktrees");
|
|
53095
54506
|
}
|
|
53096
54507
|
function isAgentInManagedWorktree(agentCwd) {
|
|
53097
54508
|
const worktreesDir = resolveAppostleWorktreesDir();
|
|
@@ -53165,7 +54576,7 @@ async function runLsCommand7(options, _command) {
|
|
|
53165
54576
|
}
|
|
53166
54577
|
|
|
53167
54578
|
// ../cli/src/commands/worktree/archive.ts
|
|
53168
|
-
import
|
|
54579
|
+
import path22 from "path";
|
|
53169
54580
|
var archiveSchema2 = {
|
|
53170
54581
|
idField: "name",
|
|
53171
54582
|
columns: [
|
|
@@ -53209,7 +54620,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
|
|
|
53209
54620
|
throw error;
|
|
53210
54621
|
}
|
|
53211
54622
|
const worktree = listResponse.worktrees.find((wt) => {
|
|
53212
|
-
const name =
|
|
54623
|
+
const name = path22.basename(wt.worktreePath);
|
|
53213
54624
|
return name === nameArg || wt.branchName === nameArg;
|
|
53214
54625
|
});
|
|
53215
54626
|
if (!worktree) {
|
|
@@ -53231,7 +54642,7 @@ async function runArchiveCommand2(nameArg, options, _command) {
|
|
|
53231
54642
|
};
|
|
53232
54643
|
throw error;
|
|
53233
54644
|
}
|
|
53234
|
-
const worktreeName =
|
|
54645
|
+
const worktreeName = path22.basename(worktree.worktreePath) || nameArg;
|
|
53235
54646
|
return {
|
|
53236
54647
|
type: "single",
|
|
53237
54648
|
data: {
|
|
@@ -53272,7 +54683,7 @@ function createWorktreeCommand() {
|
|
|
53272
54683
|
import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
|
|
53273
54684
|
import { Command as Command14, Option as Option4 } from "commander";
|
|
53274
54685
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
53275
|
-
import
|
|
54686
|
+
import path23 from "node:path";
|
|
53276
54687
|
var DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
53277
54688
|
var OnboardCancelledError = class extends Error {
|
|
53278
54689
|
};
|
|
@@ -53315,7 +54726,7 @@ function toCliOverrides(options) {
|
|
|
53315
54726
|
return cliOverrides;
|
|
53316
54727
|
}
|
|
53317
54728
|
function savePersistedConfig2(appostleHome, config) {
|
|
53318
|
-
const configPath =
|
|
54729
|
+
const configPath = path23.join(appostleHome, "config.json");
|
|
53319
54730
|
writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
|
|
53320
54731
|
`);
|
|
53321
54732
|
}
|
|
@@ -53436,7 +54847,7 @@ ${recentLogs}` : null
|
|
|
53436
54847
|
);
|
|
53437
54848
|
}
|
|
53438
54849
|
function printNextSteps(pairingUrl, appostleHome, richUi) {
|
|
53439
|
-
const daemonLogPath =
|
|
54850
|
+
const daemonLogPath = path23.join(appostleHome, "daemon.log");
|
|
53440
54851
|
const nextStepsLines = [
|
|
53441
54852
|
pairingUrl ? "1. Open Appostle and scan the QR code above, or paste the pairing link." : "1. Open Appostle and connect to your daemon.",
|
|
53442
54853
|
"2. Web app: https://appostle.app",
|
|
@@ -53690,18 +55101,18 @@ function createCli() {
|
|
|
53690
55101
|
// ../cli/src/classify.ts
|
|
53691
55102
|
import { existsSync as existsSync12, statSync as statSync3 } from "node:fs";
|
|
53692
55103
|
import { homedir as homedir8 } from "node:os";
|
|
53693
|
-
import
|
|
55104
|
+
import path24 from "node:path";
|
|
53694
55105
|
function expandUserPath2(inputPath) {
|
|
53695
55106
|
if (inputPath === "~") {
|
|
53696
55107
|
return homedir8();
|
|
53697
55108
|
}
|
|
53698
55109
|
if (inputPath.startsWith("~/")) {
|
|
53699
|
-
return
|
|
55110
|
+
return path24.join(homedir8(), inputPath.slice(2));
|
|
53700
55111
|
}
|
|
53701
55112
|
return inputPath;
|
|
53702
55113
|
}
|
|
53703
55114
|
function isExistingDirectory(input) {
|
|
53704
|
-
const resolvedPath =
|
|
55115
|
+
const resolvedPath = path24.resolve(input.cwd, expandUserPath2(input.pathArg));
|
|
53705
55116
|
if (!existsSync12(resolvedPath)) {
|
|
53706
55117
|
return false;
|
|
53707
55118
|
}
|
|
@@ -53721,7 +55132,7 @@ function classifyInvocation(input) {
|
|
|
53721
55132
|
if (isExistingDirectory({ pathArg: firstArg, cwd: input.cwd })) {
|
|
53722
55133
|
return {
|
|
53723
55134
|
kind: "open-project",
|
|
53724
|
-
resolvedPath:
|
|
55135
|
+
resolvedPath: path24.resolve(input.cwd, expandUserPath2(firstArg))
|
|
53725
55136
|
};
|
|
53726
55137
|
}
|
|
53727
55138
|
return { kind: "cli", argv: input.argv };
|
|
@@ -53731,12 +55142,12 @@ function classifyInvocation(input) {
|
|
|
53731
55142
|
import { existsSync as existsSync13 } from "node:fs";
|
|
53732
55143
|
import { spawn as spawn8 } from "node:child_process";
|
|
53733
55144
|
import { homedir as homedir9 } from "node:os";
|
|
53734
|
-
import
|
|
55145
|
+
import path25 from "node:path";
|
|
53735
55146
|
function findDesktopApp() {
|
|
53736
55147
|
if (process.platform === "darwin") {
|
|
53737
55148
|
const candidates = [
|
|
53738
55149
|
"/Applications/Appostle.app",
|
|
53739
|
-
|
|
55150
|
+
path25.join(homedir9(), "Applications", "Appostle.app")
|
|
53740
55151
|
];
|
|
53741
55152
|
for (const candidate of candidates) {
|
|
53742
55153
|
if (existsSync13(candidate)) {
|
|
@@ -53749,7 +55160,7 @@ function findDesktopApp() {
|
|
|
53749
55160
|
const candidates = [
|
|
53750
55161
|
"/usr/bin/Appostle",
|
|
53751
55162
|
"/opt/Appostle/Appostle",
|
|
53752
|
-
|
|
55163
|
+
path25.join(homedir9(), "Applications", "Appostle.AppImage")
|
|
53753
55164
|
];
|
|
53754
55165
|
for (const candidate of candidates) {
|
|
53755
55166
|
if (existsSync13(candidate)) {
|
|
@@ -53763,7 +55174,7 @@ function findDesktopApp() {
|
|
|
53763
55174
|
if (!localAppData) {
|
|
53764
55175
|
return null;
|
|
53765
55176
|
}
|
|
53766
|
-
const candidate =
|
|
55177
|
+
const candidate = path25.join(localAppData, "Programs", "Appostle", "Appostle.exe");
|
|
53767
55178
|
return existsSync13(candidate) ? candidate : null;
|
|
53768
55179
|
}
|
|
53769
55180
|
return null;
|