teamshare-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -0
- package/dist/bridge/daemon.d.ts +1 -0
- package/dist/bridge/daemon.js +230 -0
- package/dist/bridge/daemon.js.map +1 -0
- package/dist/bridge/index.d.ts +2 -0
- package/dist/bridge/index.js +273 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/local-server.d.ts +29 -0
- package/dist/bridge/local-server.js +87 -0
- package/dist/bridge/local-server.js.map +1 -0
- package/dist/bridge/protocol.d.ts +4 -0
- package/dist/bridge/protocol.js +151 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/service.d.ts +32 -0
- package/dist/bridge/service.js +248 -0
- package/dist/bridge/service.js.map +1 -0
- package/dist/bridge/spawn.d.ts +15 -0
- package/dist/bridge/spawn.js +271 -0
- package/dist/bridge/spawn.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +543 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/lib/api.d.ts +191 -0
- package/dist/lib/api.js +209 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/brief.d.ts +16 -0
- package/dist/lib/brief.js +115 -0
- package/dist/lib/brief.js.map +1 -0
- package/dist/lib/chat-reply.d.ts +29 -0
- package/dist/lib/chat-reply.js +126 -0
- package/dist/lib/chat-reply.js.map +1 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +81 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/doc-reply.d.ts +22 -0
- package/dist/lib/doc-reply.js +108 -0
- package/dist/lib/doc-reply.js.map +1 -0
- package/dist/lib/llm.d.ts +50 -0
- package/dist/lib/llm.js +444 -0
- package/dist/lib/llm.js.map +1 -0
- package/dist/lib/lock.d.ts +41 -0
- package/dist/lib/lock.js +148 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/models.d.ts +30 -0
- package/dist/lib/models.js +51 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/partial-stream.d.ts +25 -0
- package/dist/lib/partial-stream.js +99 -0
- package/dist/lib/partial-stream.js.map +1 -0
- package/dist/lib/session-stream.d.ts +38 -0
- package/dist/lib/session-stream.js +191 -0
- package/dist/lib/session-stream.js.map +1 -0
- package/package.json +44 -0
package/dist/lib/llm.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QUESTION_MARKER = exports.DEFAULT_LLM_MODEL = exports.DEFAULT_LLM_BASE_URL = void 0;
|
|
4
|
+
exports.buildTools = buildTools;
|
|
5
|
+
exports.runLlmSession = runLlmSession;
|
|
6
|
+
exports.complete = complete;
|
|
7
|
+
exports.completeStream = completeStream;
|
|
8
|
+
/**
|
|
9
|
+
* Headless agent loop (`--self`): an OpenAI-compatible function-calling chat
|
|
10
|
+
* loop that works a task through the TeamShare REST API. No harness needed -
|
|
11
|
+
* just an LLM API key (env LLM_BASE_URL / LLM_API_KEY / LLM_MODEL).
|
|
12
|
+
*/
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_os_1 = require("node:os");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
exports.DEFAULT_LLM_BASE_URL = 'https://api.openai.com/v1';
|
|
17
|
+
exports.DEFAULT_LLM_MODEL = 'deepseek-chat';
|
|
18
|
+
const MAX_ITERATIONS = 12;
|
|
19
|
+
const TIMEOUT_MS = 120_000;
|
|
20
|
+
const STREAM_TIMEOUT_MS = 180_000;
|
|
21
|
+
function tool(fn, name, description, parameters) {
|
|
22
|
+
return { def: { name, description, parameters }, run: fn };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Phase B ask-human (IMP-530): markers + polling for the `--self` loop.
|
|
26
|
+
* The MCP server implements the same contract for opencode sessions.
|
|
27
|
+
*/
|
|
28
|
+
exports.QUESTION_MARKER = '[question]';
|
|
29
|
+
const ASK_POLL_MS = 5_000;
|
|
30
|
+
const ASK_DEFAULT_TIMEOUT_MS = 15 * 60_000;
|
|
31
|
+
function askTimeoutMs() {
|
|
32
|
+
const env = Number(process.env.TEAMSHARE_ASK_TIMEOUT);
|
|
33
|
+
return Number.isFinite(env) && env > 0 ? env * 1000 : ASK_DEFAULT_TIMEOUT_MS;
|
|
34
|
+
}
|
|
35
|
+
function buildTools(api, askTimeoutMsOverride) {
|
|
36
|
+
const askTimeout = askTimeoutMsOverride ?? askTimeoutMs();
|
|
37
|
+
return [
|
|
38
|
+
tool(async (a) => api.getTask(String(a.taskId)), 'get_task', 'Get a task with its comment thread.', {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: { taskId: { type: 'string' } },
|
|
41
|
+
required: ['taskId'],
|
|
42
|
+
}),
|
|
43
|
+
tool(async (a) => api.listTasks({ projectId: a.projectId ? String(a.projectId) : undefined, status: a.status ? String(a.status) : undefined }), 'list_tasks', 'List tasks in a project.', {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
projectId: { type: 'string' },
|
|
47
|
+
status: { type: 'string', description: 'open | in_progress | in_review | resolved | closed' },
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
tool(async (a) => api.createTask({ projectId: String(a.projectId), title: String(a.title), description: a.description ? String(a.description) : undefined, priority: a.priority ? String(a.priority) : undefined }), 'create_task', 'Create a task (e.g. a subtask of the assigned task).', {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
projectId: { type: 'string' },
|
|
54
|
+
title: { type: 'string' },
|
|
55
|
+
description: { type: 'string' },
|
|
56
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
57
|
+
},
|
|
58
|
+
required: ['projectId', 'title'],
|
|
59
|
+
}),
|
|
60
|
+
tool(async (a) => api.updateTask(String(a.taskId), {
|
|
61
|
+
status: a.status ? String(a.status) : undefined,
|
|
62
|
+
priority: a.priority ? String(a.priority) : undefined,
|
|
63
|
+
description: a.description ? String(a.description) : undefined,
|
|
64
|
+
}), 'update_task', 'Update a task: status (open -> in_progress -> in_review -> resolved, reopen allowed), priority, description.', {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
taskId: { type: 'string' },
|
|
68
|
+
status: { type: 'string', enum: ['open', 'in_progress', 'in_review', 'resolved', 'closed'] },
|
|
69
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
70
|
+
description: { type: 'string' },
|
|
71
|
+
},
|
|
72
|
+
required: ['taskId'],
|
|
73
|
+
}),
|
|
74
|
+
tool(async (a) => {
|
|
75
|
+
const taskId = a.taskId ? String(a.taskId) : undefined;
|
|
76
|
+
const documentId = a.documentId ? String(a.documentId) : undefined;
|
|
77
|
+
return api.addComment({
|
|
78
|
+
taskId,
|
|
79
|
+
documentId,
|
|
80
|
+
body: String(a.body),
|
|
81
|
+
});
|
|
82
|
+
}, 'add_comment', 'Post a progress update or result comment on a task (taskId) or a document (documentId - Phase F document assistant). Exactly one of taskId/documentId is required.', {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
taskId: { type: 'string' },
|
|
86
|
+
documentId: { type: 'string' },
|
|
87
|
+
body: { type: 'string' },
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
tool(async (a) => api.listDocuments(String(a.projectId)), 'list_documents', 'List project files.', { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] }),
|
|
91
|
+
tool(async (a) => {
|
|
92
|
+
const docs = await api.listDocuments(String(a.projectId));
|
|
93
|
+
const doc = docs.find((d) => d.id === String(a.documentId));
|
|
94
|
+
if (!doc)
|
|
95
|
+
return { error: 'document not found' };
|
|
96
|
+
// Phase F: office/PDF documents are extracted server-side.
|
|
97
|
+
if (!doc.mime ||
|
|
98
|
+
!['text/plain', 'text/markdown', 'text/csv'].includes(doc.mime)) {
|
|
99
|
+
try {
|
|
100
|
+
const extraction = await api.getDocumentExtract(doc.id);
|
|
101
|
+
return {
|
|
102
|
+
name: doc.name,
|
|
103
|
+
mime: doc.mime,
|
|
104
|
+
content: extraction.text,
|
|
105
|
+
truncated: extraction.truncated,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* fall through to the generic download path */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return api.getDocumentContent(doc);
|
|
113
|
+
}, 'read_document', 'Read a document\'s content (plain text for text files; extracted text for Word/PPT/Excel/PDF; URL for links/uploads).', {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
projectId: { type: 'string' },
|
|
117
|
+
documentId: { type: 'string' },
|
|
118
|
+
},
|
|
119
|
+
required: ['projectId', 'documentId'],
|
|
120
|
+
}),
|
|
121
|
+
tool(async (a) => api.search(String(a.q)), 'search', 'Search tasks, comments, documents and chat.', { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] }),
|
|
122
|
+
tool(async (a) => {
|
|
123
|
+
const taskId = String(a.taskId);
|
|
124
|
+
const question = String(a.question);
|
|
125
|
+
// Race safety: only ONE pending question per task (same contract as
|
|
126
|
+
// the backend MCP ask_human tool).
|
|
127
|
+
const existing = await api.listComments(taskId).catch(() => []);
|
|
128
|
+
let pending = false;
|
|
129
|
+
for (const c of existing) {
|
|
130
|
+
if (c.author?.kind === 'agent' && c.body.startsWith(exports.QUESTION_MARKER)) {
|
|
131
|
+
pending = true;
|
|
132
|
+
}
|
|
133
|
+
else if (c.author?.kind === 'human') {
|
|
134
|
+
pending = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (pending) {
|
|
138
|
+
return { error: 'QUESTION_ALREADY_PENDING: answer the pending question first' };
|
|
139
|
+
}
|
|
140
|
+
const body = [exports.QUESTION_MARKER, question].filter(Boolean).join(' ');
|
|
141
|
+
const comment = await api.addComment({ taskId, body });
|
|
142
|
+
return { posted: true, commentId: comment.id };
|
|
143
|
+
}, 'ask_human', `Pause and ask the human a question (posts a "[question]" comment on the task). Refuses while a question is already pending on the task - answer the pending one first. Then call wait_for_answer with the returned commentId.`, {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
taskId: { type: 'string' },
|
|
147
|
+
question: { type: 'string', description: 'The question to ask' },
|
|
148
|
+
choices: {
|
|
149
|
+
type: 'array',
|
|
150
|
+
items: { type: 'string' },
|
|
151
|
+
description: 'Optional answer choices',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
required: ['taskId', 'question'],
|
|
155
|
+
}),
|
|
156
|
+
tool(async (a) => {
|
|
157
|
+
const commentId = String(a.commentId);
|
|
158
|
+
const timeoutSec = a.timeoutSec !== undefined ? Number(a.timeoutSec) : 300;
|
|
159
|
+
const deadline = Date.now() + timeoutSec * 1000;
|
|
160
|
+
const question = await api.listComments(String(a.taskId)).catch(() => []);
|
|
161
|
+
const q = question.find((c) => c.id === commentId);
|
|
162
|
+
if (!q)
|
|
163
|
+
return { error: 'comment not found' };
|
|
164
|
+
const qAt = new Date(q.createdAt).getTime();
|
|
165
|
+
for (;;) {
|
|
166
|
+
const comments = await api.listComments(String(a.taskId)).catch(() => []);
|
|
167
|
+
const answer = comments.find((c) => new Date(c.createdAt).getTime() > qAt &&
|
|
168
|
+
c.author?.kind === 'human');
|
|
169
|
+
if (answer) {
|
|
170
|
+
return {
|
|
171
|
+
answer: answer.body,
|
|
172
|
+
author: { id: answer.author?.id ?? null, name: answer.author?.name ?? null },
|
|
173
|
+
at: answer.createdAt,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (Date.now() >= deadline)
|
|
177
|
+
return { timedOut: true };
|
|
178
|
+
await new Promise((r) => setTimeout(r, ASK_POLL_MS));
|
|
179
|
+
}
|
|
180
|
+
}, 'wait_for_answer', `Blocks (up to timeoutSec) until a human replies to the question comment created by ask_human, then returns { answer, author, at }. Returns { timedOut: true } on timeout - decide whether to proceed with assumptions or summarize.`, {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
commentId: { type: 'string', description: 'commentId from ask_human' },
|
|
184
|
+
taskId: { type: 'string' },
|
|
185
|
+
timeoutSec: { type: 'number', description: 'Max seconds to wait (5-900)' },
|
|
186
|
+
},
|
|
187
|
+
required: ['commentId', 'taskId'],
|
|
188
|
+
}),
|
|
189
|
+
tool(async (a) => api.listSubtasks(String(a.taskId)), 'list_subtasks', 'List the subtask tree of the task (nested, with done flags).', { type: 'object', properties: { taskId: { type: 'string' } }, required: ['taskId'] }),
|
|
190
|
+
tool(async (a) => api.createSubtask({
|
|
191
|
+
taskId: String(a.taskId),
|
|
192
|
+
title: String(a.title),
|
|
193
|
+
parentId: a.parentId ? String(a.parentId) : null,
|
|
194
|
+
assigneeId: a.assigneeId ? String(a.assigneeId) : null,
|
|
195
|
+
}), 'create_subtask', 'Break the task down: create a subtask under the task (or nested under another subtask via parentId).', {
|
|
196
|
+
type: 'object',
|
|
197
|
+
properties: {
|
|
198
|
+
taskId: { type: 'string', description: 'The parent task id' },
|
|
199
|
+
title: { type: 'string' },
|
|
200
|
+
parentId: { type: 'string', description: 'Optional subtask to nest under' },
|
|
201
|
+
assigneeId: { type: 'string' },
|
|
202
|
+
},
|
|
203
|
+
required: ['taskId', 'title'],
|
|
204
|
+
}),
|
|
205
|
+
tool(async (a) => api.updateSubtask(String(a.subtaskId), {
|
|
206
|
+
done: a.done !== undefined ? Boolean(a.done) : undefined,
|
|
207
|
+
title: a.title ? String(a.title) : undefined,
|
|
208
|
+
parentId: a.parentId !== undefined ? (a.parentId ? String(a.parentId) : null) : undefined,
|
|
209
|
+
}), 'update_subtask', 'Check a subtask off (done: true) or reopen it (done: false) - report progress as you work. Can also rename or move it.', {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {
|
|
212
|
+
subtaskId: { type: 'string' },
|
|
213
|
+
done: { type: 'boolean', description: 'true = completed (check it off)' },
|
|
214
|
+
title: { type: 'string' },
|
|
215
|
+
parentId: { type: 'string', description: 'null to move to top level' },
|
|
216
|
+
},
|
|
217
|
+
required: ['subtaskId'],
|
|
218
|
+
}),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
/** ~/.teamshare/sessions/task-<taskId>.json - per-task message history (Phase E). */
|
|
222
|
+
function continuationPath(taskId) {
|
|
223
|
+
return (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'sessions', `task-${taskId}.json`);
|
|
224
|
+
}
|
|
225
|
+
function saveContinuation(taskId, messages) {
|
|
226
|
+
try {
|
|
227
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'sessions'), { recursive: true });
|
|
228
|
+
(0, node_fs_1.writeFileSync)(continuationPath(taskId), JSON.stringify({ taskId, updatedAt: new Date().toISOString(), messages }), 'utf8');
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* best-effort */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function loadContinuation(taskId) {
|
|
235
|
+
try {
|
|
236
|
+
const path = continuationPath(taskId);
|
|
237
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
238
|
+
return null;
|
|
239
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
240
|
+
return Array.isArray(raw.messages) && raw.messages.length ? raw.messages : null;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function runLlmSession(api, taskId, brief, opts) {
|
|
247
|
+
const tools = buildTools(api);
|
|
248
|
+
const systemBase = `You are ${opts.agentName}, an AI agent working a task in TeamShare. ` +
|
|
249
|
+
(opts.systemPrompt ? `${opts.systemPrompt}\n\n` : '') +
|
|
250
|
+
'Work the assigned task described in the user message. Post a progress comment at milestones via add_comment. ' +
|
|
251
|
+
'BREAK THE TASK DOWN FIRST: before doing the work, plan 3-8 concrete subtasks with create_subtask ' +
|
|
252
|
+
'(list_subtasks shows the current breakdown; get_task also returns it). ' +
|
|
253
|
+
'Then check each subtask off with update_subtask (done: true) as you complete it, so the human sees live progress in the app. ' +
|
|
254
|
+
'When all subtasks are done, post a detailed final summary comment (mention the subtask progress) and move the task to in_review (or resolved if you are confident). ' +
|
|
255
|
+
'Prefer several small tool calls over one giant one.';
|
|
256
|
+
const previous = opts.continue ? loadContinuation(taskId) : null;
|
|
257
|
+
const messages = previous
|
|
258
|
+
? [
|
|
259
|
+
...previous,
|
|
260
|
+
{
|
|
261
|
+
role: 'system',
|
|
262
|
+
content: 'You are continuing a previous session on this task. Pick up where you left off - ' +
|
|
263
|
+
're-check the task state if needed, and complete the remaining work.',
|
|
264
|
+
},
|
|
265
|
+
{ role: 'user', content: `Updated task brief:\n\n${brief}` },
|
|
266
|
+
]
|
|
267
|
+
: [
|
|
268
|
+
{ role: 'system', content: systemBase },
|
|
269
|
+
{ role: 'user', content: `Task brief:\n\n${brief}` },
|
|
270
|
+
];
|
|
271
|
+
let iterations = 0;
|
|
272
|
+
let tokensUsed = 0;
|
|
273
|
+
let summary = '';
|
|
274
|
+
let finishedBy = 'no-tool-call';
|
|
275
|
+
for (;;) {
|
|
276
|
+
iterations += 1;
|
|
277
|
+
const reply = await chatOnce(messages, opts, tools.map((t) => t.def));
|
|
278
|
+
tokensUsed += reply.usage?.total_tokens ?? 0;
|
|
279
|
+
const toolCalls = reply.message?.tool_calls ?? [];
|
|
280
|
+
if (toolCalls.length === 0) {
|
|
281
|
+
summary = reply.message?.content ?? '';
|
|
282
|
+
finishedBy = 'no-tool-call';
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
messages.push(reply.message);
|
|
286
|
+
for (const call of toolCalls) {
|
|
287
|
+
let resultText;
|
|
288
|
+
try {
|
|
289
|
+
const args = JSON.parse(call.function.arguments ?? '{}');
|
|
290
|
+
const fn = tools.find((t) => t.def.name === call.function.name);
|
|
291
|
+
const result = fn ? await fn.run(args) : `unknown tool: ${call.function.name}`;
|
|
292
|
+
resultText = JSON.stringify(result);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
resultText = `TOOL_ERROR: ${err instanceof Error ? err.message : String(err)}`;
|
|
296
|
+
}
|
|
297
|
+
messages.push({
|
|
298
|
+
role: 'tool',
|
|
299
|
+
tool_call_id: call.id,
|
|
300
|
+
content: resultText.slice(0, 20_000),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
// Phase E: persist the conversation at every iteration so a re-run with
|
|
304
|
+
// --continue resumes exactly where this session left off.
|
|
305
|
+
saveContinuation(taskId, messages);
|
|
306
|
+
if (iterations >= MAX_ITERATIONS) {
|
|
307
|
+
summary = 'Reached the iteration budget - see the task comments for progress.';
|
|
308
|
+
finishedBy = 'max-iterations';
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const task = await api.getTask(taskId).catch(() => null);
|
|
313
|
+
if (task && ['in_review', 'resolved'].includes(task.status))
|
|
314
|
+
finishedBy = 'task-done';
|
|
315
|
+
return { summary, iterations, tokensUsed, finishedBy };
|
|
316
|
+
}
|
|
317
|
+
async function chatOnce(messages, opts, toolDefs) {
|
|
318
|
+
const url = `${opts.baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
319
|
+
const controller = new AbortController();
|
|
320
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
321
|
+
try {
|
|
322
|
+
const res = await fetch(url, {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: {
|
|
325
|
+
'Content-Type': 'application/json',
|
|
326
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
327
|
+
},
|
|
328
|
+
body: JSON.stringify({
|
|
329
|
+
model: opts.model,
|
|
330
|
+
messages,
|
|
331
|
+
tools: toolDefs.map((t) => ({ type: 'function', function: t })),
|
|
332
|
+
tool_choice: 'auto',
|
|
333
|
+
temperature: opts.temperature ?? 0.2,
|
|
334
|
+
...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}),
|
|
335
|
+
}),
|
|
336
|
+
signal: controller.signal,
|
|
337
|
+
});
|
|
338
|
+
if (!res.ok) {
|
|
339
|
+
const text = await res.text().catch(() => '');
|
|
340
|
+
throw new Error(`LLM HTTP ${res.status}: ${text.slice(0, 300)}`);
|
|
341
|
+
}
|
|
342
|
+
const json = (await res.json());
|
|
343
|
+
return { message: json.choices?.[0]?.message ?? null, usage: json.usage };
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
clearTimeout(timer);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Single-shot chat completion (no tools) - used by the chat-reply loop to
|
|
351
|
+
* draft a reply to a mention. Same env contract as `--self`.
|
|
352
|
+
*/
|
|
353
|
+
async function complete(prompt, opts) {
|
|
354
|
+
const messages = [
|
|
355
|
+
{
|
|
356
|
+
role: 'system',
|
|
357
|
+
content: 'You are a helpful TeamShare project assistant replying in the project chat. ' +
|
|
358
|
+
'Reply in plain text, concise and useful, addressing the person who mentioned you. ' +
|
|
359
|
+
'Never use markdown headers; keep it under 4000 characters.',
|
|
360
|
+
},
|
|
361
|
+
{ role: 'user', content: prompt },
|
|
362
|
+
];
|
|
363
|
+
const { message } = await chatOnce(messages, opts, []);
|
|
364
|
+
return message?.content?.trim() ?? '';
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Phase C streaming (IMP-530): OpenAI-compatible SSE `chat/completions`
|
|
368
|
+
* (stream: true). Calls `onToken` with each content delta as it arrives;
|
|
369
|
+
* resolves with the full trimmed reply. Works with any OpenAI-compatible
|
|
370
|
+
* provider (Gemini, DeepSeek, OpenAI, Groq, OpenRouter, Mistral).
|
|
371
|
+
*/
|
|
372
|
+
async function completeStream(prompt, opts, onToken) {
|
|
373
|
+
const messages = [
|
|
374
|
+
{
|
|
375
|
+
role: 'system',
|
|
376
|
+
content: 'You are a helpful TeamShare project assistant replying in the project chat. ' +
|
|
377
|
+
'Reply in plain text, concise and useful, addressing the person who mentioned you. ' +
|
|
378
|
+
'Never use markdown headers; keep it under 4000 characters.',
|
|
379
|
+
},
|
|
380
|
+
{ role: 'user', content: prompt },
|
|
381
|
+
];
|
|
382
|
+
const url = `${opts.baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
383
|
+
const controller = new AbortController();
|
|
384
|
+
const timer = setTimeout(() => controller.abort(), STREAM_TIMEOUT_MS);
|
|
385
|
+
try {
|
|
386
|
+
const res = await fetch(url, {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
headers: {
|
|
389
|
+
'Content-Type': 'application/json',
|
|
390
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
391
|
+
},
|
|
392
|
+
body: JSON.stringify({
|
|
393
|
+
model: opts.model,
|
|
394
|
+
messages,
|
|
395
|
+
stream: true,
|
|
396
|
+
temperature: opts.temperature ?? 0.2,
|
|
397
|
+
...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}),
|
|
398
|
+
}),
|
|
399
|
+
signal: controller.signal,
|
|
400
|
+
});
|
|
401
|
+
if (!res.ok || !res.body) {
|
|
402
|
+
const text = await res.text().catch(() => '');
|
|
403
|
+
throw new Error(`LLM HTTP ${res.status}: ${text.slice(0, 300)}`);
|
|
404
|
+
}
|
|
405
|
+
const reader = res.body.getReader();
|
|
406
|
+
const decoder = new TextDecoder();
|
|
407
|
+
let buffer = '';
|
|
408
|
+
let full = '';
|
|
409
|
+
for (;;) {
|
|
410
|
+
const { done, value } = await reader.read();
|
|
411
|
+
if (done)
|
|
412
|
+
break;
|
|
413
|
+
buffer += decoder.decode(value, { stream: true });
|
|
414
|
+
const lines = buffer.split('\n');
|
|
415
|
+
buffer = lines.pop() ?? '';
|
|
416
|
+
for (const line of lines) {
|
|
417
|
+
const trimmed = line.trim();
|
|
418
|
+
if (!trimmed.startsWith('data:'))
|
|
419
|
+
continue;
|
|
420
|
+
const payload = trimmed.slice(5).trim();
|
|
421
|
+
if (payload === '[DONE]') {
|
|
422
|
+
buffer = '';
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const json = JSON.parse(payload);
|
|
427
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
428
|
+
if (delta) {
|
|
429
|
+
full += delta;
|
|
430
|
+
onToken(delta);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
/* skip malformed keep-alive frames */
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return full.trim();
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
clearTimeout(timer);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/lib/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type LockKind = 'task' | 'chat' | 'doc';
|
|
2
|
+
export interface AgentLock {
|
|
3
|
+
pid: number;
|
|
4
|
+
startedAt: string;
|
|
5
|
+
kind: LockKind;
|
|
6
|
+
taskId?: string;
|
|
7
|
+
projectId?: string;
|
|
8
|
+
documentId?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const LOCK_DIR: string;
|
|
11
|
+
export declare const MAX_SESSION_MS: number;
|
|
12
|
+
export declare const LOCK_POLL_MS = 10000;
|
|
13
|
+
export declare const LOCK_MAX_WAIT_MS: number;
|
|
14
|
+
export declare function lockPath(agentId: string): string;
|
|
15
|
+
export declare function readLock(agentId: string): AgentLock | null;
|
|
16
|
+
/** One-shot acquire: ok=true on success, ok=false + busy set when held. */
|
|
17
|
+
export declare function tryAcquireLock(agentId: string, kind: LockKind, meta?: Omit<AgentLock, 'pid' | 'startedAt' | 'kind'>): {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
busy?: AgentLock;
|
|
20
|
+
};
|
|
21
|
+
/** Release the lock, but only if we hold it (pid match). */
|
|
22
|
+
export declare function releaseLock(agentId: string): void;
|
|
23
|
+
/** Remove the lock unconditionally (manual override, `clear-lock`). */
|
|
24
|
+
export declare function clearLock(agentId: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Staleness-aware busy check for the daemon / probe: the agent is busy only
|
|
27
|
+
* while the lock is held by a LIVE pid and younger than MAX_SESSION_MS. A
|
|
28
|
+
* crashed session (dead pid) or an expired lock does NOT block - the queue
|
|
29
|
+
* flush can spawn immediately instead of stalling on a dead lock.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isLockBusy(agentId: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Wait until the lock is free, then acquire it. Used by the CLI when a
|
|
34
|
+
* session starts so a manual run waits for the daemon-spawned session (and
|
|
35
|
+
* vice versa). Returns the acquired lock, or null when maxWaitMs elapses.
|
|
36
|
+
*/
|
|
37
|
+
export declare function acquireLock(agentId: string, kind: LockKind, meta?: Omit<AgentLock, 'pid' | 'startedAt' | 'kind'>, opts?: {
|
|
38
|
+
pollMs?: number;
|
|
39
|
+
maxWaitMs?: number;
|
|
40
|
+
onWaiting?: (busy: AgentLock) => void;
|
|
41
|
+
}): Promise<AgentLock | null>;
|
package/dist/lib/lock.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LOCK_MAX_WAIT_MS = exports.LOCK_POLL_MS = exports.MAX_SESSION_MS = exports.LOCK_DIR = void 0;
|
|
4
|
+
exports.lockPath = lockPath;
|
|
5
|
+
exports.readLock = readLock;
|
|
6
|
+
exports.tryAcquireLock = tryAcquireLock;
|
|
7
|
+
exports.releaseLock = releaseLock;
|
|
8
|
+
exports.clearLock = clearLock;
|
|
9
|
+
exports.isLockBusy = isLockBusy;
|
|
10
|
+
exports.acquireLock = acquireLock;
|
|
11
|
+
/**
|
|
12
|
+
* Per-agent session lock (~/.teamshare/locks/<agentId>.lock).
|
|
13
|
+
*
|
|
14
|
+
* Guarantees a PC never runs two concurrent sessions for the same agent:
|
|
15
|
+
* the CLI acquires the lock when a session starts (run/chat/doc) and
|
|
16
|
+
* releases it when the session ends; the daemon reads the lock before
|
|
17
|
+
* spawning and keeps wakes queued while it is held. Cross-process by
|
|
18
|
+
* design - the daemon, the CLI and manual terminals all go through it.
|
|
19
|
+
*
|
|
20
|
+
* Stale lock = holder PID no longer alive, or older than MAX_SESSION_MS
|
|
21
|
+
* (8h comfortably covers the 20-min status watch + LLM work).
|
|
22
|
+
*/
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_os_1 = require("node:os");
|
|
25
|
+
const node_path_1 = require("node:path");
|
|
26
|
+
exports.LOCK_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'locks');
|
|
27
|
+
exports.MAX_SESSION_MS = 8 * 60 * 60 * 1000;
|
|
28
|
+
exports.LOCK_POLL_MS = 10_000;
|
|
29
|
+
exports.LOCK_MAX_WAIT_MS = 6 * 60 * 60 * 1000;
|
|
30
|
+
function lockPath(agentId) {
|
|
31
|
+
return (0, node_path_1.join)(exports.LOCK_DIR, `${agentId}.lock`);
|
|
32
|
+
}
|
|
33
|
+
function readLock(agentId) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = (0, node_fs_1.readFileSync)(lockPath(agentId), 'utf8');
|
|
36
|
+
const lock = JSON.parse(raw);
|
|
37
|
+
return lock && typeof lock.pid === 'number' ? lock : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function pidAlive(pid) {
|
|
44
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
process.kill(pid, 0);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
// EPERM = exists but not ours; ESRCH (or anything else) = gone.
|
|
52
|
+
return err.code === 'EPERM';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isStale(lock) {
|
|
56
|
+
if (!pidAlive(lock.pid))
|
|
57
|
+
return true;
|
|
58
|
+
const age = Date.now() - new Date(lock.startedAt).getTime();
|
|
59
|
+
return !Number.isFinite(age) || age > exports.MAX_SESSION_MS;
|
|
60
|
+
}
|
|
61
|
+
/** One-shot acquire: ok=true on success, ok=false + busy set when held. */
|
|
62
|
+
function tryAcquireLock(agentId, kind, meta = {}) {
|
|
63
|
+
(0, node_fs_1.mkdirSync)(exports.LOCK_DIR, { recursive: true });
|
|
64
|
+
const lock = {
|
|
65
|
+
pid: process.pid,
|
|
66
|
+
startedAt: new Date().toISOString(),
|
|
67
|
+
kind,
|
|
68
|
+
...meta,
|
|
69
|
+
};
|
|
70
|
+
try {
|
|
71
|
+
(0, node_fs_1.writeFileSync)(lockPath(agentId), JSON.stringify(lock, null, 2), {
|
|
72
|
+
flag: 'wx',
|
|
73
|
+
});
|
|
74
|
+
return { ok: true };
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err.code !== 'EEXIST')
|
|
78
|
+
return { ok: false };
|
|
79
|
+
const existing = readLock(agentId);
|
|
80
|
+
if (existing && !isStale(existing))
|
|
81
|
+
return { ok: false, busy: existing };
|
|
82
|
+
try {
|
|
83
|
+
// Stale lock - take it over.
|
|
84
|
+
(0, node_fs_1.writeFileSync)(lockPath(agentId), JSON.stringify(lock, null, 2));
|
|
85
|
+
return { ok: true };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Release the lock, but only if we hold it (pid match). */
|
|
93
|
+
function releaseLock(agentId) {
|
|
94
|
+
try {
|
|
95
|
+
const lock = readLock(agentId);
|
|
96
|
+
if (lock && lock.pid === process.pid)
|
|
97
|
+
(0, node_fs_1.rmSync)(lockPath(agentId));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* best-effort */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Remove the lock unconditionally (manual override, `clear-lock`). */
|
|
104
|
+
function clearLock(agentId) {
|
|
105
|
+
try {
|
|
106
|
+
(0, node_fs_1.rmSync)(lockPath(agentId));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* best-effort */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Staleness-aware busy check for the daemon / probe: the agent is busy only
|
|
114
|
+
* while the lock is held by a LIVE pid and younger than MAX_SESSION_MS. A
|
|
115
|
+
* crashed session (dead pid) or an expired lock does NOT block - the queue
|
|
116
|
+
* flush can spawn immediately instead of stalling on a dead lock.
|
|
117
|
+
*/
|
|
118
|
+
function isLockBusy(agentId) {
|
|
119
|
+
const lock = readLock(agentId);
|
|
120
|
+
if (!lock)
|
|
121
|
+
return false;
|
|
122
|
+
if (!pidAlive(lock.pid))
|
|
123
|
+
return false;
|
|
124
|
+
const age = Date.now() - new Date(lock.startedAt).getTime();
|
|
125
|
+
if (!Number.isFinite(age) || age > exports.MAX_SESSION_MS)
|
|
126
|
+
return false;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Wait until the lock is free, then acquire it. Used by the CLI when a
|
|
131
|
+
* session starts so a manual run waits for the daemon-spawned session (and
|
|
132
|
+
* vice versa). Returns the acquired lock, or null when maxWaitMs elapses.
|
|
133
|
+
*/
|
|
134
|
+
async function acquireLock(agentId, kind, meta = {}, opts = {}) {
|
|
135
|
+
const pollMs = opts.pollMs ?? exports.LOCK_POLL_MS;
|
|
136
|
+
const deadline = Date.now() + (opts.maxWaitMs ?? exports.LOCK_MAX_WAIT_MS);
|
|
137
|
+
for (;;) {
|
|
138
|
+
const attempt = tryAcquireLock(agentId, kind, meta);
|
|
139
|
+
if (attempt.ok)
|
|
140
|
+
return readLock(agentId);
|
|
141
|
+
if (attempt.busy)
|
|
142
|
+
opts.onWaiting?.(attempt.busy);
|
|
143
|
+
if (Date.now() >= deadline)
|
|
144
|
+
return null;
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=lock.js.map
|