@zhigang1992/happy-cli 0.12.3 → 0.12.4

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/lib.d.cts CHANGED
@@ -291,6 +291,16 @@ type RawJSONLines = z.infer<typeof RawJSONLinesSchema>;
291
291
  /**
292
292
  * Common RPC types and interfaces for both session and machine clients
293
293
  */
294
+ /**
295
+ * Session context for RPC handlers
296
+ * Contains environment and path information for the current session
297
+ */
298
+ interface RpcSessionContext {
299
+ /** Working directory path for the session */
300
+ path: string;
301
+ /** Environment variables (includes direnv if available) */
302
+ env: Record<string, string>;
303
+ }
294
304
  /**
295
305
  * Generic RPC handler function type
296
306
  * @template TRequest - The request data type
@@ -326,7 +336,19 @@ declare class RpcHandlerManager {
326
336
  private readonly encryptionVariant;
327
337
  private readonly logger;
328
338
  private socket;
339
+ private sessionContext;
329
340
  constructor(config: RpcHandlerConfig);
341
+ /**
342
+ * Set the session context (path and environment)
343
+ * This should be called after direnv environment is loaded
344
+ * @param context - The session context with path and environment
345
+ */
346
+ setSessionContext(context: RpcSessionContext): void;
347
+ /**
348
+ * Get the current session context
349
+ * @returns The session context or null if not set
350
+ */
351
+ getSessionContext(): RpcSessionContext | null;
330
352
  /**
331
353
  * Register an RPC handler for a specific method
332
354
  * @param method - The method name (without prefix)
@@ -423,6 +445,30 @@ declare class ApiSessionClient extends EventEmitter {
423
445
  */
424
446
  flush(): Promise<void>;
425
447
  close(): Promise<void>;
448
+ /**
449
+ * Download and decrypt a blob from the server
450
+ * @param blobId - The blob ID to download
451
+ * @returns The decrypted binary data and metadata, or null if download/decryption fails
452
+ */
453
+ downloadBlob(blobId: string): Promise<{
454
+ data: Uint8Array;
455
+ mimeType: string;
456
+ size: number;
457
+ } | null>;
458
+ /**
459
+ * Convert an image_ref content block to a Claude API image content block
460
+ * Downloads, decrypts, and base64 encodes the image
461
+ * @param imageRef - The image reference content block
462
+ * @returns Claude API image content block, or null if conversion fails
463
+ */
464
+ resolveImageRef(imageRef: ImageRefContent): Promise<{
465
+ type: 'image';
466
+ source: {
467
+ type: 'base64';
468
+ media_type: string;
469
+ data: string;
470
+ };
471
+ } | null>;
426
472
  }
427
473
 
428
474
  type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
@@ -505,9 +551,32 @@ type Machine = {
505
551
  daemonState: DaemonState | null;
506
552
  daemonStateVersion: number;
507
553
  };
