@agentmeshhq/agent 0.2.0 → 0.3.0

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 (147) hide show
  1. package/README.md +39 -0
  2. package/dist/__tests__/context-template.test.d.ts +4 -0
  3. package/dist/__tests__/context-template.test.js +233 -0
  4. package/dist/__tests__/context-template.test.js.map +1 -0
  5. package/dist/__tests__/loader.test.js +140 -28
  6. package/dist/__tests__/loader.test.js.map +1 -1
  7. package/dist/__tests__/no-respawn.test.d.ts +1 -0
  8. package/dist/__tests__/no-respawn.test.js +254 -0
  9. package/dist/__tests__/no-respawn.test.js.map +1 -0
  10. package/dist/__tests__/onboard.test.d.ts +5 -0
  11. package/dist/__tests__/onboard.test.js +341 -0
  12. package/dist/__tests__/onboard.test.js.map +1 -0
  13. package/dist/__tests__/orphan-process.test.d.ts +11 -0
  14. package/dist/__tests__/orphan-process.test.js +286 -0
  15. package/dist/__tests__/orphan-process.test.js.map +1 -0
  16. package/dist/__tests__/runner.test.js +16 -0
  17. package/dist/__tests__/runner.test.js.map +1 -1
  18. package/dist/__tests__/shared-resource-guards.test.d.ts +7 -0
  19. package/dist/__tests__/shared-resource-guards.test.js +260 -0
  20. package/dist/__tests__/shared-resource-guards.test.js.map +1 -0
  21. package/dist/__tests__/watchdog.test.js +138 -12
  22. package/dist/__tests__/watchdog.test.js.map +1 -1
  23. package/dist/cli/status.js +11 -0
  24. package/dist/cli/status.js.map +1 -1
  25. package/dist/cli/stop.js +7 -2
  26. package/dist/cli/stop.js.map +1 -1
  27. package/dist/config/loader.d.ts +0 -4
  28. package/dist/config/loader.js +102 -42
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/config/schema.d.ts +6 -4
  31. package/dist/core/daemon/assignment-message.d.ts +12 -0
  32. package/dist/core/daemon/assignment-message.js +36 -0
  33. package/dist/core/daemon/assignment-message.js.map +1 -0
  34. package/dist/core/daemon/bootstrap.d.ts +35 -0
  35. package/dist/core/daemon/bootstrap.js +52 -0
  36. package/dist/core/daemon/bootstrap.js.map +1 -0
  37. package/dist/core/daemon/context-template.d.ts +11 -0
  38. package/dist/core/daemon/context-template.js +144 -0
  39. package/dist/core/daemon/context-template.js.map +1 -0
  40. package/dist/core/daemon/crash-log.d.ts +14 -0
  41. package/dist/core/daemon/crash-log.js +23 -0
  42. package/dist/core/daemon/crash-log.js.map +1 -0
  43. package/dist/core/daemon/git-auth.d.ts +18 -0
  44. package/dist/core/daemon/git-auth.js +88 -0
  45. package/dist/core/daemon/git-auth.js.map +1 -0
  46. package/dist/core/daemon/health-policy.d.ts +17 -0
  47. package/dist/core/daemon/health-policy.js +24 -0
  48. package/dist/core/daemon/health-policy.js.map +1 -0
  49. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  50. package/dist/core/daemon/sandbox-config.js +17 -0
  51. package/dist/core/daemon/sandbox-config.js.map +1 -0
  52. package/dist/core/daemon/state.d.ts +33 -0
  53. package/dist/core/daemon/state.js +78 -0
  54. package/dist/core/daemon/state.js.map +1 -0
  55. package/dist/core/daemon/tmux-session.d.ts +17 -0
  56. package/dist/core/daemon/tmux-session.js +34 -0
  57. package/dist/core/daemon/tmux-session.js.map +1 -0
  58. package/dist/core/daemon/workspace.d.ts +23 -0
  59. package/dist/core/daemon/workspace.js +90 -0
  60. package/dist/core/daemon/workspace.js.map +1 -0
  61. package/dist/core/daemon.d.ts +9 -12
  62. package/dist/core/daemon.js +293 -393
  63. package/dist/core/daemon.js.map +1 -1
  64. package/dist/core/injector.d.ts +5 -1
  65. package/dist/core/injector.js +83 -0
  66. package/dist/core/injector.js.map +1 -1
  67. package/dist/core/registry.d.ts +62 -0
  68. package/dist/core/registry.js +18 -0
  69. package/dist/core/registry.js.map +1 -1
  70. package/dist/core/runner/build.d.ts +9 -0
  71. package/dist/core/runner/build.js +53 -0
  72. package/dist/core/runner/build.js.map +1 -0
  73. package/dist/core/runner/detect.d.ts +5 -0
  74. package/dist/core/runner/detect.js +14 -0
  75. package/dist/core/runner/detect.js.map +1 -0
  76. package/dist/core/runner/index.d.ts +5 -0
  77. package/dist/core/runner/index.js +5 -0
  78. package/dist/core/runner/index.js.map +1 -0
  79. package/dist/core/runner/model.d.ts +5 -0
  80. package/dist/core/runner/model.js +7 -0
  81. package/dist/core/runner/model.js.map +1 -0
  82. package/dist/core/runner/opencode-models.d.ts +15 -0
  83. package/dist/core/runner/opencode-models.js +70 -0
  84. package/dist/core/runner/opencode-models.js.map +1 -0
  85. package/dist/core/runner/types.d.ts +19 -0
  86. package/dist/core/runner/types.js +8 -0
  87. package/dist/core/runner/types.js.map +1 -0
  88. package/dist/core/runner.d.ts +5 -47
  89. package/dist/core/runner.js +5 -167
  90. package/dist/core/runner.js.map +1 -1
  91. package/dist/core/tmux-runtime.d.ts +13 -0
  92. package/dist/core/tmux-runtime.js +72 -0
  93. package/dist/core/tmux-runtime.js.map +1 -0
  94. package/dist/core/tmux.d.ts +7 -1
  95. package/dist/core/tmux.js +75 -45
  96. package/dist/core/tmux.js.map +1 -1
  97. package/dist/core/watchdog.d.ts +18 -1
  98. package/dist/core/watchdog.js +78 -29
  99. package/dist/core/watchdog.js.map +1 -1
  100. package/package.json +24 -4
  101. package/src/__tests__/context.test.ts +0 -464
  102. package/src/__tests__/injector.test.ts +0 -29
  103. package/src/__tests__/jwt.test.ts +0 -112
  104. package/src/__tests__/loader.test.ts +0 -239
  105. package/src/__tests__/runner.test.ts +0 -104
  106. package/src/__tests__/sandbox.test.ts +0 -435
  107. package/src/__tests__/watchdog.test.ts +0 -368
  108. package/src/cli/attach.ts +0 -22
  109. package/src/cli/build.ts +0 -145
  110. package/src/cli/config.ts +0 -148
  111. package/src/cli/context.ts +0 -231
  112. package/src/cli/deploy.ts +0 -155
  113. package/src/cli/index.ts +0 -376
  114. package/src/cli/init.ts +0 -75
  115. package/src/cli/list.ts +0 -70
  116. package/src/cli/local.ts +0 -183
  117. package/src/cli/logs.ts +0 -64
  118. package/src/cli/migrate.ts +0 -212
  119. package/src/cli/nudge.ts +0 -81
  120. package/src/cli/restart.ts +0 -59
  121. package/src/cli/slack.ts +0 -70
  122. package/src/cli/start.ts +0 -118
  123. package/src/cli/status.ts +0 -91
  124. package/src/cli/stop.ts +0 -48
  125. package/src/cli/test.ts +0 -143
  126. package/src/cli/token.ts +0 -188
  127. package/src/cli/whoami.ts +0 -142
  128. package/src/config/loader.ts +0 -121
  129. package/src/config/schema.ts +0 -68
  130. package/src/context/handoff.ts +0 -122
  131. package/src/context/index.ts +0 -8
  132. package/src/context/schema.ts +0 -111
  133. package/src/context/storage.ts +0 -197
  134. package/src/core/daemon.ts +0 -1317
  135. package/src/core/heartbeat.ts +0 -129
  136. package/src/core/injector.ts +0 -292
  137. package/src/core/registry.ts +0 -159
  138. package/src/core/runner.ts +0 -225
  139. package/src/core/sandbox.ts +0 -547
  140. package/src/core/session-id.ts +0 -111
  141. package/src/core/tmux.ts +0 -405
  142. package/src/core/watchdog.ts +0 -238
  143. package/src/core/websocket.ts +0 -94
  144. package/src/index.ts +0 -10
  145. package/src/utils/jwt.ts +0 -87
  146. package/tsconfig.json +0 -8
  147. package/vitest.config.ts +0 -12
