teamshare-bridge 0.21.11 → 0.21.13

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.
@@ -59,13 +59,13 @@ const utils_1 = require("../utils");
59
59
  const harness_1 = require("./harness");
60
60
  const shared_1 = require("./shared");
61
61
  const TOOL_SYSTEM_LABEL = {
62
- 'web-research': 'web researcher',
63
- 'data-collector': 'data collection agent',
64
- 'lead-finder': 'lead research agent',
65
- 'file-harvester': 'file harvesting agent',
62
+ "web-research": "web researcher",
63
+ "data-collector": "data collection agent",
64
+ "lead-finder": "lead research agent",
65
+ "file-harvester": "file harvesting agent",
66
66
  };
67
67
  function asString(v) {
68
- return typeof v === 'string' ? v.trim() : '';
68
+ return typeof v === "string" ? v.trim() : "";
69
69
  }
70
70
  /** Markdown brief the agent reads at the top of the session. */
71
71
  function buildBrief(run, projectName) {
@@ -73,144 +73,145 @@ function buildBrief(run, projectName) {
73
73
  const spec = run.spec ?? null;
74
74
  const lines = [];
75
75
  lines.push(`# Tool run: ${spec?.toolTitle ?? run.toolKey}`);
76
- lines.push('');
76
+ lines.push("");
77
77
  lines.push(`- **Project:** ${projectName} (${run.projectId})`);
78
78
  lines.push(`- **Run:** ${run.id}`);
79
79
  if (run.taskId)
80
80
  lines.push(`- **Task:** ${run.taskId}`);
81
81
  if (spec?.kind)
82
82
  lines.push(`- **Kind:** ${spec.kind} (${spec.execution})`);
83
- lines.push('');
83
+ lines.push("");
84
84
  const goal = asString(p.prompt) ||
85
85
  asString(p.goal) ||
86
86
  asString(p.topic) ||
87
87
  asString(p.outline);
88
88
  if (goal) {
89
- lines.push('## Goal');
90
- lines.push('');
89
+ lines.push("## Goal");
90
+ lines.push("");
91
91
  lines.push(goal);
92
- lines.push('');
92
+ lines.push("");
93
93
  }
94
94
  // Exact data the user supplied (author tools): use it VERBATIM.
95
95
  if (asString(p.data)) {
96
- lines.push('## Supplied data (use exactly — do not alter)');
97
- lines.push('');
98
- lines.push('```');
96
+ lines.push("## Supplied data (use exactly — do not alter)");
97
+ lines.push("");
98
+ lines.push("```");
99
99
  lines.push(asString(p.data));
100
- lines.push('```');
101
- lines.push('');
100
+ lines.push("```");
101
+ lines.push("");
102
102
  }
103
103
  if (asString(p.dataFile)) {
104
- lines.push('## Supplied data file (use exactly — do not alter)');
105
- lines.push('');
106
- lines.push('```');
104
+ lines.push("## Supplied data file (use exactly — do not alter)");
105
+ lines.push("");
106
+ lines.push("```");
107
107
  lines.push(asString(p.dataFile));
108
- lines.push('```');
109
- lines.push('');
108
+ lines.push("```");
109
+ lines.push("");
110
110
  }
111
111
  // Column contract — the authoritative spreadsheet headers.
112
112
  const columns = spec?.columns ?? [];
113
113
  if (columns.length) {
114
- lines.push('## Column contract (spreadsheet headers — exact, in this order)');
115
- lines.push('');
116
- lines.push('| # | Header (label) | Key | Type | Required | Cited |');
117
- lines.push('| --- | --- | --- | --- | --- | --- |');
114
+ lines.push("## Column contract (spreadsheet headers — exact, in this order)");
115
+ lines.push("");
116
+ lines.push("| # | Header (label) | Key | Type | Required | Cited |");
117
+ lines.push("| --- | --- | --- | --- | --- | --- |");
118
118
  columns.forEach((c, i) => {
119
- lines.push(`| ${i + 1} | ${c.label} | ${c.key} | ${c.type} | ${c.required ? 'yes' : 'no'} | ${c.citationRequired ? 'yes' : 'no'} |`);
119
+ lines.push(`| ${i + 1} | ${c.label} | ${c.key} | ${c.type} | ${c.required ? "yes" : "no"} | ${c.citationRequired ? "yes" : "no"} |`);
120
120
  });
121
- lines.push('');
121
+ lines.push("");
122
122
  }
123
123
  // Recipe — deterministic steps.
124
124
  if (spec?.recipe?.steps?.length) {
125
125
  lines.push(`## Recipe (${spec.recipe.id} v${spec.recipe.version} — follow in order)`);
126
- lines.push('');
126
+ lines.push("");
127
127
  spec.recipe.steps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
128
- lines.push('');
128
+ lines.push("");
129
129
  }
130
130
  // Output contract.
131
131
  if (spec?.outputContract) {
132
- lines.push('## Output contract');
133
- lines.push('');
132
+ lines.push("## Output contract");
133
+ lines.push("");
134
134
  if (spec.outputContract.folders.length) {
135
- lines.push(`- Folders: ${spec.outputContract.folders.join(', ')}`);
135
+ lines.push(`- Folders: ${spec.outputContract.folders.join(", ")}`);
136
136
  }
137
137
  for (const d of spec.outputContract.documents) {
138
138
  const role = d.label ? `${d.label} (${d.type})` : `${d.name} (${d.type})`;
139
- lines.push(`- ${role}${d.required ? ' — REQUIRED' : ''}`);
139
+ lines.push(`- ${role}${d.required ? " — REQUIRED" : ""}`);
140
140
  }
141
141
  if (spec.outputContract.manifest) {
142
142
  lines.push(`- Source manifest — REQUIRED: \`<Subject> — Sources (YYYY-MM-DD).md\` mapping every output to its source URL(s) and local path (table: Output | Type | Source URL(s) | Local path | Retrieved). State "No external sources" when the output is purely derived.`);
143
143
  }
144
- lines.push('');
144
+ lines.push("");
145
145
  }
146
146
  // Naming rule (2026-09): deliverables are named from the task subject so the
147
147
  // Documents tab is identifiable; handoff/spec files keep their exact names.
148
148
  const baseName = spec?.baseName?.trim();
149
149
  const today = new Date().toISOString().slice(0, 10);
150
- lines.push('## Naming (deliverable file names)');
151
- lines.push('');
152
- lines.push(`Name every DELIVERABLE \`<Subject> — <Artifact> (YYYY-MM-DD).<ext>\`, where Subject is ${baseName ? `\`${baseName}\`` : 'a short, specific label for this task'} (shorten it if the content is more specific). ` +
153
- `Example: \`${baseName ?? 'Abuja Web Dev Prospects'} — Leads (${today}).xlsx\`.`);
154
- lines.push('Never use a bare generic name (e.g. `leads.xlsx`, `summary.md`). Exception: render-handoff/spec files keep their EXACT name (`chart-data.csv`, `data.csv`, `outline.md`, `document.docx`) — only the final deliverable gets the descriptive name.');
155
- lines.push('The source manifest is a DELIVERABLE: name it `<Subject> — Sources (YYYY-MM-DD).md` (artifact = `Sources`), never a bare `manifest.md`/`sources.md`.');
156
- lines.push('');
150
+ lines.push("## Naming (deliverable file names)");
151
+ lines.push("");
152
+ lines.push(`Name every DELIVERABLE \`<Subject> — <Artifact> (YYYY-MM-DD).<ext>\`, where Subject is ${baseName ? `\`${baseName}\`` : "a short, specific label for this task"} (shorten it if the content is more specific). ` +
153
+ `Example: \`${baseName ?? "Abuja Web Dev Prospects"} — Leads (${today}).xlsx\`.`);
154
+ lines.push("Never use a bare generic name (e.g. `leads.xlsx`, `summary.md`). Exception: render-handoff/spec files keep their EXACT name (`chart-data.csv`, `data.csv`, `outline.md`, `document.docx`) — only the final deliverable gets the descriptive name.");
155
+ lines.push("The source manifest is a DELIVERABLE: name it `<Subject> — Sources (YYYY-MM-DD).md` (artifact = `Sources`), never a bare `manifest.md`/`sources.md`.");
156
+ lines.push("");
157
157
  // Allowed TeamShare MCP tools.
