@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.
|
|
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.
|
|
53
|
-
"@ottocode/database": "0.1.
|
|
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'],
|
package/src/routes/skills.ts
CHANGED
|
@@ -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]
|
|
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
|
);
|