joonecli 0.2.0 → 0.2.2

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.
Files changed (116) hide show
  1. package/dist/__tests__/config.test.js +1 -0
  2. package/dist/__tests__/config.test.js.map +1 -1
  3. package/dist/__tests__/installHostDeps.test.js +45 -0
  4. package/dist/__tests__/installHostDeps.test.js.map +1 -0
  5. package/dist/__tests__/whitelistedBackend.test.js +18 -0
  6. package/dist/__tests__/whitelistedBackend.test.js.map +1 -0
  7. package/dist/cli/config.d.ts +2 -0
  8. package/dist/cli/config.js +1 -0
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.js +120 -107
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/startupBenchmark.d.ts +13 -0
  13. package/dist/cli/startupBenchmark.js +27 -0
  14. package/dist/cli/startupBenchmark.js.map +1 -0
  15. package/dist/core/agentLoop.d.ts +10 -29
  16. package/dist/core/agentLoop.js +73 -238
  17. package/dist/core/agentLoop.js.map +1 -1
  18. package/dist/core/promptBuilder.js.map +1 -1
  19. package/dist/hitl/bridge.js +1 -27
  20. package/dist/hitl/bridge.js.map +1 -1
  21. package/dist/middleware/loopDetection.d.ts +7 -23
  22. package/dist/middleware/loopDetection.js +38 -42
  23. package/dist/middleware/loopDetection.js.map +1 -1
  24. package/dist/sandbox/whitelistedBackend.d.ts +5 -0
  25. package/dist/sandbox/whitelistedBackend.js +27 -0
  26. package/dist/sandbox/whitelistedBackend.js.map +1 -0
  27. package/dist/tools/askUser.d.ts +12 -3
  28. package/dist/tools/askUser.js +16 -28
  29. package/dist/tools/askUser.js.map +1 -1
  30. package/dist/tools/bashTool.d.ts +11 -0
  31. package/dist/tools/bashTool.js +51 -0
  32. package/dist/tools/bashTool.js.map +1 -0
  33. package/dist/tools/index.d.ts +15 -28
  34. package/dist/tools/index.js +9 -189
  35. package/dist/tools/index.js.map +1 -1
  36. package/dist/tools/installHostDeps.d.ts +8 -2
  37. package/dist/tools/installHostDeps.js +38 -31
  38. package/dist/tools/installHostDeps.js.map +1 -1
  39. package/dist/ui/App.d.ts +4 -1
  40. package/dist/ui/App.js +200 -63
  41. package/dist/ui/App.js.map +1 -1
  42. package/dist/ui/components/MessageBubble.js +1 -1
  43. package/dist/ui/components/MessageBubble.js.map +1 -1
  44. package/package.json +7 -2
  45. package/dist/__tests__/m55.test.js +0 -160
  46. package/dist/__tests__/m55.test.js.map +0 -1
  47. package/dist/__tests__/middleware.test.js +0 -169
  48. package/dist/__tests__/middleware.test.js.map +0 -1
  49. package/dist/__tests__/optimizations.test.d.ts +0 -1
  50. package/dist/__tests__/optimizations.test.js +0 -136
  51. package/dist/__tests__/optimizations.test.js.map +0 -1
  52. package/dist/__tests__/security.test.d.ts +0 -1
  53. package/dist/__tests__/security.test.js +0 -86
  54. package/dist/__tests__/security.test.js.map +0 -1
  55. package/dist/__tests__/streaming.test.d.ts +0 -1
  56. package/dist/__tests__/streaming.test.js +0 -71
  57. package/dist/__tests__/streaming.test.js.map +0 -1
  58. package/dist/__tests__/toolRouter.test.d.ts +0 -1
  59. package/dist/__tests__/toolRouter.test.js +0 -37
  60. package/dist/__tests__/toolRouter.test.js.map +0 -1
  61. package/dist/__tests__/tools.test.d.ts +0 -1
  62. package/dist/__tests__/tools.test.js +0 -112
  63. package/dist/__tests__/tools.test.js.map +0 -1
  64. package/dist/core/subAgent.d.ts +0 -56
  65. package/dist/core/subAgent.js +0 -240
  66. package/dist/core/subAgent.js.map +0 -1
  67. package/dist/debug_google.d.ts +0 -1
  68. package/dist/debug_google.js +0 -23
  69. package/dist/debug_google.js.map +0 -1
  70. package/dist/middleware/commandSanitizer.d.ts +0 -18
  71. package/dist/middleware/commandSanitizer.js +0 -50
  72. package/dist/middleware/commandSanitizer.js.map +0 -1
  73. package/dist/middleware/permission.d.ts +0 -17
  74. package/dist/middleware/permission.js +0 -60
  75. package/dist/middleware/permission.js.map +0 -1
  76. package/dist/middleware/pipeline.d.ts +0 -31
  77. package/dist/middleware/pipeline.js +0 -62
  78. package/dist/middleware/pipeline.js.map +0 -1
  79. package/dist/middleware/preCompletion.d.ts +0 -29
  80. package/dist/middleware/preCompletion.js +0 -82
  81. package/dist/middleware/preCompletion.js.map +0 -1
  82. package/dist/middleware/types.d.ts +0 -40
  83. package/dist/middleware/types.js +0 -8
  84. package/dist/middleware/types.js.map +0 -1
  85. package/dist/skills/loader.d.ts +0 -55
  86. package/dist/skills/loader.js +0 -132
  87. package/dist/skills/loader.js.map +0 -1
  88. package/dist/skills/tools.d.ts +0 -5
  89. package/dist/skills/tools.js +0 -78
  90. package/dist/skills/tools.js.map +0 -1
  91. package/dist/test_cache.d.ts +0 -1
  92. package/dist/test_cache.js +0 -55
  93. package/dist/test_cache.js.map +0 -1
  94. package/dist/test_google.d.ts +0 -1
  95. package/dist/test_google.js +0 -36
  96. package/dist/test_google.js.map +0 -1
  97. package/dist/tools/browser.d.ts +0 -19
  98. package/dist/tools/browser.js +0 -114
  99. package/dist/tools/browser.js.map +0 -1
  100. package/dist/tools/registry.d.ts +0 -31
  101. package/dist/tools/registry.js +0 -168
  102. package/dist/tools/registry.js.map +0 -1
  103. package/dist/tools/router.d.ts +0 -34
  104. package/dist/tools/router.js +0 -76
  105. package/dist/tools/router.js.map +0 -1
  106. package/dist/tools/security.d.ts +0 -28
  107. package/dist/tools/security.js +0 -183
  108. package/dist/tools/security.js.map +0 -1
  109. package/dist/tools/spawnAgent.d.ts +0 -19
  110. package/dist/tools/spawnAgent.js +0 -132
  111. package/dist/tools/spawnAgent.js.map +0 -1
  112. package/dist/tools/webSearch.d.ts +0 -6
  113. package/dist/tools/webSearch.js +0 -120
  114. package/dist/tools/webSearch.js.map +0 -1
  115. /package/dist/__tests__/{m55.test.d.ts → installHostDeps.test.d.ts} +0 -0
  116. /package/dist/__tests__/{middleware.test.d.ts → whitelistedBackend.test.d.ts} +0 -0
