crewly 1.11.5 → 1.12.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 (208) hide show
  1. package/config/skills/agent/onboarding/synthesize-hierarchy/SKILL.md +65 -0
  2. package/config/skills/agent/onboarding/synthesize-hierarchy/execute.sh +61 -0
  3. package/config/skills/agent/web-search/SKILL.md +70 -0
  4. package/config/skills/agent/web-search/execute.sh +170 -0
  5. package/config/skills/agent/web-search/skill.json +23 -0
  6. package/dist/backend/backend/src/constants.d.ts +34 -1
  7. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  8. package/dist/backend/backend/src/constants.js +34 -1
  9. package/dist/backend/backend/src/constants.js.map +1 -1
  10. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +22 -0
  11. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  12. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +58 -0
  13. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  14. package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
  15. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +3 -1
  16. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.d.ts +27 -0
  18. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.d.ts.map +1 -1
  19. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.js +108 -0
  20. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.js.map +1 -1
  21. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.d.ts +6 -2
  22. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.d.ts.map +1 -1
  23. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.js +9 -3
  24. package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.js.map +1 -1
  25. package/dist/backend/backend/src/index.d.ts.map +1 -1
  26. package/dist/backend/backend/src/index.js +36 -2
  27. package/dist/backend/backend/src/index.js.map +1 -1
  28. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +18 -0
  29. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +24 -2
  31. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  32. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  33. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  34. package/dist/backend/backend/src/services/backup/backup-archive.service.js +309 -0
  35. package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -0
  36. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  37. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  38. package/dist/backend/backend/src/services/backup/backup-cloud.client.js +134 -0
  39. package/dist/backend/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  40. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  41. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/backup/backup-restore.service.js +358 -0
  43. package/dist/backend/backend/src/services/backup/backup-restore.service.js.map +1 -0
  44. package/dist/backend/backend/src/services/backup/backup.types.d.ts +163 -0
  45. package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/backup/backup.types.js +13 -0
  47. package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -0
  48. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +29 -2
  49. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  50. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +97 -13
  51. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  52. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts +102 -0
  53. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +164 -0
  55. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -0
  56. package/dist/backend/backend/src/services/fission/fission-guard.service.d.ts +21 -0
  57. package/dist/backend/backend/src/services/fission/fission-guard.service.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/fission/fission-guard.service.js +30 -0
  59. package/dist/backend/backend/src/services/fission/fission-guard.service.js.map +1 -1
  60. package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.d.ts +4 -0
  61. package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.js +8 -0
  63. package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.js.map +1 -1
  64. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.d.ts +79 -58
  65. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js +140 -65
  67. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js.map +1 -1
  68. package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.d.ts +117 -0
  69. package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.d.ts.map +1 -0
  70. package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.js +189 -0
  71. package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.js.map +1 -0
  72. package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.d.ts.map +1 -1
  73. package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.js +1 -0
  74. package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.js.map +1 -1
  75. package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.js +2 -0
  77. package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.js.map +1 -1
  78. package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.js +17 -1
  80. package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.js.map +1 -1
  81. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +50 -0
  82. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +71 -0
  84. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  85. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +18 -0
  86. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +75 -1
  88. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +115 -0
  90. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +189 -3
  92. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  93. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
  94. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/session/pty/pty-session.js +61 -1
  96. package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
  97. package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
  98. package/dist/backend/backend/src/services/template/template.service.js +67 -2
  99. package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
  100. package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts +19 -1
  101. package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts.map +1 -1
  102. package/dist/backend/backend/src/services/v3/cascade-request-status.js +39 -2
  103. package/dist/backend/backend/src/services/v3/cascade-request-status.js.map +1 -1
  104. package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts +41 -0
  105. package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
  106. package/dist/backend/backend/src/services/v3/escalation-router.service.js +169 -0
  107. package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
  108. package/dist/backend/backend/src/services/v3/request-cascade.subscriber.d.ts +4 -1
  109. package/dist/backend/backend/src/services/v3/request-cascade.subscriber.d.ts.map +1 -1
  110. package/dist/backend/backend/src/services/v3/request-cascade.subscriber.js +21 -0
  111. package/dist/backend/backend/src/services/v3/request-cascade.subscriber.js.map +1 -1
  112. package/dist/backend/backend/src/types/intent-task.types.d.ts.map +1 -1
  113. package/dist/backend/backend/src/types/intent-task.types.js +8 -0
  114. package/dist/backend/backend/src/types/intent-task.types.js.map +1 -1
  115. package/dist/backend/backend/src/types/v2/request.types.d.ts +1 -1
  116. package/dist/backend/backend/src/types/v2/request.types.d.ts.map +1 -1
  117. package/dist/backend/backend/src/types/v2/request.types.js +1 -0
  118. package/dist/backend/backend/src/types/v2/request.types.js.map +1 -1
  119. package/dist/cli/backend/src/constants.d.ts +34 -1
  120. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  121. package/dist/cli/backend/src/constants.js +34 -1
  122. package/dist/cli/backend/src/constants.js.map +1 -1
  123. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts +70 -0
  124. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts.map +1 -0
  125. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js +427 -0
  126. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js.map +1 -0
  127. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  128. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  129. package/dist/cli/backend/src/services/backup/backup-archive.service.js +309 -0
  130. package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -0
  131. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  132. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  133. package/dist/cli/backend/src/services/backup/backup-cloud.client.js +134 -0
  134. package/dist/cli/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  135. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  136. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  137. package/dist/cli/backend/src/services/backup/backup-restore.service.js +358 -0
  138. package/dist/cli/backend/src/services/backup/backup-restore.service.js.map +1 -0
  139. package/dist/cli/backend/src/services/backup/backup.types.d.ts +163 -0
  140. package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -0
  141. package/dist/cli/backend/src/services/backup/backup.types.js +13 -0
  142. package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -0
  143. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +410 -0
  144. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -0
  145. package/dist/cli/backend/src/services/cloud/cloud-client.service.js +863 -0
  146. package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -0
  147. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +292 -0
  148. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -0
  149. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +1093 -0
  150. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -0
  151. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +328 -0
  152. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -0
  153. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +171 -0
  154. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -0
  155. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts +89 -0
  156. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts.map +1 -0
  157. package/dist/cli/backend/src/services/cloud/device-identity.service.js +148 -0
  158. package/dist/cli/backend/src/services/cloud/device-identity.service.js.map +1 -0
  159. package/dist/cli/backend/src/services/user/user-identity.service.d.ts +86 -0
  160. package/dist/cli/backend/src/services/user/user-identity.service.d.ts.map +1 -0
  161. package/dist/cli/backend/src/services/user/user-identity.service.js +190 -0
  162. package/dist/cli/backend/src/services/user/user-identity.service.js.map +1 -0
  163. package/dist/cli/cli/src/commands/backup.d.ts +31 -0
  164. package/dist/cli/cli/src/commands/backup.d.ts.map +1 -0
  165. package/dist/cli/cli/src/commands/backup.js +280 -0
  166. package/dist/cli/cli/src/commands/backup.js.map +1 -0
  167. package/dist/cli/cli/src/index.js +10 -0
  168. package/dist/cli/cli/src/index.js.map +1 -1
  169. package/package.json +9 -3
  170. package/packages/crewly-agent/README.md +27 -0
  171. package/packages/crewly-agent/bin/crewly-agent +33 -0
  172. package/packages/crewly-agent/package.json +39 -0
  173. package/packages/crewly-agent/src/cli.ts +168 -0
  174. package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +2355 -0
  175. package/packages/crewly-agent/src/runtime/agent-runner.service.ts +1827 -0
  176. package/packages/crewly-agent/src/runtime/agent-stream.service.test.ts +153 -0
  177. package/packages/crewly-agent/src/runtime/agent-stream.service.ts +225 -0
  178. package/packages/crewly-agent/src/runtime/agent-worker.test.ts +171 -0
  179. package/packages/crewly-agent/src/runtime/agent-worker.ts +193 -0
  180. package/packages/crewly-agent/src/runtime/api-client.ts +143 -0
  181. package/packages/crewly-agent/src/runtime/approval-queue.service.ts +307 -0
  182. package/packages/crewly-agent/src/runtime/audit-log.service.test.ts +208 -0
  183. package/packages/crewly-agent/src/runtime/audit-log.service.ts +332 -0
  184. package/packages/crewly-agent/src/runtime/audit-trail.service.test.ts +178 -0
  185. package/packages/crewly-agent/src/runtime/audit-trail.service.ts +151 -0
  186. package/packages/crewly-agent/src/runtime/auditor-tools.test.ts +274 -0
  187. package/packages/crewly-agent/src/runtime/auditor-tools.ts +311 -0
  188. package/packages/crewly-agent/src/runtime/cloud-config.ts +67 -0
  189. package/packages/crewly-agent/src/runtime/deepseek-sse-transform.test.ts +165 -0
  190. package/packages/crewly-agent/src/runtime/deepseek-sse-transform.ts +168 -0
  191. package/packages/crewly-agent/src/runtime/env-isolation.service.ts +246 -0
  192. package/packages/crewly-agent/src/runtime/in-process-log-buffer.test.ts +280 -0
  193. package/packages/crewly-agent/src/runtime/in-process-log-buffer.ts +317 -0
  194. package/packages/crewly-agent/src/runtime/index.ts +38 -0
  195. package/packages/crewly-agent/src/runtime/mcp-tool-bridge.test.ts +352 -0
  196. package/packages/crewly-agent/src/runtime/mcp-tool-bridge.ts +244 -0
  197. package/packages/crewly-agent/src/runtime/model-manager.test.ts +326 -0
  198. package/packages/crewly-agent/src/runtime/model-manager.ts +363 -0
  199. package/packages/crewly-agent/src/runtime/output-filter.service.ts +175 -0
  200. package/packages/crewly-agent/src/runtime/prompt-guard.service.ts +303 -0
  201. package/packages/crewly-agent/src/runtime/rate-limiter.test.ts +228 -0
  202. package/packages/crewly-agent/src/runtime/rate-limiter.ts +353 -0
  203. package/packages/crewly-agent/src/runtime/tool-registry.test.ts +2510 -0
  204. package/packages/crewly-agent/src/runtime/tool-registry.ts +2104 -0
  205. package/packages/crewly-agent/src/runtime/types.test.ts +519 -0
  206. package/packages/crewly-agent/src/runtime/types.ts +637 -0
  207. package/packages/crewly-agent/src/runtime/web-search.tool.test.ts +131 -0
  208. package/packages/crewly-agent/src/runtime/web-search.tool.ts +140 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Tests for AgentStreamService
