hoomanjs 1.0.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.
Files changed (96) hide show
  1. package/.github/screenshot.png +0 -0
  2. package/.github/workflows/build-publish.yml +49 -0
  3. package/LICENSE +21 -0
  4. package/README.md +399 -0
  5. package/docker-compose.yml +13 -0
  6. package/package.json +78 -0
  7. package/src/acp/acp-agent.ts +803 -0
  8. package/src/acp/approvals.ts +147 -0
  9. package/src/acp/index.ts +1 -0
  10. package/src/acp/meta/system-prompt.ts +44 -0
  11. package/src/acp/meta/user-id.ts +44 -0
  12. package/src/acp/prompt-invoke.ts +149 -0
  13. package/src/acp/sessions/config-options.ts +56 -0
  14. package/src/acp/sessions/replay.ts +131 -0
  15. package/src/acp/sessions/store.ts +158 -0
  16. package/src/acp/sessions/title.ts +22 -0
  17. package/src/acp/utils/paths.ts +5 -0
  18. package/src/acp/utils/tool-kind.ts +38 -0
  19. package/src/acp/utils/tool-locations.ts +46 -0
  20. package/src/acp/utils/tool-result-content.ts +27 -0
  21. package/src/chat/app.tsx +428 -0
  22. package/src/chat/approvals.ts +96 -0
  23. package/src/chat/components/ApprovalPrompt.tsx +25 -0
  24. package/src/chat/components/ChatMessage.tsx +47 -0
  25. package/src/chat/components/Composer.tsx +39 -0
  26. package/src/chat/components/EmptyChatBanner.tsx +26 -0
  27. package/src/chat/components/ReasoningStrip.tsx +30 -0
  28. package/src/chat/components/Spinner.tsx +34 -0
  29. package/src/chat/components/StatusBar.tsx +65 -0
  30. package/src/chat/components/ThinkingStatus.tsx +128 -0
  31. package/src/chat/components/ToolEvent.tsx +34 -0
  32. package/src/chat/components/Transcript.tsx +34 -0
  33. package/src/chat/components/ascii-logo.ts +11 -0
  34. package/src/chat/components/shared.ts +70 -0
  35. package/src/chat/index.tsx +42 -0
  36. package/src/chat/types.ts +21 -0
  37. package/src/cli.ts +146 -0
  38. package/src/configure/app.tsx +911 -0
  39. package/src/configure/components/BusyScreen.tsx +22 -0
  40. package/src/configure/components/HomeScreen.tsx +43 -0
  41. package/src/configure/components/MenuScreen.tsx +44 -0
  42. package/src/configure/components/PromptForm.tsx +40 -0
  43. package/src/configure/components/SelectMenuItem.tsx +30 -0
  44. package/src/configure/index.tsx +43 -0
  45. package/src/configure/open-in-editor.ts +133 -0
  46. package/src/configure/types.ts +45 -0
  47. package/src/configure/utils.ts +113 -0
  48. package/src/core/agent/index.ts +76 -0
  49. package/src/core/config.ts +157 -0
  50. package/src/core/index.ts +54 -0
  51. package/src/core/mcp/config.ts +80 -0
  52. package/src/core/mcp/index.ts +13 -0
  53. package/src/core/mcp/manager.ts +109 -0
  54. package/src/core/mcp/prefixed-mcp-tool.ts +45 -0
  55. package/src/core/mcp/tools.ts +92 -0
  56. package/src/core/mcp/types.ts +37 -0
  57. package/src/core/memory/index.ts +17 -0
  58. package/src/core/memory/ltm/embed.ts +67 -0
  59. package/src/core/memory/ltm/index.ts +18 -0
  60. package/src/core/memory/ltm/store.ts +376 -0
  61. package/src/core/memory/ltm/tools.ts +146 -0
  62. package/src/core/memory/ltm/types.ts +111 -0
  63. package/src/core/memory/ltm/utils.ts +218 -0
  64. package/src/core/memory/stm/index.ts +17 -0
  65. package/src/core/models/anthropic.ts +53 -0
  66. package/src/core/models/bedrock.ts +54 -0
  67. package/src/core/models/google.ts +51 -0
  68. package/src/core/models/index.ts +16 -0
  69. package/src/core/models/ollama/index.ts +13 -0
  70. package/src/core/models/ollama/strands-ollama.ts +439 -0
  71. package/src/core/models/openai.ts +12 -0
  72. package/src/core/prompts/index.ts +23 -0
  73. package/src/core/prompts/skills.ts +66 -0
  74. package/src/core/prompts/static/fetch.md +33 -0
  75. package/src/core/prompts/static/filesystem.md +38 -0
  76. package/src/core/prompts/static/identity.md +22 -0
  77. package/src/core/prompts/static/ltm.md +39 -0
  78. package/src/core/prompts/static/memory.md +39 -0
  79. package/src/core/prompts/static/shell.md +34 -0
  80. package/src/core/prompts/static/skills.md +19 -0
  81. package/src/core/prompts/static/thinking.md +27 -0
  82. package/src/core/prompts/system.ts +109 -0
  83. package/src/core/skills/index.ts +2 -0
  84. package/src/core/skills/registry.ts +239 -0
  85. package/src/core/skills/tools.ts +80 -0
  86. package/src/core/toolkit.ts +13 -0
  87. package/src/core/tools/fetch.ts +288 -0
  88. package/src/core/tools/filesystem.ts +747 -0
  89. package/src/core/tools/index.ts +5 -0
  90. package/src/core/tools/shell.ts +426 -0
  91. package/src/core/tools/thinking.ts +184 -0
  92. package/src/core/tools/time.ts +121 -0
  93. package/src/core/utils/cwd-context.ts +11 -0
  94. package/src/core/utils/paths.ts +28 -0
  95. package/src/exec/approvals.ts +85 -0
  96. package/tsconfig.json +30 -0