@@ -32,6 +32,7 @@ describe("Config Manager", () => {
32
32
  expect(config.maxTokens).toBe(4096);
33
33
  expect(config.temperature).toBe(0);
34
34
  expect(config.streaming).toBe(true);
35
+ expect(config.executionMode).toBe("host");
35
36
  });
36
37
  // ─── RED Test #2: saveConfig roundtrips with loadConfig ───
37
38
  it("saves config to disk and loads it back correctly", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { installHostDependenciesTool } from "../tools/installHostDeps.js";
3
+ describe("InstallHostDependenciesTool (Security)", () => {
4
+ it("allows permitted package manager commands", async () => {
5
+ // We test with a dummy emitter to just ensure it doesn't fail early
6
+ // We can't easily mock execAsync purely without mocking module,
7
+ // but we can check if it gets past the security check and fails on execution
8
+ // (meaning it was allowed). Better yet, we can mock exec if needed, but for now
9
+ // we'll just check the error message structure if it fails.
10
+ // Actually, since this runs a real command, let's use a safe one or expect an execution error, not a security error.
11
+ });
12
+ it("blocks unknown binary", async () => {
13
+ const result = await installHostDependenciesTool.invoke({
14
+ command: "malicious_binary install foo",
15
+ });
16
+ expect(typeof result).toBe("string");
17
+ expect(result).toMatch(/Security Error/i);
18
+ expect(result).toMatch(/is not allowed/);
19
+ });
20
+ it("blocks disallowed subcommands for allowed binary", async () => {
21
+ const result = await installHostDependenciesTool.invoke({
22
+ command: "npm publish",
23
+ });
24
+ expect(typeof result).toBe("string");
25
+ expect(result).toMatch(/Security Error/i);
26
+ expect(result).toMatch(/npm publish/);
27
+ });
28
+ it("blocks forbidden shell operators", async () => {
29
+ const result = await installHostDependenciesTool.invoke({
30
+ command: "npm install express && rm -rf /",
31
+ });
32
+ expect(typeof result).toBe("string");
33
+ expect(result).toMatch(/Security Error/i);
34
+ expect(result).toMatch(/forbidden shell operators/);
35
+ });
36
+ it("blocks backticks", async () => {
37
+ const result = await installHostDependenciesTool.invoke({
38
+ command: "npm install `whoami`",
39
+ });
40
+ expect(typeof result).toBe("string");
41
+ expect(result).toMatch(/Security Error/i);
42
+ expect(result).toMatch(/forbidden shell operators/);
43
+ });
44
+ });
45
+ //# sourceMappingURL=installHostDeps.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installHostDeps.test.js","sourceRoot":"","sources":["../../src/__tests__/installHostDeps.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAE1E,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACvD,oEAAoE;QACpE,iEAAiE;QACjE,8EAA8E;QAC9E,iFAAiF;QACjF,4DAA4D;QAC5D,qHAAqH;IACzH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,MAAM,CAAC;YACpD,OAAO,EAAE,8BAA8B;SAC1C,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,MAAM,CAAC;YACpD,OAAO,EAAE,aAAa;SACzB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,MAAM,CAAC;YACpD,OAAO,EAAE,iCAAiC;SAC7C,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,MAAM,CAAC;YACpD,OAAO,EAAE,sBAAsB;SAClC,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { WhitelistedLocalShellBackend } from "../sandbox/whitelistedBackend.js";
3
+ describe("WhitelistedLocalShellBackend", () => {
4
+ it("allows safe commands", async () => {
5
+ const backend = new WhitelistedLocalShellBackend({ rootDir: process.cwd() });
6
+ const result = await backend.execute("echo hello");
7
+ expect(result.output).toMatch(/hello/i);
8
+ expect(result.exitCode).toBe(0); // Works on Windows native echo
9
+ });
10
+ it("blocks dangerous commands", async () => {
11
+ const backend = new WhitelistedLocalShellBackend({ rootDir: process.cwd() });
12
+ const result = await backend.execute("rm -rf /");
13
+ expect(result.exitCode).toBe(1);
14
+ expect(result.output).toMatch(/Security Error/i);
15
+ expect(result.output).toMatch(/not allowed/i);
16
+ });
17
+ });
18
+ //# sourceMappingURL=whitelistedBackend.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whitelistedBackend.test.js","sourceRoot":"","sources":["../../src/__tests__/whitelistedBackend.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAEhF,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QAClC,MAAM,OAAO,GAAG,IAAI,4BAA4B,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAE7E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,+BAA+B;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,4BAA4B,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAE7E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -30,6 +30,8 @@ export interface JooneConfig {
30
30
  compactModel?: string;
31
31
  /** Override model for sub-agents (default: auto-selected fast model from same provider). */
32
32
  subAgentModel?: string;
33
+ /** Execution mode: 'host' (local shell) or 'sandbox' (secure cloud). */
34
+ executionMode?: "host" | "sandbox";
33
35
  }
