@electric-agent/studio 1.5.0 → 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 (109) hide show
  1. package/dist/api-schemas.d.ts +225 -0
  2. package/dist/api-schemas.d.ts.map +1 -0
  3. package/dist/api-schemas.js +95 -0
  4. package/dist/api-schemas.js.map +1 -0
  5. package/dist/bridge/claude-code-base.d.ts +121 -0
  6. package/dist/bridge/claude-code-base.d.ts.map +1 -0
  7. package/dist/bridge/claude-code-base.js +263 -0
  8. package/dist/bridge/claude-code-base.js.map +1 -0
  9. package/dist/bridge/claude-code-docker.d.ts +13 -73
  10. package/dist/bridge/claude-code-docker.d.ts.map +1 -1
  11. package/dist/bridge/claude-code-docker.js +91 -302
  12. package/dist/bridge/claude-code-docker.js.map +1 -1
  13. package/dist/bridge/claude-code-sprites.d.ts +12 -59
  14. package/dist/bridge/claude-code-sprites.d.ts.map +1 -1
  15. package/dist/bridge/claude-code-sprites.js +88 -281
  16. package/dist/bridge/claude-code-sprites.js.map +1 -1
  17. package/dist/bridge/claude-md-generator.d.ts +22 -5
  18. package/dist/bridge/claude-md-generator.d.ts.map +1 -1
  19. package/dist/bridge/claude-md-generator.js +81 -213
  20. package/dist/bridge/claude-md-generator.js.map +1 -1
  21. package/dist/bridge/codex-docker.d.ts +56 -51
  22. package/dist/bridge/codex-docker.js +222 -230
  23. package/dist/bridge/codex-json-parser.d.ts +11 -11
  24. package/dist/bridge/codex-json-parser.js +231 -238
  25. package/dist/bridge/codex-md-generator.d.ts +3 -3
  26. package/dist/bridge/codex-md-generator.js +42 -32
  27. package/dist/bridge/codex-sprites.d.ts +50 -45
  28. package/dist/bridge/codex-sprites.js +212 -222
  29. package/dist/bridge/daytona.d.ts +25 -25
  30. package/dist/bridge/daytona.js +131 -136
  31. package/dist/bridge/docker-stdio.d.ts +21 -21
  32. package/dist/bridge/docker-stdio.js +126 -132
  33. package/dist/bridge/hosted.d.ts +3 -2
  34. package/dist/bridge/hosted.d.ts.map +1 -1
  35. package/dist/bridge/hosted.js +4 -0
  36. package/dist/bridge/hosted.js.map +1 -1
  37. package/dist/bridge/message-parser.d.ts +24 -0
  38. package/dist/bridge/message-parser.d.ts.map +1 -0
  39. package/dist/bridge/message-parser.js +39 -0
  40. package/dist/bridge/message-parser.js.map +1 -0
  41. package/dist/bridge/role-skills.d.ts +25 -0
  42. package/dist/bridge/role-skills.d.ts.map +1 -0
  43. package/dist/bridge/role-skills.js +120 -0
  44. package/dist/bridge/role-skills.js.map +1 -0
  45. package/dist/bridge/room-messaging-skill.d.ts +11 -0
  46. package/dist/bridge/room-messaging-skill.d.ts.map +1 -0
  47. package/dist/bridge/room-messaging-skill.js +41 -0
  48. package/dist/bridge/room-messaging-skill.js.map +1 -0
  49. package/dist/bridge/sprites.d.ts +22 -22
  50. package/dist/bridge/sprites.js +123 -128
  51. package/dist/bridge/stream-json-parser.js +12 -5
  52. package/dist/bridge/stream-json-parser.js.map +1 -1
  53. package/dist/bridge/types.d.ts +4 -10
  54. package/dist/bridge/types.d.ts.map +1 -1
  55. package/dist/client/assets/index-BfvQSMwH.css +1 -0
  56. package/dist/client/assets/index-CiwD5LkP.js +235 -0
  57. package/dist/client/index.html +2 -2
  58. package/dist/index.d.ts +4 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -3
  61. package/dist/index.js.map +1 -1
  62. package/dist/invite-code.d.ts +5 -0
  63. package/dist/invite-code.d.ts.map +1 -0
  64. package/dist/invite-code.js +14 -0
  65. package/dist/invite-code.js.map +1 -0
  66. package/dist/project-utils.d.ts.map +1 -1
  67. package/dist/project-utils.js.map +1 -1
  68. package/dist/registry.d.ts +11 -4
  69. package/dist/registry.d.ts.map +1 -1
  70. package/dist/registry.js +1 -1
  71. package/dist/registry.js.map +1 -1
  72. package/dist/room-router.d.ts +73 -0
  73. package/dist/room-router.d.ts.map +1 -0
  74. package/dist/room-router.js +345 -0
  75. package/dist/room-router.js.map +1 -0
  76. package/dist/sandbox/docker.d.ts.map +1 -1
  77. package/dist/sandbox/docker.js +5 -6
  78. package/dist/sandbox/docker.js.map +1 -1
  79. package/dist/sandbox/index.d.ts +0 -1
  80. package/dist/sandbox/index.d.ts.map +1 -1
  81. package/dist/sandbox/index.js +0 -1
  82. package/dist/sandbox/index.js.map +1 -1
  83. package/dist/sandbox/sprites.d.ts.map +1 -1
  84. package/dist/sandbox/sprites.js +40 -10
  85. package/dist/sandbox/sprites.js.map +1 -1
  86. package/dist/sandbox/types.d.ts +4 -2
  87. package/dist/sandbox/types.d.ts.map +1 -1
  88. package/dist/server.d.ts +12 -0
  89. package/dist/server.d.ts.map +1 -1
  90. package/dist/server.js +824 -309
  91. package/dist/server.js.map +1 -1
  92. package/dist/session-auth.d.ts +9 -0
  93. package/dist/session-auth.d.ts.map +1 -1
  94. package/dist/session-auth.js +30 -0
  95. package/dist/session-auth.js.map +1 -1
  96. package/dist/sessions.d.ts +7 -1
  97. package/dist/sessions.d.ts.map +1 -1
  98. package/dist/sessions.js.map +1 -1
  99. package/dist/streams.d.ts +2 -6
  100. package/dist/streams.d.ts.map +1 -1
  101. package/dist/streams.js +6 -17
  102. package/dist/streams.js.map +1 -1
  103. package/dist/validate.d.ts +10 -0
  104. package/dist/validate.d.ts.map +1 -0
  105. package/dist/validate.js +24 -0
  106. package/dist/validate.js.map +1 -0
  107. package/package.json +6 -9
  108. package/dist/client/assets/index-DDzmxYub.js +0 -234
  109. package/dist/client/assets/index-DcP7prsZ.css +0 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Parses agent output (assistant_message text) for @room or @<name> messages.