@@ -0,0 +1,70 @@
1
+ import { execSync } from "node:child_process";
2
+ let cachedOpenCodeModels = null;
3
+ /**
4
+ * Returns available OpenCode models, with lightweight in-process caching.
5
+ */
6
+ export function getOpenCodeModels() {
7
+ if (cachedOpenCodeModels)
8
+ return cachedOpenCodeModels;
9
+ try {
10
+ const output = execSync("opencode models 2>/dev/null", {
11
+ encoding: "utf-8",
12
+ timeout: 10000,
13
+ });
14
+ cachedOpenCodeModels = output
15
+ .split("\n")
16
+ .map((line) => line.trim())
17
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
18
+ return cachedOpenCodeModels;
19
+ }
20
+ catch {
21
+ // Degrade gracefully when opencode is unavailable in current environment.
22
+ return [];
23
+ }
24
+ }
25
+ /**
26
+ * Validates whether the requested model can be resolved by OpenCode.
27
+ */
28
+ export function validateOpenCodeModel(model) {
29
+ const models = getOpenCodeModels();
30
+ // If model listing is unavailable, do not block startup.
31
+ if (models.length === 0)
32
+ return { valid: true };
33
+ if (models.includes(model))
34
+ return { valid: true };
35
+ const partialMatch = models.find((m) => m.endsWith(`/${model}`) || m === model || m.split("/").pop() === model);
36
+ if (partialMatch)
37
+ return { valid: true };
38
+ return {
39
+ valid: false,
40
+ error: `Model "${model}" not found in OpenCode. Run 'opencode models' to see available models.`,
41
+ };
42
+ }
43
+ const MODEL_ALIASES = {
44
+ "claude-sonnet-4": "anthropic/claude-sonnet-4-5",
45
+ "claude-sonnet-4-5": "anthropic/claude-sonnet-4-5",
46
+ "claude-opus-4": "anthropic/claude-opus-4-5",
47
+ "claude-opus-4-5": "anthropic/claude-opus-4-5",
48
+ "claude-haiku-4": "anthropic/claude-haiku-4-5",
49
+ "claude-haiku-4-5": "anthropic/claude-haiku-4-5",
50
+ "gpt-4o": "openai/gpt-4o",
51
+ "gpt-4": "openai/gpt-4",
52
+ o3: "openai/o3",
53
+ "o3-mini": "openai/o3-mini",
54
+ codex: "openai/codex",
55
+ };
56
+ /**
57
+ * Normalizes model aliases into a provider-qualified OpenCode model where possible.
58
+ */
59
+ export function normalizeOpenCodeModel(model) {
60
+ if (MODEL_ALIASES[model])
61
+ return MODEL_ALIASES[model];
62
+ if (model.includes("/"))
63
+ return model;
64
+ const models = getOpenCodeModels();
65
+ if (models.includes(model))
66
+ return model;
67
+ const fullPath = models.find((m) => m.endsWith(`/${model}`) || m.split("/").pop() === model);
68
+ return fullPath || model;
69
+ }
70
+ //# sourceMappingURL=opencode-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-models.js","sourceRoot":"","sources":["../../../src/core/runner/opencode-models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,IAAI,oBAAoB,GAAoB,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,oBAAoB;QAAE,OAAO,oBAAoB,CAAC;IAEtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,6BAA6B,EAAE;YACrD,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QAEH,oBAAoB,GAAG,MAAM;aAC1B,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAE9D,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IAEnC,yDAAyD;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAEnD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,CAC9E,CAAC;IAEF,IAAI,YAAY;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAEzC,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,UAAU,KAAK,yEAAyE;KAChG,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAA2B;IAC5C,iBAAiB,EAAE,6BAA6B;IAChD,mBAAmB,EAAE,6BAA6B;IAClD,eAAe,EAAE,2BAA2B;IAC5C,iBAAiB,EAAE,2BAA2B;IAC9C,gBAAgB,EAAE,4BAA4B;IAC9C,kBAAkB,EAAE,4BAA4B;IAChD,QAAQ,EAAE,eAAe;IACzB,OAAO,EAAE,cAAc;IACvB,EAAE,EAAE,WAAW;IACf,SAAS,EAAE,gBAAgB;IAC3B,KAAK,EAAE,cAAc;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,IAAI,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAEtC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,CAAC,CAAC;IAC7F,OAAO,QAAQ,IAAI,KAAK,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Runner type and config contracts.
3
+ *
4
+ * This file intentionally contains only shared types so runtime modules can stay
5
+ * loosely coupled and easy to test independently.
6
+ */
7
+ export type RunnerType = "opencode" | "claude" | "codex" | "custom";
8
+ export interface RunnerConfig {
9
+ type: RunnerType;
10
+ command: string;
11
+ model: string;
12
+ env: Record<string, string>;
13
+ }
14
+ export interface ModelResolutionInput {
15
+ cliModel?: string;
16
+ agentModel?: string;
17
+ defaultModel: string;
18
+ command: string;
19
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Runner type and config contracts.
3
+ *
4
+ * This file intentionally contains only shared types so runtime modules can stay
5
+ * loosely coupled and easy to test independently.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/runner/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -1,49 +1,7 @@
1
1
  /**
2
- * Runner Module
3
- * Handles model resolution, validation, and runner-specific configuration
2
+ * Backward-compatible runner facade.
3
+ *
4
+ * The runner implementation is split into focused modules under ./runner/ to
5
+ * keep detection, model resolution, and provider-specific behavior isolated.
4
6
  */
5
- export type RunnerType = "opencode" | "claude" | "custom";
6
- export interface RunnerConfig {
7
- type: RunnerType;
8
- command: string;
9
- model: string;
10
- env: Record<string, string>;
11
- }
12
- export interface ModelResolutionInput {
13
- cliModel?: string;
14
- agentModel?: string;
15
- defaultModel: string;
16
- command: string;
17
- }
18
- /**
19
- * Detects the runner type from the command
20
- */
21
- export declare function detectRunner(command: string): RunnerType;
22
- /**
23
- * Resolves the effective model from CLI > agent config > defaults
24
- */
25
- export declare function resolveModel(input: ModelResolutionInput): string;
26
- /**
27
- * Gets available OpenCode models (cached)
28
- */
29
- export declare function getOpenCodeModels(): string[];
30
- /**
31
- * Validates that a model is available in OpenCode
32
- */
33
- export declare function validateOpenCodeModel(model: string): {
34
- valid: boolean;
35
- error?: string;
36
- };
37
- /**
38
- * Normalizes a model name for OpenCode
39
- * e.g., "claude-sonnet-4" -> "anthropic/claude-sonnet-4-5" if that's what OpenCode expects
40
- */
41
- export declare function normalizeOpenCodeModel(model: string): string;
42
- /**
43
- * Builds the complete runner configuration including environment variables
44
- */
45
- export declare function buildRunnerConfig(input: ModelResolutionInput): RunnerConfig;
46
- /**
47
- * Gets a human-readable runner name
48
- */
49
- export declare function getRunnerDisplayName(runnerType: RunnerType): string;
7
+ export * from "./runner/index.js";
@@ -1,170 +1,8 @@
1
1
  /**
2
- * Runner Module
3
- * Handles model resolution, validation, and runner-specific configuration
2
+ * Backward-compatible runner facade.
3
+ *
4
+ * The runner implementation is split into focused modules under ./runner/ to
5
+ * keep detection, model resolution, and provider-specific behavior isolated.
4
6
  */
5
- import { execSync } from "node:child_process";
6
- // ============================================================================
7
- // Runner Detection
8
- // ============================================================================
9
- /**
10
- * Detects the runner type from the command
11
- */
12
- export function detectRunner(command) {
13
- const cmd = command.toLowerCase().trim();
14
- if (cmd === "opencode" || cmd.startsWith("opencode ")) {
15
- return "opencode";
16
- }
17
- if (cmd === "claude" || cmd.startsWith("claude ")) {
18
- return "claude";
19
- }
20
- return "custom";
21
- }
22
- // ============================================================================
23
- // Model Resolution
24
- // ============================================================================
25
- /**
26
- * Resolves the effective model from CLI > agent config > defaults
27
- */
28
- export function resolveModel(input) {
29
- // Priority: CLI flag > agent config > default
30
- return input.cliModel || input.agentModel || input.defaultModel;
31
- }
32
- // ============================================================================
33
- // OpenCode Integration
34
- // ============================================================================
35
- let cachedOpenCodeModels = null;
36
- /**
37
- * Gets available OpenCode models (cached)
38
- */
39
- export function getOpenCodeModels() {
40
- if (cachedOpenCodeModels) {
41
- return cachedOpenCodeModels;
42
- }
43
- try {
44
- const output = execSync("opencode models 2>/dev/null", {
45
- encoding: "utf-8",
46
- timeout: 10000,
47
- });
48
- cachedOpenCodeModels = output
49
- .split("\n")
50
- .map((line) => line.trim())
51
- .filter((line) => line.length > 0 && !line.startsWith("#"));
52
- return cachedOpenCodeModels;
53
- }
54
- catch {
55
- // OpenCode not available or failed
56
- return [];
57
- }
58
- }
59
- /**
60
- * Validates that a model is available in OpenCode
61
- */
62
- export function validateOpenCodeModel(model) {
63
- const models = getOpenCodeModels();
64
- // If we couldn't get models list, allow any (graceful degradation)
65
- if (models.length === 0) {
66
- return { valid: true };
67
- }
68
- if (models.includes(model)) {
69
- return { valid: true };
70
- }
71
- // Check for partial match (e.g., "claude-sonnet-4" matches "anthropic/claude-sonnet-4")
72
- const partialMatch = models.find((m) => m.endsWith(`/${model}`) || m === model || m.split("/").pop() === model);
73
- if (partialMatch) {
74
- return { valid: true };
75
- }
76
- return {
77
- valid: false,
78
- error: `Model "${model}" not found in OpenCode. Run 'opencode models' to see available models.`,
79
- };
80
- }
81
- // Common model aliases to their full OpenCode names
82
- const MODEL_ALIASES = {
83
- "claude-sonnet-4": "anthropic/claude-sonnet-4-5",
84
- "claude-sonnet-4-5": "anthropic/claude-sonnet-4-5",
85
- "claude-opus-4": "anthropic/claude-opus-4-5",
86
- "claude-opus-4-5": "anthropic/claude-opus-4-5",
87
- "claude-haiku-4": "anthropic/claude-haiku-4-5",
88
- "claude-haiku-4-5": "anthropic/claude-haiku-4-5",
89
- "gpt-4o": "openai/gpt-4o",
90
- "gpt-4": "openai/gpt-4",
91
- o3: "openai/o3",
92
- "o3-mini": "openai/o3-mini",
93
- codex: "openai/codex",
94
- };
95
- /**
96
- * Normalizes a model name for OpenCode
97
- * e.g., "claude-sonnet-4" -> "anthropic/claude-sonnet-4-5" if that's what OpenCode expects
98
- */
99
- export function normalizeOpenCodeModel(model) {
100
- // Check hardcoded aliases first (works even if opencode not installed)
101
- if (MODEL_ALIASES[model]) {
102
- return MODEL_ALIASES[model];
103
- }
104
- // Already has provider prefix
105
- if (model.includes("/")) {
106
- return model;
107
- }
108
- const models = getOpenCodeModels();
109
- // Direct match
110
- if (models.includes(model)) {
111
- return model;
112
- }
113
- // Try to find full path version
114
- const fullPath = models.find((m) => m.endsWith(`/${model}`) || m.split("/").pop() === model);
115
- return fullPath || model;
116
- }
117
- // ============================================================================
118
- // Runner Configuration
119
- // ============================================================================
120
- /**
121
- * Builds the complete runner configuration including environment variables
122
- */
123
- export function buildRunnerConfig(input) {
124
- const runnerType = detectRunner(input.command);
125
- const model = resolveModel(input);
126
- const env = {};
127
- switch (runnerType) {
128
- case "opencode": {
129
- // Validate model for OpenCode
130
- const validation = validateOpenCodeModel(model);
131
- if (!validation.valid) {
132
- console.warn(`Warning: ${validation.error}`);
133
- }
134
- // Normalize and set OPENCODE_MODEL
135
- const normalizedModel = normalizeOpenCodeModel(model);
136
- env.OPENCODE_MODEL = normalizedModel;
137
- break;
138
- }
139
- case "claude": {
140
- // Claude CLI uses ANTHROPIC_MODEL or similar
141
- // For now, just pass the model - Claude CLI will handle it
142
- env.CLAUDE_MODEL = model;
143
- break;
144
- }
145
- case "custom":
146
- // Custom runners don't get automatic model env
147
- // User is responsible for configuring their tool
148
- break;
149
- }
150
- return {
151
- type: runnerType,
152
- command: input.command,
153
- model,
154
- env,
155
- };
156
- }
157
- /**
158
- * Gets a human-readable runner name
159
- */
160
- export function getRunnerDisplayName(runnerType) {
161
- switch (runnerType) {
162
- case "opencode":
163
- return "OpenCode";
164
- case "claude":
165
- return "Claude CLI";
166
- case "custom":
167
- return "Custom";
168
- }
169
- }
7
+ export * from "./runner/index.js";
170
8
  //# sourceMappingURL=runner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type RunnerCommandKind = "opencode" | "claude" | "codex" | "other";
2
+ /**
3
+ * Classifies the interactive command so tmux startup can apply runner-specific behavior.
4
+ */
5
+ export declare function detectRunnerCommandKind(command: string): RunnerCommandKind;
6
+ /**
7
+ * Prepares per-agent OpenCode storage and auth inheritance.
8
+ */
9
+ export declare function prepareOpenCodeRuntime(agentName: string): string;
10
+ /**
11
+ * Builds the final interactive command with runner-specific model/session flags.
12
+ */
13
+ export declare function buildInteractiveCommand(command: string, env?: Record<string, string | undefined>, opencodeSessionId?: string): string;
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ /**
5
+ * Classifies the interactive command so tmux startup can apply runner-specific behavior.
6
+ */
7
+ export function detectRunnerCommandKind(command) {
8
+ if (command === "opencode" || command.startsWith("opencode "))
9
+ return "opencode";
10
+ if (command === "claude" || command.startsWith("claude "))
11
+ return "claude";
12
+ if (command === "codex" || command.startsWith("codex "))
13
+ return "codex";
14
+ return "other";
15
+ }
16
+ /**
17
+ * Prepares per-agent OpenCode storage and auth inheritance.
18
+ */
19
+ export function prepareOpenCodeRuntime(agentName) {
20
+ const agentDataDir = path.join(os.homedir(), ".agentmesh", "opencode-data", agentName);
21
+ const agentOpencodeDir = path.join(agentDataDir, "opencode");
22
+ if (!fs.existsSync(agentOpencodeDir)) {
23
+ fs.mkdirSync(agentOpencodeDir, { recursive: true });
24
+ }
25
+ const agentAuthPath = path.join(agentOpencodeDir, "auth.json");
26
+ const sourceAuthPath = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
27
+ if (!fs.existsSync(agentAuthPath) && fs.existsSync(sourceAuthPath)) {
28
+ try {
29
+ const auth = JSON.parse(fs.readFileSync(sourceAuthPath, "utf-8"));
30
+ delete auth.xai;
31
+ fs.writeFileSync(agentAuthPath, JSON.stringify(auth, null, 2));
32
+ }
33
+ catch {
34
+ // Non-fatal — agent can authenticate manually later.
35
+ }
36
+ }
37
+ return agentDataDir;
38
+ }
39
+ /**
40
+ * Builds the final interactive command with runner-specific model/session flags.
41
+ */
42
+ export function buildInteractiveCommand(command, env, opencodeSessionId) {
43
+ const kind = detectRunnerCommandKind(command);
44
+ let finalCommand = command;
45
+ if (kind === "opencode" &&
46
+ env?.OPENCODE_MODEL &&
47
+ !command.includes("--model") &&
48
+ !command.includes("-m ")) {
49
+ finalCommand = `${command} --model ${env.OPENCODE_MODEL}`;
50
+ }
51
+ if (kind === "claude" &&
52
+ env?.CLAUDE_MODEL &&
53
+ !command.includes("--model") &&
54
+ !command.includes("-m ")) {
55
+ finalCommand = `${command} --model ${env.CLAUDE_MODEL}`;
56
+ }
57
+ if (kind === "codex" &&
58
+ env?.CODEX_MODEL &&
59
+ !command.includes("--model") &&
60
+ !command.includes("-m ")) {
61
+ finalCommand = `${command} --model ${env.CODEX_MODEL}`;
62
+ }
63
+ if (kind === "opencode" &&
64
+ opencodeSessionId &&
65
+ !finalCommand.includes("--session") &&
66
+ !finalCommand.includes("--continue")) {
67
+ finalCommand = `${finalCommand} --session ${opencodeSessionId} --continue`;
68
+ console.log(`[TMUX] Resuming OpenCode session: ${opencodeSessionId}`);
69
+ }
70
+ return finalCommand;
71
+ }
72
+ //# sourceMappingURL=tmux-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmux-runtime.js","sourceRoot":"","sources":["../../src/core/tmux-runtime.ts"],"names":[],"mappings":"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"}
@@ -5,12 +5,18 @@ export interface SessionEnv {
5
5
  AGENTMESH_AGENT_ID?: string;
6
6
  OPENCODE_MODEL?: string;
7
7
  CLAUDE_MODEL?: string;
8
+ CODEX_MODEL?: string;
8
9
  [key: string]: string | undefined;
9
10
  }
10
11
  export declare function createSession(agentName: string, command: string, workdir?: string, env?: SessionEnv, opencodeSessionId?: string): boolean;
11
12
  export declare function setSessionEnvironment(sessionName: string, env: SessionEnv): boolean;
12
13
  export declare function updateSessionEnvironment(agentName: string, env: SessionEnv): boolean;
13
- export declare function destroySession(agentName: string): boolean;
14
+ /**
15
+ * Kill a list of PIDs with SIGTERM, then escalate to SIGKILL after a timeout.
16
+ * Returns true if all processes were confirmed dead.
17
+ */
18
+ export declare function killProcessTree(pids: number[], timeoutMs?: number): boolean;
19
+ export declare function destroySession(agentName: string, childPids?: number[]): boolean;
14
20
  export declare function sendKeys(agentName: string, message: string): boolean;
15
21
  export declare function attachSession(agentName: string): void;
16
22
  export declare function listSessions(): string[];
package/dist/core/tmux.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { execFileSync, execSync, spawn } from "node:child_process";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
2
+ import { buildInteractiveCommand, detectRunnerCommandKind, prepareOpenCodeRuntime, } from "./tmux-runtime.js";
5
3
  const SESSION_PREFIX = "agentmesh-";
6
4
  export function getSessionName(agentName) {
7
5
  return `${SESSION_PREFIX}${agentName}`;
@@ -22,34 +20,20 @@ export function createSession(agentName, command, workdir, env, opencodeSessionI
22
20
  return false;
23
21
  }
24
22
  try {
25
- // Isolate OpenCode's SQLite database per agent to prevent WAL corruption
26
- // from multiple concurrent processes sharing one opencode.db file.
27
- // See docs/RCA-OPENCODE-SQLITE-CORRUPTION.md for details.
28
- const agentDataDir = path.join(os.homedir(), ".agentmesh", "opencode-data", agentName);
29
- const agentOpencodeDir = path.join(agentDataDir, "opencode");
30
- if (!fs.existsSync(agentOpencodeDir)) {
31
- fs.mkdirSync(agentOpencodeDir, { recursive: true });
32
- }
33
- // Copy auth.json from default OpenCode data dir so agents inherit API keys.
34
- // Strips xAI provider to prevent OpenCode from defaulting to non-Anthropic models.
35
- const agentAuthPath = path.join(agentOpencodeDir, "auth.json");
36
- const sourceAuthPath = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
37
- if (!fs.existsSync(agentAuthPath) && fs.existsSync(sourceAuthPath)) {
38
- try {
39
- const auth = JSON.parse(fs.readFileSync(sourceAuthPath, "utf-8"));
40
- delete auth.xai;
41
- fs.writeFileSync(agentAuthPath, JSON.stringify(auth, null, 2));
42
- }
43
- catch {
44
- // Non-fatal — agent will just need manual auth
45
- }
23
+ const commandKind = detectRunnerCommandKind(command);
24
+ const isOpenCodeCommand = commandKind === "opencode";
25
+ let agentDataDir = "";
26
+ if (isOpenCodeCommand) {
27
+ agentDataDir = prepareOpenCodeRuntime(agentName);
46
28
  }
47
29
  // Build environment prefix for the command
48
30
  // This ensures env vars are set BEFORE the process starts
49
- const mergedEnv = {
50
- XDG_DATA_HOME: agentDataDir,
51
- ...env,
52
- };
31
+ const mergedEnv = isOpenCodeCommand
32
+ ? {
33
+ XDG_DATA_HOME: agentDataDir,
34
+ ...env,
35
+ }
36
+ : { ...env };
53
37
  let envPrefix = "";
54
38
  const envParts = [];
55
39
  for (const [key, value] of Object.entries(mergedEnv)) {
@@ -62,22 +46,7 @@ export function createSession(agentName, command, workdir, env, opencodeSessionI
62
46
  if (envParts.length > 0) {
63
47
  envPrefix = `${envParts.join(" ")} `;
64
48
  }
65
- // Append --model flag for opencode if OPENCODE_MODEL is set
66
- let finalCommand = command;
67
- if (env?.OPENCODE_MODEL && (command === "opencode" || command.startsWith("opencode "))) {
68
- // Check if --model is already in the command
69
- if (!command.includes("--model") && !command.includes("-m ")) {
70
- finalCommand = `${command} --model ${env.OPENCODE_MODEL}`;
71
- }
72
- }
73
- // Append --session --continue flags for native session resume
74
- if (opencodeSessionId &&
75
- (finalCommand === "opencode" || finalCommand.startsWith("opencode ")) &&
76
- !finalCommand.includes("--session") &&
77
- !finalCommand.includes("--continue")) {
78
- finalCommand = `${finalCommand} --session ${opencodeSessionId} --continue`;
79
- console.log(`[TMUX] Resuming OpenCode session: ${opencodeSessionId}`);
80
- }
49
+ const finalCommand = buildInteractiveCommand(command, env, opencodeSessionId);
81
50
  const fullCommand = `${envPrefix}${finalCommand}`;
82
51
  // Set reasonable terminal size for TUI applications
83
52
  const args = ["new-session", "-d", "-s", sessionName, "-x", "200", "-y", "50"];
@@ -119,8 +88,69 @@ export function updateSessionEnvironment(agentName, env) {
119
88
  }
120
89
  return setSessionEnvironment(sessionName, env);
121
90
  }
122
- export function destroySession(agentName) {
91
+ /**
92
+ * Kill a list of PIDs with SIGTERM, then escalate to SIGKILL after a timeout.
93
+ * Returns true if all processes were confirmed dead.
94
+ */
95
+ export function killProcessTree(pids, timeoutMs = 5000) {
96
+ if (pids.length === 0) {
97
+ return true;
98
+ }
99
+ const alive = (pid) => {
100
+ try {
101
+ process.kill(pid, 0);
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ };
108
+ const livePids = pids.filter(alive);
109
+ if (livePids.length === 0) {
110
+ return true;
111
+ }
112
+ console.log(`[CLEANUP] Sending SIGTERM to ${livePids.length} processes: ${livePids.join(", ")}`);
113
+ // Step 1: SIGTERM
114
+ for (const pid of livePids) {
115
+ try {
116
+ process.kill(pid, "SIGTERM");
117
+ }
118
+ catch {
119
+ // Already gone
120
+ }
121
+ }
122
+ // Step 2: Poll for graceful exit
123
+ const deadline = Date.now() + timeoutMs;
124
+ while (Date.now() < deadline) {
125
+ const stillAlive = livePids.filter(alive);
126
+ if (stillAlive.length === 0) {
127
+ console.log("[CLEANUP] All processes terminated gracefully");
128
+ return true;
129
+ }
130
+ // Busy-wait in 100ms increments
131
+ execSync("sleep 0.1");
132
+ }
133
+ // Step 3: SIGKILL stragglers
134
+ const stragglers = livePids.filter(alive);
135
+ if (stragglers.length > 0) {
136
+ console.log(`[CLEANUP] Force killing ${stragglers.length} processes with SIGKILL: ${stragglers.join(", ")}`);
137
+ for (const pid of stragglers) {
138
+ try {
139
+ process.kill(pid, "SIGKILL");
140
+ }
141
+ catch {
142
+ // Ignore
143
+ }
144
+ }
145
+ }
146
+ return livePids.filter(alive).length === 0;
147
+ }
148
+ export function destroySession(agentName, childPids) {
123
149
  const sessionName = getSessionName(agentName);
150
+ // Kill child processes first (opencode + LSP servers) before killing the tmux session
151
+ if (childPids && childPids.length > 0) {
152
+ killProcessTree(childPids);
153
+ }
124
154
  if (!sessionExists(sessionName)) {
125
155
  return true; // Already gone
126
156
  }