@@ -0,0 +1,439 @@
1
+ import {
2
+ ContextWindowOverflowError,
3
+ Model,
4
+ ModelError,
5
+ } from "@strands-agents/sdk";
6
+ import type { SystemPrompt } from "@strands-agents/sdk";
7
+ import type { BaseModelConfig, StreamOptions } from "@strands-agents/sdk";
8
+ import {
9
+ ModelContentBlockDeltaEvent,
10
+ ModelContentBlockStartEvent,
11
+ ModelContentBlockStopEvent,
12
+ ModelMessageStartEvent,
13
+ ModelMessageStopEvent,
14
+ ModelMetadataEvent,
15
+ } from "@strands-agents/sdk";
16
+ import type { ModelStreamEvent } from "@strands-agents/sdk";
17
+ import type { ToolSpec } from "@strands-agents/sdk";
18
+ import { Message, ToolResultBlock, ToolUseBlock } from "@strands-agents/sdk";
19
+ import type {
20
+ ContentBlock,
21
+ ImageBlock,
22
+ JsonBlock,
23
+ ToolResultContent,
24
+ } from "@strands-agents/sdk";
25
+ import { Ollama } from "ollama";
26
+ import type {
27
+ ChatRequest,
28
+ ChatResponse,
29
+ Message as OllamaMessage,
30
+ Tool as OllamaTool,
31
+ } from "ollama";
32
+
33
+ export interface OllamaModelConfig extends BaseModelConfig {
34
+ modelId?: string;
35
+ /** Ollama server URL (default `http://127.0.0.1:11434` or `OLLAMA_HOST`). */
36
+ host?: string;
37
+ /** Passed through to Ollama `keep_alive`. */
38
+ keepAlive?: string | number;
39
+ /** Merged into Ollama `options` (e.g. num_ctx). */
40
+ options?: Record<string, unknown>;
41
+ /**
42
+ * Ollama `think` flag (controls `message.thinking` on supported models).
43
+ * - **Omitted (`undefined`):** do not send `think` — server default (often streams `thinking`, which we map to
44
+ * Strands `ReasoningBlock` via `reasoningContentDelta`).
45
+ * - **`false`:** disable the thinking channel; the model puts prose in `content` only (shows up as normal
46
+ * `TextBlock` in persisted sessions).
47
+ * - **`true` or `"high" | "medium" | "low"`:** force extended thinking at that level.
48
+ */
49
+ think?: boolean | "high" | "medium" | "low";
50
+ }
51
+
52
+ function extractSystemText(system?: SystemPrompt): string | undefined {
53
+ if (system === undefined) {
54
+ return undefined;
55
+ }
56
+ if (typeof system === "string") {
57
+ return system;
58
+ }
59
+ const parts: string[] = [];
60
+ for (const block of system) {
61
+ if (block.type === "textBlock") {
62
+ parts.push(block.text);
63
+ }
64
+ }
65
+ const joined = parts.join("\n").trim();
66
+ return joined.length > 0 ? joined : undefined;
67
+ }
68
+
69
+ /** One Ollama chat message carrying an image (bytes or URL string). */
70
+ function formatImageBlock(
71
+ role: "user" | "assistant" | "tool",
72
+ block: ImageBlock,
73
+ ): OllamaMessage[] {
74
+ const src = block.source;
75
+ if (src.type === "imageSourceBytes") {
76
+ return [{ role, content: "", images: [src.bytes] }];
77
+ }
78
+ if (src.type === "imageSourceUrl") {
79
+ return [{ role, content: "", images: [src.url] }];
80
+ }
81
+ return [
82
+ {
83
+ role,
84
+ content:
85
+ "(Ollama: image sources other than bytes or URL are not supported for this provider)",
86
+ },
87
+ ];
88
+ }
89
+
90
+ /**
91
+ * Flatten a tool result into one Ollama `role: "tool"` message per content item, matching
92
+ * Python `OllamaModel._format_request_message_contents` for `toolResult` (text, JSON as text,
93
+ * nested images as `images`, etc.).
94
+ */
95
+ function formatToolResultContentsToOllama(
96
+ block: ToolResultBlock,
97
+ ): OllamaMessage[] {
98
+ const out: OllamaMessage[] = [];
99
+ for (const c of block.content) {
100
+ out.push(...formatToolResultContentToOllama(c));
101
+ }
102
+ if (out.length > 0) {
103
+ return out;
104
+ }
105
+ const fallback = block.status === "error" ? "(tool error)" : "";
106
+ return [{ role: "tool", content: fallback }];
107
+ }
108
+
109
+ function formatToolResultContentToOllama(
110
+ c: ToolResultContent,
111
+ ): OllamaMessage[] {
112
+ if (c.type === "textBlock") {
113
+ return [{ role: "tool", content: c.text }];
114
+ }
115
+ if (c.type === "jsonBlock") {
116
+ const j = c as JsonBlock;
117
+ return [{ role: "tool", content: JSON.stringify(j.json) }];
118
+ }
119
+ if (c.type === "imageBlock") {
120
+ return formatImageBlock("tool", c as ImageBlock);
121
+ }
122
+ if (c.type === "videoBlock" || c.type === "documentBlock") {
123
+ return [
124
+ {
125
+ role: "tool",
126
+ content: `(Ollama: tool result ${c.type} is not supported)`,
127
+ },
128
+ ];
129
+ }
130
+ return [];
131
+ }
132
+
133
+ function formatContentBlock(
134
+ role: "user" | "assistant",
135
+ block: ContentBlock,
136
+ ): OllamaMessage[] {
137
+ if (block.type === "textBlock") {
138
+ return [{ role, content: block.text }];
139
+ }
140
+ if (block.type === "imageBlock") {
141
+ return formatImageBlock(role, block as ImageBlock);
142
+ }
143
+ if (block.type === "toolUseBlock") {
144
+ const b = block as ToolUseBlock;
145
+ const args =
146
+ typeof b.input === "object" && b.input !== null && !Array.isArray(b.input)
147
+ ? (b.input as Record<string, unknown>)
148
+ : {};
149
+ return [
150
+ {
151
+ role: "assistant",
152
+ content: "",
153
+ tool_calls: [
154
+ {
155
+ function: {
156
+ name: b.name,
157
+ arguments: args,
158
+ },
159
+ },
160
+ ],
161
+ },
162
+ ];
163
+ }
164
+ if (block.type === "toolResultBlock") {
165
+ return formatToolResultContentsToOllama(block as ToolResultBlock);
166
+ }
167
+ if (block.type === "reasoningBlock") {
168
+ // Ollama chat history has no structured reasoning part. Re-sending prior turns' chain
169
+ // of thought as plain text would pollute `content` and break separation in persistence — omit.
170
+ return [];
171
+ }
172
+ return [];
173
+ }
174
+
175
+ function strandsMessagesToOllama(
176
+ messages: Message[],
177
+ systemText: string | undefined,
178
+ ): OllamaMessage[] {
179
+ const out: OllamaMessage[] = [];
180
+ if (systemText) {
181
+ out.push({ role: "system", content: systemText });
182
+ }
183
+ for (const msg of messages) {
184
+ for (const block of msg.content) {
185
+ out.push(...formatContentBlock(msg.role, block));
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+
191
+ function strandsToolsToOllama(
192
+ toolSpecs: ToolSpec[] | undefined,
193
+ ): OllamaTool[] | undefined {
194
+ if (!toolSpecs?.length) {
195
+ return undefined;
196
+ }
197
+ return toolSpecs.map(
198
+ (spec) =>
199
+ ({
200
+ type: "function",
201
+ function: {
202
+ name: spec.name,
203
+ description: spec.description,
204
+ parameters: spec.inputSchema ?? { type: "object", properties: {} },
205
+ },
206
+ }) as OllamaTool,
207
+ );
208
+ }
209
+
210
+ function mapDoneReason(reason: string | undefined): "endTurn" | "maxTokens" {
211
+ const r = (reason ?? "").toLowerCase();
212
+ if (r.includes("length") || r === "max_tokens") {
213
+ return "maxTokens";
214
+ }
215
+ return "endTurn";
216
+ }
217
+
218
+ /** Stable key so we do not re-emit identical `tool_calls` on repeated stream chunks. */
219
+ function toolCallsSnapshotKey(
220
+ calls: NonNullable<NonNullable<ChatResponse["message"]>["tool_calls"]>,
221
+ ): string {
222
+ return JSON.stringify(
223
+ calls.map((tc) => ({
224
+ name: tc.function?.name,
225
+ args: tc.function?.arguments,
226
+ })),
227
+ );
228
+ }
229
+
230
+ export class StrandsOllamaModel extends Model<OllamaModelConfig> {
231
+ private config: OllamaModelConfig;
232
+ private readonly client: Ollama;
233
+
234
+ constructor(config: OllamaModelConfig) {
235
+ super();
236
+ this.config = { ...config };
237
+ this.client = new Ollama({
238
+ host:
239
+ this.config.host ?? process.env.OLLAMA_HOST ?? "http://127.0.0.1:11434",
240
+ });
241
+ }
242
+
243
+ updateConfig(modelConfig: OllamaModelConfig): void {
244
+ this.config = { ...this.config, ...modelConfig };
245
+ }
246
+
247
+ getConfig(): OllamaModelConfig {
248
+ return { ...this.config };
249
+ }
250
+
251
+ async *stream(
252
+ messages: Message[],
253
+ options?: StreamOptions,
254
+ ): AsyncIterable<ModelStreamEvent> {
255
+ const modelId = this.config.modelId;
256
+ if (!modelId) {
257
+ throw new ModelError("Ollama modelId is not configured");
258
+ }
259
+
260
+ const systemText = extractSystemText(options?.systemPrompt);
261
+ const ollamaMessages = strandsMessagesToOllama(messages, systemText);
262
+ const tools = strandsToolsToOllama(options?.toolSpecs);
263
+
264
+ const request: ChatRequest = {
265
+ model: modelId,
266
+ messages: ollamaMessages,
267
+ stream: true,
268
+ tools,
269
+ options: {
270
+ num_predict: this.config.maxTokens,
271
+ temperature: this.config.temperature,
272
+ top_p: this.config.topP,
273
+ ...(this.config.options ?? {}),
274
+ },
275
+ };
276
+ if (this.config.think !== undefined) {
277
+ request.think = this.config.think;
278
+ }
279
+ if (this.config.keepAlive !== undefined) {
280
+ request.keep_alive = this.config.keepAlive;
281
+ }
282
+
283
+ let stream: AsyncIterable<ChatResponse>;
284
+ try {
285
+ stream = await this.client.chat({ ...request, stream: true });
286
+ } catch (e) {
287
+ const msg = e instanceof Error ? e.message : String(e);
288
+ if (msg.toLowerCase().includes("context") || msg.includes("token")) {
289
+ throw new ContextWindowOverflowError(msg);
290
+ }
291
+ throw new ModelError(`Ollama chat error: ${msg}`, { cause: e });
292
+ }
293
+
294
+ // `message.content` is treated as an incremental fragment per chunk (Ollama / ollama-js).
295
+ // **Tool calls:** many models (e.g. gemma4:e4b) send `tool_calls` only on `done: false` chunks;
296
+ // the final `done: true` line often omits `tool_calls`, so we must not require `part.done`.
297
+ // Emit tools when a new snapshot differs from the last emitted (Strands concatenates tool deltas).
298
+ yield new ModelMessageStartEvent({
299
+ type: "modelMessageStartEvent",
300
+ role: "assistant",
301
+ });
302
+
303
+ let last: ChatResponse | undefined;
304
+ let toolRequested = false;
305
+ let textBlockOpen = false;
306
+ let reasoningBlockOpen = false;
307
+ let lastEmittedToolCallsKey: string | undefined;
308
+
309
+ for await (const part of stream) {
310
+ last = part;
311
+ const msg = part.message;
312
+ if (!msg) {
313
+ continue;
314
+ }
315
+
316
+ const calls = msg.tool_calls;
317
+ if (calls?.length) {
318
+ const snapKey = toolCallsSnapshotKey(calls);
319
+ if (snapKey !== lastEmittedToolCallsKey) {
320
+ lastEmittedToolCallsKey = snapKey;
321
+ toolRequested = true;
322
+ if (reasoningBlockOpen) {
323
+ yield new ModelContentBlockStopEvent({
324
+ type: "modelContentBlockStopEvent",
325
+ });
326
+ reasoningBlockOpen = false;
327
+ }
328
+ if (textBlockOpen) {
329
+ yield new ModelContentBlockStopEvent({
330
+ type: "modelContentBlockStopEvent",
331
+ });
332
+ textBlockOpen = false;
333
+ }
334
+ let toolIndex = 0;
335
+ for (const toolCall of calls) {
336
+ const name = toolCall.function?.name ?? "tool";
337
+ const apiId = (toolCall as { id?: string }).id;
338
+ const toolUseId =
339
+ apiId ?? (calls.length > 1 ? `${name}_${toolIndex}` : name);
340
+ toolIndex += 1;
341
+ const argsRaw = toolCall.function?.arguments;
342
+ const inputStr =
343
+ typeof argsRaw === "string"
344
+ ? argsRaw
345
+ : JSON.stringify(argsRaw ?? {});
346
+ yield new ModelContentBlockStartEvent({
347
+ type: "modelContentBlockStartEvent",
348
+ start: { type: "toolUseStart", name, toolUseId },
349
+ });
350
+ yield new ModelContentBlockDeltaEvent({
351
+ type: "modelContentBlockDeltaEvent",
352
+ delta: {
353
+ type: "toolUseInputDelta",
354
+ input: inputStr,
355
+ },
356
+ });
357
+ yield new ModelContentBlockStopEvent({
358
+ type: "modelContentBlockStopEvent",
359
+ });
360
+ }
361
+ }
362
+ }
363
+
364
+ const thinking = (msg as { thinking?: string }).thinking ?? "";
365
+ const content = msg.content ?? "";
366
+
367
+ if (thinking.length > 0) {
368
+ if (textBlockOpen) {
369
+ yield new ModelContentBlockStopEvent({
370
+ type: "modelContentBlockStopEvent",
371
+ });
372
+ textBlockOpen = false;
373
+ }
374
+ if (!reasoningBlockOpen) {
375
+ yield new ModelContentBlockStartEvent({
376
+ type: "modelContentBlockStartEvent",
377
+ });
378
+ reasoningBlockOpen = true;
379
+ }
380
+ yield new ModelContentBlockDeltaEvent({
381
+ type: "modelContentBlockDeltaEvent",
382
+ delta: { type: "reasoningContentDelta", text: thinking },
383
+ });
384
+ }
385
+
386
+ if (content.length > 0) {
387
+ if (reasoningBlockOpen) {
388
+ yield new ModelContentBlockStopEvent({
389
+ type: "modelContentBlockStopEvent",
390
+ });
391
+ reasoningBlockOpen = false;
392
+ }
393
+ if (!textBlockOpen) {
394
+ yield new ModelContentBlockStartEvent({
395
+ type: "modelContentBlockStartEvent",
396
+ });
397
+ textBlockOpen = true;
398
+ }
399
+ yield new ModelContentBlockDeltaEvent({
400
+ type: "modelContentBlockDeltaEvent",
401
+ delta: { type: "textDelta", text: content },
402
+ });
403
+ }
404
+ }
405
+
406
+ if (reasoningBlockOpen) {
407
+ yield new ModelContentBlockStopEvent({
408
+ type: "modelContentBlockStopEvent",
409
+ });
410
+ }
411
+ if (textBlockOpen) {
412
+ yield new ModelContentBlockStopEvent({
413
+ type: "modelContentBlockStopEvent",
414
+ });
415
+ }
416
+
417
+ const doneReason = last?.done_reason;
418
+ const stopReason = toolRequested
419
+ ? ("toolUse" as const)
420
+ : mapDoneReason(doneReason);
421
+
422
+ yield new ModelMessageStopEvent({
423
+ type: "modelMessageStopEvent",
424
+ stopReason,
425
+ });
426
+
427
+ yield new ModelMetadataEvent({
428
+ type: "modelMetadataEvent",
429
+ usage: {
430
+ inputTokens: last?.prompt_eval_count ?? 0,
431
+ outputTokens: last?.eval_count ?? 0,
432
+ totalTokens: (last?.prompt_eval_count ?? 0) + (last?.eval_count ?? 0),
433
+ },
434
+ metrics: last?.total_duration
435
+ ? { latencyMs: last.total_duration / 1_000_000 }
436
+ : undefined,
437
+ });
438
+ }
439
+ }
@@ -0,0 +1,12 @@
1
+ import { OpenAIModel } from "@strands-agents/sdk/models/openai";
2
+
3
+ export function create(
4
+ model: string,
5
+ params: { apiKey?: string },
6
+ ): OpenAIModel {
7
+ return new OpenAIModel({
8
+ api: "chat",
9
+ modelId: model,
10
+ ...params,
11
+ });
12
+ }
@@ -0,0 +1,23 @@
1
+ import type { Config } from "../config.ts";
2
+ import type { Registry } from "../skills/registry.ts";
3
+ import type { Toolkit } from "../toolkit.ts";
4
+ import { Skills } from "./skills.ts";
5
+ import { System } from "./system.ts";
6
+
7
+ export { Skills, System };
8
+
9
+ export async function system(
10
+ path: string,
11
+ config: Config,
12
+ toolkit: Toolkit,
13
+ ): Promise<System> {
14
+ const prompt = new System(path, config, toolkit);
15
+ await prompt.reload();
16
+ return prompt;
17
+ }
18
+
19
+ export async function skills(registry: Registry): Promise<Skills> {
20
+ const prompt = new Skills(registry);
21
+ await prompt.reload();
22
+ return prompt;
23
+ }
@@ -0,0 +1,66 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import type { Registry } from "../skills/registry.ts";
4
+
5
+ /**
6
+ * Builds the dynamic **Available skills** markdown block using
7
+ * {@link Registry.list} (same source as the skills CLI).
8
+ *
9
+ * Mirrors {@link System}: call {@link reload}, then read {@link content}.
10
+ */
11
+ export class Skills {
12
+ private readonly registry: Registry;
13
+ private data = "";
14
+
15
+ public constructor(registry: Registry) {
16
+ this.registry = registry;
17
+ }
18
+
19
+ get content(): string {
20
+ return this.data;
21
+ }
22
+
23
+ public async reload(): Promise<void> {
24
+ let entries;
25
+ try {
26
+ entries = await this.registry.list();
27
+ } catch {
28
+ this.data = [
29
+ "## Available skills",
30
+ "",
31
+ "The skills list could not be loaded (for example if `npx skills` is unavailable). Skills management tools may still work; try again later.",
32
+ "",
33
+ ].join("\n");
34
+ return;
35
+ }
36
+
37
+ const lines: string[] = [
38
+ "## Available skills",
39
+ "",
40
+ "Installed skills for this environment. When one matches the user's request or would materially help the current turn, read its `SKILL.md` at the path shown (for example with your file-reading tool) and follow that guidance.",
41
+ "",
42
+ ];
43
+
44
+ if (entries.length === 0) {
45
+ lines.push(
46
+ "No skills are installed yet. Use the skills management tools to search and install packages, or add them under your agent skills directory.",
47
+ "",
48
+ );
49
+ this.data = lines.join("\n").trim();
50
+ return;
51
+ }
52
+
53
+ const sorted = [...entries].sort((a, b) =>
54
+ a.name.localeCompare(b.name, undefined, { sensitivity: "base" }),
55
+ );
56
+
57
+ for (const e of sorted) {
58
+ lines.push(`- **${e.name}**`);
59
+ lines.push(` - Description: ${e.description}`);
60
+ lines.push(` - Path: \`${e.path}\``);
61
+ lines.push("");
62
+ }
63
+
64
+ this.data = lines.join("\n").trim();
65
+ }
66
+ }
@@ -0,0 +1,33 @@
1
+ ## Fetch
2
+
3
+ You have access to a `fetch` tool for retrieving content from remote HTTP(S) URLs.
4
+
5
+ ### When To Use It
6
+
7
+ - Use `fetch` when the task depends on information from a remote web page or API response
8
+ - Especially use it for:
9
+ - reading documentation or reference material from the web
10
+ - checking the current contents of a public web page
11
+ - retrieving remote JSON or text responses
12
+ - bringing external context into the conversation when local knowledge is insufficient
13
+ - Prefer `fetch` over `shell` for straightforward remote content retrieval
14
+ - Do NOT use `fetch` when the answer can be given from local context alone
15
+
16
+ ### How To Use It
17
+
18
+ - Use normal mode by default so HTML pages are simplified to markdown
19
+ - Use `raw` only when the original response text or HTML is specifically needed
20
+ - Use `start_index` and `max_length` to page through long responses instead of fetching everything repeatedly
21
+ - Set a timeout when the request may be slow or unreliable
22
+ - Use request headers only when they are actually needed
23
+
24
+ ### Safety
25
+
26
+ - Use `fetch` only for remote public URLs
27
+ - Do not treat fetched content as automatically trustworthy
28
+ - Prefer the smallest fetch that answers the question
29
+
30
+ ### Goal
31
+
32
+ - Use `fetch` to gather up-to-date remote information efficiently
33
+ - Prefer markdown-friendly, concise content over verbose raw HTML when possible
@@ -0,0 +1,38 @@
1
+ ## Filesystem
2
+
3
+ You have access to filesystem tools for reading, writing, editing, moving, listing, and searching files and directories.
4
+
5
+ ### When To Use Them
6
+
7
+ - Use filesystem tools when the task is primarily about file contents, directory structure, or metadata
8
+ - Especially use them for:
9
+ - reading source files or configuration files
10
+ - editing files directly and precisely
11
+ - listing directories or exploring project structure
12
+ - searching for files by name or pattern
13
+ - retrieving file metadata such as size or timestamps
14
+ - Prefer filesystem tools over `shell` when a task is fundamentally a file operation
15
+
16
+ ### How To Choose
17
+
18
+ - Use `read_file` for inspecting file contents
19
+ - Use `read_multiple_files` when you need several files at once
20
+ - Use `write_file` to create or overwrite text files
21
+ - Use `edit_file` for targeted replacements instead of rewriting the whole file
22
+ - Use `create_directory` for directory creation
23
+ - Use `list_directory` or `directory_tree` for structure discovery
24
+ - Use `move_file` for renaming or relocating files and directories
25
+ - Use `search_files` to locate files by pattern
26
+ - Use `get_file_info` for metadata without opening the file
27
+
28
+ ### Safety
29
+
30
+ - Prefer the narrowest operation that solves the task
31
+ - Read before editing when verification helps avoid mistakes
32
+ - Be especially careful with overwrites, renames, and broad recursive operations
33
+ - Avoid unnecessary file churn when a smaller edit is sufficient
34
+
35
+ ### Goal
36
+
37
+ - Use filesystem tools for direct, precise file work
38
+ - Keep file operations intentional, minimal, and easy to verify
@@ -0,0 +1,22 @@
1
+ ## Identity
2
+
3
+ You are an agent named **{{ name }}**.
4
+ You are a reliable, structured, and thoughtful assistant.
5
+
6
+ ### Behavior
7
+
8
+ - Be clear, concise, and practical
9
+ - Prefer structured answers when helpful
10
+ - Avoid unnecessary verbosity
11
+
12
+ ### Decision Making
13
+
14
+ - Use available tools when they improve accuracy or context
15
+ - Do not rely on tools unnecessarily
16
+ - Prioritize correctness over speed
17
+
18
+ ### Interaction Style
19
+
20
+ - Be direct and helpful
21
+ - Do not make assumptions without evidence
22
+ - Ask for clarification if needed
@@ -0,0 +1,39 @@
1
+ ## Memory
2
+
3
+ You have access to long-term memory tools.
4
+
5
+ ### Retrieval (search_memory)
6
+
7
+ - Use memory when the current request may depend on past interactions, preferences, or ongoing tasks
8
+ - Especially use it for:
9
+ - follow-ups ("last time", "previously", "continue")
10
+ - user-specific preferences or history
11
+ - long-running tasks or projects
12
+ - Do NOT search memory for simple, self-contained questions
13
+
14
+ ### Storage (store_memory)
15
+
16
+ - Only store information that is:
17
+ - reusable across conversations
18
+ - specific to the user (preferences, facts, goals, tasks)
19
+ - Good examples:
20
+ - "User prefers TypeScript"
21
+ - "User is building a CV SaaS"
22
+ - Do NOT store:
23
+ - one-off questions
24
+ - temporary context
25
+ - obvious or generic information
26
+
27
+ ### Updates (update_memory)
28
+
29
+ - If new information corrects or refines an existing memory, update it instead of creating a new one
30
+
31
+ ### Archival (archive_memory)
32
+
33
+ - If a memory becomes irrelevant, outdated, or incorrect, archive it instead of deleting
34
+
35
+ ### General Rules
36
+
37
+ - Avoid redundant or duplicate memory
38
+ - Keep memory concise and compressed
39
+ - Prioritize current context over memory if they conflict