3
+ *
4
+ * Convention:
5
+ * @room <body> → broadcast to all participants
6
+ * @<name> <body> → direct message to a specific participant
7
+ * @room DONE: <summary> → signal conversation completion
8
+ * @room GATE: <question> → request human input
9
+ *
10
+ * If no @room/@name prefix is found, returns null (agent chose silence).
11
+ * Uses the LAST match in the text so agents can do work first, then talk.
12
+ */
13
+ export interface ParsedRoomMessage {
14
+ /** Recipient name, or undefined for broadcast */
15
+ to?: string;
16
+ /** Message body */
17
+ body: string;
18
+ /** True if body starts with "DONE:" — signals conversation end */
19
+ isDone: boolean;
20
+ /** True if body starts with "GATE:" — agent requests human input */
21
+ isGateRequest: boolean;
22
+ }
23
+ export declare function parseRoomMessage(text: string, _senderName: string, knownParticipants?: string[]): ParsedRoomMessage | null;
24
+ //# sourceMappingURL=message-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-parser.d.ts","sourceRoot":"","sources":["../../src/bridge/message-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,iBAAiB;IACjC,iDAAiD;IACjD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAA;IACf,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;CACtB;AAKD,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAC1B,iBAAiB,GAAG,IAAI,CA2B1B"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Parses agent output (assistant_message text) for @room or @<name> messages.
3
+ *
4
+ * Convention:
5
+ * @room <body> → broadcast to all participants
6
+ * @<name> <body> → direct message to a specific participant
7
+ * @room DONE: <summary> → signal conversation completion
8
+ * @room GATE: <question> → request human input
9
+ *
10
+ * If no @room/@name prefix is found, returns null (agent chose silence).
11
+ * Uses the LAST match in the text so agents can do work first, then talk.
12
+ */
13
+ // Matches @room or @<name> at the start of a line, capturing just the target.
14
+ const ROOM_PREFIX_RE = /^@(\S+)\s/gm;
15
+ export function parseRoomMessage(text, _senderName, knownParticipants) {
16
+ // Find all @room / @name positions in the text
17
+ const hits = [];
18
+ // Reset lastIndex — /g regexes retain state between exec() calls
19
+ ROOM_PREFIX_RE.lastIndex = 0;
20
+ let match = ROOM_PREFIX_RE.exec(text);
21
+ while (match !== null) {
22
+ const target = match[1];
23
+ if (target === "room" || knownParticipants?.includes(target)) {
24
+ hits.push({ target, startOfBody: match.index + match[0].length });
25
+ }
26
+ match = ROOM_PREFIX_RE.exec(text);
27
+ }
28
+ if (hits.length === 0)
29
+ return null;
30
+ // Use the last match — agent does work first, then talks
31
+ const last = hits[hits.length - 1];
32
+ // Body runs from after "@room " to end of string (or next hit, but we want the last one)
33
+ const body = text.slice(last.startOfBody).trim();
34
+ const to = last.target === "room" ? undefined : last.target;
35
+ const isDone = body.startsWith("DONE:");
36
+ const isGateRequest = body.startsWith("GATE:");
37
+ return { to, body, isDone, isGateRequest };
38
+ }
39
+ //# sourceMappingURL=message-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-parser.js","sourceRoot":"","sources":["../../src/bridge/message-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,8EAA8E;AAC9E,MAAM,cAAc,GAAG,aAAa,CAAA;AAEpC,MAAM,UAAU,gBAAgB,CAC/B,IAAY,EACZ,WAAmB,EACnB,iBAA4B;IAE5B,+CAA+C;IAC/C,MAAM,IAAI,GAAmD,EAAE,CAAA;IAE/D,iEAAiE;IACjE,cAAc,CAAC,SAAS,GAAG,CAAC,CAAA;IAC5B,IAAI,KAAK,GAA2B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7D,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACvB,IAAI,MAAM,KAAK,MAAM,IAAI,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,yDAAyD;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAElC,yFAAyF;IACzF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IAE9C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Role-based skill loader.
3
+ *
4
+ * Maps agent roles to skill files and tool permission sets.
5
+ * Built-in roles (coder, reviewer) get specific behavioral guidelines
6
+ * and restricted tool permissions to enforce isolation between roles.
7
+ */
8
+ export interface RoleSkill {
9
+ /** Canonical role name (e.g. "coder", "reviewer"). */
10
+ roleName: string;
11
+ /** Markdown content of the role skill file. */
12
+ skillContent: string;
13
+ /** Allowed tools for this role. When undefined, use default tools. */
14
+ allowedTools?: string[];
15
+ }
16
+ /**
17
+ * Resolve a role string to its skill content and tool permissions.
18
+ * Returns undefined if the role doesn't match a built-in role.
19
+ */
20
+ export declare function resolveRoleSkill(role?: string): RoleSkill | undefined;
21
+ /**
22
+ * Get the list of all known built-in role names.
23
+ */
24
+ export declare function getBuiltInRoles(): string[];
25
+ //# sourceMappingURL=role-skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-skills.d.ts","sourceRoot":"","sources":["../../src/bridge/role-skills.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkGH,MAAM,WAAW,SAAS;IACzB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAA;IAChB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAsBrE;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Role-based skill loader.
3
+ *
4
+ * Maps agent roles to skill files and tool permission sets.
5
+ * Built-in roles (coder, reviewer) get specific behavioral guidelines
6
+ * and restricted tool permissions to enforce isolation between roles.
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ // ---------------------------------------------------------------------------
13
+ // Role aliases → canonical role name
14
+ // ---------------------------------------------------------------------------
15
+ const ROLE_ALIASES = {
16
+ coder: "coder",
17
+ developer: "coder",
18
+ programmer: "coder",
19
+ engineer: "coder",
20
+ reviewer: "reviewer",
21
+ "code reviewer": "reviewer",
22
+ "pr reviewer": "reviewer",
23
+ };
24
+ // ---------------------------------------------------------------------------
25
+ // Tool permissions per role
26
+ // ---------------------------------------------------------------------------
27
+ /** Full tool set — same as DEFAULT_ALLOWED_TOOLS in the bridges. */
28
+ const ALL_TOOLS = [
29
+ "Read",
30
+ "Write",
31
+ "Edit",
32
+ "Bash",
33
+ "Glob",
34
+ "Grep",
35
+ "WebSearch",
36
+ "TodoWrite",
37
+ "AskUserQuestion",
38
+ "Skill",
39
+ ];
40
+ /**
41
+ * Coder gets full write access — can read, write, edit, run commands.
42
+ */
43
+ const CODER_TOOLS = [...ALL_TOOLS];
44
+ /**
45
+ * Reviewer is read-only + Bash (for gh CLI).
46
+ * Cannot Write or Edit files — only review and comment.
47
+ */
48
+ const REVIEWER_TOOLS = [
49
+ "Read",
50
+ "Bash",
51
+ "Glob",
52
+ "Grep",
53
+ "WebSearch",
54
+ "TodoWrite",
55
+ "AskUserQuestion",
56
+ "Skill",
57
+ ];
58
+ const ROLE_TOOLS = {
59
+ coder: CODER_TOOLS,
60
+ reviewer: REVIEWER_TOOLS,
61
+ };
62
+ // ---------------------------------------------------------------------------
63
+ // Skill file loading
64
+ // ---------------------------------------------------------------------------
65
+ const roleSkillCache = new Map();
66
+ function loadSkillFile(roleName) {
67
+ const candidates = [
68
+ // From studio/dist/bridge/ → project root
69
+ path.resolve(__dirname, `../../../../.claude/skills/roles/${roleName}/SKILL.md`),
70
+ // From studio/src/bridge/ → project root (dev mode)
71
+ path.resolve(__dirname, `../../../.claude/skills/roles/${roleName}/SKILL.md`),
72
+ // Monorepo root from packages/studio/
73
+ path.resolve(__dirname, `../../.claude/skills/roles/${roleName}/SKILL.md`),
74
+ ];
75
+ for (const candidate of candidates) {
76
+ try {
77
+ if (fs.existsSync(candidate)) {
78
+ return fs.readFileSync(candidate, "utf-8");
79
+ }
80
+ }
81
+ catch {
82
+ // Continue to next candidate
83
+ }
84
+ }
85
+ return undefined;
86
+ }
87
+ /**
88
+ * Resolve a role string to its skill content and tool permissions.
89
+ * Returns undefined if the role doesn't match a built-in role.
90
+ */
91
+ export function resolveRoleSkill(role) {
92
+ if (!role)
93
+ return undefined;
94
+ const normalized = role.toLowerCase().trim();
95
+ const roleName = ROLE_ALIASES[normalized];
96
+ if (!roleName)
97
+ return undefined;
98
+ // Load skill content (with cache)
99
+ if (!roleSkillCache.has(roleName)) {
100
+ const content = loadSkillFile(roleName);
101
+ if (!content) {
102
+ console.warn(`[role-skills] Skill file not found for role "${roleName}"`);
103
+ return undefined;
104
+ }
105
+ roleSkillCache.set(roleName, content);
106
+ }
107
+ const skillContent = roleSkillCache.get(roleName);
108
+ return {
109
+ roleName,
110
+ skillContent,
111
+ allowedTools: ROLE_TOOLS[roleName],
112
+ };
113
+ }
114
+ /**
115
+ * Get the list of all known built-in role names.
116
+ */
117
+ export function getBuiltInRoles() {
118
+ return [...new Set(Object.values(ROLE_ALIASES))];
119
+ }
120
+ //# sourceMappingURL=role-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-skills.js","sourceRoot":"","sources":["../../src/bridge/role-skills.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The room-messaging skill content as a string constant.
3
+ *
4
+ * Exported so the server can write the skill into sandboxes, ensuring agents
5
+ * have persistent access to the multi-agent messaging protocol even after the
6
+ * initial discovery prompt scrolls out of context.
7
+ *
8
+ * The content must stay in sync with .claude/skills/room-messaging/SKILL.md.
9
+ */
10
+ export declare const roomMessagingSkillContent: string;
11
+ //# sourceMappingURL=room-messaging-skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-messaging-skill.d.ts","sourceRoot":"","sources":["../../src/bridge/room-messaging-skill.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH,eAAO,MAAM,yBAAyB,QAAqB,CAAA"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The room-messaging skill content as a string constant.
3
+ *
4
+ * Exported so the server can write the skill into sandboxes, ensuring agents
5
+ * have persistent access to the multi-agent messaging protocol even after the
6
+ * initial discovery prompt scrolls out of context.
7
+ *
8
+ * The content must stay in sync with .claude/skills/room-messaging/SKILL.md.
9
+ */
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ /**
15
+ * Read the room-messaging skill from the project root.
16
+ * Falls back to an empty string if the file can't be found.
17
+ */
18
+ function loadSkillContent() {
19
+ const candidates = [
20
+ // From studio/dist/bridge/ → project root .claude/skills/room-messaging/SKILL.md
21
+ path.resolve(__dirname, "../../../../.claude/skills/room-messaging/SKILL.md"),
22
+ // From studio/src/bridge/ → project root (dev mode)
23
+ path.resolve(__dirname, "../../../.claude/skills/room-messaging/SKILL.md"),
24
+ // Monorepo root from packages/studio/
25
+ path.resolve(__dirname, "../../.claude/skills/room-messaging/SKILL.md"),
26
+ ];
27
+ for (const candidate of candidates) {
28
+ try {
29
+ if (fs.existsSync(candidate)) {
30
+ return fs.readFileSync(candidate, "utf-8");
31
+ }
32
+ }
33
+ catch {
34
+ // Continue to next candidate
35
+ }
36
+ }
37
+ console.warn("[room-messaging-skill] Could not find SKILL.md in any expected location");
38
+ return "";
39
+ }
40
+ export const roomMessagingSkillContent = loadSkillContent();
41
+ //# sourceMappingURL=room-messaging-skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-messaging-skill.js","sourceRoot":"","sources":["../../src/bridge/room-messaging-skill.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAE9D;;;GAGG;AACH,SAAS,gBAAgB;IACxB,MAAM,UAAU,GAAG;QAClB,iFAAiF;QACjF,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,oDAAoD,CAAC;QAC7E,oDAAoD;QACpD,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,iDAAiD,CAAC;QAC1E,sCAAsC;QACtC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,8CAA8C,CAAC;KACvE,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACpC,IAAI,CAAC;YACJ,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9B,OAAO,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;YAC3C,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,6BAA6B;QAC9B,CAAC;IACF,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAA;IACvF,OAAO,EAAE,CAAA;AACV,CAAC;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,EAAE,CAAA"}
@@ -6,27 +6,27 @@
6
6
  * the Sprites session (for agent communication). The agent inside the
