@stevezhou/sisu 0.3.6 → 0.3.8

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/commands.js CHANGED
@@ -26,7 +26,6 @@ const child_process_1 = require("child_process");
26
26
  const fs_1 = __importDefault(require("fs"));
27
27
  const path_1 = __importDefault(require("path"));
28
28
  const http_1 = require("./http");
29
- const adapter_1 = require("./runtime/adapter");
30
29
  const loop_1 = require("./runtime/loop");
31
30
  const models_1 = require("./runtime/models");
32
31
  Object.defineProperty(exports, "fetchModelCatalog", { enumerable: true, get: function () { return models_1.fetchModelCatalog; } });
@@ -265,9 +264,7 @@ function resolveBoundWorkspace(projectId) {
265
264
  throw new Error('no local workspace — run sisu open <dir> --project <id>');
266
265
  throw new Error('multiple workspaces — pass --project');
267
266
  }
268
- function listLocalCommand(projectId) {
269
- (0, store_1.requireAuth)();
270
- const bound = resolveBoundWorkspace(projectId);
267
+ function formatDirListing(bound) {
271
268
  const names = fs_1.default.readdirSync(bound.path).filter((name) => !name.startsWith('.'));
272
269
  if (!names.length)
273
270
  return `${bound.path} (empty)`;
@@ -277,24 +274,31 @@ function listLocalCommand(projectId) {
277
274
  return `${name}${suffix}`;
278
275
  }).join('\n');
279
276
  }
277
+ function listLocalCommand(projectId) {
278
+ (0, store_1.requireAuth)();
279
+ const workspaces = (0, store_1.readWorkspaces)();
280
+ const requested = (projectId || '').trim();
281
+ if (requested) {
282
+ if (!workspaces[requested])
283
+ throw new Error(`unknown workspace ${requested}`);
284
+ return formatDirListing({ projectId: requested, path: workspaces[requested] });
285
+ }
286
+ const entries = Object.entries(workspaces);
287
+ if (!entries.length)
288
+ throw new Error('no local workspace — run sisu open <dir> --project <id>');
289
+ if (entries.length === 1)
290
+ return formatDirListing({ projectId: entries[0][0], path: entries[0][1] });
291
+ return entries.map(([id, dir]) => `${id} ${dir}`).join('\n');
292
+ }
280
293
  async function execCommand(prompt, options = {}, http = http_1.defaultHttp) {
281
294
  const text = prompt.trim();
282
295
  if (!text)
283
296
  throw new Error('prompt is required');
284
297
  const stub = Boolean(options.stub || process.env.SISU_RUNTIME_STUB === '1');
285
298
  const cwd = options.cwd || process.cwd();
286
- const modelClient = options.modelClient || (stub
287
- ? (0, loop_1.createLaunchStubModel)()
288
- : (() => {
289
- const auth = (0, store_1.requireAuth)();
290
- return (0, adapter_1.createSisuCloudModel)(http, {
291
- apiBase: auth.api_base,
292
- token: auth.token,
293
- client: options.client || 'cli',
294
- });
295
- })());
296
299
  if (!stub)
297
300
  (0, store_1.requireAuth)();
301
+ const modelClient = options.modelClient || (stub ? (0, loop_1.createLaunchStubModel)() : undefined);
298
302
  if (options.projectId) {
299
303
  (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_project_id: options.projectId });
300
304
  }
@@ -312,7 +316,7 @@ async function execCommand(prompt, options = {}, http = http_1.defaultHttp) {
312
316
  }