3
+ *
4
+ * @module services/agent/crewly-agent/agent-stream.service.test
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, vi, type Mocked, type MockInstance } from 'vitest';
8
+ import {
9
+ AgentStreamService,
10
+ type AgentStreamEvent,
11
+ type TextChunkEvent,
12
+ type ToolCallStartEvent,
13
+ type ToolCallFinishEvent,
14
+ type StepFinishEvent,
15
+ } from './agent-stream.service.js';
16
+
17
+ describe('AgentStreamService', () => {
18
+ beforeEach(() => {
19
+ AgentStreamService.resetInstance();
20
+ });
21
+
22
+ it('should return the same singleton instance', () => {
23
+ const a = AgentStreamService.getInstance();
24
+ const b = AgentStreamService.getInstance();
25
+ expect(a).toBe(b);
26
+ });
27
+
28
+ it('should return a new instance after reset', () => {
29
+ const a = AgentStreamService.getInstance();
30
+ AgentStreamService.resetInstance();
31
+ const b = AgentStreamService.getInstance();
32
+ expect(a).not.toBe(b);
33
+ });
34
+
35
+ describe('emitTextChunk', () => {
36
+ it('should emit a text_chunk event with correct shape', () => {
37
+ const service = AgentStreamService.getInstance();
38
+ const handler = vi.fn();
39
+ service.on('stream', handler);
40
+
41
+ service.emitTextChunk('test-agent', 'Hello ');
42
+
43
+ expect(handler).toHaveBeenCalledTimes(1);
44
+ const event = handler.mock.calls[0][0] as TextChunkEvent;
45
+ expect(event.type).toBe('text_chunk');
46
+ expect(event.sessionName).toBe('test-agent');
47
+ expect(event.text).toBe('Hello ');
48
+ expect(event.timestamp).toBeDefined();
49
+ });
50
+ });
51
+
52
+ describe('emitToolCallStart', () => {
53
+ it('should emit a tool_call_start event with args preview', () => {
54
+ const service = AgentStreamService.getInstance();
55
+ const handler = vi.fn();
56
+ service.on('stream', handler);
57
+
58
+ service.emitToolCallStart('test-agent', 'bash_exec', { command: 'ls -la' });
59
+
60
+ const event = handler.mock.calls[0][0] as ToolCallStartEvent;
61
+ expect(event.type).toBe('tool_call_start');
62
+ expect(event.toolName).toBe('bash_exec');
63
+ expect(event.argsPreview).toContain('ls -la');
64
+ });
65
+
66
+ it('should truncate long args to 200 chars', () => {
67
+ const service = AgentStreamService.getInstance();
68
+ const handler = vi.fn();
69
+ service.on('stream', handler);
70
+
71
+ const longArg = 'x'.repeat(300);
72
+ service.emitToolCallStart('test-agent', 'write_file', { content: longArg });
73
+
74
+ const event = handler.mock.calls[0][0] as ToolCallStartEvent;
75
+ expect(event.argsPreview.length).toBeLessThanOrEqual(200);
76
+ });
77
+ });
78
+
79
+ describe('emitToolCallFinish', () => {
80
+ it('should emit a tool_call_finish event with duration', () => {
81
+ const service = AgentStreamService.getInstance();
82
+ const handler = vi.fn();
83
+ service.on('stream', handler);
84
+
85
+ service.emitToolCallFinish('test-agent', 'bash_exec', { command: 'pwd' }, '/home/user', 42);
86
+
87
+ const event = handler.mock.calls[0][0] as ToolCallFinishEvent;
88
+ expect(event.type).toBe('tool_call_finish');
89
+ expect(event.toolName).toBe('bash_exec');
90
+ expect(event.resultPreview).toContain('/home/user');
91
+ expect(event.durationMs).toBe(42);
92
+ });
93
+
94
+ it('should handle null result gracefully', () => {
95
+ const service = AgentStreamService.getInstance();
96
+ const handler = vi.fn();
97
+ service.on('stream', handler);
98
+
99
+ service.emitToolCallFinish('test-agent', 'send_message', {}, null, 10);
100
+
101
+ const event = handler.mock.calls[0][0] as ToolCallFinishEvent;
102
+ expect(event.resultPreview).toBe('void');
103
+ });
104
+ });
105
+
106
+ describe('emitStepFinish', () => {
107
+ it('should emit a step_finish event', () => {
108
+ const service = AgentStreamService.getInstance();
109
+ const handler = vi.fn();
110
+ service.on('stream', handler);
111
+
112
+ service.emitStepFinish('test-agent', 3, true);
113
+
114
+ const event = handler.mock.calls[0][0] as StepFinishEvent;
115
+ expect(event.type).toBe('step_finish');
116
+ expect(event.stepIndex).toBe(3);
117
+ expect(event.hasToolCalls).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe('multiple subscribers', () => {
122
+ it('should deliver events to all subscribers', () => {
123
+ const service = AgentStreamService.getInstance();
124
+ const handler1 = vi.fn();
125
+ const handler2 = vi.fn();
126
+ service.on('stream', handler1);
127
+ service.on('stream', handler2);
128
+
129
+ service.emitTextChunk('test-agent', 'hello');
130
+
131
+ expect(handler1).toHaveBeenCalledTimes(1);
132
+ expect(handler2).toHaveBeenCalledTimes(1);
133
+ });
134
+
135
+ it('should filter by sessionName at subscriber level', () => {
136
+ const service = AgentStreamService.getInstance();
137
+ const agentAEvents: AgentStreamEvent[] = [];
138
+ const agentBEvents: AgentStreamEvent[] = [];
139
+
140
+ service.on('stream', (event: AgentStreamEvent) => {
141
+ if (event.sessionName === 'agent-a') agentAEvents.push(event);
142
+ if (event.sessionName === 'agent-b') agentBEvents.push(event);
143
+ });
144
+
145
+ service.emitTextChunk('agent-a', 'hello');
146
+ service.emitTextChunk('agent-b', 'world');
147
+ service.emitTextChunk('agent-a', '!');
148
+
149
+ expect(agentAEvents).toHaveLength(2);
150
+ expect(agentBEvents).toHaveLength(1);
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Agent Stream Service
3
+ *
4
+ * Singleton EventEmitter that broadcasts structured streaming events from
5
+ * in-process Crewly Agent runtimes. SSE endpoints subscribe to this service
6
+ * to forward real-time agent output to the frontend Dashboard.
7
+ *
8
+ * Event types:
9
+ * - text_chunk: Incremental text from the model
10
+ * - tool_call_start: A tool call has started executing
11
+ * - tool_call_finish: A tool call completed (with result and duration)
12
+ * - step_finish: A reasoning step completed
13
+ *
14
+ * @module services/agent/crewly-agent/agent-stream
15
+ */
16
+
17
+ import { EventEmitter } from 'events';
18
+
19
+ /**
20
+ * Streaming event types emitted to SSE clients
21
+ */
22
+ export type StreamEventType =
23
+ | 'text_chunk'
24
+ | 'tool_call_start'
25
+ | 'tool_call_finish'
26
+ | 'step_finish'
27
+ | 'connected'
28
+ | 'heartbeat';
29
+
30
+ /**
31
+ * Base shape for all stream events
32
+ */
33
+ export interface StreamEvent {
34
+ /** Event type */
35
+ type: StreamEventType;
36
+ /** Agent session name */
37
+ sessionName: string;
38
+ /** ISO timestamp */
39
+ timestamp: string;
40
+ }
41
+
42
+ /**
43
+ * Text chunk event — incremental model output
44
+ */
45
+ export interface TextChunkEvent extends StreamEvent {
46
+ type: 'text_chunk';
47
+ /** Text content */
48
+ text: string;
49
+ }
50
+
51
+ /**
52
+ * Tool call start event
53
+ */
54
+ export interface ToolCallStartEvent extends StreamEvent {
55
+ type: 'tool_call_start';
56
+ /** Name of the tool being called */
57
+ toolName: string;
58
+ /** Arguments passed to the tool (serialized preview) */
59
+ argsPreview: string;
60
+ }
61
+
62
+ /**
63
+ * Tool call finish event
64
+ */
65
+ export interface ToolCallFinishEvent extends StreamEvent {
66
+ type: 'tool_call_finish';
67
+ /** Name of the tool that completed */
68
+ toolName: string;
69
+ /** Arguments passed to the tool (serialized preview) */
70
+ argsPreview: string;
71
+ /** Result preview (truncated) */
72
+ resultPreview: string;
73
+ /** Execution duration in milliseconds */
74
+ durationMs: number;
75
+ }
76
+
77
+ /**
78
+ * Step finish event — a reasoning step completed
79
+ */
80
+ export interface StepFinishEvent extends StreamEvent {
81
+ type: 'step_finish';
82
+ /** Step index (1-based) */
83
+ stepIndex: number;
84
+ /** Whether this step included tool calls */
85
+ hasToolCalls: boolean;
86
+ }
87
+
88
+ /**
89
+ * Union of all stream event types
90
+ */
91
+ export type AgentStreamEvent =
92
+ | TextChunkEvent
93
+ | ToolCallStartEvent
94
+ | ToolCallFinishEvent
95
+ | StepFinishEvent;
96
+
97
+ /** Maximum preview length for serialized args/results */
98
+ const MAX_PREVIEW_LENGTH = 200;
99
+
100
+ /**
101
+ * Singleton service that broadcasts agent streaming events.
102
+ *
103
+ * Runtime services call emit methods when streaming callbacks fire.
104
+ * SSE endpoints subscribe via the EventEmitter 'stream' event.
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const streamService = AgentStreamService.getInstance();
109
+ *
110
+ * // Publish events (from runtime)
111
+ * streamService.emitTextChunk('my-agent', 'Hello ');
112
+ *
113
+ * // Subscribe (from SSE endpoint)
114
+ * streamService.on('stream', (event: AgentStreamEvent) => {
115
+ * if (event.sessionName === targetSession) {
116
+ * res.write(`data: ${JSON.stringify(event)}\n\n`);
117
+ * }
118
+ * });
119
+ * ```
120
+ */
121
+ export class AgentStreamService extends EventEmitter {
122
+ private static instance: AgentStreamService | null = null;
123
+
124
+ /**
125
+ * Get the singleton instance.
126
+ *
127
+ * @returns Shared AgentStreamService instance
128
+ */
129
+ static getInstance(): AgentStreamService {
130
+ if (!AgentStreamService.instance) {
131
+ AgentStreamService.instance = new AgentStreamService();
132
+ }
133
+ return AgentStreamService.instance;
134
+ }
135
+
136
+ /**
137
+ * Reset the singleton (for testing).
138
+ */
139
+ static resetInstance(): void {
140
+ if (AgentStreamService.instance) {
141
+ AgentStreamService.instance.removeAllListeners();
142
+ }
143
+ AgentStreamService.instance = null;
144
+ }
145
+
146
+ /**
147
+ * Emit a text chunk event.
148
+ *
149
+ * @param sessionName - Agent session name
150
+ * @param text - Text content
151
+ */
152
+ emitTextChunk(sessionName: string, text: string): void {
153
+ const event: TextChunkEvent = {
154
+ type: 'text_chunk',
155
+ sessionName,
156
+ timestamp: new Date().toISOString(),
157
+ text,
158
+ };
159
+ this.emit('stream', event);
160
+ }
161
+
162
+ /**
163
+ * Emit a tool call start event.
164
+ *
165
+ * @param sessionName - Agent session name
166
+ * @param toolName - Name of the tool
167
+ * @param args - Tool arguments
168
+ */
169
+ emitToolCallStart(sessionName: string, toolName: string, args: Record<string, unknown>): void {
170
+ const event: ToolCallStartEvent = {
171
+ type: 'tool_call_start',
172
+ sessionName,
173
+ timestamp: new Date().toISOString(),
174
+ toolName,
175
+ argsPreview: JSON.stringify(args).substring(0, MAX_PREVIEW_LENGTH),
176
+ };
177
+ this.emit('stream', event);
178
+ }
179
+
180
+ /**
181
+ * Emit a tool call finish event.
182
+ *
183
+ * @param sessionName - Agent session name
184
+ * @param toolName - Name of the tool
185
+ * @param args - Tool arguments
186
+ * @param result - Tool execution result
187
+ * @param durationMs - Execution time in milliseconds
188
+ */
189
+ emitToolCallFinish(
190
+ sessionName: string,
191
+ toolName: string,
192
+ args: Record<string, unknown>,
193
+ result: unknown,
194
+ durationMs: number,
195
+ ): void {
196
+ const event: ToolCallFinishEvent = {
197
+ type: 'tool_call_finish',
198
+ sessionName,
199
+ timestamp: new Date().toISOString(),
200
+ toolName,
201
+ argsPreview: JSON.stringify(args).substring(0, MAX_PREVIEW_LENGTH),
202
+ resultPreview: result ? JSON.stringify(result).substring(0, MAX_PREVIEW_LENGTH) : 'void',
203
+ durationMs,
204
+ };
205
+ this.emit('stream', event);
206
+ }
207
+
208
+ /**
209
+ * Emit a step finish event.
210
+ *
211
+ * @param sessionName - Agent session name
212
+ * @param stepIndex - Step number (1-based)
213
+ * @param hasToolCalls - Whether the step included tool calls
214
+ */
215
+ emitStepFinish(sessionName: string, stepIndex: number, hasToolCalls: boolean): void {
216
+ const event: StepFinishEvent = {
217
+ type: 'step_finish',
218
+ sessionName,
219
+ timestamp: new Date().toISOString(),
220
+ stepIndex,
221
+ hasToolCalls,
222
+ };
223
+ this.emit('stream', event);
224
+ }
225
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Tests for Agent Worker IPC protocol and CrewlyAgentRuntimeService worker mode.
3
+ *
4
+ * Tests the IPC message types and worker lifecycle without actually forking
5
+ * a real child process (which would require full AI SDK setup).
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import type { ParentMessage, WorkerMessage } from './agent-worker.js';
10
+
11
+ describe('Agent Worker IPC Protocol', () => {
12
+ describe('ParentMessage types', () => {
13
+ it('should accept init message with config', () => {
14
+ const msg: ParentMessage = {
15
+ type: 'init',
16
+ config: {
17
+ model: { provider: 'google', modelId: 'gemini-3-flash-preview' },
18
+ maxSteps: 500,
19
+ sessionName: 'test-session',
20
+ apiBaseUrl: 'http://localhost:8787',
21
+ systemPrompt: 'You are a test agent.',
22
+ maxHistoryMessages: 100,
23
+ compactionThreshold: 0.8,
24
+ },
25
+ };
26
+ expect(msg.type).toBe('init');
27
+ expect(msg.config.sessionName).toBe('test-session');
28
+ expect(msg.config.maxSteps).toBe(500);
29
+ });
30
+
31
+ it('should accept run message with optional fields', () => {
32
+ const msg: ParentMessage = {
33
+ type: 'run',
34
+ message: 'Hello',
35
+ conversationId: 'conv-123',
36
+ metadata: { channelId: 'C123' },
37
+ };
38
+ expect(msg.type).toBe('run');
39
+ expect(msg.message).toBe('Hello');
40
+ expect(msg.conversationId).toBe('conv-123');
41
+ });
42
+
43
+ it('should accept run message without optional fields', () => {
44
+ const msg: ParentMessage = {
45
+ type: 'run',
46
+ message: 'Hello',
47
+ };
48
+ expect(msg.type).toBe('run');
49
+ expect(msg.conversationId).toBeUndefined();
50
+ });
51
+
52
+ it('should accept abort message', () => {
53
+ const msg: ParentMessage = { type: 'abort' };
54
+ expect(msg.type).toBe('abort');
55
+ });
56
+
57
+ it('should accept get-state message', () => {
58
+ const msg: ParentMessage = { type: 'get-state' };
59
+ expect(msg.type).toBe('get-state');
60
+ });
61
+
62
+ it('should accept shutdown message', () => {
63
+ const msg: ParentMessage = { type: 'shutdown' };
64
+ expect(msg.type).toBe('shutdown');
65
+ });
66
+ });
67
+
68
+ describe('WorkerMessage types', () => {
69
+ it('should accept ready message', () => {
70
+ const msg: WorkerMessage = { type: 'ready' };
71
+ expect(msg.type).toBe('ready');
72
+ });
73
+
74
+ it('should accept result message with AgentRunResult', () => {
75
+ const msg: WorkerMessage = {
76
+ type: 'result',
77
+ data: {
78
+ text: 'Done',
79
+ steps: 3,
80
+ usage: { input: 100, output: 50 },
81
+ toolCalls: [{ toolName: 'bash_exec', args: { command: 'ls' }, result: 'files' }],
82
+ finishReason: 'stop',
83
+ },
84
+ };
85
+ expect(msg.type).toBe('result');
86
+ expect(msg.data.steps).toBe(3);
87
+ expect(msg.data.toolCalls).toHaveLength(1);
88
+ });
89
+
90
+ it('should accept error message with code', () => {
91
+ const msg: WorkerMessage = {
92
+ type: 'error',
93
+ error: 'Init failed: bad API key',
94
+ code: 'INIT_FAILED',
95
+ };
96
+ expect(msg.type).toBe('error');
97
+ expect(msg.error).toContain('Init failed');
98
+ expect(msg.code).toBe('INIT_FAILED');
99
+ });
100
+
101
+ it('should accept log message at all levels', () => {
102
+ const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['debug', 'info', 'warn', 'error'];
103
+ for (const level of levels) {
104
+ const msg: WorkerMessage = { type: 'log', level, message: `Test ${level}` };
105
+ expect(msg.level).toBe(level);
106
+ }
107
+ });
108
+
109
+ it('should accept stream text event', () => {
110
+ const msg: WorkerMessage = {
111
+ type: 'stream',
112
+ event: 'text',
113
+ data: { chunk: 'Hello world' },
114
+ };
115
+ expect(msg.type).toBe('stream');
116
+ expect(msg.event).toBe('text');
117
+ });
118
+
119
+ it('should accept stream toolStart event', () => {
120
+ const msg: WorkerMessage = {
121
+ type: 'stream',
122
+ event: 'toolStart',
123
+ data: { toolName: 'bash_exec', args: { command: 'npm test' } },
124
+ };
125
+ expect(msg.event).toBe('toolStart');
126
+ expect(msg.data.toolName).toBe('bash_exec');
127
+ });
128
+
129
+ it('should accept stream toolFinish event', () => {
130
+ const msg: WorkerMessage = {
131
+ type: 'stream',
132
+ event: 'toolFinish',
133
+ data: {
134
+ toolName: 'bash_exec',
135
+ args: { command: 'npm test' },
136
+ result: 'All tests passed',
137
+ durationMs: 1234,
138
+ },
139
+ };
140
+ expect(msg.event).toBe('toolFinish');
141
+ expect(msg.data.durationMs).toBe(1234);
142
+ });
143
+
144
+ it('should accept stream stepFinish event', () => {
145
+ const msg: WorkerMessage = {
146
+ type: 'stream',
147
+ event: 'stepFinish',
148
+ data: { stepIndex: 2, hasToolCalls: true },
149
+ };
150
+ expect(msg.event).toBe('stepFinish');
151
+ expect(msg.data.stepIndex).toBe(2);
152
+ });
153
+
154
+ it('should accept state message', () => {
155
+ const msg: WorkerMessage = {
156
+ type: 'state',
157
+ data: {
158
+ historyLength: 10,
159
+ isProcessing: false,
160
+ isInitialized: true,
161
+ },
162
+ };
163
+ expect(msg.type).toBe('state');
164
+ expect(msg.data.historyLength).toBe(10);
165
+ });
166
+ });
167
+ });
168
+
169
+ // Worker mode tests for CrewlyAgentRuntimeService are in
170
+ // crewly-agent-runtime.service.test.ts alongside the existing tests,
171
+ // since they share the same mock setup.