7
7
  * sprite uses the stdio adapter — the bridge relays events to the stream.
8
8
  */
9
- import type { EngineEvent } from "@electric-agent/protocol";
10
- import type { Sprite } from "@fly/sprites";
11
- import type { StreamConnectionInfo } from "../streams.js";
12
- import type { SessionBridge } from "./types.js";
9
+ import type { EngineEvent } from "@electric-agent/protocol"
10
+ import type { Sprite } from "@fly/sprites"
11
+ import type { StreamConnectionInfo } from "../streams.js"
12
+ import type { SessionBridge } from "./types.js"
13
13
  export declare class SpritesStdioBridge implements SessionBridge {
14
- readonly sessionId: string;
15
- readonly streamUrl: string;
16
- readonly streamHeaders: Record<string, string>;
17
- private sprite;
18
- private writer;
19
- private agentEventCallbacks;
20
- private completeCallbacks;
21
- private closed;
22
- private cmd;
23
- constructor(sessionId: string, connection: StreamConnectionInfo, sprite: Sprite);
24
- emit(event: EngineEvent): Promise<void>;
25
- sendCommand(cmd: Record<string, unknown>): Promise<void>;
26
- sendGateResponse(gate: string, value: Record<string, unknown>): Promise<void>;
27
- onAgentEvent(cb: (event: EngineEvent) => void): void;
28
- onComplete(cb: (success: boolean) => void): void;
29
- start(): Promise<void>;
30
- close(): void;
14
+ readonly sessionId: string
15
+ readonly streamUrl: string
16
+ readonly streamHeaders: Record<string, string>
17
+ private sprite
18
+ private writer
19
+ private agentEventCallbacks
20
+ private completeCallbacks
21
+ private closed
22
+ private cmd
23
+ constructor(sessionId: string, connection: StreamConnectionInfo, sprite: Sprite)
24
+ emit(event: EngineEvent): Promise<void>
25
+ sendCommand(cmd: Record<string, unknown>): Promise<void>
26
+ sendGateResponse(gate: string, value: Record<string, unknown>): Promise<void>
27
+ onAgentEvent(cb: (event: EngineEvent) => void): void
28
+ onComplete(cb: (success: boolean) => void): void
29
+ start(): Promise<void>
30
+ close(): void
31
31
  }
