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.
- package/dist/bridge/daemon.js +23 -0
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/spawn.d.ts +6 -0
- package/dist/bridge/spawn.js +27 -0
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/cli/commands/doc-job.d.ts +1 -0
- package/dist/cli/commands/doc-job.js +88 -0
- package/dist/cli/commands/doc-job.js.map +1 -0
- package/dist/cli/commands/research.js +173 -163
- package/dist/cli/commands/research.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/api.d.ts +79 -0
- package/dist/lib/api.js +61 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brief.js +65 -24
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/documents/convert.d.ts +36 -0
- package/dist/lib/documents/convert.js +188 -0
- package/dist/lib/documents/convert.js.map +1 -0
- package/dist/lib/documents/job-runner.d.ts +27 -0
- package/dist/lib/documents/job-runner.js +224 -0
- package/dist/lib/documents/job-runner.js.map +1 -0
- package/dist/lib/tools/docx-to-pdf.js +8 -7
- package/dist/lib/tools/docx-to-pdf.js.map +1 -1
- package/package.json +4 -2
|
@@ -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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 ===
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
115
|
-
lines.push(
|
|
116
|
-
lines.push(
|
|
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 ?
|
|
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(
|
|
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 ?
|
|
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(
|
|
151
|
-
lines.push(
|
|
152
|
-
lines.push(`Name every DELIVERABLE \`<Subject> — <Artifact> (YYYY-MM-DD).<ext>\`, where Subject is ${baseName ? `\`${baseName}\`` :
|
|
153
|
-
`Example: \`${baseName ??
|
|
154
|
-
lines.push(
|
|
155
|
-
lines.push(
|
|
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(
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 ===
|
|
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(
|
|
200
|
+
extras.push(`- **${key}:** ${Array.isArray(p[key]) ? p[key].join(", ") : String(p[key])}`);
|
|
201
201
|
}
|
|
202
202
|
if (extras.length) {
|
|
203
|
-
lines.push(
|
|
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(
|
|
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 = [
|
|
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 ===
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ===
|
|
283
|
+
const browserKey = (typeof p.browserKey === "string" && p.browserKey) ||
|
|
283
284
|
process.env.BROWSER_AGENT_KEY ||
|
|
284
|
-
|
|
285
|
+
"";
|
|
285
286
|
if (!browserKey) {
|
|
286
|
-
log(
|
|
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:
|
|
290
|
-
errorSummary:
|
|
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 = [
|
|
297
|
-
const model = typeof p.model ===
|
|
298
|
-
args.push(
|
|
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(
|
|
301
|
-
const endless = String(p.durationMode ??
|
|
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(
|
|
304
|
+
args.push("--continuous");
|
|
305
305
|
else if (p.timeoutMinutes)
|
|
306
|
-
args.push(
|
|
306
|
+
args.push("--timeout", String(Number(p.timeoutMinutes) * 60));
|
|
307
307
|
if (p.maxActions)
|
|
308
|
-
args.push(
|
|
308
|
+
args.push("--max-actions", String(p.maxActions));
|
|
309
309
|
if (p.intervalMs)
|
|
310
|
-
args.push(
|
|
310
|
+
args.push("--interval", String(p.intervalMs));
|
|
311
311
|
if (p.quality)
|
|
312
|
-
args.push(
|
|
312
|
+
args.push("--quality", String(p.quality));
|
|
313
313
|
if (p.charDelayMs)
|
|
314
|
-
args.push(
|
|
314
|
+
args.push("--char-delay", String(p.charDelayMs));
|
|
315
315
|
if (p.maxConsecutiveFailures)
|
|
316
|
-
args.push(
|
|
316
|
+
args.push("--max-consecutive-failures", String(p.maxConsecutiveFailures));
|
|
317
317
|
if (p.optimize !== false)
|
|
318
|
-
args.push(
|
|
318
|
+
args.push("--optimize");
|
|
319
319
|
if (p.stealth === true)
|
|
320
|
-
args.push(
|
|
320
|
+
args.push("--stealth");
|
|
321
321
|
if (p.hybrid === true)
|
|
322
|
-
args.push(
|
|
322
|
+
args.push("--hybrid");
|
|
323
323
|
const child = (0, node_child_process_1.spawn)(process.execPath, [cli, ...args], {
|
|
324
|
-
stdio: [
|
|
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 ===
|
|
332
|
+
if (current.status === "cancelled") {
|
|
333
333
|
cancelled = true;
|
|
334
|
-
child.kill(
|
|
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:
|
|
347
|
+
.appendToolLog(run.id, [{ stream, text }], { phase: "collecting" })
|
|
348
348
|
.catch(() => undefined);
|
|
349
349
|
};
|
|
350
|
-
child.stdout?.on(
|
|
351
|
-
child.stderr?.on(
|
|
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(
|
|
354
|
-
child.on(
|
|
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 ([
|
|
379
|
-
|
|
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}`,
|
|
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`,
|
|
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,
|
|
401
|
-
const runId = flags.get(
|
|
405
|
+
const agentId = (0, utils_1.need)(flags, "agent");
|
|
406
|
+
const runId = flags.get("run");
|
|
402
407
|
if (!runId) {
|
|
403
|
-
console.error(
|
|
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,
|
|
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(
|
|
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(
|
|
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)}`,
|
|
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 ===
|
|
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 ===
|
|
432
|
-
const streamLock = await (0, lock_1.acquireLock)(agentId,
|
|
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(
|
|
444
|
+
sessionId: flags.get("session") ?? (0, session_stream_1.newSessionId)(),
|
|
440
445
|
agentId,
|
|
441
|
-
mode:
|
|
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,
|
|
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 ?
|
|
459
|
-
progress: { phase: code === 0 ?
|
|
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
|
-
?
|
|
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,
|
|
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(
|
|
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(
|
|
493
|
+
sessionId: flags.get("session") ?? (0, session_stream_1.newSessionId)(),
|
|
489
494
|
agentId,
|
|
490
|
-
mode:
|
|
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,
|
|
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 ===
|
|
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,
|
|
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:
|
|
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] ??
|
|
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,
|
|
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,
|
|
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(
|
|
558
|
+
log("--self requires a provider API key", "stderr");
|
|
554
559
|
await api
|
|
555
|
-
.completeToolRun(runId, {
|
|
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 +
|
|
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 =
|
|
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:
|
|
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 ?
|
|
601
|
-
progress: { phase: ok ?
|
|
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)}`,
|
|
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,
|
|
609
|
-
log(`[research] ${run.toolKey} ${ok ?
|
|
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)}`,
|
|
624
|
+
log(`[research] error: ${(0, config_1.msg)(err)}`, "stderr");
|
|
615
625
|
await api
|
|
616
626
|
.completeToolRun(runId, {
|
|
617
|
-
status:
|
|
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)(),
|
|
641
|
+
return (0, node_path_1.join)((0, node_os_1.homedir)(), ".teamshare", "research");
|
|
632
642
|
}
|
|
633
643
|
//# sourceMappingURL=research.js.map
|