158
158
  if (spec?.requiredMcpTools?.length) {
159
- lines.push('## TeamShare MCP tools you may use');
160
- lines.push('');
161
- lines.push(spec.requiredMcpTools.map((t) => `\`${t}\``).join(', '));
162
- lines.push('');
159
+ lines.push("## TeamShare MCP tools you may use");
160
+ lines.push("");
161
+ lines.push(spec.requiredMcpTools.map((t) => `\`${t}\``).join(", "));
162
+ lines.push("");
163
163
  }
164
164
  const extras = [];
165
165
  for (const key of [
166
- 'depth',
167
- 'sources',
168
- 'sourceAuthority',
169
- 'maxRows',
170
- 'maxLeads',
171
- 'maxFiles',
172
- 'recencyDays',
173
- 'locations',
174
- 'countries',
175
- 'states',
176
- 'cities',
177
- 'industries',
178
- 'fileTypes',
179
- 'domains',
180
- 'dataSource',
181
- 'tone',
182
- 'pageSize',
183
- 'orientation',
184
- 'chartTypes',
185
- 'xKey',
186
- 'yKey',
187
- 'title',
188
- 'operations',
189
- 'groupBy',
190
- 'theme',
191
- 'aspect',
192
- 'freezeHeader',
166
+ "depth",
167
+ "sources",
168
+ "sourceAuthority",
169
+ "maxRows",
170
+ "maxLeads",
171
+ "maxFiles",
172
+ "recencyDays",
173
+ "locations",
174
+ "countries",
175
+ "states",
176
+ "cities",
177
+ "industries",
178
+ "fileTypes",
179
+ "domains",
180
+ "dataSource",
181
+ "tone",
182
+ "pageSize",
183
+ "orientation",
184
+ "chartTypes",
185
+ "xKey",
186
+ "yKey",
187
+ "title",
188
+ "operations",
189
+ "groupBy",
190
+ "theme",
191
+ "aspect",
192
+ "freezeHeader",
193
193
  ]) {
194
- if (key === 'columns' ||
194
+ if (key === "columns" ||
195
195
  p[key] === undefined ||
196
- p[key] === '' ||
196
+ p[key] === "" ||
197
197
  (Array.isArray(p[key]) && p[key].length === 0)) {
198
198
  continue;
199
199
  }
200
- extras.push(`- **${key}:** ${Array.isArray(p[key]) ? p[key].join(', ') : String(p[key])}`);
200
+ extras.push(`- **${key}:** ${Array.isArray(p[key]) ? p[key].join(", ") : String(p[key])}`);
201
201
  }
202
202
  if (extras.length) {
203
- lines.push('## Parameters');
204
- lines.push('');
203
+ lines.push("## Parameters");
204
+ lines.push("");
205
205
  lines.push(...extras);
206
- lines.push('');
206
+ lines.push("");
207
207
  }
208
- return lines.join('\n');
208
+ return lines.join("\n");
209
209
  }