32
- //# sourceMappingURL=sprites.d.ts.map
32
+ //# sourceMappingURL=sprites.d.ts.map
@@ -6,133 +6,128 @@
6
6
  * the Sprites session (for agent communication). The agent inside the
7
7
  * sprite uses the stdio adapter — the bridge relays events to the stream.
8
8
  */
9
- import * as readline from "node:readline";
10
- import { DurableStream } from "@durable-streams/client";
11
- const SPRITES_SESSION_ID = "agent-session";
9
+ import * as readline from "node:readline"
10
+ import { DurableStream } from "@durable-streams/client"
11
+
12
+ const SPRITES_SESSION_ID = "agent-session"
12
13
  export class SpritesStdioBridge {
13
- sessionId;
14
- streamUrl;
15
- streamHeaders;
16
- sprite;
17
- writer;
18
- agentEventCallbacks = [];
19
- completeCallbacks = [];
20
- closed = false;
21
- cmd = null;
22
- constructor(sessionId, connection, sprite) {
23
- this.sessionId = sessionId;
24
- this.streamUrl = connection.url;
25
- this.streamHeaders = connection.headers;
26
- this.sprite = sprite;
27
- this.writer = new DurableStream({
28
- url: connection.url,
29
- headers: connection.headers,
30
- contentType: "application/json",
31
- });
32
- }
33
- async emit(event) {
34
- if (this.closed)
35
- return;
36
- const msg = { source: "server", ...event };
37
- await this.writer.append(JSON.stringify(msg));
38
- }
39
- async sendCommand(cmd) {
40
- if (this.closed || !this.cmd)
41
- return;
42
- const line = JSON.stringify({ type: "command", ...cmd });
43
- this.cmd.stdin.write(`${line}\n`);
44
- }
45
- async sendGateResponse(gate, value) {
46
- if (this.closed || !this.cmd)
47
- return;
48
- const line = JSON.stringify({ type: "gate_response", gate, ...value });
49
- this.cmd.stdin.write(`${line}\n`);
50
- }
51
- onAgentEvent(cb) {
52
- this.agentEventCallbacks.push(cb);
53
- }
54
- onComplete(cb) {
55
- this.completeCallbacks.push(cb);
56
- }
57
- async start() {
58
- if (this.closed)
59
- return;
60
- // Create a persistent session in the sprite so we can reconnect if needed
61
- this.cmd = this.sprite.createSession("bash", [
62
- "-c",
63
- "source /etc/profile.d/npm-global.sh 2>/dev/null; source /etc/profile.d/electric-agent.sh && electric-agent headless",
64
- ], { detachable: true, sessionId: SPRITES_SESSION_ID });
65
- console.log(`[sprites-bridge] Agent started: session=${this.sessionId}`);
66
- // Read stdout line by line (NDJSON)
67
- const rl = readline.createInterface({
68
- input: this.cmd.stdout,
69
- terminal: false,
70
- });
71
- rl.on("line", (line) => {
72
- if (this.closed)
73
- return;
74
- const trimmed = line.trim();
75
- if (!trimmed)
76
- return;
77
- let event;
78
- try {
79
- event = JSON.parse(trimmed);
80
- }
81
- catch {
82
- console.log(`[sprites-bridge] Non-JSON stdout: ${trimmed}`);
83
- return;
84
- }
85
- // Write to Durable Stream for UI
86
- const msg = { source: "agent", ...event };
87
- this.writer.append(JSON.stringify(msg)).catch(() => { });
88
- // Dispatch to callbacks
89
- for (const cb of this.agentEventCallbacks) {
90
- try {
91
- cb(event);
92
- }
93
- catch {
94
- // Swallow callback errors
95
- }
96
- }
97
- // Detect session_end
98
- if (event.type === "session_end" && "success" in event) {
99
- const success = event.success;
100
- for (const cb of this.completeCallbacks) {
101
- try {
102
- cb(success);
103
- }
104
- catch {
105
- // Swallow callback errors
106
- }
107
- }
108
- }
109
- });
110
- // Log stderr
111
- const stderrRl = readline.createInterface({
112
- input: this.cmd.stderr,
113
- terminal: false,
114
- });
115
- stderrRl.on("line", (line) => {
116
- if (!this.closed) {
117
- console.error(`[sprites-bridge:stderr] ${line}`);
118
- }
119
- });
120
- // Handle process exit
121
- this.cmd.on("exit", (code) => {
122
- console.log(`[sprites-bridge] Agent process exited: code=${code} session=${this.sessionId}`);
123
- });
124
- }
125
- close() {
126
- this.closed = true;
127
- if (this.cmd) {
128
- try {
129
- this.cmd.kill();
130
- }
131
- catch {
132
- // Process may already be dead
133
- }
134
- this.cmd = null;
135
- }
136
- }
14
+ sessionId
15
+ streamUrl
16
+ streamHeaders
17
+ sprite
18
+ writer
19
+ agentEventCallbacks = []
20
+ completeCallbacks = []
21
+ closed = false
22
+ cmd = null
23
+ constructor(sessionId, connection, sprite) {
24
+ this.sessionId = sessionId
25
+ this.streamUrl = connection.url
26
+ this.streamHeaders = connection.headers
27
+ this.sprite = sprite
28
+ this.writer = new DurableStream({
29
+ url: connection.url,
30
+ headers: connection.headers,
31
+ contentType: "application/json",
32
+ })
33
+ }
34
+ async emit(event) {
35
+ if (this.closed) return
36
+ const msg = { source: "server", ...event }
37
+ await this.writer.append(JSON.stringify(msg))
38
+ }
39
+ async sendCommand(cmd) {
40
+ if (this.closed || !this.cmd) return
41
+ const line = JSON.stringify({ type: "command", ...cmd })
42
+ this.cmd.stdin.write(`${line}\n`)
43
+ }
44
+ async sendGateResponse(gate, value) {
45
+ if (this.closed || !this.cmd) return
46
+ const line = JSON.stringify({ type: "gate_response", gate, ...value })
47
+ this.cmd.stdin.write(`${line}\n`)
48
+ }
49
+ onAgentEvent(cb) {
50
+ this.agentEventCallbacks.push(cb)
51
+ }
52
+ onComplete(cb) {
53
+ this.completeCallbacks.push(cb)
54
+ }
55
+ async start() {
56
+ if (this.closed) return
57
+ // Create a persistent session in the sprite so we can reconnect if needed
58
+ this.cmd = this.sprite.createSession(
59
+ "bash",
60
+ [
61
+ "-c",
62
+ "source /etc/profile.d/npm-global.sh 2>/dev/null; source /etc/profile.d/electric-agent.sh && electric-agent headless",
63
+ ],
64
+ { detachable: true, sessionId: SPRITES_SESSION_ID },
65
+ )
66
+ console.log(`[sprites-bridge] Agent started: session=${this.sessionId}`)
67
+ // Read stdout line by line (NDJSON)
68
+ const rl = readline.createInterface({
69
+ input: this.cmd.stdout,
70
+ terminal: false,
71
+ })
72
+ rl.on("line", (line) => {
73
+ if (this.closed) return
74
+ const trimmed = line.trim()
75
+ if (!trimmed) return
76
+ let event
77
+ try {
78
+ event = JSON.parse(trimmed)
79
+ } catch {
80
+ console.log(`[sprites-bridge] Non-JSON stdout: ${trimmed}`)
81
+ return
82
+ }
83
+ // Write to Durable Stream for UI
84
+ const msg = { source: "agent", ...event }
85
+ this.writer.append(JSON.stringify(msg)).catch(() => {})
86
+ // Dispatch to callbacks
87
+ for (const cb of this.agentEventCallbacks) {
88
+ try {
89
+ cb(event)
90
+ } catch {
91
+ // Swallow callback errors
92
+ }
93
+ }
94
+ // Detect session_end
95
+ if (event.type === "session_end" && "success" in event) {
96
+ const success = event.success
97
+ for (const cb of this.completeCallbacks) {
98
+ try {
99
+ cb(success)
100
+ } catch {
101
+ // Swallow callback errors
102
+ }
103
+ }
104
+ }
105
+ })
106
+ // Log stderr
107
+ const stderrRl = readline.createInterface({
108
+ input: this.cmd.stderr,
109
+ terminal: false,
110
+ })
111
+ stderrRl.on("line", (line) => {
112
+ if (!this.closed) {
113
+ console.error(`[sprites-bridge:stderr] ${line}`)
114
+ }
115
+ })
116
+ // Handle process exit
117
+ this.cmd.on("exit", (code) => {
118
+ console.log(`[sprites-bridge] Agent process exited: code=${code} session=${this.sessionId}`)
119
+ })
120
+ }
121
+ close() {
122
+ this.closed = true
123
+ if (this.cmd) {
124
+ try {
125
+ this.cmd.kill()
126
+ } catch {
127
+ // Process may already be dead
128
+ }
129
+ this.cmd = null
130
+ }
131
+ }
137
132
  }
138
- //# sourceMappingURL=sprites.js.map
133
+ //# sourceMappingURL=sprites.js.map
@@ -157,15 +157,22 @@ function handleUser(msg, state) {
157
157
  }
158
158
  return events;
159
159
  }
160
- function handleResult(msg, state) {
161
- const events = [];
160
+ function handleResult(msg, _state) {
162
161
  const success = msg.subtype === "success";
163
- events.push({
162
+ const event = {
164
163
  type: "session_end",
165
164
  success,
166
165
  ts: ts(),
167
- });
168
- return events;
166
+ };
167
+ if (msg.cost_usd != null)
168
+ event.cost_usd = msg.cost_usd;
169
+ if (msg.num_turns != null)
170
+ event.num_turns = msg.num_turns;
171
+ if (msg.duration_ms != null)
172
+ event.duration_ms = msg.duration_ms;
173
+ if (msg.duration_api_ms != null)
174
+ event.duration_api_ms = msg.duration_api_ms;
175
+ return [event];
169
176
  }
170
177
  // ---------------------------------------------------------------------------
171
178
  // Helpers