554
+ /**
555
+ * Image reference content block for user messages (E2E encrypted images)
556
+ */
557
+ declare const ImageRefContentSchema: z.ZodObject<{
558
+ type: z.ZodLiteral<"image_ref">;
559
+ blobId: z.ZodString;
560
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
561
+ width: z.ZodOptional<z.ZodNumber>;
562
+ height: z.ZodOptional<z.ZodNumber>;
563
+ }, "strip", z.ZodTypeAny, {
564
+ type: "image_ref";
565
+ blobId: string;
566
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
567
+ width?: number | undefined;
568
+ height?: number | undefined;
569
+ }, {
570
+ type: "image_ref";
571
+ blobId: string;
572
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
573
+ width?: number | undefined;
574
+ height?: number | undefined;
575
+ }>;
576
+ type ImageRefContent = z.infer<typeof ImageRefContentSchema>;
508
577
  declare const UserMessageSchema: z.ZodObject<{
509
578
  role: z.ZodLiteral<"user">;
510
- content: z.ZodObject<{
579
+ content: z.ZodUnion<[z.ZodObject<{
511
580
  type: z.ZodLiteral<"text">;
512
581
  text: z.ZodString;
513
582
  }, "strip", z.ZodTypeAny, {
@@ -516,7 +585,34 @@ declare const UserMessageSchema: z.ZodObject<{
516
585
  }, {
517
586
  type: "text";
518
587
  text: string;
519
- }>;
588
+ }>, z.ZodArray<z.ZodUnion<[z.ZodObject<{
589
+ type: z.ZodLiteral<"text">;
590
+ text: z.ZodString;
591
+ }, "strip", z.ZodTypeAny, {
592
+ type: "text";
593
+ text: string;
594
+ }, {
595
+ type: "text";
596
+ text: string;
597
+ }>, z.ZodObject<{
598
+ type: z.ZodLiteral<"image_ref">;
599
+ blobId: z.ZodString;
600
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
601
+ width: z.ZodOptional<z.ZodNumber>;
602
+ height: z.ZodOptional<z.ZodNumber>;
603
+ }, "strip", z.ZodTypeAny, {
604
+ type: "image_ref";
605
+ blobId: string;
606
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
607
+ width?: number | undefined;
608
+ height?: number | undefined;
609
+ }, {
610
+ type: "image_ref";
611
+ blobId: string;
612
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
613
+ width?: number | undefined;
614
+ height?: number | undefined;
615
+ }>]>, "many">]>;
520
616
  localKey: z.ZodOptional<z.ZodString>;
521
617
  meta: z.ZodOptional<z.ZodObject<{
522
618
  sentFrom: z.ZodOptional<z.ZodString>;
@@ -550,7 +646,16 @@ declare const UserMessageSchema: z.ZodObject<{
550
646
  content: {
551
647
  type: "text";
552
648
  text: string;
553
- };
649
+ } | ({
650
+ type: "text";
651
+ text: string;
652
+ } | {
653
+ type: "image_ref";
654
+ blobId: string;
655
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
656
+ width?: number | undefined;
657
+ height?: number | undefined;
658
+ })[];
554
659
  role: "user";
555
660
  localKey?: string | undefined;
556
661
  meta?: {
@@ -567,7 +672,16 @@ declare const UserMessageSchema: z.ZodObject<{
567
672
  content: {
568
673
  type: "text";
569
674
  text: string;
570
- };
675
+ } | ({
676
+ type: "text";
677
+ text: string;
678
+ } | {
679
+ type: "image_ref";
680
+ blobId: string;
681
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
682
+ width?: number | undefined;
683
+ height?: number | undefined;
684
+ })[];
571
685
  role: "user";
572
686
  localKey?: string | undefined;
573
687
  meta?: {
package/dist/lib.d.mts CHANGED
@@ -291,6 +291,16 @@ type RawJSONLines = z.infer<typeof RawJSONLinesSchema>;
291
291
  /**
292
292
  * Common RPC types and interfaces for both session and machine clients
293
293
  */
294
+ /**
295
+ * Session context for RPC handlers
296
+ * Contains environment and path information for the current session
297
+ */
298
+ interface RpcSessionContext {
299
+ /** Working directory path for the session */
300
+ path: string;
301
+ /** Environment variables (includes direnv if available) */
302
+ env: Record<string, string>;
303
+ }
294
304
  /**
295
305
  * Generic RPC handler function type
296
306
  * @template TRequest - The request data type
@@ -326,7 +336,19 @@ declare class RpcHandlerManager {
326
336
  private readonly encryptionVariant;
327
337
  private readonly logger;
328
338
  private socket;
339
+ private sessionContext;
329
340
  constructor(config: RpcHandlerConfig);
341
+ /**
342
+ * Set the session context (path and environment)
343
+ * This should be called after direnv environment is loaded
344
+ * @param context - The session context with path and environment
345
+ */
346
+ setSessionContext(context: RpcSessionContext): void;
347
+ /**
348
+ * Get the current session context
349
+ * @returns The session context or null if not set
350
+ */
351
+ getSessionContext(): RpcSessionContext | null;
330
352
  /**
331
353
  * Register an RPC handler for a specific method
332
354
  * @param method - The method name (without prefix)
@@ -423,6 +445,30 @@ declare class ApiSessionClient extends EventEmitter {
423
445
  */
424
446
  flush(): Promise<void>;
425
447
  close(): Promise<void>;
448
+ /**
449
+ * Download and decrypt a blob from the server
450
+ * @param blobId - The blob ID to download
451
+ * @returns The decrypted binary data and metadata, or null if download/decryption fails
452
+ */
453
+ downloadBlob(blobId: string): Promise<{
454
+ data: Uint8Array;
455
+ mimeType: string;
456
+ size: number;
457
+ } | null>;
458
+ /**
459
+ * Convert an image_ref content block to a Claude API image content block
460
+ * Downloads, decrypts, and base64 encodes the image
461
+ * @param imageRef - The image reference content block
462
+ * @returns Claude API image content block, or null if conversion fails
463
+ */
464
+ resolveImageRef(imageRef: ImageRefContent): Promise<{
465
+ type: 'image';
466
+ source: {
467
+ type: 'base64';
468
+ media_type: string;
469
+ data: string;
470
+ };
471
+ } | null>;
426
472
  }
427
473
 
428
474
  type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
@@ -505,9 +551,32 @@ type Machine = {
505
551
  daemonState: DaemonState | null;
506
552
  daemonStateVersion: number;
507
553
  };
554
+ /**
555
+ * Image reference content block for user messages (E2E encrypted images)
556
+ */
557
+ declare const ImageRefContentSchema: z.ZodObject<{
558
+ type: z.ZodLiteral<"image_ref">;
559
+ blobId: z.ZodString;
560
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
561
+ width: z.ZodOptional<z.ZodNumber>;
562
+ height: z.ZodOptional<z.ZodNumber>;
563
+ }, "strip", z.ZodTypeAny, {
564
+ type: "image_ref";
565
+ blobId: string;
566
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
567
+ width?: number | undefined;
568
+ height?: number | undefined;
569
+ }, {
570
+ type: "image_ref";
571
+ blobId: string;
572
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
573
+ width?: number | undefined;
574
+ height?: number | undefined;
575
+ }>;
576
+ type ImageRefContent = z.infer<typeof ImageRefContentSchema>;
508
577
  declare const UserMessageSchema: z.ZodObject<{
509
578
  role: z.ZodLiteral<"user">;
510
- content: z.ZodObject<{
579
+ content: z.ZodUnion<[z.ZodObject<{
511
580
  type: z.ZodLiteral<"text">;
512
581
  text: z.ZodString;
513
582
  }, "strip", z.ZodTypeAny, {
@@ -516,7 +585,34 @@ declare const UserMessageSchema: z.ZodObject<{
516
585
  }, {
517
586
  type: "text";
518
587
  text: string;
519
- }>;
588
+ }>, z.ZodArray<z.ZodUnion<[z.ZodObject<{
589
+ type: z.ZodLiteral<"text">;
590
+ text: z.ZodString;
591
+ }, "strip", z.ZodTypeAny, {
592
+ type: "text";
593
+ text: string;
594
+ }, {
595
+ type: "text";
596
+ text: string;
597
+ }>, z.ZodObject<{
598
+ type: z.ZodLiteral<"image_ref">;
599
+ blobId: z.ZodString;
600
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
601
+ width: z.ZodOptional<z.ZodNumber>;
602
+ height: z.ZodOptional<z.ZodNumber>;
603
+ }, "strip", z.ZodTypeAny, {
604
+ type: "image_ref";
605
+ blobId: string;
606
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
607
+ width?: number | undefined;
608
+ height?: number | undefined;
609
+ }, {
610
+ type: "image_ref";
611
+ blobId: string;
612
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
613
+ width?: number | undefined;
614
+ height?: number | undefined;
615
+ }>]>, "many">]>;
520
616
  localKey: z.ZodOptional<z.ZodString>;
521
617
  meta: z.ZodOptional<z.ZodObject<{
522
618
  sentFrom: z.ZodOptional<z.ZodString>;
@@ -550,7 +646,16 @@ declare const UserMessageSchema: z.ZodObject<{
550
646
  content: {
551
647
  type: "text";
552
648
  text: string;
553
- };
649
+ } | ({
650
+ type: "text";
651
+ text: string;
652
+ } | {
653
+ type: "image_ref";
654
+ blobId: string;
655
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
656
+ width?: number | undefined;
657
+ height?: number | undefined;
658
+ })[];
554
659
  role: "user";
555
660
  localKey?: string | undefined;
556
661
  meta?: {
@@ -567,7 +672,16 @@ declare const UserMessageSchema: z.ZodObject<{
567
672
  content: {
568
673
  type: "text";
569
674
  text: string;
570
- };
675
+ } | ({
676
+ type: "text";
677
+ text: string;
678
+ } | {
679
+ type: "image_ref";
680
+ blobId: string;
681
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
682
+ width?: number | undefined;
683
+ height?: number | undefined;
684
+ })[];
571
685
  role: "user";
572
686
  localKey?: string | undefined;
573
687
  meta?: {
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-D4_aCy-H.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-Cw6y7GyQ.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { c as configuration, l as logger, d as decodeBase64, b as decrypt, f as formatTimeAgo, e as libsodiumDecryptFromPublicKey } from './types-D4_aCy-H.mjs';
1
+ import { c as configuration, l as logger, d as decodeBase64, b as decrypt, f as formatTimeAgo, e as libsodiumDecryptFromPublicKey } from './types-Cw6y7GyQ.mjs';
2
2
  import axios from 'axios';
3
3
  import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
4
4
  import { join } from 'path';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-CllU28mx.cjs');
3
+ var types = require('./types-Q-euvEmG.cjs');
4
4
  var axios = require('axios');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
@@ -1,4 +1,4 @@
1
- import { c as configuration, b as decrypt, d as decodeBase64, l as logger, g as encodeBase64, h as encrypt } from './types-D4_aCy-H.mjs';
1
+ import { c as configuration, b as decrypt, d as decodeBase64, l as logger, g as encodeBase64, h as encrypt } from './types-Cw6y7GyQ.mjs';
2
2
  import axios from 'axios';
3
3
  import { io } from 'socket.io-client';
4
4
  import 'chalk';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-CllU28mx.cjs');
3
+ var types = require('./types-Q-euvEmG.cjs');
4
4
  var axios = require('axios');
5
5
  var socket_ioClient = require('socket.io-client');
6
6
  require('chalk');
@@ -1,13 +1,13 @@
1
1
  import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
- import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, i as packageJson } from './types-D4_aCy-H.mjs';
3
+ import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, i as packageJson } from './types-Cw6y7GyQ.mjs';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
6
  import { z } from 'zod';
7
7
  import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
8
  import { execSync } from 'child_process';
9
9
  import { randomUUID } from 'node:crypto';
10
- import { i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as startHappyServer, t as trimIdent, b as stopCaffeinate } from './index-DNeoLdzx.mjs';
10
+ import { i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as startHappyServer, t as trimIdent, b as stopCaffeinate } from './index-BERBU6rR.mjs';
11
11
  import os from 'node:os';
12
12
  import { resolve, join } from 'node:path';
13
13
  import fs from 'node:fs';
@@ -752,6 +752,21 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
752
752
  ));
753
753
  };
754
754
 
755
+ function extractTextFromContent(content) {
756
+ if (!Array.isArray(content)) {
757
+ if (content.type === "text") {
758
+ return content.text;
759
+ }
760
+ return "";
761
+ }
762
+ const textParts = [];
763
+ for (const block of content) {
764
+ if (block.type === "text") {
765
+ textParts.push(block.text);
766
+ }
767
+ }
768
+ return textParts.join("\n");
769
+ }
755
770
  function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
756
771
  if (shouldExit) {
757
772
  return false;
@@ -847,7 +862,8 @@ async function runCodex(opts) {
847
862
  permissionMode: messagePermissionMode || "default",
848
863
  model: messageModel
849
864
  };
850
- messageQueue.push(message.content.text, enhancedMode);
865
+ const textContent = extractTextFromContent(message.content);
866
+ messageQueue.push(textContent, enhancedMode);
851
867
  });
852
868
  let thinking = false;
853
869
  session.keepAlive(thinking, "remote");
@@ -2,14 +2,14 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-CllU28mx.cjs');
5
+ var types = require('./types-Q-euvEmG.cjs');
6
6
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
7
7
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
8
8
  var z = require('zod');
9
9
  var types_js = require('@modelcontextprotocol/sdk/types.js');
10
10
  var child_process = require('child_process');
11
11
  var node_crypto = require('node:crypto');
12
- var index = require('./index-y8CVImEp.cjs');
12
+ var index = require('./index-CHEjP0zg.cjs');
13
13
  var os = require('node:os');
14
14
  var node_path = require('node:path');
15
15
  var fs = require('node:fs');
@@ -754,6 +754,21 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
754
754
  ));
755
755
  };