313
317
  async function listConversationsCommand(http = http_1.defaultHttp) {
314
318
  const auth = (0, store_1.requireAuth)();
315
- const response = await http(`${auth.api_base}/api/chat/conversations?limit=30`, {
319
+ const response = await http(`${auth.api_base}/api/chat/conversations?source=cli&limit=30`, {
316
320
  headers: (0, http_1.authHeaders)(auth.token),
317
321
  });
318
322
  const body = await response.json().catch(() => []);
@@ -322,16 +326,37 @@ async function listConversationsCommand(http = http_1.defaultHttp) {
322
326
  if (!rows.length)
323
327
  return 'no saved conversations';
324
328
  return rows.map((row) => {
325
- const client = row.client ? ` [${row.client}]` : '';
326
- return `${row.id} ${row.title || '(untitled)'}${client}`;
329
+ const tag = row.source || row.client;
330
+ const suffix = tag ? ` [${tag}]` : '';
331
+ return `${row.id} ${row.title || '(untitled)'}${suffix}`;
327
332
  }).join('\n');
328
333
  }
329
- function openConversationCommand(conversationId) {
334
+ async function openConversationCommand(conversationId, http = http_1.defaultHttp) {
330
335
  const id = conversationId.trim();
331
336
  if (!id)
332
337
  throw new Error('conversation id is required');
333
338
  (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: id });
334
- return `opened ${id}`;
339
+ const auth = (0, store_1.requireAuth)();
340
+ const response = await http(`${auth.api_base}/api/chat/conversations/${id}`, {
341
+ headers: (0, http_1.authHeaders)(auth.token),
342
+ });
343
+ const body = await response.json().catch(() => ({}));
344
+ if (!response.ok)
345
+ throw new Error((0, http_1.errorDetail)(body, `thread failed (${response.status})`));
346
+ const messages = Array.isArray(body.messages) ? body.messages : [];
347
+ const lines = [`opened ${id}`];
348
+ if (body.title)
349
+ lines.push(String(body.title));
350
+ for (const msg of messages) {
351
+ const role = String(msg?.role || '').trim();
352
+ const content = String(msg?.content || '').trim();
353
+ if (!role || !content)
354
+ continue;
355
+ lines.push(`${role}: ${content}`);
356
+ }
357
+ if (lines.length === 1)
358
+ lines.push('(no messages)');
359
+ return lines.join('\n');
335
360
  }
336
361
  async function setTrainingCommand(optIn, http = http_1.defaultHttp) {
337
362
  const auth = (0, store_1.requireAuth)();
package/dist/main.js CHANGED
@@ -163,18 +163,21 @@ async function runCli(argv, deps = {}) {
163
163
  model: parsed.flags['--model'],
164
164
  newConversation: parsed.switches.has('new'),
165
165
  stub: parsed.switches.has('stub') || process.env.SISU_RUNTIME_STUB === '1',
166
- });
167
- if (result.text)
166
+ }, http);
167
+ if (result.text.trim()) {
168
168
  process.stdout.write(`${result.text}\n`);
169
- return 0;
169
+ return 0;
170
+ }
171
+ process.stderr.write('no model text\n');
172
+ return 1;
170
173
  }
171
174
  if (command === 'history') {
172
- process.stdout.write(`${await (0, commands_1.listConversationsCommand)(http_1.defaultHttp)}\n`);
175
+ process.stdout.write(`${await (0, commands_1.listConversationsCommand)(http)}\n`);
173
176
  return 0;
174
177
  }
175
178
  if (command === 'thread') {
176
179
  const id = args.find((item) => !item.startsWith('--')) || '';
177
- process.stdout.write(`${(0, commands_1.openConversationCommand)(id)}\n`);
180
+ process.stdout.write(`${await (0, commands_1.openConversationCommand)(id, http)}\n`);
178
181
  return 0;
179
182
  }
