@ottocode/server 0.1.215 → 0.1.216

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/server",
3
- "version": "0.1.215",
3
+ "version": "0.1.216",
4
4
  "description": "HTTP API server for ottocode",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -49,8 +49,8 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@ottocode/sdk": "0.1.215",
53
- "@ottocode/database": "0.1.215",
52
+ "@ottocode/sdk": "0.1.216",
53
+ "@ottocode/database": "0.1.216",
54
54
  "drizzle-orm": "^0.44.5",
55
55
  "hono": "^4.9.9",
56
56
  "zod": "^4.3.6"
@@ -93,6 +93,90 @@ export const skillsPaths = {
93
93
  },
94
94
  },
95
95
  },
96
+ '/v1/skills/{name}/files': {
97
+ get: {
98
+ tags: ['config'],
99
+ operationId: 'listSkillFiles',
100
+ summary: 'List files in a skill directory',
101
+ parameters: [
102
+ {
103
+ in: 'path',
104
+ name: 'name',
105
+ required: true,
106
+ schema: { type: 'string' },
107
+ },
108
+ projectQueryParam(),
109
+ ],
110
+ responses: {
111
+ 200: {
112
+ description: 'OK',
113
+ content: {
114
+ 'application/json': {
115
+ schema: {
116
+ type: 'object',
117
+ properties: {
118
+ files: {
119
+ type: 'array',
120
+ items: {
121
+ type: 'object',
122
+ properties: {
123
+ relativePath: { type: 'string' },
124
+ size: { type: 'number' },
125
+ },
126
+ required: ['relativePath', 'size'],
127
+ },
128
+ },
129
+ },
130
+ required: ['files'],
131
+ },
132
+ },
133
+ },
134
+ },
135
+ 500: errorResponse(),
136
+ },
137
+ },
138
+ },
139
+ '/v1/skills/{name}/files/{filePath}': {
140
+ get: {
141
+ tags: ['config'],
142
+ operationId: 'getSkillFile',
143
+ summary: 'Read a specific file from a skill directory',
144
+ parameters: [
145
+ {
146
+ in: 'path',
147
+ name: 'name',
148
+ required: true,
149
+ schema: { type: 'string' },
150
+ },
151
+ {
152
+ in: 'path',
153
+ name: 'filePath',
154
+ required: true,
155
+ schema: { type: 'string' },
156
+ },
157
+ projectQueryParam(),
158
+ ],
159
+ responses: {
160
+ 200: {
161
+ description: 'OK',
162
+ content: {
163
+ 'application/json': {
164
+ schema: {
165
+ type: 'object',
166
+ properties: {
167
+ content: { type: 'string' },
168
+ path: { type: 'string' },
169
+ },
170
+ required: ['content', 'path'],
171
+ },
172
+ },
173
+ },
174
+ },
175
+ 404: errorResponse(),
176
+ 500: errorResponse(),
177
+ },
178
+ },
179
+ },
96
180
  '/v1/skills/validate': {
97
181
  post: {
98
182
  tags: ['config'],
@@ -2,6 +2,8 @@ import type { Hono } from 'hono';
2
2
  import {
3
3
  discoverSkills,
4
4
  loadSkill,
5
+ loadSkillFile,
6
+ discoverSkillFiles,
5
7
  findGitRoot,
6
8
  validateSkillName,
7
9
  parseSkillFile,
@@ -61,6 +63,51 @@ export function registerSkillsRoutes(app: Hono) {
61
63
  });
62
64
 
63
65
  app.post('/v1/skills/validate', async (c) => {
66
+ app.get('/v1/skills/:name/files', async (c) => {
67
+ try {
68
+ const name = c.req.param('name');
69
+ const projectRoot = c.req.query('project') || process.cwd();
70
+ const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
71
+ await discoverSkills(projectRoot, repoRoot);
72
+
73
+ const files = await discoverSkillFiles(name);
74
+ return c.json({ files });
75
+ } catch (error) {
76
+ logger.error('Failed to list skill files', error);
77
+ const errorResponse = serializeError(error);
78
+ return c.json(
79
+ errorResponse,
80
+ (errorResponse.error.status || 500) as 500,
81
+ );
82
+ }
83
+ });
84
+
85
+ app.get('/v1/skills/:name/files/*', async (c) => {
86
+ try {
87
+ const name = c.req.param('name');
88
+ const filePath = c.req.path.replace(`/v1/skills/${name}/files/`, '');
89
+ const projectRoot = c.req.query('project') || process.cwd();
90
+ const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
91
+ await discoverSkills(projectRoot, repoRoot);
92
+
93
+ const result = await loadSkillFile(name, filePath);
94
+ if (!result) {
95
+ return c.json(
96
+ { error: `File '${filePath}' not found in skill '${name}'` },
97
+ 404,
98
+ );
99
+ }
100
+ return c.json({ content: result.content, path: result.resolvedPath });
101
+ } catch (error) {
102
+ logger.error('Failed to load skill file', error);
103
+ const errorResponse = serializeError(error);
104
+ return c.json(
105
+ errorResponse,
106
+ (errorResponse.error.status || 500) as 500,
107
+ );
108
+ }
109
+ });
110
+
64
111
  try {
65
112
  const body = await c.req.json<{ content: string; path?: string }>();
66
113
  if (!body.content) {
@@ -24,26 +24,30 @@ export async function buildHistoryMessages(
24
24
  const toolHistory = new ToolHistoryTracker();
25
25
 
26
26
  for (const m of rows) {
27
+ const parts = await db
28
+ .select()
29
+ .from(messageParts)
30
+ .where(eq(messageParts.messageId, m.id))
31
+ .orderBy(asc(messageParts.index));
32
+
27
33
  if (
28
34
  m.role === 'assistant' &&
29
35
  m.status !== 'complete' &&
30
36
  m.status !== 'completed' &&
31
- m.status !== 'error' &&
32
- m.id !== currentMessageId
37
+ m.status !== 'error'
33
38
  ) {
39
+ if (parts.length === 0) {
40
+ debugLog(
41
+ `[buildHistoryMessages] Skipping empty assistant message ${m.id} with status ${m.status}`,
42
+ );
43
+ continue;
44
+ }
45
+
34
46
  debugLog(
35
- `[buildHistoryMessages] Skipping assistant message ${m.id} with status ${m.status}`,
47
+ `[buildHistoryMessages] Including non-complete assistant message ${m.id} (status: ${m.status}) with ${parts.length} parts to preserve context`,
36
48
  );
37
- logPendingToolParts(db, m.id);
38
- continue;
39
49
  }
40
50
 
41
- const parts = await db
42
- .select()
43
- .from(messageParts)
44
- .where(eq(messageParts.messageId, m.id))
45
- .orderBy(asc(messageParts.index));
46
-
47
51
  if (m.role === 'user') {
48
52
  const uparts: UIMessage['parts'] = [];
49
53
  for (const p of parts) {
@@ -123,7 +127,6 @@ export async function buildHistoryMessages(
123
127
  if (t) assistantParts.push({ type: 'text', text: t });
124
128
  } catch {}
125
129
  } else if (p.type === 'tool_call') {
126
- // Skip compacted tool calls entirely
127
130
  if (p.compactedAt) continue;
128
131
 
129
132
  try {
@@ -141,7 +144,6 @@ export async function buildHistoryMessages(
141
144
  }
142
145
  } catch {}
143
146
  } else if (p.type === 'tool_result') {
144
- // Skip compacted tool results entirely
145
147
  if (p.compactedAt) continue;
146
148
 
147
149
  try {
@@ -159,7 +161,6 @@ export async function buildHistoryMessages(
159
161
  }
160
162
  } catch {}
161
163
  }
162
- // Skip error parts in history
163
164
  }
164
165
 
165
166
  const toolResultsById = new Map(
@@ -167,14 +168,12 @@ export async function buildHistoryMessages(
167
168
  );
168
169
 
169
170
  for (const call of toolCalls) {
170
- // Skip finish tool from history - it's internal loop control
171
171
  if (call.name === 'finish') continue;
172
172
 
173
173
  const toolType = `tool-${call.name}` as `tool-${string}`;
174
174
  let result = toolResultsById.get(call.callId);
175
175
 
176
176
  if (!result) {
177
- // Synthesize a result for incomplete tool calls to preserve history
178
177
  debugLog(
179
178
  `[buildHistoryMessages] Synthesizing error result for incomplete tool call ${call.name}#${call.callId}`,
180
179
  );