756
756
 
757
+ function extractTextFromContent(content) {
758
+ if (!Array.isArray(content)) {
759
+ if (content.type === "text") {
760
+ return content.text;
761
+ }
762
+ return "";
763
+ }
764
+ const textParts = [];
765
+ for (const block of content) {
766
+ if (block.type === "text") {
767
+ textParts.push(block.text);
768
+ }
769
+ }
770
+ return textParts.join("\n");
771
+ }
757
772
  function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
758
773
  if (shouldExit) {
759
774
  return false;
@@ -849,7 +864,8 @@ async function runCodex(opts) {
849
864
  permissionMode: messagePermissionMode || "default",
850
865
  model: messageModel
851
866
  };
852
- messageQueue.push(message.content.text, enhancedMode);
867
+ const textContent = extractTextFromContent(message.content);
868
+ messageQueue.push(textContent, enhancedMode);
853
869
  });
854
870
  let thinking = false;
855
871
  session.keepAlive(thinking, "remote");
@@ -20,7 +20,7 @@ import { fileURLToPath } from 'url';
20
20
  import { Expo } from 'expo-server-sdk';
21
21
 
22
22
  var name = "@zhigang1992/happy-cli";
23
- var version = "0.12.2";
23
+ var version = "0.12.4";
24
24
  var description = "Mobile and Web client for Claude Code and Codex";
