tide-commander 1.47.0 → 1.49.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 (57) hide show
  1. package/dist/assets/{BossLogsModal-BEZSJWBV.js → BossLogsModal-buht3LyW.js} +1 -1
  2. package/dist/assets/BossSpawnModal-D7M1JocE.js +1 -0
  3. package/dist/assets/{ControlsModal-BZKmtLz7.js → ControlsModal-C3-j1E6y.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-DD8gRVu6.js → DockerLogsModal-CA2j24QX.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-DMcKiZ5q.js → EmbeddedEditor-CrLtAaIO.js} +1 -1
  6. package/dist/assets/GmailOAuthSetup-CG2BmCyg.js +303 -0
  7. package/dist/assets/GoogleOAuthSetup-DlaXAoyq.js +236 -0
  8. package/dist/assets/{IframeModal-DJn-dNqr.js → IframeModal-CvJ6LEIu.js} +1 -1
  9. package/dist/assets/IntegrationsPanel-iTByepHj.js +2 -0
  10. package/dist/assets/{LogViewerModal-BgIfdR-l.js → LogViewerModal-C75Yyse9.js} +1 -1
  11. package/dist/assets/{MonitoringModal-Dm8wbcHY.js → MonitoringModal-C3NapNrP.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-CDULU4yk.js → PM2LogsModal-CaacRI13.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-DLuiZj6S.js → RestoreArchivedAreaModal-mwQqJ4qp.js} +1 -1
  14. package/dist/assets/{SaveSnapshotModal-CbfjHVJz.js → SaveSnapshotModal-Didvj3hf.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-Dw9o4Fk8.js → Scene2DCanvas-chSGLBlj.js} +1 -1
  16. package/dist/assets/{SceneManager-KAhHYpUf.js → SceneManager-G0H-cWU4.js} +1 -1
  17. package/dist/assets/{SkillsPanel-BYy3W8hd.js → SkillsPanel-nKzb0E59.js} +1 -1
  18. package/dist/assets/{SnapshotManager-LxlHIjaa.js → SnapshotManager-DoQRZCNb.js} +1 -1
  19. package/dist/assets/SpawnModal-CPTZGYNR.js +1 -0
  20. package/dist/assets/{SubordinateAssignmentModal-zuSTZAqj.js → SubordinateAssignmentModal-B2XP8wZU.js} +1 -1
  21. package/dist/assets/{SupervisorPanel-tTfMa-0x.js → SupervisorPanel-LlgMdGCQ.js} +1 -1
  22. package/dist/assets/{TriggerManagerPanel-BoHdbpqk.js → TriggerManagerPanel-DMiK0_y5.js} +1 -1
  23. package/dist/assets/{WorkflowEditorPanel-Cusd-51G.js → WorkflowEditorPanel-DPwmjV2f.js} +1 -1
  24. package/dist/assets/{index-BoFICHSl.js → index-BCuYxI5F.js} +2 -2
  25. package/dist/assets/{index-BPn1NjZ5.js → index-BIt0Cgpm.js} +1 -1
  26. package/dist/assets/{index-CeMGJMtZ.js → index-BM1s5vTS.js} +3 -3
  27. package/dist/assets/{index-ChZ1qMl3.js → index-CG-I_YeN.js} +1 -1
  28. package/dist/assets/{index-ifiZ65Cu.js → index-CxEHgPe3.js} +1 -1
  29. package/dist/assets/{index-C_3LXJYS.js → index-D33VRpYX.js} +1 -1
  30. package/dist/assets/{index-D3KwWKEv.js → index-DYwDeYnm.js} +1 -1
  31. package/dist/assets/{index-Cxx3kFoj.js → index-XXW1ctrV.js} +1 -1
  32. package/dist/assets/{main-BsOvZp5M.js → main-D-o_EGqJ.js} +35 -35
  33. package/dist/assets/{web-B5moyGVG.js → web-Blb26Kj7.js} +1 -1
  34. package/dist/assets/{web-BYzo_b4X.js → web-hHpxvpTK.js} +1 -1
  35. package/dist/index.html +1 -1
  36. package/dist/src/packages/server/codex/backend.js +5 -1
  37. package/dist/src/packages/server/data/builtin-skills/report-task-to-boss.js +1 -1
  38. package/dist/src/packages/server/data/event-queries.js +15 -0
  39. package/dist/src/packages/server/data/migrations/005_drive_actions.sql +19 -0
  40. package/dist/src/packages/server/index.js +1 -0
  41. package/dist/src/packages/server/integrations/gmail/index.js +8 -6
  42. package/dist/src/packages/server/integrations/google-drive/drive-client.js +605 -0
  43. package/dist/src/packages/server/integrations/google-drive/drive-config.js +127 -0
  44. package/dist/src/packages/server/integrations/google-drive/drive-routes.js +365 -0
  45. package/dist/src/packages/server/integrations/google-drive/drive-skill.js +248 -0
  46. package/dist/src/packages/server/integrations/google-drive/index.js +60 -0
  47. package/dist/src/packages/server/integrations/integration-registry.js +2 -0
  48. package/dist/src/packages/server/routes/agents.js +45 -17
  49. package/dist/src/packages/server/services/agent-service.js +7 -1
  50. package/dist/src/packages/server/services/llm-matcher-service.js +3 -1
  51. package/dist/src/packages/shared/agent-types.js +9 -1
  52. package/package.json +1 -1
  53. package/dist/assets/BossSpawnModal-BknVJp2f.js +0 -1
  54. package/dist/assets/GmailOAuthSetup-CO2LqLvU.js +0 -270
  55. package/dist/assets/GoogleOAuthSetup-OVzavssB.js +0 -203
  56. package/dist/assets/IntegrationsPanel-C0JSiDOw.js +0 -2
  57. package/dist/assets/SpawnModal-Ch7yKsdS.js +0 -1
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Google Drive Skill
3
+ * BuiltinSkillDefinition with curl instructions for agents to manage Drive files.
4
+ */
5
+ export const driveSkill = {
6
+ slug: 'google-drive',
7
+ name: 'Google Drive',
8
+ description: 'Read, create, edit, and search files in Google Drive',
9
+ allowedTools: ['Bash(curl:*)'],
10
+ content: `# Google Drive
11
+
12
+ ## List Files
13
+
14
+ \`\`\`bash
15
+ curl -s "http://localhost:5174/api/drive/files?maxResults=20"
16
+ \`\`\`
17
+
18
+ Query params: \`folderId\`, \`mimeType\`, \`maxResults\`, \`pageToken\`, \`orderBy\` (default: \`modifiedTime desc\`), \`trashed\` (true/false), \`driveId\` (scope to a Shared Drive), \`includeItemsFromAllDrives\` (true/false — include files from all Shared Drives).
19
+
20
+ ## Get File Metadata
21
+
22
+ \`\`\`bash
23
+ curl -s "http://localhost:5174/api/drive/files/FILE_ID"
24
+ \`\`\`
25
+
26
+ ## Read File Content
27
+
28
+ \`\`\`bash
29
+ curl -s "http://localhost:5174/api/drive/files/FILE_ID/content"
30
+ \`\`\`
31
+
32
+ For Google Docs/Sheets/Slides, content is automatically exported as text. Override with \`?exportAs=text/html\` or \`?exportAs=application/pdf\`.
33
+
34
+ Export format defaults:
35
+ - Google Docs -> text/plain
36
+ - Google Sheets -> text/csv
37
+ - Google Slides -> text/plain
38
+
39
+ ## Create a File
40
+
41
+ \`\`\`bash
42
+ curl -s -X POST http://localhost:5174/api/drive/files \\
43
+ -H "Content-Type: application/json" \\
44
+ -d '{"name":"report.txt","content":"File contents here","mimeType":"text/plain"}'
45
+ \`\`\`
46
+
47
+ Optional fields: \`folderId\`, \`description\`, \`agentId\` (for audit logging).
48
+
49
+ To create a Google Doc instead of a plain file, use \`mimeType: "application/vnd.google-apps.document"\` and provide the content as plain text or HTML.
50
+
51
+ ## Copy a File (Create from Template)
52
+
53
+ \`\`\`bash
54
+ curl -s -X POST http://localhost:5174/api/drive/files/TEMPLATE_FILE_ID/copy \\
55
+ -H "Content-Type: application/json" \\
56
+ -d '{"name":"New Document From Template","folderId":"OPTIONAL_DESTINATION_FOLDER"}'
57
+ \`\`\`
58
+
59
+ Duplicates an existing Drive file into a new one. Typically used to instantiate a document from a template (e.g. a Google Doc template). Body fields:
60
+ - \`name\` — optional new name (defaults to \"Copy of <original>\").
61
+ - \`folderId\` — optional destination folder; defaults to the source file's parent.
62
+ - \`description\` — optional description for the copy.
63
+ - \`agentId\`, \`workflowInstanceId\` — optional audit logging fields.
64
+
65
+ Works for both My Drive files and files inside Shared Drives.
66
+
67
+ ## Update a File
68
+
69
+ \`\`\`bash
70
+ curl -s -X PATCH http://localhost:5174/api/drive/files/FILE_ID \\
71
+ -H "Content-Type: application/json" \\
72
+ -d '{"content":"Updated file contents","name":"renamed.txt"}'
73
+ \`\`\`
74
+
75
+ Only include fields you want to change: \`content\`, \`name\`, \`description\`, \`mimeType\`.
76
+
77
+ Note: sending \`content\` overwrites the entire file body. To edit a Google Doc in-place while preserving formatting, use the replace-text endpoint below.
78
+
79
+ ## Move a File
80
+
81
+ \`\`\`bash
82
+ curl -s -X POST http://localhost:5174/api/drive/files/FILE_ID/move \\
83
+ -H "Content-Type: application/json" \\
84
+ -d '{"folderId":"TARGET_FOLDER_ID"}'
85
+ \`\`\`
86
+
87
+ Moves a file between folders, including across My Drive and Shared Drives. Use \`"folderId":"root"\` to move a file into the authenticated user's My Drive root.
88
+
89
+ Body fields:
90
+ - \`folderId\` — required destination folder. Use \`"root"\` for My Drive root.
91
+ - \`removeFromFolderIds\` — optional list of parent folders to detach. If omitted, every current parent is removed so the file ends up only in the destination.
92
+ - \`agentId\`, \`workflowInstanceId\` — optional audit logging fields.
93
+
94
+ ## Find and Replace Text in a Google Doc
95
+
96
+ \`\`\`bash
97
+ curl -s -X POST http://localhost:5174/api/drive/files/FILE_ID/replace-text \\
98
+ -H "Content-Type: application/json" \\
99
+ -d '{"replacements":[{"find":"Título del documento","replace":"My New Title"},{"find":"dd/mm/aaa","replace":"2026-04-16"}]}'
100
+ \`\`\`
101
+
102
+ Convenience wrapper around the Docs API \`replaceAllText\` and \`insertText\` operations. Preserves all existing formatting (fonts, headings, tables, images). Ideal for filling in templates that contain placeholder text.
103
+
104
+ Body fields:
105
+ - \`replacements\` — array of \`{ find, replace, matchCase? }\` pairs. Each pair becomes a \`replaceAllText\` operation.
106
+ - \`appendText\` — optional plain text appended to the end of the document after all replacements run.
107
+ - \`agentId\`, \`workflowInstanceId\` — optional audit logging fields.
108
+
109
+ Response: \`{ fileId, totalOccurrencesChanged, appended }\`.
110
+
111
+ Only works on native Google Docs (\`application/vnd.google-apps.document\`). For non-Doc files, use \`PATCH /api/drive/files/FILE_ID\` instead.
112
+
113
+ ## Google Docs API (Advanced)
114
+
115
+ For fine-grained control over Google Docs (beyond simple find/replace), use the Docs API passthrough endpoints. These operate only on native Google Docs.
116
+
117
+ ### Create a Blank Google Doc
118
+
119
+ \`\`\`bash
120
+ curl -s -X POST http://localhost:5174/api/drive/docs \\
121
+ -H "Content-Type: application/json" \\
122
+ -d '{"title":"My New Document"}'
123
+ \`\`\`
124
+
125
+ Creates a blank Google Doc via the Docs API. Returns the same DriveFile shape as \`POST /files\`. To create a Doc with initial content converted from HTML or plain text, use \`POST /files\` with \`mimeType: "application/vnd.google-apps.document"\` instead.
126
+
127
+ ### Get the Structured Document
128
+
129
+ \`\`\`bash
130
+ curl -s "http://localhost:5174/api/drive/docs/DOC_ID"
131
+ \`\`\`
132
+
133
+ Returns the full Docs API \`Document\` resource: body (with content elements — paragraphs, tables, section breaks), headers, footers, footnotes, inline objects (images, drawings), positioned objects, lists, named ranges, named styles, document style, revision metadata. Much richer than \`GET /files/:fileId/content\` (which only returns exported plain text).
134
+
135
+ Useful for:
136
+ - Finding exact insertion indexes for \`batch-update\` operations
137
+ - Extracting the document outline (headings, table of contents)
138
+ - Introspecting table structure, list IDs, named ranges
139
+
140
+ ### Batch Update (Full Docs API Access)
141
+
142
+ \`\`\`bash
143
+ curl -s -X POST http://localhost:5174/api/drive/docs/DOC_ID/batch-update \\
144
+ -H "Content-Type: application/json" \\
145
+ -d '{"requests":[{"insertText":{"location":{"index":1},"text":"Hello world"}}]}'
146
+ \`\`\`
147
+
148
+ Generic passthrough to \`documents.batchUpdate\`. Accepts the raw Docs API \`requests\` array, so every Docs API mutation type is supported with no additional server code.
149
+
150
+ Body fields:
151
+ - \`requests\` — array of Docs API request objects (required, non-empty).
152
+ - \`writeControl\` — optional \`{ requiredRevisionId }\` or \`{ targetRevisionId }\` for optimistic concurrency.
153
+ - \`agentId\`, \`workflowInstanceId\` — optional audit logging fields.
154
+
155
+ Response: \`{ documentId, replies, writeControl }\`. The \`replies\` array matches the \`requests\` array positionally — some request types (createNamedRange, insertInlineImage, createFootnote) return IDs in their reply.
156
+
157
+ **Common request types** (see the Google Docs API reference for the full list):
158
+
159
+ | Category | Request Types |
160
+ |---|---|
161
+ | Text | \`insertText\`, \`deleteContentRange\`, \`replaceAllText\` |
162
+ | Formatting | \`updateTextStyle\`, \`updateParagraphStyle\`, \`updateDocumentStyle\`, \`updateSectionStyle\` |
163
+ | Structure | \`insertSectionBreak\`, \`insertPageBreak\`, \`insertTable\`, \`insertTableRow\`, \`insertTableColumn\`, \`deleteTableRow\`, \`deleteTableColumn\`, \`updateTableCellStyle\`, \`updateTableColumnProperties\`, \`updateTableRowStyle\`, \`mergeTableCells\`, \`unmergeTableCells\`, \`pinTableHeaderRows\` |
164
+ | Lists | \`createParagraphBullets\`, \`deleteParagraphBullets\` |
165
+ | Images | \`insertInlineImage\`, \`replaceImage\` |
166
+ | Headers / Footers | \`createHeader\`, \`createFooter\`, \`deleteHeader\`, \`deleteFooter\` |
167
+ | Footnotes | \`createFootnote\` |
168
+ | Named Ranges | \`createNamedRange\`, \`deleteNamedRange\`, \`replaceNamedRangeContent\` |
169
+ | Positioned Objects | \`deletePositionedObject\` |
170
+
171
+ **Index tips:** Every element in a Doc has a numeric start/end index (1 = body start). Use \`GET /docs/:docId\` to inspect \`body.content[].startIndex\` / \`endIndex\` to target specific locations. Index 1 is before the first character; the body always ends with a trailing newline.
172
+
173
+ **Example — add a bold, centered title at the top:**
174
+ \`\`\`json
175
+ {
176
+ "requests": [
177
+ { "insertText": { "location": { "index": 1 }, "text": "My Title\\n" } },
178
+ { "updateParagraphStyle": { "range": { "startIndex": 1, "endIndex": 10 }, "paragraphStyle": { "namedStyleType": "HEADING_1", "alignment": "CENTER" }, "fields": "namedStyleType,alignment" } },
179
+ { "updateTextStyle": { "range": { "startIndex": 1, "endIndex": 9 }, "textStyle": { "bold": true }, "fields": "bold" } }
180
+ ]
181
+ }
182
+ \`\`\`
183
+
184
+ **Example — insert a 2x3 table at the end of the document:**
185
+ \`\`\`json
186
+ {
187
+ "requests": [
188
+ { "insertTable": { "endOfSegmentLocation": {}, "rows": 2, "columns": 3 } }
189
+ ]
190
+ }
191
+ \`\`\`
192
+
193
+ ## Delete a File
194
+
195
+ \`\`\`bash
196
+ curl -s -X DELETE "http://localhost:5174/api/drive/files/FILE_ID"
197
+ \`\`\`
198
+
199
+ ## Create a Folder
200
+
201
+ \`\`\`bash
202
+ curl -s -X POST http://localhost:5174/api/drive/folders \\
203
+ -H "Content-Type: application/json" \\
204
+ -d '{"name":"Project Files","parentFolderId":"OPTIONAL_PARENT_ID"}'
205
+ \`\`\`
206
+
207
+ ## Search Files
208
+
209
+ \`\`\`bash
210
+ curl -s "http://localhost:5174/api/drive/search?q=quarterly+report&maxResults=10"
211
+ \`\`\`
212
+
213
+ Full-text search across file names and content. Includes results from all Shared Drives the user has access to.
214
+
215
+ ## List Shared Drives (Team Drives)
216
+
217
+ \`\`\`bash
218
+ curl -s "http://localhost:5174/api/drive/drives?maxResults=50"
219
+ \`\`\`
220
+
221
+ Lists every Shared Drive the authenticated user can access. Query params: \`maxResults\`, \`pageToken\`, \`q\` (filter by name), \`useDomainAdminAccess\` (true/false, admin only).
222
+
223
+ Response shape: \`{ drives: SharedDrive[], nextPageToken?: string }\`. Each drive exposes \`driveId\`, \`name\`, \`createdTime\`, \`hidden\`, \`colorRgb\`, \`themeId\`, \`backgroundImageLink\`, and \`capabilities\`.
224
+
225
+ ## Get Shared Drive Metadata
226
+
227
+ \`\`\`bash
228
+ curl -s "http://localhost:5174/api/drive/drives/DRIVE_ID"
229
+ \`\`\`
230
+
231
+ ## List Files Inside a Shared Drive
232
+
233
+ \`\`\`bash
234
+ curl -s "http://localhost:5174/api/drive/files?driveId=DRIVE_ID&maxResults=20"
235
+ \`\`\`
236
+
237
+ Use the \`driveId\` query param to scope file listing to a specific Shared Drive. Combine with \`folderId\` to drill into a folder within that drive.
238
+
239
+ To list files across every Shared Drive the user has access to, set \`includeItemsFromAllDrives=true\` instead of a specific \`driveId\`.
240
+
241
+ ## Notes
242
+ - Auth headers are added automatically by the system.
243
+ - Google Workspace files (Docs, Sheets, Slides) are exported to text when reading content.
244
+ - File IDs can be found in Google Drive URLs or from list/search results.
245
+ - Use \`folderId\` to scope operations to a specific folder.
246
+ - Shared Drives (formerly Team Drives) are distinct from \"My Drive\" folders and require the \`driveId\` param for targeted queries.
247
+ `,
248
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Google Drive Integration Plugin
3
+ * Exports googleDrivePlugin implementing IntegrationPlugin.
4
+ * Shares OAuth2 credentials with Gmail/Calendar plugins via the shared secrets system.
5
+ * No trigger handler — Drive doesn't provide triggers.
6
+ */
7
+ import * as driveClient from './drive-client.js';
8
+ import driveRoutes from './drive-routes.js';
9
+ import { driveSkill } from './drive-skill.js';
10
+ import { driveConfigSchema, getConfigValues, setConfigValues, loadConfig } from './drive-config.js';
11
+ let integrationCtx = null;
12
+ export const googleDrivePlugin = {
13
+ id: 'google-drive',
14
+ name: 'Google Drive',
15
+ description: 'Read, create, and edit files in Google Drive',
16
+ routePrefix: '/drive',
17
+ async init(ctx) {
18
+ integrationCtx = ctx;
19
+ await driveClient.init(ctx);
20
+ },
21
+ async shutdown() {
22
+ await driveClient.shutdown();
23
+ },
24
+ getRoutes() {
25
+ return driveRoutes;
26
+ },
27
+ getSkills() {
28
+ return [driveSkill];
29
+ },
30
+ getTriggerHandler() {
31
+ return null; // Drive doesn't provide triggers
32
+ },
33
+ getStatus() {
34
+ return driveClient.getStatus();
35
+ },
36
+ getConfigSchema() {
37
+ return driveConfigSchema;
38
+ },
39
+ getConfig() {
40
+ if (!integrationCtx) {
41
+ const config = loadConfig();
42
+ return {
43
+ enabled: config.enabled,
44
+ defaultFolderId: config.defaultFolderId,
45
+ GOOGLE_CLIENT_ID: '',
46
+ GOOGLE_CLIENT_SECRET: '',
47
+ GOOGLE_REFRESH_TOKEN: '',
48
+ };
49
+ }
50
+ return getConfigValues(integrationCtx.secrets);
51
+ },
52
+ async setConfig(config) {
53
+ if (!integrationCtx)
54
+ throw new Error('Google Drive not initialized');
55
+ await setConfigValues(config, integrationCtx.secrets);
56
+ },
57
+ getCustomSettingsComponent() {
58
+ return 'google-oauth';
59
+ },
60
+ };
@@ -9,6 +9,7 @@ import { gmailPlugin } from './gmail/index.js';
9
9
  import { docxPlugin } from './docx/index.js';
10
10
  import { jiraPlugin } from './jira/index.js';
11
11
  import { googleCalendarPlugin } from './google-calendar/index.js';
12
+ import { googleDrivePlugin } from './google-drive/index.js';
12
13
  const log = createLogger('Integrations');
13
14
  // ─── Plugin Registration ───
14
15
  // Explicit registration — no dynamic scanning.
@@ -17,6 +18,7 @@ const ALL_PLUGINS = [
17
18
  slackPlugin,
18
19
  gmailPlugin,
19
20
  googleCalendarPlugin,
21
+ googleDrivePlugin,
20
22
  docxPlugin,
21
23
  jiraPlugin,
22
24
  ];
@@ -687,46 +687,74 @@ router.post('/:id/report-task', async (req, res) => {
687
687
  res.status(404).json({ error: `Agent not found: ${subordinateId}` });
688
688
  return;
689
689
  }
690
- // Find the boss for this subordinate (via active delegation tracking)
690
+ // Try active delegation tracking first, fall back to agent.bossId if delegation has cleared
691
691
  const delegation = getBossForSubordinate(subordinateId);
692
- if (!delegation) {
693
- res.status(400).json({ error: `No active delegation found for agent ${agent.name}. Not currently working under a boss.` });
694
- return;
695
- }
696
- const bossAgent = agentService.getAgent(delegation.bossId);
697
- const bossName = bossAgent?.name || delegation.bossId;
692
+ const resolvedBossId = delegation?.bossId || agent.bossId;
698
693
  const taskStatus = status || 'completed';
699
694
  const success = taskStatus === 'completed';
700
- log.log(`Agent ${agent.name} reporting task ${taskStatus} to boss ${bossName}: "${(summary || '').slice(0, 80)}"`);
695
+ if (!resolvedBossId) {
696
+ log.log(`Agent ${agent.name} reported task ${taskStatus} but has no boss association. Accepting and logging: "${(summary || '').slice(0, 120)}"`);
697
+ res.status(200).json({
698
+ success: true,
699
+ subordinateId: agent.id,
700
+ subordinateName: agent.name,
701
+ bossId: null,
702
+ bossName: null,
703
+ taskStatus,
704
+ accepted: true,
705
+ forwarded: false,
706
+ reason: 'No active delegation and no recorded boss — report logged only.',
707
+ });
708
+ return;
709
+ }
710
+ const bossAgent = agentService.getAgent(resolvedBossId);
711
+ const bossName = bossAgent?.name || resolvedBossId;
712
+ const taskDescription = delegation?.taskDescription || '(no active delegation record — original task unknown)';
713
+ log.log(`Agent ${agent.name} reporting task ${taskStatus} to boss ${bossName}${delegation ? '' : ' (via fallback bossId, delegation already cleared)'}: "${(summary || '').slice(0, 80)}"`);
701
714
  // 1. Broadcast agent_task_completed to update the progress indicator on the client
702
715
  if (broadcastFn) {
703
716
  broadcastFn({
704
717
  type: 'agent_task_completed',
705
718
  payload: {
706
- bossId: delegation.bossId,
719
+ bossId: resolvedBossId,
707
720
  subordinateId,
708
721
  success,
709
722
  },
710
723
  });
711
724
  }
712
- // 2. Clear the active delegation tracking
725
+ // 2. Clear the active delegation tracking (no-op if already cleared)
713
726
  clearDelegation(subordinateId);
714
- // 3. Send a message to the boss so it knows the task finished and can decide next steps
715
- const reportMessage = `[TASK REPORT from ${agent.name} (${subordinateId})]\n\nStatus: ${taskStatus === 'completed' ? 'COMPLETED' : 'FAILED'}\nOriginal task: ${delegation.taskDescription}\n${summary ? `\nSummary: ${summary}` : ''}\n\nYou may review the result, give follow-up instructions, or dismiss this agent's progress indicator.`;
716
- if (bossAgent?.isBoss || bossAgent?.class === 'boss') {
717
- const { message: bossMessage, systemPrompt } = await bossMessageService.buildBossMessage(delegation.bossId, reportMessage);
718
- await runtimeService.sendCommand(delegation.bossId, bossMessage, systemPrompt);
727
+ // 3. Send a message to the boss so it knows the task finished and can decide next steps.
728
+ // If the boss agent is no longer available, accept the report without failing.
729
+ let forwarded = false;
730
+ if (bossAgent) {
731
+ const reportMessage = `[TASK REPORT from ${agent.name} (${subordinateId})]\n\nStatus: ${taskStatus === 'completed' ? 'COMPLETED' : 'FAILED'}\nOriginal task: ${taskDescription}\n${summary ? `\nSummary: ${summary}` : ''}\n\nYou may review the result, give follow-up instructions, or dismiss this agent's progress indicator.`;
732
+ try {
733
+ if (bossAgent.isBoss || bossAgent.class === 'boss') {
734
+ const { message: bossMessage, systemPrompt } = await bossMessageService.buildBossMessage(resolvedBossId, reportMessage);
735
+ await runtimeService.sendCommand(resolvedBossId, bossMessage, systemPrompt);
736
+ }
737
+ else {
738
+ await runtimeService.sendCommand(resolvedBossId, reportMessage);
739
+ }
740
+ forwarded = true;
741
+ }
742
+ catch (forwardErr) {
743
+ log.error(` Failed to forward task report to boss ${bossName}, accepting report anyway:`, forwardErr);
744
+ }
719
745
  }
720
746
  else {
721
- await runtimeService.sendCommand(delegation.bossId, reportMessage);
747
+ log.log(` Boss agent ${resolvedBossId} not found — accepting report without forwarding.`);
722
748
  }
723
749
  res.status(200).json({
724
750
  success: true,
725
751
  subordinateId: agent.id,
726
752
  subordinateName: agent.name,
727
- bossId: delegation.bossId,
753
+ bossId: resolvedBossId,
728
754
  bossName,
729
755
  taskStatus,
756
+ accepted: true,
757
+ forwarded,
730
758
  });
731
759
  }
732
760
  catch (err) {
@@ -10,7 +10,13 @@ import { listSessions, getSessionSummary, getProjectDir, loadSession, loadToolHi
10
10
  import { loadSubagentHistory } from '../claude/subagent-history-loader.js';
11
11
  import { logger, generateId } from '../utils/index.js';
12
12
  const log = logger.agent;
13
- const CLAUDE_MODELS = new Set(['sonnet', 'opus', 'haiku']);
13
+ const CLAUDE_MODELS = new Set([
14
+ 'sonnet',
15
+ 'opus',
16
+ 'haiku',
17
+ 'claude-opus-4-7',
18
+ 'claude-opus-4-6',
19
+ ]);
14
20
  const DEFAULT_CLAUDE_CONTEXT_LIMIT = 200000;
15
21
  const DEFAULT_CODEX_CONTEXT_LIMIT = 258400;
16
22
  const DEFAULT_OPENCODE_CONTEXT_LIMIT = 200000;
@@ -15,7 +15,9 @@ const TIMEOUT_MS = 15_000;
15
15
  const MODEL_MAP = {
16
16
  haiku: 'claude-haiku-4-5-20251001',
17
17
  sonnet: 'claude-sonnet-4-6-20250514',
18
- opus: 'claude-opus-4-6-20250514',
18
+ opus: 'claude-opus-4-7',
19
+ 'claude-opus-4-7': 'claude-opus-4-7',
20
+ 'claude-opus-4-6': 'claude-opus-4-6-20250514',
19
21
  };
20
22
  function resolveModel(model) {
21
23
  if (!model)
@@ -50,15 +50,23 @@ export const CODEX_MODELS = {
50
50
  };
51
51
  export const CLAUDE_MODELS = {
52
52
  sonnet: { label: 'Sonnet', description: 'Balanced performance and cost (recommended)', icon: '⚡' },
53
- opus: { label: 'Opus', description: 'Most capable, higher cost', icon: '🧠' },
53
+ 'claude-opus-4-7': { label: 'Opus 4.7', description: 'Latest Opus — most capable, highest cost', icon: '🧠' },
54
+ opus: { label: 'Opus (legacy)', description: 'Legacy alias — prefer Opus 4.7', icon: '🧠', deprecated: true },
55
+ 'claude-opus-4-6': { label: 'Opus 4.6', description: 'Previous Opus generation (retained for existing agents)', icon: '🧠', deprecated: true },
54
56
  haiku: { label: 'Haiku', description: 'Fast and economical', icon: '🚀' },
55
57
  };
56
58
  export const CLAUDE_EFFORTS = {
57
59
  low: { label: 'Low', description: 'Minimal reasoning, fastest responses', icon: '🏃' },
58
60
  medium: { label: 'Medium', description: 'Balanced reasoning effort', icon: '⚖️' },
59
61
  high: { label: 'High', description: 'Deep reasoning for complex tasks (default)', icon: '🔬' },
62
+ xHigh: { label: 'X-High', description: 'Extra-high reasoning (Opus 4.7+)', icon: '🧪' },
60
63
  max: { label: 'Max', description: 'Maximum reasoning, most thorough', icon: '🧠' },
61
64
  };
65
+ // Model IDs that should be hidden from the "new agent" model picker.
66
+ // They remain valid ClaudeModel values so existing agents keep working.
67
+ export function isDeprecatedClaudeModel(model) {
68
+ return CLAUDE_MODELS[model]?.deprecated === true;
69
+ }
62
70
  // Boss context delimiters - used to inject subordinate context at the beginning of user messages
63
71
  // The frontend detects these to collapse/hide the context section in the UI
64
72
  export const BOSS_CONTEXT_START = '<<<BOSS_CONTEXT_START>>>';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.47.0",
3
+ "version": "1.49.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- import{u as Ae,f as Le,N as Ee,M as Ie,r as t,U as _e,S as ee,l as De,V as O,A as se,a3 as T,a4 as j,j as s,C as S,X as Be,Z as Re,_ as P,$ as L,a0 as E,a2 as Oe,s as Te}from"./main-BsOvZp5M.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";function ae(m){const h=j.filter(g=>!m.has(`Boss ${g}`));return h.length===0?`Boss ${j[Math.floor(Math.random()*j.length)]}-${Date.now()%1e3}`:`Boss ${h[Math.floor(Math.random()*h.length)]}`}function ze({isOpen:m,onClose:h,onSpawnStart:g,onSpawnEnd:I,spawnPosition:ne}){const{t:n}=Ae(["terminal","common"]),{agents:C}=Le(),o=Ee(),k=Ie(),[r,p]=t.useState(""),[M,le]=t.useState(()=>_e(ee.LAST_CWD)),[c,_]=t.useState("boss"),[U,q]=t.useState(!1),[te,y]=t.useState(!1),[F,ie]=t.useState(!0),[z,ce]=t.useState("bypass"),[d,D]=t.useState("claude"),[b,N]=t.useState({fullAuto:!0,sandbox:"workspace-write",approvalMode:"on-request",search:!1}),[W,oe]=t.useState("opus"),[G,re]=t.useState("gpt-5.3-codex"),[v,$]=t.useState(new Set),[K,B]=t.useState(new Set),[u,de]=t.useState(""),[f,pe]=t.useState(""),[H,ue]=t.useState(""),A=t.useRef(null),R=t.useRef(!1),J=t.useRef(!1),x=t.useMemo(()=>k.filter(e=>e.enabled),[k]),V=t.useMemo(()=>{if(!f.trim())return x;const e=f.toLowerCase();return x.filter(a=>a.name.toLowerCase().includes(e)||a.description.toLowerCase().includes(e)||a.slug.toLowerCase().includes(e))},[x,f]),me=t.useMemo(()=>{var a;const e=o.find(l=>l.id===c);return(a=e==null?void 0:e.defaultSkillIds)!=null&&a.length?k.filter(l=>e.defaultSkillIds.includes(l.id)):[]},[o,c,k]),he=["full-notifications","streaming-exec","task-label","report-task-to-boss"];t.useEffect(()=>{if(m&&!J.current&&x.length>0){const a=x.filter(l=>he.includes(l.slug)).map(l=>l.id);a.length>0&&B(new Set(a))}J.current=m},[m,x]);const we=t.useCallback(e=>{B(a=>{const l=new Set(a);return l.has(e)?l.delete(e):l.add(e),l})},[]),i=t.useMemo(()=>o.find(e=>e.id===c),[o,c]),xe=t.useMemo(()=>{if(i!=null&&i.model)return i.model},[i]),be=t.useMemo(()=>{if(i!=null&&i.customModelPath)return De(`/api/custom-models/${i.id}`)},[i]),fe=i==null?void 0:i.modelScale,je=t.useMemo(()=>i?"scout":c==="boss"?"architect":c,[c,i]),X=Array.from(C.values()).filter(e=>!e.isBoss&&e.class!=="boss"&&!e.bossId),Y=t.useMemo(()=>{if(!u.trim())return o;const e=u.toLowerCase();return o.filter(a=>a.name.toLowerCase().includes(e)||a.description.toLowerCase().includes(e)||a.id.toLowerCase().includes(e))},[o,u]),Z=t.useMemo(()=>{if(!u.trim())return O;const e=u.toLowerCase();return O.filter(a=>{const l=se[a.id];return l?a.name.toLowerCase().includes(e)||a.id.toLowerCase().includes(e)||l.description.toLowerCase().includes(e):!1})},[u]),Q=t.useMemo(()=>{if(!u.trim())return!0;const e=u.toLowerCase(),a=T.boss;return"boss".includes(e)||a.description.toLowerCase().includes(e)},[u]);t.useEffect(()=>{if(m&&!R.current){R.current=!0;const e=new Set(Array.from(C.values()).map(l=>l.name)),a=o.find(l=>l.id===c);if(a){const l=j.filter(w=>!e.has(`${a.name} ${w}`));if(l.length===0){const w=j[Math.floor(Math.random()*j.length)];p(`${a.name} ${w}-${Date.now()%1e3}`)}else p(`${a.name} ${l[Math.floor(Math.random()*l.length)]}`)}else p(ae(e));$(new Set),A.current&&(A.current.focus(),A.current.select())}else m||(R.current=!1)},[m,C]),t.useEffect(()=>{if(!m)return;const e=o.find(a=>a.id===c);if(e)if(r.startsWith("Boss ")){const a=r.substring(5);p(`${e.name} ${a}`)}else{const a=o.find(l=>r.startsWith(l.name+" "));if(a){const l=r.substring(a.name.length+1);p(`${e.name} ${l}`)}else p(`${e.name} ${r}`)}else{const a=o.find(l=>r.startsWith(l.name+" "));if(a){const l=r.substring(a.name.length+1);p(`Boss ${l}`)}else r.startsWith("Boss ")||p(`Boss ${r}`)}},[c]);const Ne=()=>{if(y(!1),!M.trim()){y(!0);return}if(!r.trim()){const l=new Set(Array.from(C.values()).map(w=>w.name));p(ae(l));return}Oe(ee.LAST_CWD,M),g();const e=H.trim()||void 0,a=Array.from(K);Te.spawnBossAgent(r.trim(),c,M.trim(),ne||void 0,Array.from(v),d==="claude"?F:!1,z,d,d==="codex"?b:void 0,d==="codex"?G:void 0,d==="claude"?W:void 0,e,a),p(""),$(new Set),B(new Set),I(),h()},ve=()=>{q(!1),p(""),$(new Set),I(),h()},Se=()=>{q(!1),y(!0),I()};t.useEffect(()=>(window.__bossSpawnModalSuccess=ve,window.__bossSpawnModalError=Se,()=>{delete window.__bossSpawnModalSuccess,delete window.__bossSpawnModalError}),[r]);const ge=e=>{e.target===e.currentTarget&&h()},Ce=e=>{e.key==="Escape"&&h()},ke=e=>{const a=new Set(v);a.has(e)?a.delete(e):a.add(e),$(a)};if(!m)return null;const Me=T.boss;return s.jsx("div",{className:`modal-overlay ${m?"visible":""}`,onClick:ge,onKeyDown:Ce,children:s.jsxs("div",{className:"modal boss-spawn-modal",children:[s.jsxs("div",{className:"modal-header",children:[s.jsx(S,{classId:"boss",size:22,className:"boss-header-icon"}),n("terminal:spawn.deployBossTitle")]}),s.jsxs("div",{className:"modal-body spawn-modal-body",children:[s.jsxs("div",{className:"spawn-top-section",children:[s.jsx("div",{className:"spawn-preview-compact",children:s.jsx(Be,{agentClass:je,modelFile:xe,customModelUrl:be,modelScale:fe,width:100,height:120})}),s.jsxs("div",{className:"spawn-class-section",children:[s.jsx("div",{className:"spawn-class-label",children:n("terminal:spawn.bossClass")}),o.length+O.length+1>6&&s.jsx("input",{type:"text",className:"spawn-input class-search-input",placeholder:n("terminal:spawn.filterClasses"),value:u,onChange:e=>de(e.target.value)}),s.jsxs("div",{className:"class-selector-inline",children:[Y.map(e=>s.jsxs("button",{className:`class-chip ${c===e.id?"selected":""}`,onClick:()=>_(e.id),title:e.description,children:[s.jsx(S,{classId:e.id,size:18,className:"class-chip-icon"}),s.jsx("span",{className:"class-chip-name",children:e.name})]},e.id)),Q&&s.jsxs("button",{className:`class-chip ${c==="boss"?"selected":""}`,onClick:()=>_("boss"),title:Me.description,children:[s.jsx(S,{classId:"boss",size:18,className:"class-chip-icon"}),s.jsx("span",{className:"class-chip-name",children:n("terminal:spawn.bossClassName")})]}),Z.map(e=>{const a=se[e.id];return a?s.jsxs("button",{className:`class-chip ${c===e.id?"selected":""}`,onClick:()=>_(e.id),title:a.description,children:[s.jsx(S,{classId:e.id,size:18,className:"class-chip-icon"}),s.jsx("span",{className:"class-chip-name",children:e.name})]},e.id):null}),u&&Y.length===0&&!Q&&Z.length===0&&s.jsx("div",{className:"class-search-empty",children:n("terminal:spawn.noClassesMatch",{query:u})})]})]})]}),s.jsxs("div",{className:"spawn-form-section",children:[s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.name")}),s.jsx("input",{ref:A,type:"text",className:"spawn-input",placeholder:n("terminal:spawn.bossNamePlaceholder"),value:r,onChange:e=>p(e.target.value)})]}),s.jsxs("div",{className:"spawn-field spawn-field-wide",children:[s.jsx("label",{className:"spawn-label",children:n("terminal:spawn.workingDir")}),s.jsx(Re,{value:M,onChange:e=>{le(e),y(!1)},placeholder:n("terminal:spawn.workingDirPlaceholder"),className:"spawn-input",hasError:te,directoriesOnly:!0})]})]}),s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.runtime")}),s.jsxs("div",{className:"spawn-select-row",children:[s.jsxs("button",{className:`spawn-select-btn ${d==="claude"?"selected":""}`,onClick:()=>D("claude"),title:n("terminal:spawn.useClaudeCli"),children:[s.jsx("img",{src:"/assets/claude.ico",alt:"Claude",className:"spawn-provider-icon"}),s.jsx("span",{children:"Claude"})]}),s.jsxs("button",{className:`spawn-select-btn ${d==="codex"?"selected":""}`,onClick:()=>D("codex"),title:n("terminal:spawn.useCodexCli"),children:[s.jsx("img",{src:"/assets/codex.ico",alt:"Codex",className:"spawn-provider-icon"}),s.jsx("span",{children:"Codex"})]}),s.jsxs("button",{className:`spawn-select-btn spawn-select-btn--opencode ${d==="opencode"?"selected":""}`,onClick:()=>D("opencode"),title:"Use OpenCode CLI (multi-provider)",children:[s.jsx("span",{children:"🟢"}),s.jsx("span",{children:"OpenCode"})]})]})]}),s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.permissions")}),s.jsx("div",{className:"spawn-select-row",children:Object.keys(P).map(e=>s.jsxs("button",{className:`spawn-select-btn ${z===e?"selected":""}`,onClick:()=>ce(e),title:P[e].description,children:[s.jsx("span",{children:e==="bypass"?"⚡":"🔐"}),s.jsx("span",{children:P[e].label})]},e))})]})]}),s.jsxs("div",{className:"spawn-form-row",children:[s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.model")}),d==="claude"?s.jsx("div",{className:"spawn-select-row",children:Object.keys(L).map(e=>s.jsxs("button",{className:`spawn-select-btn ${W===e?"selected":""}`,onClick:()=>oe(e),title:L[e].description,children:[s.jsx("span",{children:L[e].icon}),s.jsx("span",{children:L[e].label})]},e))}):d==="codex"?s.jsx("div",{className:"spawn-select-row spawn-select-row--codex-models",children:Object.keys(E).map(e=>s.jsxs("button",{className:`spawn-select-btn ${G===e?"selected":""}`,onClick:()=>re(e),title:E[e].description,children:[s.jsx("span",{children:E[e].icon}),s.jsx("span",{children:E[e].label})]},e))}):d==="opencode"?s.jsx("input",{type:"text",className:"spawn-input",defaultValue:"minimax/MiniMax-M1-80k",placeholder:"provider/model (e.g., minimax/MiniMax-M1-80k)"}):s.jsx("div",{className:"spawn-inline-hint",children:n("terminal:spawn.codex.configuration")})]}),s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("common:labels.browser")}),s.jsx("div",{className:"spawn-form-row spawn-options-row",children:s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:F,onChange:e=>ie(e.target.checked),disabled:d!=="claude"}),s.jsx("span",{children:n("terminal:spawn.chromeBrowser")})]})})]})]}),d==="codex"&&s.jsx("div",{className:"spawn-form-row",children:s.jsxs("div",{className:"spawn-field",children:[s.jsx("label",{className:"spawn-label",children:n("terminal:spawn.codex.config")}),s.jsxs("div",{className:"spawn-options-row",style:{display:"flex",flexDirection:"column",gap:8},children:[s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:b.fullAuto!==!1,onChange:e=>N(a=>({...a,fullAuto:e.target.checked}))}),s.jsx("span",{children:n("terminal:spawn.codex.useFullAuto")})]}),s.jsxs("label",{className:"spawn-checkbox",children:[s.jsx("input",{type:"checkbox",checked:!!b.search,onChange:e=>N(a=>({...a,search:e.target.checked}))}),s.jsx("span",{children:n("terminal:spawn.codex.enableSearch")})]}),b.fullAuto===!1&&s.jsxs(s.Fragment,{children:[s.jsxs("select",{className:"spawn-input",value:b.sandbox||"workspace-write",onChange:e=>N(a=>({...a,sandbox:e.target.value})),children:[s.jsx("option",{value:"read-only",children:n("terminal:spawn.codex.sandboxReadOnly")}),s.jsx("option",{value:"workspace-write",children:n("terminal:spawn.codex.sandboxWorkspaceWrite")}),s.jsx("option",{value:"danger-full-access",children:n("terminal:spawn.codex.sandboxDangerFullAccess")})]}),s.jsxs("select",{className:"spawn-input",value:b.approvalMode||"on-request",onChange:e=>N(a=>({...a,approvalMode:e.target.value})),children:[s.jsx("option",{value:"untrusted",children:n("terminal:spawn.codex.approvalsUntrusted")}),s.jsx("option",{value:"on-failure",children:n("terminal:spawn.codex.approvalsOnFailure")}),s.jsx("option",{value:"on-request",children:n("terminal:spawn.codex.approvalsOnRequest")}),s.jsx("option",{value:"never",children:n("terminal:spawn.codex.approvalsNever")})]})]}),s.jsx("input",{type:"text",className:"spawn-input",placeholder:n("terminal:spawn.codex.profileOptional"),value:b.profile||"",onChange:e=>N(a=>({...a,profile:e.target.value||void 0}))})]})]})}),x.length>0&&s.jsxs("div",{className:"spawn-skills-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.skills")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),x.length>6&&s.jsx("input",{type:"text",className:"spawn-input skill-search-input",placeholder:n("terminal:spawn.filterSkills"),value:f,onChange:e=>pe(e.target.value)}),s.jsxs("div",{className:"spawn-skills-inline",children:[V.map(e=>{const a=K.has(e.id);return me.some(w=>w.id===e.id)?null:s.jsxs("button",{className:`spawn-skill-chip ${a?"selected":""}`,onClick:()=>we(e.id),title:e.description,children:[a&&s.jsx("span",{className:"spawn-skill-check",children:"✓"}),s.jsx("span",{children:e.name}),e.builtin&&s.jsx("span",{className:"spawn-skill-builtin",children:"TC"})]},e.id)}),f&&V.length===0&&s.jsx("div",{className:"skill-search-empty",children:n("terminal:spawn.noSkillsMatch",{query:f})})]})]}),s.jsxs("div",{className:"spawn-custom-instructions-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.customInstructions")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),s.jsx("textarea",{className:"spawn-input spawn-textarea",placeholder:n("terminal:spawn.customInstructionsBossPlaceholder"),value:H,onChange:e=>ue(e.target.value),rows:3})]}),s.jsxs("div",{className:"spawn-subordinates-section",children:[s.jsxs("label",{className:"spawn-label",children:[n("terminal:spawn.initialSubordinates")," ",s.jsxs("span",{className:"spawn-label-hint",children:["(",n("common:labels.optional"),")"]})]}),s.jsx("div",{className:"subordinates-selector-compact",children:X.length===0?s.jsx("div",{className:"subordinates-empty",children:n("terminal:spawn.noAvailableAgents")}):X.map(e=>{const a=v.has(e.id),l=T[e.class],w=o.find($e=>$e.id===e.class),ye=l||w||{color:"#888888"};return s.jsxs("button",{className:`subordinate-chip ${a?"selected":""}`,onClick:()=>ke(e.id),children:[a&&s.jsx("span",{className:"subordinate-check",children:"✓"}),s.jsx("span",{className:"subordinate-chip-icon",style:{color:ye.color},children:s.jsx(S,{classId:e.class,size:16})}),s.jsx("span",{className:"subordinate-chip-name",children:e.name})]},e.id)})}),v.size>0&&s.jsxs("div",{className:"subordinates-count",children:[v.size," ",n("common:labels.selected").toLowerCase()]})]})]})]}),s.jsxs("div",{className:"modal-footer",children:[s.jsx("button",{className:"btn btn-secondary",onClick:h,children:n("common:buttons.cancel")}),s.jsx("button",{className:"btn btn-boss",onClick:Ne,disabled:U,children:n(U?"common:buttons.deploying":"common:buttons2.deployBoss")})]})]})})}export{ze as BossSpawnModal};