210
210
  /** Tool-specific work instructions appended to the harness prompt. */
211
211
  function buildInstructions(run, folderBase) {
212
212
  const spec = run.spec ?? null;
213
213
  const common = `\n\n## How to work\n` +
214
+ `0. **Check prior art FIRST (do not flood yourself):** call \`find_similar\` with this run's \`runId\` (or \`search\` with the projectId) to find similar tasks and existing documents. Pick only the few that look relevant, read them with \`read_document\` (or \`get_task\`), and \`get_project_context\` for the project's conventions/gotchas. EXTEND existing work — never duplicate or overwrite an existing deliverable.\n` +
214
215
  `1. Discover sources with your own web access; when fetching a specific URL use the teamshare MCP tool web_fetch (browser-like headers + reader-proxy fallback) rather than the built-in WebFetch.\n` +
215
216
  `2. **If web_fetch is blocked or unusable** (returns WEB_FETCH_FAILED / 403 / 406 / 429, or the extracted text is empty/nav-only), escalate to the browser: call \`browser_open({ autoLaunch: true })\`, then \`browser_action({ action: "navigate", url })\`, then \`browser_action({ action: "readPage" })\`. readPage returns title + text + links and is text-only (as cheap as web_fetch) - prefer it. Use getLinks to find downloadable document URLs (pdf/docx/xlsx/csv) and save them with download_file_to_document. Call browser_close when done. The browser uses a persistent profile the human has signed into: if a page needs a login you do NOT have, STOP and note it - never attempt to enter credentials or log in.\n` +
216
217
  `3. Extract details with proper parsing: read the page text, pull the exact fields requested, normalize whitespace, and keep tables as rows/columns. Do not guess or fabricate values.\n` +
@@ -220,14 +221,14 @@ function buildInstructions(run, folderBase) {
220
221
  `7. When done, post a short summary comment on the task and leave it in in_review.\n`;
221
222
  // Typed spec path (2026-09): the recipe + column contract are authoritative.
222
223
  if (spec) {
223
- const parts = ['\n\n## Spec-driven deliverable'];
224
+ const parts = ["\n\n## Spec-driven deliverable"];
224
225
  if (spec.columns.length) {
225
226
  parts.push(`This is a DATA COLLECTION contract. The spreadsheet headers MUST equal the declared column labels, in the declared order — no renames, no extras, no reordering. ` +
226
227
  `Persist it with create_document({ format: "xlsx", sheets: [{ name, headers: ${JSON.stringify(spec.columns.map((c) => c.label))}, rows }] }). ` +
227
228
  `For every row, fill each column with the exact value found; leave it blank and record a reason when not found. Every non-empty value must cite a source URL.`);
228
229
  }
229
230
  for (const d of spec.outputContract.documents) {
230
- parts.push(`Produce ${d.type === 'xlsx' ? 'an Excel workbook' : `a \`${d.name}\``} (${d.type})${d.required ? ' — required' : ''} under \`${folderBase}\`.`);
231
+ parts.push(`Produce ${d.type === "xlsx" ? "an Excel workbook" : `a \`${d.name}\``} (${d.type})${d.required ? " — required" : ""} under \`${folderBase}\`.`);
231
232
  }
232
233
  if (spec.outputContract.manifest) {
233
234
  parts.push(`Write the SOURCE MANIFEST \`<subject> — Sources (YYYY-MM-DD).md\` (a note via create_document) under \`${folderBase}\`. ` +
@@ -235,36 +236,36 @@ function buildInstructions(run, folderBase) {
235
236
  `Use numbered citations \`[n](url)\` that match the ones inside the deliverable, then a "Sources" list. ` +
236
237
  `When the output is purely derived (no web research), state "No external sources" explicitly and record the input document as the provenance root.`);
237
238
  }
238
- return parts.join('\n') + common;
239
+ return parts.join("\n") + common;
239
240
  }
240
241
  // Legacy fallback for runs created before the typed spec existed.
241
242
  switch (run.toolKey) {
242
- case 'web-research':
243
+ case "web-research":
243
244
  return (`\n\n## Deliverable\n` +
244
245
  `Write a research brief as a markdown note via create_document (name it \`brief.md\`) in \`${folderBase}/reports\`. ` +
245
246
  `Use numbered citations \`[n](url)\`, a "Sources" section, a confidence label per major claim, and an explicit "Unknowns" section. ` +
246
247
  `If the brief contains a comparison table or collected figures, also emit an Excel workbook via create_document (format: "xlsx", sheets).` +
247
248
  common);
248
- case 'data-collector':
249
+ case "data-collector":
249
250
  return (`\n\n## Deliverable\n` +
250
251
  `Collect rows matching the declared columns and persist an Excel workbook: create_document with format: "xlsx" and \`sheets: [{ name, headers, rows }]\` ` +
251
252
  `(headers = the declared columns, rows = the collected values). Also write a short \`summary.md\` note describing sources and any gaps. ` +
252
253
  `Deduplicate rows and cap the count as specified.` +
253
254
  common);
254
- case 'lead-finder':
255
+ case "lead-finder":
255
256
  return (`\n\n## Deliverable\n` +
256
257
  `Build a lead spreadsheet via create_document (format: "xlsx", sheets) with columns: Company, Website, Contact, Email, Location, Industry, Source. ` +
257
258
  `Use PUBLIC sources only (company sites, directories, press). Do NOT scrape behind logins or use leaked/breach data. ` +
258
259
  `Add a footer note in a \`summary.md\` that the list is public-sourced and the recipient must comply with GDPR/CAN-SPAM before outreach.` +
259
260
  common);
260
- case 'topic-to-tasks':
261
+ case "topic-to-tasks":
261
262
  return (`\n\n## Deliverable\n` +
262
263
  `Research the topic, then propose an ordered task plan by calling the teamshare MCP tool \`propose_tasks\` with \`runId: "${run.id}"\` and \`tasks: [{ title, description, priority, tags }]\`. ` +
263
264
  `The plan is NOT executed automatically - a human reviews it in the Tools tab and approves it. ` +
264
265
  `Also write a short \`plan.md\` note under \`${folderBase}/reports\` explaining the breakdown and dependencies. ` +
265
266
  `Keep it to at most 40 tasks; order them so each can start once its predecessors are done.` +
266
267
  common);
267
- case 'file-harvester':
268
+ case "file-harvester":
268
269
  return (`\n\n## Deliverable\n` +
269
270
  `For each file you find, call the teamshare MCP tool download_file_to_document (projectId pinned, set folderPath to \`${folderBase}/raw\`). ` +
270
271
  `Also save a local copy under \`<linked-folder>/teamshare/research/<taskId>/raw/\` when the project folder is linked. ` +
@@ -279,49 +280,48 @@ function buildInstructions(run, folderBase) {
279
280
  /** Long-running web auto-answer stream (web-auto-answer tool). */
280
281
  async function runAutoAnswer(api, run, log) {
281
282
  const p = (run.params ?? {});
282
- const browserKey = (typeof p.browserKey === 'string' && p.browserKey) ||
283
+ const browserKey = (typeof p.browserKey === "string" && p.browserKey) ||
283
284
  process.env.BROWSER_AGENT_KEY ||
284
- '';
285
+ "";
285
286
  if (!browserKey) {
286
- log('[research] web-auto-answer requires a browser agent key (set BROWSER_AGENT_KEY)', 'stderr');
287
+ log("[research] web-auto-answer requires a browser agent key (set BROWSER_AGENT_KEY)", "stderr");
287
288
  await api
288
289
  .completeToolRun(run.id, {
289
- status: 'failed',
290
- errorSummary: 'No browser agent key configured (set BROWSER_AGENT_KEY).',
290
+ status: "failed",
291
+ errorSummary: "No browser agent key configured (set BROWSER_AGENT_KEY).",
291
292
  })
292
293
  .catch(() => undefined);
293
294
  return 1;
294
295
  }
295
- const cli = process.argv[1] ?? '';
296
- const args = ['browse', '--auto-answer', '--key', browserKey];
297
- const model = typeof p.model === 'string' && p.model ? p.model : 'mimo:mimo-v2.5';
298
- args.push('--model', model);
296
+ const cli = process.argv[1] ?? "";
297
+ const args = ["browse", "--auto-answer", "--key", browserKey];
298
+ const model = typeof p.model === "string" && p.model ? p.model : "mimo:mimo-v2.5";
299
+ args.push("--model", model);
299
300
  if (p.port)
300
- args.push('--port', String(p.port));
301
- const endless = String(p.durationMode ?? 'endless') === 'endless' ||
302
- p.continuous === true;
301
+ args.push("--port", String(p.port));
302
+ const endless = String(p.durationMode ?? "endless") === "endless" || p.continuous === true;
303
303
  if (endless)
304
- args.push('--continuous');
304
+ args.push("--continuous");
305
305
  else if (p.timeoutMinutes)
306
- args.push('--timeout', String(Number(p.timeoutMinutes) * 60));
306
+ args.push("--timeout", String(Number(p.timeoutMinutes) * 60));
307
307
  if (p.maxActions)
308
- args.push('--max-actions', String(p.maxActions));
308
+ args.push("--max-actions", String(p.maxActions));
309
309
  if (p.intervalMs)
310
- args.push('--interval', String(p.intervalMs));
310
+ args.push("--interval", String(p.intervalMs));
311
311
  if (p.quality)
312
- args.push('--quality', String(p.quality));
312
+ args.push("--quality", String(p.quality));
313
313
  if (p.charDelayMs)
314
- args.push('--char-delay', String(p.charDelayMs));
314
+ args.push("--char-delay", String(p.charDelayMs));
315
315
  if (p.maxConsecutiveFailures)
316
- args.push('--max-consecutive-failures', String(p.maxConsecutiveFailures));
316
+ args.push("--max-consecutive-failures", String(p.maxConsecutiveFailures));
317
317
  if (p.optimize !== false)
318
- args.push('--optimize');
318
+ args.push("--optimize");
319
319
  if (p.stealth === true)
320
- args.push('--stealth');
320
+ args.push("--stealth");
321
321
  if (p.hybrid === true)
322
- args.push('--hybrid');
322
+ args.push("--hybrid");
323
323
  const child = (0, node_child_process_1.spawn)(process.execPath, [cli, ...args], {
324
- stdio: ['ignore', 'pipe', 'pipe'],
324
+ stdio: ["ignore", "pipe", "pipe"],
325
325
  env: process.env,
326
326
  });
327
327
  let cancelled = false;
@@ -329,9 +329,9 @@ async function runAutoAnswer(api, run, log) {
329
329
  void api
330
330
  .getToolRun(run.id)
331
331
  .then((current) => {
332
- if (current.status === 'cancelled') {
332
+ if (current.status === "cancelled") {
333
333
  cancelled = true;
334
- child.kill('SIGTERM');
334
+ child.kill("SIGTERM");
335
335
  }
336
336
  })
337
337
  .catch(() => undefined);
@@ -344,14 +344,14 @@ async function runAutoAnswer(api, run, log) {
344
344
  log(`[auto-answer] ${line}`, stream);
345
345
  }
346
346
  void api
347
- .appendToolLog(run.id, [{ stream, text }], { phase: 'collecting' })
347
+ .appendToolLog(run.id, [{ stream, text }], { phase: "collecting" })
348
348
  .catch(() => undefined);
349
349
  };
350
- child.stdout?.on('data', (c) => push('stdout', c));
351
- child.stderr?.on('data', (c) => push('stderr', c));
350
+ child.stdout?.on("data", (c) => push("stdout", c));
351
+ child.stderr?.on("data", (c) => push("stderr", c));
352
352
  const code = await new Promise((resolve) => {
353
- child.on('close', (c) => resolve(c ?? 0));
354
- child.on('error', () => resolve(1));
353
+ child.on("close", (c) => resolve(c ?? 0));
354
+ child.on("error", () => resolve(1));
355
355
  });
356
356
  clearInterval(poll);
357
357
  return cancelled ? 0 : code;
@@ -374,17 +374,22 @@ async function claimBackingTask(api, taskId, log) {
374
374
  }
375
375
  catch (err) {
376
376
  const e = err;
377
- const code = e.code ?? '';
378
- if (['TASK_CLAIMED_BY', 'AGENT_TASK_LOCKED', 'AGENT_UNASSIGNED_BLOCKED', 'TASK_NOT_OPEN'].includes(code)) {
379
- log(`[claim] cannot work task ${taskId}: ${e.message ?? code}`, 'stderr');
377
+ const code = e.code ?? "";
378
+ if ([
379
+ "TASK_CLAIMED_BY",
380
+ "AGENT_TASK_LOCKED",
381
+ "AGENT_UNASSIGNED_BLOCKED",
382
+ "TASK_NOT_OPEN",
383
+ ].includes(code)) {
384
+ log(`[claim] cannot work task ${taskId}: ${e.message ?? code}`, "stderr");
380
385
  return { ok: false, release: async () => undefined };
381
386
  }
382
- log(`[claim] warning: could not claim task ${taskId}: ${e.message ?? code}`, 'stderr');
387
+ log(`[claim] warning: could not claim task ${taskId}: ${e.message ?? code}`, "stderr");
383
388
  return { ok: true, release: async () => undefined };
384
389
  }
385
390
  const timer = setInterval(() => {
386
391
  void api.renewClaim(taskId).catch(() => {
387
- log(`[claim] renewal failed for ${taskId} - claim may have expired`, 'stderr');
392
+ log(`[claim] renewal failed for ${taskId} - claim may have expired`, "stderr");
388
393
  clearInterval(timer);
389
394
  });
390
395
  }, 5 * 60 * 1000);
@@ -397,48 +402,48 @@ async function claimBackingTask(api, taskId, log) {
397
402
  };
398
403
  }
399
404
  async function cmdResearch(flags) {
400
- const agentId = (0, utils_1.need)(flags, 'agent');
401
- const runId = flags.get('run');
405
+ const agentId = (0, utils_1.need)(flags, "agent");
406
+ const runId = flags.get("run");
402
407
  if (!runId) {
403
- console.error('research requires --run <runId>');
408
+ console.error("research requires --run <runId>");
404
409
  process.exitCode = 2;
405
410
  return;
406
411
  }
407
412
  const apiKey = (0, utils_1.resolveApiKey)(flags);
408
- const api = new api_1.TeamshareApi(process.env.TEAMSHARE_API_URL, apiKey, 'run');
413
+ const api = new api_1.TeamshareApi(process.env.TEAMSHARE_API_URL, apiKey, "run");
409
414
  let log = (0, utils_1.makeLog)(null);
410
415
  const settings = await (0, utils_1.fetchAgentSettings)(api, agentId, log);
411
- let selfMode = flags.get('self') === 'true';
416
+ let selfMode = flags.get("self") === "true";
412
417
  const harness = (0, utils_1.harnessOf)(flags, settings);
413
- const modelResolved = (await Promise.resolve().then(() => __importStar(require('../../lib/llm/models')))).resolveModel((0, utils_1.modelOf)(flags, settings), process.env.LLM_BASE_URL);
418
+ const modelResolved = (await Promise.resolve().then(() => __importStar(require("../../lib/llm/models")))).resolveModel((0, utils_1.modelOf)(flags, settings), process.env.LLM_BASE_URL);
414
419
  if (modelResolved.harnessOnly && selfMode) {
415
420
  log(`[model] harness-native - switching from --self to harness mode`);
416
421
  selfMode = false;
417
422
  }
418
423
  const run = await api.getToolRun(runId).catch((err) => {
419
- log(`[research] cannot fetch run ${runId}: ${(0, config_1.msg)(err)}`, 'stderr');
424
+ log(`[research] cannot fetch run ${runId}: ${(0, config_1.msg)(err)}`, "stderr");
420
425
  return null;
421
426
  });
422
427
  if (!run) {
423
428
  process.exitCode = 1;
424
429
  return;
425
430
  }
426
- if (run.status === 'completed' || run.status === 'cancelled') {
431
+ if (run.status === "completed" || run.status === "cancelled") {
427
432
  log(`[research] run ${runId} already ${run.status} - nothing to do`);
428
433
  return;
429
434
  }
430
435
  // Long-running stream tool: the web auto-answer loop.
431
- if (run.toolKey === 'web-auto-answer') {
432
- const streamLock = await (0, lock_1.acquireLock)(agentId, 'tool', { projectId: run.projectId }, { onWaiting: () => log(`[lock] agent ${agentId} is busy - waiting...`) });
436
+ if (run.toolKey === "web-auto-answer") {
437
+ const streamLock = await (0, lock_1.acquireLock)(agentId, "tool", { projectId: run.projectId }, { onWaiting: () => log(`[lock] agent ${agentId} is busy - waiting...`) });
433
438
  if (!streamLock) {
434
439
  console.error(`[lock] gave up waiting for agent ${agentId}`);
435
440
  process.exitCode = 1;
436
441
  return;
437
442
  }
438
443
  const streamReporter = new daemon_reporter_1.DaemonReporter({
439
- sessionId: flags.get('session') ?? (0, session_stream_1.newSessionId)(),
444
+ sessionId: flags.get("session") ?? (0, session_stream_1.newSessionId)(),
440
445
  agentId,
441
- mode: 'run',
446
+ mode: "run",
442
447
  projectId: run.projectId,
443
448
  });
444
449
  streamReporter.connect();
@@ -451,21 +456,21 @@ async function cmdResearch(flags) {
451
456
  return;
452
457
  }
453
458
  try {
454
- await (0, brief_1.reportStatus)(api, agentId, 'working', run.taskId ?? undefined, streamReporter.sessionId);
459
+ await (0, brief_1.reportStatus)(api, agentId, "working", run.taskId ?? undefined, streamReporter.sessionId);
455
460
  const code = await runAutoAnswer(api, run, streamLog);
456
461
  await api
457
462
  .completeToolRun(run.id, {
458
- status: code === 0 ? 'completed' : 'failed',
459
- progress: { phase: code === 0 ? 'done' : 'failed' },
463
+ status: code === 0 ? "completed" : "failed",
464
+ progress: { phase: code === 0 ? "done" : "failed" },
460
465
  errorSummary: code === 0 ? undefined : `auto-answer exited ${code}`,
461
466
  })
462
467
  .catch(() => undefined);
463
468
  if (run.taskId) {
464
469
  await (0, brief_1.postClosingComment)(api, run.taskId, code === 0
465
- ? 'Web auto-answer session finished.'
470
+ ? "Web auto-answer session finished."
466
471
  : `Web auto-answer exited ${code}.`).catch(() => undefined);
467
472
  }
468
- await (0, brief_1.reportStatus)(api, agentId, 'online', run.taskId ?? undefined, streamReporter.sessionId);
473
+ await (0, brief_1.reportStatus)(api, agentId, "online", run.taskId ?? undefined, streamReporter.sessionId);
469
474
  if (code !== 0)
470
475
  process.exitCode = 1;
471
476
  }
@@ -479,21 +484,21 @@ async function cmdResearch(flags) {
479
484
  const project = await api.getProject(run.projectId).catch(() => null);
480
485
  const projectName = project?.name ?? run.projectId;
481
486
  const folderBase = `agents/${agentId}/tasks/${run.taskId ?? run.id}/research`;
482
- if (flags.get('dry-run') === 'true') {
487
+ if (flags.get("dry-run") === "true") {
483
488
  log(`[research] ${run.toolKey} ${runId} (${run.status})`);
484
489
  log(buildBrief(run, projectName));
485
490
  return;
486
491
  }
487
492
  const reporter = new daemon_reporter_1.DaemonReporter({
488
- sessionId: flags.get('session') ?? (0, session_stream_1.newSessionId)(),
493
+ sessionId: flags.get("session") ?? (0, session_stream_1.newSessionId)(),
489
494
  agentId,
490
- mode: 'run',
495
+ mode: "run",
491
496
  projectId: run.projectId,
492
497
  });
493
498
  const cwd = (0, shared_1.resolveWorkingDir)(flags, run.projectId, log);
494
499
  const sessionId = reporter.sessionId;
495
500
  let exitCode = 0;
496
- const lock = await (0, lock_1.acquireLock)(agentId, 'tool', { projectId: run.projectId }, {
501
+ const lock = await (0, lock_1.acquireLock)(agentId, "tool", { projectId: run.projectId }, {
497
502
  onWaiting: () => log(`[lock] agent ${agentId} is busy - waiting...`),
498
503
  });
499
504
  if (!lock) {
@@ -506,7 +511,7 @@ async function cmdResearch(flags) {
506
511
  // Re-check the run now and bail if it has already been worked.
507
512
  const freshRun = await api.getToolRun(runId).catch(() => null);
508
513
  if (freshRun &&
509
- (freshRun.status === 'completed' || freshRun.status === 'cancelled')) {
514
+ (freshRun.status === "completed" || freshRun.status === "cancelled")) {
510
515
  log(`[research] run ${runId} is already ${freshRun.status} (worked by another session) - nothing to do`);
511
516
  (0, lock_1.releaseLock)(agentId);
512
517
  return;
@@ -522,37 +527,40 @@ async function cmdResearch(flags) {
522
527
  return;
523
528
  }
524
529
  try {
525
- await (0, brief_1.reportStatus)(api, agentId, 'working', run.taskId ?? undefined, sessionId);
530
+ await (0, brief_1.reportStatus)(api, agentId, "working", run.taskId ?? undefined, sessionId);
526
531
  // Best-effort live progress: mark the backing task 'collecting' so the
527
532
  // task UI shows it is running (mirrored via /tools/runs/:id/events).
528
533
  if (run.taskId) {
529
534
  await api
530
- .appendToolLog(runId, [], { phase: 'collecting' })
535
+ .appendToolLog(runId, [], { phase: "collecting" })
531
536
  .catch(() => undefined);
532
537
  }
533
538
  const brief = buildBrief(run, projectName);
534
539
  const instructions = buildInstructions(run, folderBase);
535
540
  const systemPrompt = (0, utils_1.systemPromptOf)(flags, settings);
536
- const label = TOOL_SYSTEM_LABEL[run.toolKey] ?? 'tool agent';
541
+ const label = TOOL_SYSTEM_LABEL[run.toolKey] ?? "tool agent";
537
542
  if (cwd) {
538
543
  // Ensure the mirrored local research folder exists (best-effort).
539
544
  try {
540
- (0, node_fs_1.mkdirSync)((0, node_path_1.join)(cwd, 'teamshare', 'research', run.taskId ?? run.id, 'raw'), {
545
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(cwd, "teamshare", "research", run.taskId ?? run.id, "raw"), {
541
546
  recursive: true,
542
547
  });
543
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(cwd, 'teamshare', 'research', run.taskId ?? run.id, 'brief.md'), brief, 'utf8');
548
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(cwd, "teamshare", "research", run.taskId ?? run.id, "brief.md"), brief, "utf8");
544
549
  }
545
550
  catch {
546
551
  /* local mirror is best-effort */
547
552
  }
548
553
  }
549
- let summary = '';
554
+ let summary = "";
550
555
  if (selfMode) {
551
556
  const llmApiKey = (0, utils_1.sessionLlmApiKey)(settings);
552
557
  if (!llmApiKey) {
553
- log('--self requires a provider API key', 'stderr');
558
+ log("--self requires a provider API key", "stderr");
554
559
  await api
555
- .completeToolRun(runId, { status: 'failed', errorSummary: 'no provider key' })
560
+ .completeToolRun(runId, {
561
+ status: "failed",
562
+ errorSummary: "no provider key",
563
+ })
556
564
  .catch(() => undefined);
557
565
  process.exitCode = 1;
558
566
  return;
@@ -564,18 +572,20 @@ async function cmdResearch(flags) {
564
572
  apiKey: llmApiKey,
565
573
  model: endpoint.model,
566
574
  agentName: agentId,
567
- systemPrompt: `${systemPrompt ? systemPrompt + '\n\n' : ''}You are a ${label} for ${projectName}.`,
575
+ systemPrompt: `${systemPrompt ? systemPrompt + "\n\n" : ""}You are a ${label} for ${projectName}.`,
568
576
  temperature: (0, utils_1.temperatureOf)(flags, settings),
569
577
  maxTokens: (0, utils_1.maxTokensOf)(flags, settings),
570
578
  agentUserId: settings.userId ?? undefined,
571
579
  }, run.projectId);
572
- summary = result.summary || `Agent run finished after ${result.iterations} iterations.`;
580
+ summary =
581
+ result.summary ||
582
+ `Agent run finished after ${result.iterations} iterations.`;
573
583
  }
574
584
  else {
575
585
  const prompt = `You are a ${label} working on a TeamShare tool run for project "${projectName}".\n\n` +
576
586
  brief +
577
587
  instructions +
578
- (systemPrompt ? `\n\n## Additional instructions\n${systemPrompt}` : '');
588
+ (systemPrompt ? `\n\n## Additional instructions\n${systemPrompt}` : "");
579
589
  exitCode = await (0, harness_1.spawnHarness)(harness, prompt, log, {
580
590
  model: (0, utils_1.modelOf)(flags, settings),
581
591
  temperature: (0, utils_1.temperatureOf)(flags, settings),
@@ -586,7 +596,7 @@ async function cmdResearch(flags) {
586
596
  agentKey: api.apiKey,
587
597
  apiBaseUrl: api.baseUrl,
588
598
  providerKey: settings.providerKey ?? undefined,
589
- sessionMode: 'run',
599
+ sessionMode: "run",
590
600
  sessionId,
591
601
  });
592
602
  summary =
@@ -597,24 +607,24 @@ async function cmdResearch(flags) {
597
607
  const ok = exitCode === 0;
598
608
  await api
599
609
  .completeToolRun(runId, {
600
- status: ok ? 'completed' : 'failed',
601
- progress: { phase: ok ? 'done' : 'failed' },
610
+ status: ok ? "completed" : "failed",
611
+ progress: { phase: ok ? "done" : "failed" },
602
612
  errorSummary: ok ? undefined : summary,
603
613
  })
604
- .catch((err) => log(`[research] complete failed: ${(0, config_1.msg)(err)}`, 'stderr'));
614
+ .catch((err) => log(`[research] complete failed: ${(0, config_1.msg)(err)}`, "stderr"));
605
615
  if (run.taskId) {
606
616
  await (0, brief_1.postClosingComment)(api, run.taskId, summary).catch(() => undefined);
607
617
  }
608
- await (0, brief_1.reportStatus)(api, agentId, 'online', run.taskId ?? undefined, sessionId);
609
- log(`[research] ${run.toolKey} ${ok ? 'completed' : 'failed'}`);
618
+ await (0, brief_1.reportStatus)(api, agentId, "online", run.taskId ?? undefined, sessionId);
619
+ log(`[research] ${run.toolKey} ${ok ? "completed" : "failed"}`);
610
620
  if (!ok)
611
621
  process.exitCode = 1;
612
622
  }
613
623
  catch (err) {
614
- log(`[research] error: ${(0, config_1.msg)(err)}`, 'stderr');
624
+ log(`[research] error: ${(0, config_1.msg)(err)}`, "stderr");
615
625
  await api
616
626
  .completeToolRun(runId, {
617
- status: 'failed',
627
+ status: "failed",
618
628
  errorSummary: (0, config_1.msg)(err).slice(0, 500),
619
629
  })
620
630
  .catch(() => undefined);
@@ -628,6 +638,6 @@ async function cmdResearch(flags) {
628
638
  }
629
639
  /** Resolves the linked working dir for a project (falls back to cwd). */
630
640
  function researchHome() {
631
- return (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'research');
641
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), ".teamshare", "research");
632
642
  }
633
643
  //# sourceMappingURL=research.js.map