25
25
  var author = "Kirill Dubovitskiy";
26
26
  var license = "MIT";
@@ -311,6 +311,31 @@ function decryptWithDataKey(bundle, dataKey) {
311
311
  return null;
312
312
  }
313
313
  }
314
+ function decryptBlobWithDataKey(bundle, dataKey) {
315
+ if (bundle.length < 1) {
316
+ return null;
317
+ }
318
+ if (bundle[0] !== 0) {
319
+ return null;
320
+ }
321
+ if (bundle.length < 12 + 16 + 1) {
322
+ return null;
323
+ }
324
+ const nonce = bundle.slice(1, 13);
325
+ const authTag = bundle.slice(bundle.length - 16);
326
+ const ciphertext = bundle.slice(13, bundle.length - 16);
327
+ try {
328
+ const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
329
+ decipher.setAuthTag(authTag);
330
+ const decrypted = Buffer.concat([
331
+ decipher.update(ciphertext),
332
+ decipher.final()
333
+ ]);
334
+ return new Uint8Array(decrypted);
335
+ } catch (error) {
336
+ return null;
337
+ }
338
+ }
314
339
  function encrypt(key, variant, data) {
315
340
  if (variant === "legacy") {
316
341
  return encryptLegacy(data, key);
@@ -852,12 +877,27 @@ z$1.object({
852
877
  agentStateVersion: z$1.number()
853
878
  })
854
879
  });
