crewly 1.20.29 → 1.20.31

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 (112) hide show
  1. package/config/skills/agent/core/canva-create/SKILL.md +53 -0
  2. package/config/skills/agent/core/canva-create/execute.sh +88 -0
  3. package/config/skills/agent/core/canva-create/execute.test.sh +54 -0
  4. package/config/skills/agent/core/canva-designs/SKILL.md +51 -0
  5. package/config/skills/agent/core/canva-designs/execute.sh +96 -0
  6. package/config/skills/agent/core/canva-designs/execute.test.sh +54 -0
  7. package/config/skills/agent/core/canva-export/SKILL.md +52 -0
  8. package/config/skills/agent/core/canva-export/execute.sh +111 -0
  9. package/config/skills/agent/core/canva-export/execute.test.sh +55 -0
  10. package/config/skills/agent/core/canva-upload/SKILL.md +49 -0
  11. package/config/skills/agent/core/canva-upload/execute.sh +72 -0
  12. package/config/skills/agent/core/canva-upload/execute.test.sh +54 -0
  13. package/config/skills/agent/core/docs-read/SKILL.md +50 -0
  14. package/config/skills/agent/core/docs-read/execute.sh +68 -0
  15. package/config/skills/agent/core/docs-read/execute.test.sh +52 -0
  16. package/config/skills/agent/core/docs-write/SKILL.md +55 -0
  17. package/config/skills/agent/core/docs-write/execute.sh +90 -0
  18. package/config/skills/agent/core/docs-write/execute.test.sh +54 -0
  19. package/config/skills/agent/core/drive-read/SKILL.md +53 -0
  20. package/config/skills/agent/core/drive-read/execute.sh +79 -0
  21. package/config/skills/agent/core/drive-read/execute.test.sh +55 -0
  22. package/config/skills/agent/core/drive-search/SKILL.md +55 -0
  23. package/config/skills/agent/core/drive-search/execute.sh +87 -0
  24. package/config/skills/agent/core/drive-search/execute.test.sh +54 -0
  25. package/config/skills/agent/core/drive-upload/SKILL.md +54 -0
  26. package/config/skills/agent/core/drive-upload/execute.sh +117 -0
  27. package/config/skills/agent/core/drive-upload/execute.test.sh +57 -0
  28. package/config/skills/agent/core/sheets-read/SKILL.md +53 -0
  29. package/config/skills/agent/core/sheets-read/execute.sh +81 -0
  30. package/config/skills/agent/core/sheets-read/execute.test.sh +52 -0
  31. package/config/skills/agent/core/sheets-write/SKILL.md +56 -0
  32. package/config/skills/agent/core/sheets-write/execute.sh +103 -0
  33. package/config/skills/agent/core/sheets-write/execute.test.sh +57 -0
  34. package/config/skills/agent/core/slides-create/SKILL.md +61 -0
  35. package/config/skills/agent/core/slides-create/execute.sh +92 -0
  36. package/config/skills/agent/core/slides-create/execute.test.sh +54 -0
  37. package/config/skills/agent/core/slides-read/SKILL.md +49 -0
  38. package/config/skills/agent/core/slides-read/execute.sh +67 -0
  39. package/config/skills/agent/core/slides-read/execute.test.sh +50 -0
  40. package/dist/backend/backend/src/constants.d.ts +69 -0
  41. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  42. package/dist/backend/backend/src/constants.js +73 -0
  43. package/dist/backend/backend/src/constants.js.map +1 -1
  44. package/dist/backend/backend/src/controllers/canva/canva.controller.d.ts +48 -0
  45. package/dist/backend/backend/src/controllers/canva/canva.controller.d.ts.map +1 -0
  46. package/dist/backend/backend/src/controllers/canva/canva.controller.js +183 -0
  47. package/dist/backend/backend/src/controllers/canva/canva.controller.js.map +1 -0
  48. package/dist/backend/backend/src/controllers/canva/canva.routes.d.ts +22 -0
  49. package/dist/backend/backend/src/controllers/canva/canva.routes.d.ts.map +1 -0
  50. package/dist/backend/backend/src/controllers/canva/canva.routes.js +34 -0
  51. package/dist/backend/backend/src/controllers/canva/canva.routes.js.map +1 -0
  52. package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
  53. package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
  54. package/dist/backend/backend/src/controllers/google/google.controller.js +237 -1
  55. package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
  56. package/dist/backend/backend/src/controllers/google/google.routes.d.ts +13 -0
  57. package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
  58. package/dist/backend/backend/src/controllers/google/google.routes.js +27 -1
  59. package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
  60. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  61. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  62. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  63. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/agent/agent-registration.service.js +22 -4
  65. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  66. package/dist/backend/backend/src/services/canva/canva-token.service.d.ts +98 -0
  67. package/dist/backend/backend/src/services/canva/canva-token.service.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/canva/canva-token.service.js +198 -0
  69. package/dist/backend/backend/src/services/canva/canva-token.service.js.map +1 -0
  70. package/dist/backend/backend/src/services/canva/canva.service.d.ts +184 -0
  71. package/dist/backend/backend/src/services/canva/canva.service.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/canva/canva.service.js +274 -0
  73. package/dist/backend/backend/src/services/canva/canva.service.js.map +1 -0
  74. package/dist/backend/backend/src/services/google/docs.service.d.ts +117 -0
  75. package/dist/backend/backend/src/services/google/docs.service.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/google/docs.service.js +152 -0
  77. package/dist/backend/backend/src/services/google/docs.service.js.map +1 -0
  78. package/dist/backend/backend/src/services/google/drive.service.d.ts +157 -0
  79. package/dist/backend/backend/src/services/google/drive.service.d.ts.map +1 -0
  80. package/dist/backend/backend/src/services/google/drive.service.js +203 -0
  81. package/dist/backend/backend/src/services/google/drive.service.js.map +1 -0
  82. package/dist/backend/backend/src/services/google/google-api.client.d.ts +15 -7
  83. package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/google/google-api.client.js +13 -6
  85. package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
  86. package/dist/backend/backend/src/services/google/sheets.service.d.ts +118 -0
  87. package/dist/backend/backend/src/services/google/sheets.service.d.ts.map +1 -0
  88. package/dist/backend/backend/src/services/google/sheets.service.js +158 -0
  89. package/dist/backend/backend/src/services/google/sheets.service.js.map +1 -0
  90. package/dist/backend/backend/src/services/google/slides.service.d.ts +136 -0
  91. package/dist/backend/backend/src/services/google/slides.service.d.ts.map +1 -0
  92. package/dist/backend/backend/src/services/google/slides.service.js +153 -0
  93. package/dist/backend/backend/src/services/google/slides.service.js.map +1 -0
  94. package/dist/backend/backend/src/utils/tool-call-markup.utils.d.ts +45 -0
  95. package/dist/backend/backend/src/utils/tool-call-markup.utils.d.ts.map +1 -0
  96. package/dist/backend/backend/src/utils/tool-call-markup.utils.js +83 -0
  97. package/dist/backend/backend/src/utils/tool-call-markup.utils.js.map +1 -0
  98. package/dist/backend/build-info.json +2 -2
  99. package/dist/cli/backend/src/constants.d.ts +69 -0
  100. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  101. package/dist/cli/backend/src/constants.js +73 -0
  102. package/dist/cli/backend/src/constants.js.map +1 -1
  103. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  104. package/dist/cli/backend/src/services/agent/agent-registration.service.js +22 -4
  105. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  106. package/dist/cli/backend/src/utils/tool-call-markup.utils.d.ts +45 -0
  107. package/dist/cli/backend/src/utils/tool-call-markup.utils.d.ts.map +1 -0
  108. package/dist/cli/backend/src/utils/tool-call-markup.utils.js +83 -0
  109. package/dist/cli/backend/src/utils/tool-call-markup.utils.js.map +1 -0
  110. package/frontend/dist/assets/{index-d1bd0dec.js → index-96b51cd9.js} +243 -243
  111. package/frontend/dist/index.html +1 -1
  112. package/package.json +1 -1