34
36
  /**
35
37
  * Sensible defaults — Anthropic Claude as the default provider.
@@ -10,6 +10,7 @@ export const DEFAULT_CONFIG = {
10
10
  temperature: 0,
11
11
  streaming: true,
12
12
  permissionMode: "auto",
13
+ executionMode: "host",
13
14
  };
14
15
  /**
15
16
  * Maps provider names to their expected environment variable for the API key.
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAoClC;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,0BAA0B;IACjC,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,MAAM;CACvB,CAAC;AAEF;;GAEG;AACH,MAAM,iBAAiB,GAA2B;IAChD,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,gBAAgB;IACxB,MAAM,EAAE,gBAAgB;IACxB,OAAO,EAAE,iBAAiB;IAC1B,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,mBAAmB;IAC9B,QAAQ,EAAE,kBAAkB;IAC5B,yCAAyC;CAC1C,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,MAAmB,CAAC;IAExB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IACjC,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;YACvD,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,sCAAsC,UAAU,mBAAmB,CAAC,CAAC;YAClF,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;QACjC,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB,EAAE,MAAmB;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC5D,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK,EAAE,sCAAsC;KACpD,CAAC,CAAC;IAEH,oEAAoE;IACpE,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAsClC;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,0BAA0B;IACjC,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,IAAI;IACf,cAAc,EAAE,MAAM;IACtB,aAAa,EAAE,MAAM;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,iBAAiB,GAA2B;IAChD,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,gBAAgB;IACxB,MAAM,EAAE,gBAAgB;IACxB,OAAO,EAAE,iBAAiB;IAC1B,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,mBAAmB;IAC9B,QAAQ,EAAE,kBAAkB;IAC5B,yCAAyC;CAC1C,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,MAAmB,CAAC;IAExB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IACjC,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;YACvD,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,sCAAsC,UAAU,mBAAmB,CAAC,CAAC;YAClF,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;QACjC,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB,EAAE,MAAmB;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC5D,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK,EAAE,sCAAsC;KACpD,CAAC,CAAC;IAEH,oEAAoE;IACpE,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC"}
package/dist/cli/index.js CHANGED
@@ -6,23 +6,7 @@ import * as os from "node:os";
6
6
  import chalk from "chalk";
7
7
  import { intro, outro, select, text, password, confirm, spinner, isCancel, cancel, } from "@clack/prompts";
8
8
  import { loadConfig, saveConfig } from "./config.js";
9
- import { createModel } from "./modelFactory.js";
10
- import { installProvider, uninstallProvider } from "./providers.js";
11
- import { tryEnableLangSmithFromConfig } from "../tracing/langsmith.js";
12
- import { TraceAnalyzer } from "../tracing/analyzer.js";
13
- import { SessionTracer } from "../tracing/sessionTracer.js";
14
- import { SandboxManager } from "../sandbox/manager.js";
15
- import { MiddlewarePipeline } from "../middleware/pipeline.js";
16
- import { LoopDetectionMiddleware } from "../middleware/loopDetection.js";
17
- import { CommandSanitizerMiddleware } from "../middleware/commandSanitizer.js";
18
- import { ExecutionHarness } from "../core/agentLoop.js";
19
- import { SessionStore } from "../core/sessionStore.js";
20
- import { SessionResumer } from "../core/sessionResumer.js";
21
- import { PermissionMiddleware } from "../middleware/permission.js";
22
- import { AskUserQuestionTool } from "../tools/askUser.js";
23
- import { createDefaultAgentRegistry } from "../agents/builtinAgents.js";
24
- import { SubAgentManager } from "../core/subAgent.js";
25
- import { createSpawnAgentTools } from "../tools/spawnAgent.js";
9
+ import { StartupBenchmark } from "./startupBenchmark.js";
26
10
  const CONFIG_PATH = path.join(os.homedir(), ".joone", "config.json");
27
11
  const SUPPORTED_PROVIDERS = [
28
12
  { value: "anthropic", label: "Anthropic", hint: "Claude 4, 3.5 Sonnet, Opus, Haiku" },
@@ -82,6 +66,14 @@ const PROVIDER_MODELS = {
82
66
  ],
83
67
  };
84
68
  const program = new Command();
69
+ const startupBenchmark = new StartupBenchmark();
70
+ startupBenchmark.mark("cli:entry");
71
+ async function loadModelFactory() {
72
+ return import("./modelFactory.js");
73
+ }
74
+ async function loadProviderManager() {
75
+ return import("./providers.js");
76
+ }
85
77
  program
86
78
  .name("joone")
87
79
  .description("An autonomous coding agent powered by prompt caching and harness engineering")
@@ -218,6 +210,7 @@ async function runOnboarding() {
218
210
  langsmithKey = existingConfig.langsmithApiKey ?? "";
219
211
  const s = spinner();
220
212
  try {
213
+ const { installProvider } = await loadProviderManager();
221
214
  s.start(`Downloading and installing the ${provider} provider package...`);
222
215
  await installProvider(provider);
223
216
  s.stop(`Installed ${provider} provider package!`);
@@ -279,6 +272,7 @@ providerCmd
279
272
  const s = spinner();
280
273
  s.start(`Installing ${providerName}...`);
281
274
  try {
275
+ const { installProvider } = await loadProviderManager();
282
276
  await installProvider(providerName);
283
277
  s.stop(`Successfully installed ${providerName}`);
284
278
  }
@@ -295,6 +289,7 @@ providerCmd
295
289
  const s = spinner();
296
290
  s.start(`Uninstalling ${providerName}...`);
297
291
  try {
292
+ const { uninstallProvider } = await loadProviderManager();
298
293
  await uninstallProvider(providerName);
299
294
  s.stop(`Successfully uninstalled ${providerName}`);
300
295
  }
@@ -343,9 +338,11 @@ program
343
338
  .command("start", { isDefault: true })
344
339
  .description("Start a new Joone agent session")
345
340
  .option("--no-stream", "Disable streaming output")
341
+ .option("--benchmark-startup", "Print startup timing milestones and exit")
346
342
  .option("-r, --resume <sessionId>", "Resume a previous session by ID")
347
343
  .action(async (options) => {
348
344
  let config = loadConfig(CONFIG_PATH);
345
+ startupBenchmark.mark("cli:config-loaded");
349
346
  // Auto-trigger onboarding if no API key is configured
350
347
  if (!config.apiKey && config.provider !== "ollama") {
351
348
  console.log(chalk.yellow("\n ⚠ No configuration found.") +
@@ -355,6 +352,7 @@ program
355
352
  if (options.stream === false) {
356
353
  config.streaming = false;
357
354
  }
355
+ const { tryEnableLangSmithFromConfig } = await import("../tracing/langsmith.js");
358
356
  const tracingEnabled = tryEnableLangSmithFromConfig(config);
359
357
  console.log(chalk.cyan("\n ◆ joone") +
360
358
  chalk.dim(" v0.1.0\n") +
@@ -363,54 +361,18 @@ program
363
361
  chalk.dim(" ├ Stream: ") + chalk.white(config.streaming ? "on" : "off") + "\n" +
364
362
  chalk.dim(" └ Tracing: ") + (tracingEnabled ? chalk.green("LangSmith") : chalk.dim("local only")) + "\n");
365
363
  try {
366
- const model = await createModel(config);
367
- const pipeline = new MiddlewarePipeline();
368
- pipeline.use(new LoopDetectionMiddleware(3));
369
- pipeline.use(new CommandSanitizerMiddleware());
370
- pipeline.use(new PermissionMiddleware(config.permissionMode ?? "auto"));
371
- const tracer = new SessionTracer();
372
- const { bindSandbox } = await import("../tools/index.js");
373
- const s = spinner();
374
- s.start("Initializing Sandbox Environment...");
375
- const sandboxManager = new SandboxManager({
376
- template: config.sandboxTemplate,
377
- apiKey: config.e2bApiKey,
378
- openSandboxApiKey: config.openSandboxApiKey,
379
- openSandboxDomain: config.openSandboxDomain,
380
- });
381
- await sandboxManager.create();
382
- const { FileSync } = await import("../sandbox/sync.js");
383
- const fileSync = new FileSync(process.cwd());
384
- bindSandbox(sandboxManager, fileSync);
385
- // Sync user-level skills into the sandbox
386
- const { SkillLoader } = await import("../skills/loader.js");
387
- const skillLoader = new SkillLoader();
388
- const skillPaths = skillLoader.getDiscoveryPaths();
389
- await fileSync.syncSkillsToSandbox(sandboxManager, skillPaths);
390
- s.stop("Sandbox initialized");
391
- // For the CLI, we start by loading the CORE tools
392
- // Advanced tools (search, browser, etc.) will be dynamically loaded by the agent later
393
- // via the SearchToolsTool when the registry is fully integrated
394
- const { CORE_TOOLS } = await import("../tools/index.js");
395
- let tools = [...CORE_TOOLS, AskUserQuestionTool];
396
- // Initialize Sub-Agent Orchestration
397
- const agentRegistry = createDefaultAgentRegistry();
398
- const subAgentModelName = config.subAgentModel ?? config.model;
399
- // Use the config to determine sub-agent model, with FAST_MODEL_DEFAULTS fallback
400
- const { resolveFastModel } = await import("../core/compactor.js");
401
- const resolvedSubModel = resolveFastModel(config.provider, config.model, config.subAgentModel);
402
- const subAgentLlm = await createModel({ ...config, model: resolvedSubModel });
403
- const subAgentManager = new SubAgentManager(agentRegistry, tools, subAgentLlm);
404
- const spawnAgentTools = createSpawnAgentTools(subAgentManager, agentRegistry);
405
- tools = [...tools, ...spawnAgentTools];
406
364
  let initialState;
407
365
  let sessionId = undefined;
366
+ let cleanupRuntime = async () => { };
367
+ let pendingStartupBenchmarkReport;
408
368
  if (options.resume) {
409
369
  const s = spinner();
410
370
  s.start(`Loading session ${chalk.bold(options.resume)}...`);
371
+ const { SessionStore } = await import("../core/sessionStore.js");
411
372
  const store = new SessionStore();
412
373
  try {
413
374
  const payload = await store.loadSession(options.resume);
375
+ const { SessionResumer } = await import("../core/sessionResumer.js");
414
376
  const resumer = new SessionResumer(process.cwd());
415
377
  initialState = resumer.prepareForResume(payload);
416
378
  sessionId = options.resume;
@@ -425,22 +387,52 @@ program
425
387
  else {
426
388
  initialState = {
427
389
  globalSystemInstructions: `You are Joone, a highly capable autonomous coding agent.
428
- You run in a hybrid environment: you have read/write access to the host machine for code edits, but all code execution, testing, and dependency installation MUST happen in the isolated E2B sandbox for safety.
429
- Always use 'bash' to run terminal commands. Never read or write outside the current project directory unless explicitly requested.
390
+ You run in a hybrid environment based on user configuration. You execute commands using 'bash' and can safely evaluate tests and install dependencies.
391
+ Always use the tools provided to you. Never read or write outside the current project directory unless explicitly requested.
430
392
 
431
393
  IMPORTANT CAPABILITIES:
432
394
  - You have access to an 'ask_user_question' tool. Use it to ask the user for clarification, preferences, or approval before making significant changes.
433
395
  - Some tool calls may require user approval before execution, depending on the user's permission settings. If a tool call is denied, try an alternative approach or ask the user for guidance.
434
396
  - You have access to Skills — reusable instruction sets for specialized tasks. Use 'search_skills' to discover them and 'load_skill' to activate their instructions.`,
435
- projectMemory: "No project context loaded yet.",
397
+ projectMemory: `Initial working directory: ${process.cwd()}`,
436
398
  sessionContext: `Environment: ${process.platform}\nCWD: ${process.cwd()}`,
437
399
  conversationHistory: []
438
400
  };
439
401
  }
440
- const boundLlm = "bindTools" in model && typeof model.bindTools === "function"
441
- ? model.bindTools(tools)
442
- : model;
443
- const harness = new ExecutionHarness(boundLlm, tools, pipeline, tracer, config.provider, config.model, sessionId);
402
+ const createHarness = async () => {
403
+ startupBenchmark.mark("runtime:harness-init-start");
404
+ const { createModel } = await loadModelFactory();
405
+ const model = await createModel(config);
406
+ startupBenchmark.mark("runtime:model-ready");
407
+ const { SessionTracer } = await import("../tracing/sessionTracer.js");
408
+ const tracer = new SessionTracer();
409
+ let sandboxManager;
410
+ const { bindSandbox, CORE_TOOLS } = await import("../tools/index.js");
411
+ if (config.executionMode !== "host") {
412
+ const { SandboxManager } = await import("../sandbox/manager.js");
413
+ sandboxManager = new SandboxManager({
414
+ template: config.sandboxTemplate,
415
+ apiKey: config.e2bApiKey,
416
+ openSandboxApiKey: config.openSandboxApiKey,
417
+ openSandboxDomain: config.openSandboxDomain,
418
+ });
419
+ await sandboxManager.create();
420
+ const { FileSync } = await import("../sandbox/sync.js");
421
+ const fileSync = new FileSync(process.cwd());
422
+ bindSandbox(sandboxManager, fileSync);
423
+ }
424
+ const { askUserQuestionTool } = await import("../tools/askUser.js");
425
+ const tools = [...CORE_TOOLS, askUserQuestionTool];
426
+ const { ExecutionHarness } = await import("../core/agentLoop.js");
427
+ const harness = new ExecutionHarness(model, tools, tracer, config.provider, config.model, sessionId, config.maxTokens, config.permissionMode, config.executionMode);
428
+ cleanupRuntime = async () => {
429
+ tracer.save();
430
+ if (sandboxManager) {
431
+ await sandboxManager.destroy();
432
+ }
433
+ };
434
+ return harness;
435
+ };
444
436
  const { render } = await import("ink");
445
437
  const React = await import("react");
446
438
  const { App } = await import("../ui/App.js");
@@ -450,14 +442,21 @@ IMPORTANT CAPABILITIES:
450
442
  provider: config.provider,
451
443
  model: config.model,
452
444
  streaming: config.streaming,
453
- harness,
445
+ createHarness,
454
446
  initialState,
455
447
  maxTokens: config.maxTokens,
448
+ benchmarkStartup: Boolean(options.benchmarkStartup),
449
+ onStartupBenchmarkMark: (name) => startupBenchmark.mark(name),
450
+ onStartupBenchmarkComplete: () => {
451
+ pendingStartupBenchmarkReport = startupBenchmark.format("Joone Startup Benchmark");
452
+ },
456
453
  }));
454
+ startupBenchmark.mark("ui:render-mounted");
457
455
  await waitUntilExit();
458
- // Cleanup
459
- tracer.save();
460
- await sandboxManager.destroy();
456
+ if (pendingStartupBenchmarkReport) {
457
+ console.log(`\n${pendingStartupBenchmarkReport}\n`);
458
+ }
459
+ await cleanupRuntime();
461
460
  }
462
461
  catch (error) {
463
462
  if (error instanceof Error) {
@@ -474,6 +473,7 @@ program
474
473
  .command("sessions")
475
474
  .description("List all persistent sessions available for resumption")
476
475
  .action(async () => {
476
+ const { SessionStore } = await import("../core/sessionStore.js");
477
477
  const store = new SessionStore();
478
478
  const sessions = await store.listSessions();
479
479
  if (sessions.length === 0) {
@@ -494,7 +494,7 @@ program
494
494
  program
495
495
  .command("analyze [sessionId]")
496
496
  .description("Analyze a session trace for performance insights")
497
- .action((sessionId) => {
497
+ .action(async (sessionId) => {
498
498
  let tracePath;
499
499
  const tracesDir = path.join(os.homedir(), ".joone", "traces");
500
500
  if (sessionId) {
@@ -521,6 +521,8 @@ program
521
521
  process.exit(1);
522
522
  }
523
523
  try {
524
+ const { SessionTracer } = await import("../tracing/sessionTracer.js");
525
+ const { TraceAnalyzer } = await import("../tracing/analyzer.js");
524
526
  const trace = SessionTracer.load(tracePath);
525
527
  const analyzer = new TraceAnalyzer(trace);
526
528
  const report = analyzer.analyze();
@@ -541,6 +543,7 @@ program
541
543
  console.error(chalk.red("\n ✗ LangSmith API key is missing. Run `joone config` to set it.\n"));
542
544
  process.exit(1);
543
545
  }
546
+ const { tryEnableLangSmithFromConfig } = await import("../tracing/langsmith.js");
544
547
  tryEnableLangSmithFromConfig(config);
545
548
  console.log(chalk.cyan("\n ◆ joone evals") + chalk.dim(` (Model: ${config.model})\n`));
546
549
  try {
@@ -551,29 +554,33 @@ program
551
554
  s.start("Verifying baseline dataset...");
552
555
  const datasetName = await ensureBaselineDataset();
553
556
  s.stop(`Dataset verified: ${chalk.white(datasetName)}`);
557
+ const { createModel } = await loadModelFactory();
554
558
  const model = await createModel(config);
555
- const pipeline = new MiddlewarePipeline();
556
- pipeline.use(new LoopDetectionMiddleware(3));
557
- pipeline.use(new CommandSanitizerMiddleware());
559
+ const { SessionTracer } = await import("../tracing/sessionTracer.js");
558
560
  const tracer = new SessionTracer();
559
561
  const { bindSandbox, CORE_TOOLS } = await import("../tools/index.js");
560
562
  const tools = [...CORE_TOOLS];
561
563
  s.start("Running evaluations across dataset (this may take a few minutes)...");
562
564
  // We define a target function that the generic `evaluate` engine will call for each example
563
565
  const agentTargetFn = async (inputs) => {
566
+ const { ExecutionHarness } = await import("../core/agentLoop.js");
564
567
  const runTracer = new SessionTracer();
565
- const harness = new ExecutionHarness(model, tools, pipeline, runTracer);
566
- // Initialize an empty sandbox just for this run
567
- const sandboxManager = new SandboxManager({
568
- template: config.sandboxTemplate,
569
- apiKey: config.e2bApiKey,
570
- openSandboxApiKey: config.openSandboxApiKey,
571
- openSandboxDomain: config.openSandboxDomain,
572
- });
573
- await sandboxManager.create();
574
- const { FileSync } = await import("../sandbox/sync.js");
575
- const fileSync = new FileSync(process.cwd());
576
- bindSandbox(sandboxManager, fileSync);
568
+ const harness = new ExecutionHarness(model, tools, runTracer, config.provider, config.model, undefined, config.maxTokens, config.permissionMode, config.executionMode);
569
+ let sandboxManager;
570
+ if (config.executionMode !== "host") {
571
+ const { SandboxManager } = await import("../sandbox/manager.js");
572
+ // Initialize an empty sandbox just for this run
573
+ sandboxManager = new SandboxManager({
574
+ template: config.sandboxTemplate,
575
+ apiKey: config.e2bApiKey,
576
+ openSandboxApiKey: config.openSandboxApiKey,
577
+ openSandboxDomain: config.openSandboxDomain,
578
+ });
579
+ await sandboxManager.create();
580
+ const { FileSync } = await import("../sandbox/sync.js");
581
+ const fileSync = new FileSync(process.cwd());
582
+ bindSandbox(sandboxManager, fileSync);
583
+ }
577
584
  const { HumanMessage, AIMessage, ToolMessage } = await import("@langchain/core/messages");
578
585
  let conversationHistory = [
579
586
  new HumanMessage(inputs.instruction)
@@ -582,30 +589,32 @@ program
582
589
  let turnCount = 0;
583
590
  const MAX_TURNS = 15; // Anti-doom-loop for evals
584
591
  try {
585
- while (turnCount < MAX_TURNS) {
592
+ const state = {
593
+ globalSystemInstructions: `You are Joone, a highly capable autonomous coding agent.
594
+ You run in a hybrid environment based on user configuration. You execute commands using 'bash' and can safely evaluate tests and install dependencies.
595
+ Always use the tools provided to you. Never read or write outside the current project directory unless explicitly requested.`,
596
+ projectMemory: `Evaluation run.\nInitial working directory: ${process.cwd()}`,
597
+ sessionContext: `Environment: ${process.platform}\nCWD: ${process.cwd()}`,
598
+ conversationHistory
599
+ };
600
+ const stream = harness.run(state);
601
+ for await (const event of stream) {
586
602
  turnCount++;
587
- const state = {
588
- globalSystemInstructions: `You are Joone, a highly capable autonomous coding agent.
589
- You run in a hybrid environment: you have read/write access to the host machine for code edits, but all code execution, testing, and dependency installation MUST happen in the isolated E2B sandbox for safety.
590
- Always use 'bash' to run terminal commands. Never read or write outside the current project directory unless explicitly requested.`,
591
- projectMemory: "Evaluation run.",
592
- sessionContext: `Environment: ${process.platform}\nCWD: ${process.cwd()}`,
593
- conversationHistory
594
- };
595
- const response = await harness.step(state);
596
- conversationHistory.push(response);
597
- if (response.content && typeof response.content === "string") {
598
- finalOutput += response.content + "\n";
599
- }
600
- if (!response.tool_calls || response.tool_calls.length === 0) {
601
- break; // Task complete
603
+ if (event.event === "on_chat_model_end" && event.data?.output) {
604
+ const msg = event.data.output;
605
+ if (msg.content && typeof msg.content === "string") {
606
+ finalOutput += msg.content + "\n";
607
+ }
608
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
609
+ // LangGraph will automatically execute the tools under the hood
610
+ // We don't need to manually executeToolCalls here!
611
+ }
602
612
  }
603
- const toolResults = await harness.executeToolCalls(response, state);
604
- conversationHistory.push(...toolResults);
605
613
  }
606
614
  }
607
615
  catch (e) {
608
- await sandboxManager.destroy();
616
+ if (sandboxManager)
617
+ await sandboxManager.destroy();
609
618
  throw e; // LangSmith catches this for the error evaluation
610
619
  }
611
620
  // Gather metrics
@@ -620,15 +629,19 @@ Always use 'bash' to run terminal commands. Never read or write outside the curr
620
629
  // Check sandbox for uploaded/created files before ripping it down
621
630
  let fileManifest = [];
622
631
  try {
623
- const result = await sandboxManager.exec(`find /workspace -type f`);
624
- if (result.stdout) {
625
- fileManifest = result.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
632
+ if (sandboxManager) {
633
+ const result = await sandboxManager.exec(`find /workspace -type f`);
634
+ if (result.stdout) {
635
+ fileManifest = result.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
636
+ }
626
637
  }
627
638
  }
628
639
  catch {
629
640
  // Ignore, fallback to empty array
630
641
  }
631
- await sandboxManager.destroy();
642
+ if (sandboxManager) {
643
+ await sandboxManager.destroy();
644
+ }
632
645
  return {
633
646
  finalOutput,
634
647
  metrics,