880
+ const TextContentSchema = z$1.object({
881
+ type: z$1.literal("text"),
882
+ text: z$1.string()
883
+ });
884
+ const ImageRefContentSchema = z$1.object({
885
+ type: z$1.literal("image_ref"),
886
+ blobId: z$1.string(),
887
+ mimeType: z$1.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
888
+ width: z$1.number().optional(),
889
+ height: z$1.number().optional()
890
+ });
891
+ const ContentBlockSchema = z$1.union([TextContentSchema, ImageRefContentSchema]);
892
+ const UserMessageContentSchema = z$1.union([
893
+ TextContentSchema,
894
+ // Legacy: single text content
895
+ z$1.array(ContentBlockSchema)
896
+ // New: array of content blocks
897
+ ]);
855
898
  const UserMessageSchema = z$1.object({
856
899
  role: z$1.literal("user"),
857
- content: z$1.object({
858
- type: z$1.literal("text"),
859
- text: z$1.string()
860
- }),
900
+ content: UserMessageContentSchema,
861
901
  localKey: z$1.string().optional(),
862
902
  // Mobile messages include this
863
903
  meta: MessageMetaSchema.optional()
@@ -969,12 +1009,29 @@ class RpcHandlerManager {
969
1009
  encryptionVariant;
970
1010
  logger;
971
1011
  socket = null;
1012
+ sessionContext = null;
972
1013
  constructor(config) {
973
1014
  this.scopePrefix = config.scopePrefix;
974
1015
  this.encryptionKey = config.encryptionKey;
975
1016
  this.encryptionVariant = config.encryptionVariant;
976
1017
  this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
977
1018
  }
1019
+ /**
1020
+ * Set the session context (path and environment)
1021
+ * This should be called after direnv environment is loaded
1022
+ * @param context - The session context with path and environment
1023
+ */
1024
+ setSessionContext(context) {
1025
+ this.sessionContext = context;
1026
+ this.logger("[RPC] Session context set", { path: context.path, envVarCount: Object.keys(context.env).length });
1027
+ }
1028
+ /**
1029
+ * Get the current session context
1030
+ * @returns The session context or null if not set
1031
+ */
1032
+ getSessionContext() {
1033
+ return this.sessionContext;
1034
+ }
978
1035
  /**
979
1036
  * Register an RPC handler for a specific method
980
1037
  * @param method - The method name (without prefix)
@@ -1150,10 +1207,13 @@ function registerCommonHandlers(rpcHandlerManager) {
1150
1207
  rpcHandlerManager.registerHandler("bash", async (data) => {
1151
1208
  logger.debug("Shell command request:", data.command);
1152
1209
  try {
1210
+ const sessionContext = rpcHandlerManager.getSessionContext();
1153
1211
  const options = {
1154
1212
  cwd: data.cwd,
1155
- timeout: data.timeout || 3e4
1213
+ timeout: data.timeout || 3e4,
1156
1214
  // Default 30 seconds timeout
1215
+ // Use session environment if available, otherwise inherit process.env
1216
+ env: sessionContext?.env ?? process.env
1157
1217
  };
1158
1218
  const { stdout, stderr } = await execAsync(data.command, options);
1159
1219
  return {
@@ -1683,6 +1743,64 @@ class ApiSessionClient extends EventEmitter {
1683
1743
  logger.debug("[API] socket.close() called");
1684
1744
  this.socket.close();
1685
1745
  }
1746
+ /**
1747
+ * Download and decrypt a blob from the server
1748
+ * @param blobId - The blob ID to download
1749
+ * @returns The decrypted binary data and metadata, or null if download/decryption fails
1750
+ */
1751
+ async downloadBlob(blobId) {
1752
+ try {
1753
+ const response = await axios.get(
1754
+ `${configuration.serverUrl}/v1/sessions/${this.sessionId}/blobs/${blobId}`,
1755
+ {
1756
+ headers: {
1757
+ "Authorization": `Bearer ${this.token}`
1758
+ },
1759
+ responseType: "arraybuffer"
1760
+ }
1761
+ );
1762
+ const encryptedData = new Uint8Array(response.data);
1763
+ const mimeType = response.headers["x-blob-mimetype"];
1764
+ const originalSize = parseInt(response.headers["x-blob-size"], 10);
1765
+ if (this.encryptionVariant !== "dataKey") {
1766
+ logger.debug("[API] Cannot decrypt blob: session uses legacy encryption");
1767
+ return null;
1768
+ }
1769
+ const decryptedData = decryptBlobWithDataKey(encryptedData, this.encryptionKey);
1770
+ if (!decryptedData) {
1771
+ logger.debug("[API] Failed to decrypt blob");
1772
+ return null;
1773
+ }
1774
+ return {
1775
+ data: decryptedData,
1776
+ mimeType,
1777
+ size: originalSize
1778
+ };
1779
+ } catch (error) {
1780
+ logger.debug("[API] Failed to download blob:", error);
1781
+ return null;
1782
+ }
1783
+ }
1784
+ /**
1785
+ * Convert an image_ref content block to a Claude API image content block
1786
+ * Downloads, decrypts, and base64 encodes the image
1787
+ * @param imageRef - The image reference content block
1788
+ * @returns Claude API image content block, or null if conversion fails
1789
+ */
1790
+ async resolveImageRef(imageRef) {
1791
+ const blob = await this.downloadBlob(imageRef.blobId);
1792
+ if (!blob) {
1793
+ return null;
1794
+ }
1795
+ return {
1796
+ type: "image",
1797
+ source: {
1798
+ type: "base64",
1799
+ media_type: blob.mimeType,
1800
+ data: Buffer.from(blob.data).toString("base64")
1801
+ }
1802
+ };
1803
+ }
1686
1804
  }
1687
1805
 
1688
1806
  class ApiMachineClient {