@@ -0,0 +1,158 @@
1
+ /**
2
+ * SheetsService — Google Sheets over the owner's Workspace grant.
3
+ *
4
+ * Reads any spreadsheet the owner can see (`drive.readonly`), creates new
5
+ * spreadsheets and writes to the ones Crewly created (`drive.file`).
6
+ * Writing to a spreadsheet made elsewhere needs the `spreadsheets` scope
7
+ * the grant does not carry yet; Google answers 403 and the error says so.
8
+ *
9
+ * @module services/google/sheets.service
10
+ */
11
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
12
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
13
+ import { buildGoogleUrl, googleRequest } from './google-api.client.js';
14
+ import { requireId } from './drive.service.js';
15
+ /**
16
+ * Validate rows for a write.
17
+ *
18
+ * @param rows - Candidate rows
19
+ * @returns The rows
20
+ * @throws GoogleWorkspaceError(400, validation) when empty, not rectangular-ish, or too many
21
+ */
22
+ export function requireRows(rows) {
23
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
24
+ if (!Array.isArray(rows) || rows.length === 0)
25
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"rows" must be a non-empty array of arrays');
26
+ if (rows.length > GOOGLE_WORKSPACE_CONSTANTS.SHEETS_MAX_ROWS) {
27
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, `"rows" exceeds ${GOOGLE_WORKSPACE_CONSTANTS.SHEETS_MAX_ROWS}`);
28
+ }
29
+ for (const row of rows) {
30
+ if (!Array.isArray(row))
31
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, 'each row must be an array');
32
+ for (const cell of row) {
33
+ if (cell !== null && cell !== undefined && !['string', 'number', 'boolean'].includes(typeof cell)) {
34
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, 'cells must be strings, numbers or booleans');
35
+ }
36
+ }
37
+ }
38
+ return rows.map((row) => row.map((cell) => (cell === null || cell === undefined ? '' : cell)));
39
+ }
40
+ /**
41
+ * Sheets: read, create, append, update.
42
+ */
43
+ export class SheetsService {
44
+ deps;
45
+ base = GOOGLE_WORKSPACE_CONSTANTS.SHEETS_API_BASE;
46
+ /**
47
+ * @param deps - Token provider and optional fetch override
48
+ */
49
+ constructor(deps) {
50
+ this.deps = deps;
51
+ }
52
+ /**
53
+ * `spreadsheets.get` (properties only) — title and the sheet tabs.
54
+ *
55
+ * @param id - Spreadsheet id
56
+ * @returns Metadata
57
+ */
58
+ async info(id) {
59
+ const spreadsheetId = requireId(id);
60
+ const wire = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/spreadsheets/${encodeURIComponent(spreadsheetId)}`, {
61
+ fields: 'spreadsheetId,properties.title,spreadsheetUrl,sheets.properties(title,gridProperties(rowCount,columnCount))',
62
+ }));
63
+ return toInfo(wire);
64
+ }
65
+ /**
66
+ * `spreadsheets.values.get`.
67
+ *
68
+ * @param id - Spreadsheet id
69
+ * @param range - A1 range (default `A1:Z1000` on the first sheet)
70
+ * @returns The values (rows may be ragged)
71
+ */
72
+ async read(id, range) {
73
+ const spreadsheetId = requireId(id);
74
+ const a1 = (range ?? '').trim() || GOOGLE_WORKSPACE_CONSTANTS.SHEETS_DEFAULT_RANGE;
75
+ const data = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/spreadsheets/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(a1)}`, {
76
+ valueRenderOption: 'UNFORMATTED_VALUE',
77
+ dateTimeRenderOption: 'FORMATTED_STRING',
78
+ }));
79
+ return { spreadsheetId, range: data.range ?? a1, rows: data.values ?? [] };
80
+ }
81
+ /**
82
+ * `spreadsheets.create`, then `values.update` from A1 when rows are given.
83
+ *
84
+ * @param input - Title, first-sheet name, initial rows
85
+ * @returns The new spreadsheet
86
+ * @throws GoogleWorkspaceError(400, validation) without a title
87
+ */
88
+ async create(input) {
89
+ const title = (input.title ?? '').trim();
90
+ if (!title)
91
+ throw new GoogleWorkspaceError(400, GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES.VALIDATION, '"title" is required');
92
+ const sheetTitle = (input.sheetTitle ?? '').trim() || 'Sheet1';
93
+ const created = await googleRequest(this.deps, `${this.base}/spreadsheets`, {
94
+ method: 'POST',
95
+ body: { properties: { title }, sheets: [{ properties: { title: sheetTitle } }] },
96
+ });
97
+ const id = created.spreadsheetId ?? '';
98
+ if (input.rows && input.rows.length > 0) {
99
+ await this.update({ spreadsheetId: id, range: `${quoteSheet(sheetTitle)}!A1`, rows: input.rows });
100
+ }
101
+ return toInfo(created);
102
+ }
103
+ /**
104
+ * `spreadsheets.values.append` — rows go below the last row of the
105
+ * table that contains `range` (default: the first sheet).
106
+ *
107
+ * @param input - Spreadsheet, range, rows
108
+ * @returns Updated range and counts
109
+ */
110
+ async append(input) {
111
+ const spreadsheetId = requireId(input.spreadsheetId);
112
+ const rows = requireRows(input.rows);
113
+ const a1 = (input.range ?? '').trim() || 'A1';
114
+ const data = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/spreadsheets/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(a1)}:append`, {
115
+ valueInputOption: 'USER_ENTERED',
116
+ insertDataOption: 'INSERT_ROWS',
117
+ }), { method: 'POST', body: { values: rows } });
118
+ return { spreadsheetId, updatedRange: data.updates?.updatedRange ?? a1, updatedRows: data.updates?.updatedRows ?? rows.length };
119
+ }
120
+ /**
121
+ * `spreadsheets.values.update` — overwrite starting at `range`.
122
+ *
123
+ * @param input - Spreadsheet, anchor range, rows
124
+ * @returns Updated range and counts
125
+ */
126
+ async update(input) {
127
+ const spreadsheetId = requireId(input.spreadsheetId);
128
+ const rows = requireRows(input.rows);
129
+ const a1 = (input.range ?? '').trim() || 'A1';
130
+ const data = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/spreadsheets/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(a1)}`, {
131
+ valueInputOption: 'USER_ENTERED',
132
+ }), { method: 'PUT', body: { values: rows } });
133
+ return { spreadsheetId, updatedRange: data.updatedRange ?? a1, updatedRows: data.updatedRows ?? rows.length };
134
+ }
135
+ }
136
+ function toInfo(wire) {
137
+ const id = wire.spreadsheetId ?? '';
138
+ return {
139
+ id,
140
+ title: wire.properties?.title ?? '',
141
+ sheets: (wire.sheets ?? []).map((s) => ({
142
+ title: s.properties?.title ?? '',
143
+ ...(s.properties?.gridProperties?.rowCount !== undefined ? { rowCount: s.properties.gridProperties.rowCount } : {}),
144
+ ...(s.properties?.gridProperties?.columnCount !== undefined ? { columnCount: s.properties.gridProperties.columnCount } : {}),
145
+ })),
146
+ webViewLink: wire.spreadsheetUrl ?? `https://docs.google.com/spreadsheets/d/${id}/edit`,
147
+ };
148
+ }
149
+ /**
150
+ * Quote a sheet name for an A1 range when it needs it.
151
+ *
152
+ * @param name - Sheet title
153
+ * @returns `'My Sheet'` or `Sheet1`
154
+ */
155
+ export function quoteSheet(name) {
156
+ return /^[A-Za-z0-9_]+$/.test(name) ? name : `'${name.replace(/'/g, "''")}'`;
157
+ }
158
+ //# sourceMappingURL=sheets.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheets.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/sheets.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * SlidesService — Google Slides over the owner's Workspace grant.
3
+ *
4
+ * Reads any presentation the owner can see (`drive.readonly`) and builds
5
+ * new decks from an outline (`drive.file`). Each outline slide becomes a
6
+ * TITLE_AND_BODY slide with the title and bullet lines; the deck's default
7
+ * first slide is removed so the outline is the whole deck.
8
+ *
9
+ * @module services/google/slides.service
10
+ */
11
+ import { type GoogleApiDeps } from './google-api.client.js';
12
+ /** One slide, flattened. */
13
+ export interface SlideText {
14
+ index: number;
15
+ objectId: string;
16
+ /** Every text line on the slide, in reading order. */
17
+ lines: string[];
18
+ notes?: string;
19
+ }
20
+ /** A presentation, flattened. */
21
+ export interface PresentationText {
22
+ id: string;
23
+ title: string;
24
+ slideCount: number;
25
+ slides: SlideText[];
26
+ webViewLink: string;
27
+ }
28
+ /** One slide of an outline. */
29
+ export interface SlideOutline {
30
+ title: string;
31
+ /** Body lines; each becomes one paragraph (bulleting is the layout's default). */
32
+ bullets?: string[];
33
+ /** Speaker notes are not written (the Slides API needs the notes shape id) — kept for callers' bookkeeping. */
34
+ notes?: string;
35
+ }
36
+ /** Create input. */
37
+ export interface PresentationCreateInput {
38
+ title: string;
39
+ slides: SlideOutline[];
40
+ }
41
+ interface WireTextElement {
42
+ textRun?: {
43
+ content?: string;
44
+ };
45
+ }
46
+ interface WirePageElement {
47
+ shape?: {
48
+ text?: {
49
+ textElements?: WireTextElement[];
50
+ };
51
+ };
52
+ table?: {
53
+ tableRows?: Array<{
54
+ tableCells?: Array<{
55
+ text?: {
56
+ textElements?: WireTextElement[];
57
+ };
58
+ }>;
59
+ }>;
60
+ };
61
+ elementGroup?: {
62
+ children?: WirePageElement[];
63
+ };
64
+ }
65
+ interface WirePage {
66
+ objectId?: string;
67
+ pageElements?: WirePageElement[];
68
+ slideProperties?: {
69
+ notesPage?: {
70
+ pageElements?: WirePageElement[];
71
+ };
72
+ };
73
+ }
74
+ interface WirePresentation {
75
+ presentationId?: string;
76
+ title?: string;
77
+ slides?: WirePage[];
78
+ }
79
+ /**
80
+ * Lines of text in a page element (shape text, table cells, groups).
81
+ *
82
+ * @param el - Page element
83
+ * @returns Non-empty lines
84
+ */
85
+ export declare function elementLines(el: WirePageElement): string[];
86
+ /**
87
+ * Flatten a presentation.
88
+ *
89
+ * @param wire - `presentations.get` payload
90
+ * @returns Title + slides as text
91
+ */
92
+ export declare function toPresentationText(wire: WirePresentation): PresentationText;
93
+ /**
94
+ * The batchUpdate requests that turn an outline into slides.
95
+ *
96
+ * @param outline - Slides to add
97
+ * @param removeSlideId - Object id of the default slide to delete, if any
98
+ * @returns Requests in order (creates, inserts, then the delete)
99
+ */
100
+ export declare function buildSlideRequests(outline: SlideOutline[], removeSlideId?: string): unknown[];
101
+ /**
102
+ * Slides: read, create from outline.
103
+ */
104
+ export declare class SlidesService {
105
+ private readonly deps;
106
+ private readonly base;
107
+ /**
108
+ * @param deps - Token provider and optional fetch override
109
+ */
110
+ constructor(deps: GoogleApiDeps);
111
+ /**
112
+ * `presentations.get`, flattened to text per slide.
113
+ *
114
+ * @param id - Presentation id
115
+ * @returns Title + slides
116
+ */
117
+ read(id: string): Promise<PresentationText>;
118
+ /**
119
+ * `presentations.create` + one `batchUpdate` that adds every outline
120
+ * slide and removes the default blank one.
121
+ *
122
+ * @param input - Title and outline
123
+ * @returns The new deck, flattened (as read back from Google)
124
+ * @throws GoogleWorkspaceError(400, validation) without a title / slides, or too many slides
125
+ */
126
+ create(input: PresentationCreateInput): Promise<PresentationText>;
127
+ }
128
+ /**
129
+ * Browser link for a presentation.
130
+ *
131
+ * @param id - Presentation id
132
+ * @returns URL
133
+ */
134
+ export declare function slidesLink(id: string): string;
135
+ export {};
136
+ //# sourceMappingURL=slides.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slides.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/slides.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * SlidesService — Google Slides over the owner's Workspace grant.
3
+ *
4
+ * Reads any presentation the owner can see (`drive.readonly`) and builds
5
+ * new decks from an outline (`drive.file`). Each outline slide becomes a
6
+ * TITLE_AND_BODY slide with the title and bullet lines; the deck's default
7
+ * first slide is removed so the outline is the whole deck.
8
+ *
9
+ * @module services/google/slides.service
10
+ */
11
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
12
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
13
+ import { buildGoogleUrl, googleRequest } from './google-api.client.js';
14
+ import { requireId } from './drive.service.js';
15
+ /**
16
+ * Lines of text in a page element (shape text, table cells, groups).
17
+ *
18
+ * @param el - Page element
19
+ * @returns Non-empty lines
20
+ */
21
+ export function elementLines(el) {
22
+ const out = [];
23
+ const pushText = (elements) => {
24
+ const raw = (elements ?? []).map((t) => t.textRun?.content ?? '').join('');
25
+ for (const line of raw.split('\n'))
26
+ if (line.trim())
27
+ out.push(line.trim());
28
+ };
29
+ if (el.shape?.text)
30
+ pushText(el.shape.text.textElements);
31
+ for (const row of el.table?.tableRows ?? []) {
32
+ const cells = (row.tableCells ?? []).map((c) => (c.text?.textElements ?? []).map((t) => t.textRun?.content ?? '').join('').trim());
33
+ if (cells.some(Boolean))
34
+ out.push(cells.join(' | '));
35
+ }
36
+ for (const child of el.elementGroup?.children ?? [])
37
+ out.push(...elementLines(child));
38
+ return out;
39
+ }
40
+ /**
41
+ * Flatten a presentation.
42
+ *
43
+ * @param wire - `presentations.get` payload
44
+ * @returns Title + slides as text
45
+ */
46
+ export function toPresentationText(wire) {
47
+ const id = wire.presentationId ?? '';
48
+ const slides = (wire.slides ?? []).map((page, i) => {
49
+ const lines = (page.pageElements ?? []).flatMap(elementLines);
50
+ const notes = (page.slideProperties?.notesPage?.pageElements ?? []).flatMap(elementLines).join('\n');
51
+ return { index: i + 1, objectId: page.objectId ?? '', lines, ...(notes ? { notes } : {}) };
52
+ });
53
+ return { id, title: wire.title ?? '', slideCount: slides.length, slides, webViewLink: slidesLink(id) };
54
+ }
55
+ /**
56
+ * The batchUpdate requests that turn an outline into slides.
57
+ *
58
+ * @param outline - Slides to add
59
+ * @param removeSlideId - Object id of the default slide to delete, if any
60
+ * @returns Requests in order (creates, inserts, then the delete)
61
+ */
62
+ export function buildSlideRequests(outline, removeSlideId) {
63
+ const requests = [];
64
+ outline.forEach((slide, i) => {
65
+ const slideId = `crewly_slide_${i + 1}`;
66
+ const titleId = `${slideId}_title`;
67
+ const bodyId = `${slideId}_body`;
68
+ requests.push({
69
+ createSlide: {
70
+ objectId: slideId,
71
+ insertionIndex: i,
72
+ slideLayoutReference: { predefinedLayout: 'TITLE_AND_BODY' },
73
+ placeholderIdMappings: [
74
+ { layoutPlaceholder: { type: 'TITLE', index: 0 }, objectId: titleId },
75
+ { layoutPlaceholder: { type: 'BODY', index: 0 }, objectId: bodyId },
76
+ ],
77
+ },
78
+ });
79
+ const title = (slide.title ?? '').trim();
80
+ if (title)
81
+ requests.push({ insertText: { objectId: titleId, insertionIndex: 0, text: title } });
82
+ const body = (slide.bullets ?? []).map((b) => String(b).trim()).filter(Boolean).join('\n');
83
+ if (body)
84
+ requests.push({ insertText: { objectId: bodyId, insertionIndex: 0, text: body } });
85
+ });
86
+ if (removeSlideId)
87
+ requests.push({ deleteObject: { objectId: removeSlideId } });
88
+ return requests;
89
+ }
90
+ /**
91
+ * Slides: read, create from outline.
92
+ */
93
+ export class SlidesService {
94
+ deps;
95
+ base = GOOGLE_WORKSPACE_CONSTANTS.SLIDES_API_BASE;
96
+ /**
97
+ * @param deps - Token provider and optional fetch override
98
+ */
99
+ constructor(deps) {
100
+ this.deps = deps;
101
+ }
102
+ /**
103
+ * `presentations.get`, flattened to text per slide.
104
+ *
105
+ * @param id - Presentation id
106
+ * @returns Title + slides
107
+ */
108
+ async read(id) {
109
+ const presentationId = requireId(id);
110
+ const wire = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/presentations/${encodeURIComponent(presentationId)}`, {
111
+ fields: 'presentationId,title,slides(objectId,pageElements,slideProperties.notesPage.pageElements)',
112
+ }));
113
+ return toPresentationText(wire);
114
+ }
115
+ /**
116
+ * `presentations.create` + one `batchUpdate` that adds every outline
117
+ * slide and removes the default blank one.
118
+ *
119
+ * @param input - Title and outline
120
+ * @returns The new deck, flattened (as read back from Google)
121
+ * @throws GoogleWorkspaceError(400, validation) without a title / slides, or too many slides
122
+ */
123
+ async create(input) {
124
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
125
+ const title = (input.title ?? '').trim();
126
+ if (!title)
127
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"title" is required');
128
+ const outline = Array.isArray(input.slides) ? input.slides.filter((s) => s && typeof s === 'object') : [];
129
+ if (outline.length === 0)
130
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"slides" must be a non-empty array of {title, bullets[]}');
131
+ if (outline.length > GOOGLE_WORKSPACE_CONSTANTS.SLIDES_MAX_SLIDES) {
132
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, `"slides" exceeds ${GOOGLE_WORKSPACE_CONSTANTS.SLIDES_MAX_SLIDES}`);
133
+ }
134
+ const created = await googleRequest(this.deps, `${this.base}/presentations`, { method: 'POST', body: { title } });
135
+ const presentationId = created.presentationId ?? '';
136
+ const defaultSlide = created.slides?.[0]?.objectId;
137
+ await googleRequest(this.deps, `${this.base}/presentations/${encodeURIComponent(presentationId)}:batchUpdate`, {
138
+ method: 'POST',
139
+ body: { requests: buildSlideRequests(outline, defaultSlide) },
140
+ });
141
+ return this.read(presentationId);
142
+ }
143
+ }
144
+ /**
145
+ * Browser link for a presentation.
146
+ *
147
+ * @param id - Presentation id
148
+ * @returns URL
149
+ */
150
+ export function slidesLink(id) {
151
+ return `https://docs.google.com/presentation/d/${id}/edit`;
152
+ }
153
+ //# sourceMappingURL=slides.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slides.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/slides.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Strip tool-call markup an LLM emitted as prose.
3
+ *
4
+ * Models occasionally write their function-call envelope into the *text*
5
+ * part of a turn instead of (or as well as) making a real tool call. For a
6
+ * PTY runtime nobody sees it, but the in-process Crewly Agent forwards its
7
+ * text straight to Slack and chat, so the user gets a wall of
8
+ * `<invoke name="Bash">…` instead of an answer (2026-09-19, #crewly-support).
9
+ *
10
+ * This removes those envelopes — `function_calls` wrappers, `invoke`
11
+ * blocks and stray `parameter` tags, with or without a namespace prefix —
12
+ * while leaving fenced code blocks untouched, so an agent that is
13
+ * deliberately *explaining* the syntax keeps its example.
14
+ *
15
+ * @module utils/tool-call-markup
16
+ */
17
+ /** Result of {@link stripToolCallMarkup}. */
18
+ export interface StrippedText {
19
+ /** The text with tool-call envelopes removed and whitespace tidied. */
20
+ text: string;
21
+ /** True when something was removed. */
22
+ stripped: boolean;
23
+ }
24
+ /**
25
+ * Whether a string contains something that looks like a tool-call envelope.
26
+ *
27
+ * @param raw - Candidate text
28
+ * @returns True when an `invoke` / `function_calls` tag is present
29
+ */
30
+ export declare function hasToolCallMarkup(raw: string): boolean;
31
+ /**
32
+ * Remove tool-call markup from an agent's text response.
33
+ *
34
+ * Fenced code blocks (``` … ```) are passed through untouched: markup
35
+ * inside them is an example the agent meant to show.
36
+ *
37
+ * @param raw - The agent's text
38
+ * @returns The cleaned text and whether anything was removed
39
+ *
40
+ * @example
41
+ * stripToolCallMarkup('Working on it.\n<invoke name="Bash">…</invoke>')
42
+ * // → { text: 'Working on it.', stripped: true }
43
+ */
44
+ export declare function stripToolCallMarkup(raw: string): StrippedText;
45
+ //# sourceMappingURL=tool-call-markup.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-markup.utils.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/tool-call-markup.utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA+BH,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAwB7D"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Strip tool-call markup an LLM emitted as prose.
3
+ *
4
+ * Models occasionally write their function-call envelope into the *text*
5
+ * part of a turn instead of (or as well as) making a real tool call. For a
6
+ * PTY runtime nobody sees it, but the in-process Crewly Agent forwards its
7
+ * text straight to Slack and chat, so the user gets a wall of
8
+ * `<invoke name="Bash">…` instead of an answer (2026-09-19, #crewly-support).
9
+ *
10
+ * This removes those envelopes — `function_calls` wrappers, `invoke`
11
+ * blocks and stray `parameter` tags, with or without a namespace prefix —
12
+ * while leaving fenced code blocks untouched, so an agent that is
13
+ * deliberately *explaining* the syntax keeps its example.
14
+ *
15
+ * @module utils/tool-call-markup
16
+ */
17
+ /** Tag names that only ever appear in a tool-call envelope. */
18
+ const ENVELOPE_TAGS = ['function_calls', 'invoke', 'parameter'];
19
+ /**
20
+ * A tag like `<invoke …>`, `</invoke>`, `<||antml||invoke>` —
21
+ * any junk/namespace before the name, anything but `<`/`>` after it.
22
+ *
23
+ * @param name - Tag name
24
+ * @param closing - Match the closing form
25
+ * @returns The pattern source
26
+ */
27
+ function tagSource(name, closing) {
28
+ return `<${closing ? '\\s*/' : ''}[^<>]{0,40}?\\b${name}\\b[^<>]*>`;
29
+ }
30
+ /** Well-formed `<x>…</x>` blocks for the wrapper tags. */
31
+ const BLOCK_PATTERNS = ['function_calls', 'invoke'].map((name) => new RegExp(`${tagSource(name, false)}[\\s\\S]*?${tagSource(name, true)}`, 'gi'));
32
+ /** An opening wrapper tag with no close — the output was cut off mid-call. */
33
+ const UNCLOSED_PATTERN = new RegExp(`(?:${tagSource('function_calls', false)}|${tagSource('invoke', false)})[\\s\\S]*$`, 'i');
34
+ /** Any leftover lone tag. */
35
+ const LONE_TAG_PATTERN = new RegExp(ENVELOPE_TAGS.map((name) => `${tagSource(name, false)}|${tagSource(name, true)}`).join('|'), 'gi');
36
+ /**
37
+ * Whether a string contains something that looks like a tool-call envelope.
38
+ *
39
+ * @param raw - Candidate text
40
+ * @returns True when an `invoke` / `function_calls` tag is present
41
+ */
42
+ export function hasToolCallMarkup(raw) {
43
+ return new RegExp(`${tagSource('function_calls', false)}|${tagSource('invoke', false)}`, 'i').test(raw ?? '');
44
+ }
45
+ /**
46
+ * Remove tool-call markup from an agent's text response.
47
+ *
48
+ * Fenced code blocks (``` … ```) are passed through untouched: markup
49
+ * inside them is an example the agent meant to show.
50
+ *
51
+ * @param raw - The agent's text
52
+ * @returns The cleaned text and whether anything was removed
53
+ *
54
+ * @example
55
+ * stripToolCallMarkup('Working on it.\n<invoke name="Bash">…</invoke>')
56
+ * // → { text: 'Working on it.', stripped: true }
57
+ */
58
+ export function stripToolCallMarkup(raw) {
59
+ const input = raw ?? '';
60
+ if (!input)
61
+ return { text: '', stripped: false };
62
+ // Split on fences so the segments at odd indexes are fenced code.
63
+ const segments = input.split(/(```[\s\S]*?```)/g);
64
+ const cleaned = segments
65
+ .map((segment, i) => {
66
+ if (i % 2 === 1)
67
+ return segment; // fenced code — leave alone
68
+ let out = segment;
69
+ for (const pattern of BLOCK_PATTERNS)
70
+ out = out.replace(pattern, '');
71
+ out = out.replace(UNCLOSED_PATTERN, '');
72
+ return out.replace(LONE_TAG_PATTERN, '');
73
+ })
74
+ .join('');
75
+ const text = cleaned
76
+ .split('\n')
77
+ .map((line) => line.replace(/[ \t]+$/, ''))
78
+ .join('\n')
79
+ .replace(/\n{3,}/g, '\n\n')
80
+ .trim();
81
+ return { text, stripped: text !== input.trim() };
82
+ }
83
+ //# sourceMappingURL=tool-call-markup.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-markup.utils.js","sourceRoot":"","sources":["../../../../../backend/src/utils/tool-call-markup.utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,+DAA+D;AAC/D,MAAM,aAAa,GAAG,CAAC,gBAAgB,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC;AAEzE;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,IAAY,EAAE,OAAgB;IAC/C,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,kBAAkB,IAAI,YAAY,CAAC;AACtE,CAAC;AAED,0DAA0D;AAC1D,MAAM,cAAc,GAAG,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,GAAG,CACrD,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAC1F,CAAC;AAEF,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,MAAM,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;AAE9H,6BAA6B;AAC7B,MAAM,gBAAgB,GAAG,IAAI,MAAM,CACjC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAC3F,IAAI,CACL,CAAC;AAUF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;AAChH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,KAAK,GAAG,GAAG,IAAI,EAAE,CAAC;IACxB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAEjD,kEAAkE;IAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,QAAQ;SACrB,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QAClB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC,CAAC,4BAA4B;QAC7D,IAAI,GAAG,GAAG,OAAO,CAAC;QAClB,KAAK,MAAM,OAAO,IAAI,cAAc;YAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACrE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QACxC,OAAO,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,MAAM,IAAI,GAAG,OAAO;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;SAC1C,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;SAC1B,IAAI,EAAE,CAAC;IAEV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC"}
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "fb54dda0432bc8611a3475f7e86ac3e5e43a1079",
3
- "builtAt": "2026-09-19T19:38:05.718Z"
2
+ "commit": "560772191ade1710fd9e96295a26777158023b19",
3
+ "builtAt": "2026-09-19T21:21:42.464Z"
4
4
  }