180
183
  if (command === 'models') {
@@ -41,10 +41,10 @@ function completeUrl(apiBase) {
41
41
  function openaiCompatUrl(apiBase) {
42
42
  return `${apiBase.replace(/\/+$/, '')}${suite_1.OPENAI_COMPAT_PATH}`;
43
43
  }
44
- function completeHeaders(token) {
44
+ function completeHeaders(token, conversationId) {
45
45
  return {
46
46
  ...(0, http_1.authHeaders)(token),
47
- 'x-sisu-conversation-id': (0, store_1.ensureConversationId)(),
47
+ 'x-sisu-conversation-id': conversationId || (0, store_1.ensureConversationId)(),
48
48
  };
49
49
  }
50
50
  function buildCompleteRequest(request, options = {}) {
@@ -143,7 +143,7 @@ function createSisuCloudModel(http, options) {
143
143
  }
144
144
  const sent = await http(completeUrl(options.apiBase), {
145
145
  method: 'POST',
146
- headers: completeHeaders(options.token),
146
+ headers: completeHeaders(options.token, options.conversationId),
147
147
  body: JSON.stringify(payload),
148
148
  });
149
149
  if (!sent.ok) {
@@ -23,7 +23,10 @@ async function* runLocalTurn(options) {
23
23
  for (let round = 0; round < maxRounds; round += 1) {
24
24
  const request = { model: options.model, messages: messages.map((row) => ({ ...row })), tools };
25
25
  requests.push(request);
26
- const completion = await options.client.complete(request);
26
+ let completion = await options.client.complete(request);
27
+ if (!completion.text && !completion.tool_calls.length) {
28
+ completion = await options.client.complete(request);
29
+ }
27
30
  if (completion.text) {
28
31
  text += completion.text;
29
32
  yield { type: 'text', text: completion.text };
@@ -17,9 +17,9 @@ function sessionsDir() {
17
17
  function sessionFile(id) {
18
18
  return path_1.default.join(sessionsDir(), `${id}.json`);
19
19
  }
20
- function createLocalSession(title, cwd, model) {
20
+ function createLocalSession(title, cwd, model, id) {
21
21
  const session = {
22
- id: (0, crypto_1.randomUUID)(),
22
+ id: id || (0, crypto_1.randomUUID)(),
23
23
  title: title.slice(0, 80) || 'session',
24
24
  cwd,
25
25
  model,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLocalRuntimeTransport = createLocalRuntimeTransport;
4
4
  exports.execLocalTurn = execLocalTurn;
5
+ const crypto_1 = require("crypto");
5
6
  const store_1 = require("../store");
6
7
  const adapter_1 = require("./adapter");
7
8
  const loop_1 = require("./loop");
@@ -15,11 +16,14 @@ function createLocalRuntimeTransport(http, options = {}) {
15
16
  let conversationId = sendOptions.conversationId || (!sendOptions.newConversation ? (0, store_1.readSession)().last_conversation_id : '') || '';
16
17
  let existing = conversationId ? (0, sessions_1.loadLocalSession)(conversationId) : null;
17
18
  if (!existing || sendOptions.newConversation) {
18
- existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, (0, store_1.readSession)().last_model);
19
- conversationId = existing.id;
19
+ conversationId = (0, crypto_1.randomUUID)();
20
+ (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: conversationId });
21
+ existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, (0, store_1.readSession)().last_model, conversationId);
20
22
  }
21
- (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: conversationId });
22
- const client = options.modelClient || cloudClient(http, options.client || 'tui');
23
+ else {
24
+ (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: conversationId });
25
+ }
26
+ const client = options.modelClient || cloudClient(http, options.client || 'tui', conversationId);
23
27
  const model = options.modelClient
24
28
  ? existing.model || (0, store_1.readSession)().last_model || 'stub'
25
29
  : await (0, models_1.resolveRuntimeModel)(http, { explicit: existing.model || (0, store_1.readSession)().last_model });
@@ -64,12 +68,15 @@ function createLocalRuntimeTransport(http, options = {}) {
64
68
  async function execLocalTurn(prompt, options = {}) {
65
69
  const cwd = (0, tools_1.resolveWorkspaceRoot)(options.cwd);
66
70
  let conversationId = options.conversationId || (!options.newConversation ? (0, store_1.readSession)().last_conversation_id : '') || '';
71
+ if (options.newConversation || !conversationId) {
72
+ conversationId = (0, crypto_1.randomUUID)();
73
+ (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: conversationId });
74
+ }
67
75
  let existing = conversationId ? (0, sessions_1.loadLocalSession)(conversationId) : null;
68
76
  if (!existing) {
69
- existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, options.model);
70
- conversationId = existing.id;
77
+ existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, options.model, conversationId);
71
78
  }
72
- const client = options.modelClient || (options.http ? cloudClient(options.http, options.client || 'cli') : undefined);
79
+ const client = options.modelClient || (options.http ? cloudClient(options.http, options.client || 'cli', conversationId) : undefined);
73
80
  if (!client)
74
81
  throw new Error('model client required');
75
82
  const result = await (0, loop_1.collectLocalTurn)({
@@ -87,7 +94,12 @@ async function execLocalTurn(prompt, options = {}) {
87
94
  (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: result.conversationId, last_model: options.model || (0, store_1.readSession)().last_model });
88
95
  return { conversationId: result.conversationId, text: result.text, toolResults: result.toolResults };
89
96
  }
90
- function cloudClient(http, client) {
97
+ function cloudClient(http, client, conversationId) {
91
98
  const auth = (0, store_1.requireAuth)();
92
- return (0, adapter_1.createSisuCloudModel)(http, { apiBase: auth.api_base, token: auth.token, client });
99
+ return (0, adapter_1.createSisuCloudModel)(http, {
100
+ apiBase: auth.api_base,
101
+ token: auth.token,
102
+ client,
103
+ conversationId,
104
+ });
93
105
  }
package/dist/store.js CHANGED
@@ -131,7 +131,21 @@ function clearAuth() {
131
131
  }
132
132
  function readWorkspaces() {
133
133
  const raw = readJson(workspacePath(), {});
134
- return raw && typeof raw === 'object' ? raw : {};
134
+ if (!raw || typeof raw !== 'object')
135
+ return {};
136
+ const kept = {};
137
+ let dirty = false;
138
+ for (const [id, dir] of Object.entries(raw)) {
139
+ if (typeof dir === 'string' && dir && fs_1.default.existsSync(dir) && fs_1.default.statSync(dir).isDirectory()) {
140
+ kept[id] = dir;
141
+ }
142
+ else {
143
+ dirty = true;
144
+ }
145
+ }
146
+ if (dirty)
147
+ writeJson(workspacePath(), kept);
148
+ return kept;
135
149
  }
136
150
  function bindWorkspace(projectId, requestedPath) {
137
151
  if (!projectId.trim())
package/dist/tui.js CHANGED
@@ -397,7 +397,7 @@ async function runTui(io, deps = {}) {
397
397
  }
398
398
  if (raw.startsWith('/open ')) {
399
399
  try {
400
- io.write(`${openThread(raw.slice(6).trim())}\n`);
400
+ io.write(`${await openThread(raw.slice(6).trim(), http)}\n`);
401
401
  newConversation = false;
402
402
  }
403
403
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevezhou/sisu",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "SiSu CLI — 思溯 / SiSu · 思有所溯",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://www.sisu.chat",