@geminixiang/mama 0.2.0-beta.6 → 0.2.0-beta.7

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 (82) hide show
  1. package/README.md +19 -11
  2. package/dist/adapter.d.ts +3 -0
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts.map +1 -1
  6. package/dist/adapters/discord/bot.js +22 -5
  7. package/dist/adapters/discord/bot.js.map +1 -1
  8. package/dist/adapters/slack/bot.d.ts +8 -0
  9. package/dist/adapters/slack/bot.d.ts.map +1 -1
  10. package/dist/adapters/slack/bot.js +160 -8
  11. package/dist/adapters/slack/bot.js.map +1 -1
  12. package/dist/adapters/slack/context.d.ts.map +1 -1
  13. package/dist/adapters/slack/context.js +5 -0
  14. package/dist/adapters/slack/context.js.map +1 -1
  15. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  16. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  17. package/dist/adapters/slack/tools/attach.js.map +1 -1
  18. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  19. package/dist/adapters/telegram/bot.js +31 -0
  20. package/dist/adapters/telegram/bot.js.map +1 -1
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +39 -23
  23. package/dist/agent.js.map +1 -1
  24. package/dist/commands/index.d.ts.map +1 -1
  25. package/dist/commands/index.js +2 -0
  26. package/dist/commands/index.js.map +1 -1
  27. package/dist/commands/model.d.ts +1 -1
  28. package/dist/commands/model.d.ts.map +1 -1
  29. package/dist/commands/model.js +25 -7
  30. package/dist/commands/model.js.map +1 -1
  31. package/dist/commands/sandbox.d.ts +10 -0
  32. package/dist/commands/sandbox.d.ts.map +1 -0
  33. package/dist/commands/sandbox.js +65 -0
  34. package/dist/commands/sandbox.js.map +1 -0
  35. package/dist/commands/session-view.d.ts.map +1 -1
  36. package/dist/commands/session-view.js +29 -9
  37. package/dist/commands/session-view.js.map +1 -1
  38. package/dist/commands/utils.d.ts +3 -0
  39. package/dist/commands/utils.d.ts.map +1 -1
  40. package/dist/commands/utils.js +5 -0
  41. package/dist/commands/utils.js.map +1 -1
  42. package/dist/config.d.ts +3 -1
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +29 -0
  45. package/dist/config.js.map +1 -1
  46. package/dist/context.d.ts +1 -1
  47. package/dist/context.d.ts.map +1 -1
  48. package/dist/context.js +50 -35
  49. package/dist/context.js.map +1 -1
  50. package/dist/main.d.ts.map +1 -1
  51. package/dist/main.js +7 -1
  52. package/dist/main.js.map +1 -1
  53. package/dist/provisioner.d.ts +12 -0
  54. package/dist/provisioner.d.ts.map +1 -1
  55. package/dist/provisioner.js +41 -10
  56. package/dist/provisioner.js.map +1 -1
  57. package/dist/session-store.d.ts +1 -1
  58. package/dist/session-store.d.ts.map +1 -1
  59. package/dist/session-store.js +1 -1
  60. package/dist/session-store.js.map +1 -1
  61. package/dist/session-view/service.d.ts.map +1 -1
  62. package/dist/session-view/service.js +1 -1
  63. package/dist/session-view/service.js.map +1 -1
  64. package/dist/tools/bash.d.ts +1 -1
  65. package/dist/tools/bash.d.ts.map +1 -1
  66. package/dist/tools/bash.js.map +1 -1
  67. package/dist/tools/edit.d.ts +1 -1
  68. package/dist/tools/edit.d.ts.map +1 -1
  69. package/dist/tools/edit.js.map +1 -1
  70. package/dist/tools/event.d.ts +1 -1
  71. package/dist/tools/event.d.ts.map +1 -1
  72. package/dist/tools/event.js.map +1 -1
  73. package/dist/tools/index.d.ts +1 -1
  74. package/dist/tools/index.d.ts.map +1 -1
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/read.d.ts +1 -1
  77. package/dist/tools/read.d.ts.map +1 -1
  78. package/dist/tools/read.js.map +1 -1
  79. package/dist/tools/write.d.ts +1 -1
  80. package/dist/tools/write.d.ts.map +1 -1
  81. package/dist/tools/write.js.map +1 -1
  82. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@earendil-works/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const bashSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAiB9C,QAAA,MAAM,UAAU;;;;EAQd,CAAC;AAOH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAsE/E","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAiB9C,QAAA,MAAM,UAAU;;;;EAQd,CAAC;AAOH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAsE/E","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const editSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAqF9C,QAAA,MAAM,UAAU;;;;;EAOd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAqE/E","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAqF9C,QAAA,MAAM,UAAU;;;;;EAOd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAqE/E","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  declare const eventSchema: import("@sinclair/typebox").TObject<{
3
3
  label: import("@sinclair/typebox").TString;
4
4
  type: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"immediate">, import("@sinclair/typebox").TLiteral<"one-shot">, import("@sinclair/typebox").TLiteral<"periodic">]>;
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/tools/event.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAI7D,QAAA,MAAM,WAAW;;;;;;;;EA0Bf,CAAC;AAEH,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA4CD,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG;IACrD,IAAI,EAAE,SAAS,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAgEA","sourcesContent":["import { mkdir, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as log from \"../log.js\";\n\nconst eventSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the event you're scheduling (shown to user)\",\n }),\n type: Type.Union([Type.Literal(\"immediate\"), Type.Literal(\"one-shot\"), Type.Literal(\"periodic\")]),\n text: Type.String({ description: \"The reminder or event text to send when it fires\" }),\n at: Type.Optional(\n Type.String({\n description: \"ISO 8601 timestamp with offset, required for one-shot events\",\n }),\n ),\n schedule: Type.Optional(\n Type.String({\n description: \"Cron schedule, required for periodic events\",\n }),\n ),\n timezone: Type.Optional(\n Type.String({\n description: \"IANA timezone, required for periodic events\",\n }),\n ),\n filenamePrefix: Type.Optional(\n Type.String({\n description: \"Optional filename prefix for the event file\",\n }),\n ),\n});\n\ninterface EventToolContext {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n}\n\ntype EventToolParams = {\n label: string;\n type: \"immediate\" | \"one-shot\" | \"periodic\";\n text: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n filenamePrefix?: string;\n};\n\ntype EventPayload =\n | {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n sessionKey?: string;\n threadTs?: string;\n }\n | {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n at: string;\n }\n | {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n schedule: string;\n timezone: string;\n sessionKey?: string;\n };\n\nexport function createEventTool(workspaceDir: string): {\n tool: AgentTool<typeof eventSchema>;\n setEventContext: (context: EventToolContext) => void;\n} {\n let eventContext: EventToolContext | null = null;\n\n const tool: AgentTool<typeof eventSchema> = {\n name: \"event\",\n label: \"event\",\n description:\n \"Schedule an immediate, one-shot, or periodic event for the current conversation. This automatically writes to the correct events directory and fills the current platform, conversation, conversation kind, and requester userId.\",\n parameters: eventSchema,\n execute: async (_toolCallId: string, params: EventToolParams, signal?: AbortSignal) => {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (!eventContext) {\n throw new Error(\"Event context not configured\");\n }\n\n const payload = buildEventPayload(params, eventContext);\n const eventsDir = join(workspaceDir, \"events\");\n await mkdir(eventsDir, { recursive: true });\n\n const prefix = sanitizeFileSegment(params.filenamePrefix || payload.type || \"event\");\n const filename = `${prefix}-${Date.now()}.json`;\n const filePath = join(eventsDir, filename);\n const content = JSON.stringify(payload) + \"\\n\";\n\n log.logInfo(\n `Writing event file: ${filePath} (type=${payload.type}, platform=${payload.platform}, conversation=${payload.conversationId})`,\n );\n await writeFile(filePath, content, \"utf-8\");\n\n try {\n const fileStat = await stat(filePath);\n log.logInfo(\n `Wrote event file: ${filePath} (${fileStat.size} bytes, mtime=${fileStat.mtime.toISOString()})`,\n );\n } catch (err) {\n log.logWarning(`Event file missing immediately after write: ${filePath}`, String(err));\n }\n\n return {\n content: [\n {\n type: \"text\",\n text:\n payload.type === \"periodic\"\n ? `Scheduled periodic event ${filename} for ${payload.platform}/${payload.conversationId} (${payload.schedule} ${payload.timezone})`\n : payload.type === \"one-shot\"\n ? `Scheduled one-shot event ${filename} for ${payload.platform}/${payload.conversationId} at ${payload.at}`\n : `Queued immediate event ${filename} for ${payload.platform}/${payload.conversationId}`,\n },\n ],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setEventContext: (context: EventToolContext) => {\n eventContext = context;\n },\n };\n}\n\nfunction buildEventPayload(params: EventToolParams, context: EventToolContext): EventPayload {\n const base = {\n platform: context.platform,\n conversationId: context.conversationId,\n conversationKind: context.conversationKind,\n userId: context.userId,\n text: params.text,\n };\n\n if (params.type === \"immediate\") {\n return {\n ...base,\n type: \"immediate\",\n sessionKey: context.sessionKey,\n ...(context.threadTs ? { threadTs: context.threadTs } : {}),\n };\n }\n\n if (params.type === \"one-shot\") {\n if (!params.at) {\n throw new Error(\"`at` is required for one-shot events\");\n }\n // No sessionKey or threadTs: reminders should fire as top-level messages, not buried in old threads\n return { ...base, type: \"one-shot\", at: params.at };\n }\n\n if (!params.schedule) {\n throw new Error(\"`schedule` is required for periodic events\");\n }\n if (!params.timezone) {\n throw new Error(\"`timezone` is required for periodic events\");\n }\n // No threadTs: periodic events should always be top-level; keep sessionKey for task context\n return {\n ...base,\n type: \"periodic\",\n schedule: params.schedule,\n timezone: params.timezone,\n sessionKey: context.sessionKey,\n };\n}\n\nfunction sanitizeFileSegment(value: string): string {\n const sanitized = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"event\";\n}\n"]}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/tools/event.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,QAAA,MAAM,WAAW;;;;;;;;EA0Bf,CAAC;AAEH,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA4CD,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG;IACrD,IAAI,EAAE,SAAS,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAgEA","sourcesContent":["import { mkdir, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as log from \"../log.js\";\n\nconst eventSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the event you're scheduling (shown to user)\",\n }),\n type: Type.Union([Type.Literal(\"immediate\"), Type.Literal(\"one-shot\"), Type.Literal(\"periodic\")]),\n text: Type.String({ description: \"The reminder or event text to send when it fires\" }),\n at: Type.Optional(\n Type.String({\n description: \"ISO 8601 timestamp with offset, required for one-shot events\",\n }),\n ),\n schedule: Type.Optional(\n Type.String({\n description: \"Cron schedule, required for periodic events\",\n }),\n ),\n timezone: Type.Optional(\n Type.String({\n description: \"IANA timezone, required for periodic events\",\n }),\n ),\n filenamePrefix: Type.Optional(\n Type.String({\n description: \"Optional filename prefix for the event file\",\n }),\n ),\n});\n\ninterface EventToolContext {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n}\n\ntype EventToolParams = {\n label: string;\n type: \"immediate\" | \"one-shot\" | \"periodic\";\n text: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n filenamePrefix?: string;\n};\n\ntype EventPayload =\n | {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n sessionKey?: string;\n threadTs?: string;\n }\n | {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n at: string;\n }\n | {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n schedule: string;\n timezone: string;\n sessionKey?: string;\n };\n\nexport function createEventTool(workspaceDir: string): {\n tool: AgentTool<typeof eventSchema>;\n setEventContext: (context: EventToolContext) => void;\n} {\n let eventContext: EventToolContext | null = null;\n\n const tool: AgentTool<typeof eventSchema> = {\n name: \"event\",\n label: \"event\",\n description:\n \"Schedule an immediate, one-shot, or periodic event for the current conversation. This automatically writes to the correct events directory and fills the current platform, conversation, conversation kind, and requester userId.\",\n parameters: eventSchema,\n execute: async (_toolCallId: string, params: EventToolParams, signal?: AbortSignal) => {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (!eventContext) {\n throw new Error(\"Event context not configured\");\n }\n\n const payload = buildEventPayload(params, eventContext);\n const eventsDir = join(workspaceDir, \"events\");\n await mkdir(eventsDir, { recursive: true });\n\n const prefix = sanitizeFileSegment(params.filenamePrefix || payload.type || \"event\");\n const filename = `${prefix}-${Date.now()}.json`;\n const filePath = join(eventsDir, filename);\n const content = JSON.stringify(payload) + \"\\n\";\n\n log.logInfo(\n `Writing event file: ${filePath} (type=${payload.type}, platform=${payload.platform}, conversation=${payload.conversationId})`,\n );\n await writeFile(filePath, content, \"utf-8\");\n\n try {\n const fileStat = await stat(filePath);\n log.logInfo(\n `Wrote event file: ${filePath} (${fileStat.size} bytes, mtime=${fileStat.mtime.toISOString()})`,\n );\n } catch (err) {\n log.logWarning(`Event file missing immediately after write: ${filePath}`, String(err));\n }\n\n return {\n content: [\n {\n type: \"text\",\n text:\n payload.type === \"periodic\"\n ? `Scheduled periodic event ${filename} for ${payload.platform}/${payload.conversationId} (${payload.schedule} ${payload.timezone})`\n : payload.type === \"one-shot\"\n ? `Scheduled one-shot event ${filename} for ${payload.platform}/${payload.conversationId} at ${payload.at}`\n : `Queued immediate event ${filename} for ${payload.platform}/${payload.conversationId}`,\n },\n ],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setEventContext: (context: EventToolContext) => {\n eventContext = context;\n },\n };\n}\n\nfunction buildEventPayload(params: EventToolParams, context: EventToolContext): EventPayload {\n const base = {\n platform: context.platform,\n conversationId: context.conversationId,\n conversationKind: context.conversationKind,\n userId: context.userId,\n text: params.text,\n };\n\n if (params.type === \"immediate\") {\n return {\n ...base,\n type: \"immediate\",\n sessionKey: context.sessionKey,\n ...(context.threadTs ? { threadTs: context.threadTs } : {}),\n };\n }\n\n if (params.type === \"one-shot\") {\n if (!params.at) {\n throw new Error(\"`at` is required for one-shot events\");\n }\n // No sessionKey or threadTs: reminders should fire as top-level messages, not buried in old threads\n return { ...base, type: \"one-shot\", at: params.at };\n }\n\n if (!params.schedule) {\n throw new Error(\"`schedule` is required for periodic events\");\n }\n if (!params.timezone) {\n throw new Error(\"`timezone` is required for periodic events\");\n }\n // No threadTs: periodic events should always be top-level; keep sessionKey for task context\n return {\n ...base,\n type: \"periodic\",\n schedule: params.schedule,\n timezone: params.timezone,\n sessionKey: context.sessionKey,\n };\n}\n\nfunction sanitizeFileSegment(value: string): string {\n const sanitized = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"event\";\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/tools/event.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as log from \"../log.js\";\n\nconst eventSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the event you're scheduling (shown to user)\",\n }),\n type: Type.Union([Type.Literal(\"immediate\"), Type.Literal(\"one-shot\"), Type.Literal(\"periodic\")]),\n text: Type.String({ description: \"The reminder or event text to send when it fires\" }),\n at: Type.Optional(\n Type.String({\n description: \"ISO 8601 timestamp with offset, required for one-shot events\",\n }),\n ),\n schedule: Type.Optional(\n Type.String({\n description: \"Cron schedule, required for periodic events\",\n }),\n ),\n timezone: Type.Optional(\n Type.String({\n description: \"IANA timezone, required for periodic events\",\n }),\n ),\n filenamePrefix: Type.Optional(\n Type.String({\n description: \"Optional filename prefix for the event file\",\n }),\n ),\n});\n\ninterface EventToolContext {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n}\n\ntype EventToolParams = {\n label: string;\n type: \"immediate\" | \"one-shot\" | \"periodic\";\n text: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n filenamePrefix?: string;\n};\n\ntype EventPayload =\n | {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n sessionKey?: string;\n threadTs?: string;\n }\n | {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n at: string;\n }\n | {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n schedule: string;\n timezone: string;\n sessionKey?: string;\n };\n\nexport function createEventTool(workspaceDir: string): {\n tool: AgentTool<typeof eventSchema>;\n setEventContext: (context: EventToolContext) => void;\n} {\n let eventContext: EventToolContext | null = null;\n\n const tool: AgentTool<typeof eventSchema> = {\n name: \"event\",\n label: \"event\",\n description:\n \"Schedule an immediate, one-shot, or periodic event for the current conversation. This automatically writes to the correct events directory and fills the current platform, conversation, conversation kind, and requester userId.\",\n parameters: eventSchema,\n execute: async (_toolCallId: string, params: EventToolParams, signal?: AbortSignal) => {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (!eventContext) {\n throw new Error(\"Event context not configured\");\n }\n\n const payload = buildEventPayload(params, eventContext);\n const eventsDir = join(workspaceDir, \"events\");\n await mkdir(eventsDir, { recursive: true });\n\n const prefix = sanitizeFileSegment(params.filenamePrefix || payload.type || \"event\");\n const filename = `${prefix}-${Date.now()}.json`;\n const filePath = join(eventsDir, filename);\n const content = JSON.stringify(payload) + \"\\n\";\n\n log.logInfo(\n `Writing event file: ${filePath} (type=${payload.type}, platform=${payload.platform}, conversation=${payload.conversationId})`,\n );\n await writeFile(filePath, content, \"utf-8\");\n\n try {\n const fileStat = await stat(filePath);\n log.logInfo(\n `Wrote event file: ${filePath} (${fileStat.size} bytes, mtime=${fileStat.mtime.toISOString()})`,\n );\n } catch (err) {\n log.logWarning(`Event file missing immediately after write: ${filePath}`, String(err));\n }\n\n return {\n content: [\n {\n type: \"text\",\n text:\n payload.type === \"periodic\"\n ? `Scheduled periodic event ${filename} for ${payload.platform}/${payload.conversationId} (${payload.schedule} ${payload.timezone})`\n : payload.type === \"one-shot\"\n ? `Scheduled one-shot event ${filename} for ${payload.platform}/${payload.conversationId} at ${payload.at}`\n : `Queued immediate event ${filename} for ${payload.platform}/${payload.conversationId}`,\n },\n ],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setEventContext: (context: EventToolContext) => {\n eventContext = context;\n },\n };\n}\n\nfunction buildEventPayload(params: EventToolParams, context: EventToolContext): EventPayload {\n const base = {\n platform: context.platform,\n conversationId: context.conversationId,\n conversationKind: context.conversationKind,\n userId: context.userId,\n text: params.text,\n };\n\n if (params.type === \"immediate\") {\n return {\n ...base,\n type: \"immediate\",\n sessionKey: context.sessionKey,\n ...(context.threadTs ? { threadTs: context.threadTs } : {}),\n };\n }\n\n if (params.type === \"one-shot\") {\n if (!params.at) {\n throw new Error(\"`at` is required for one-shot events\");\n }\n // No sessionKey or threadTs: reminders should fire as top-level messages, not buried in old threads\n return { ...base, type: \"one-shot\", at: params.at };\n }\n\n if (!params.schedule) {\n throw new Error(\"`schedule` is required for periodic events\");\n }\n if (!params.timezone) {\n throw new Error(\"`timezone` is required for periodic events\");\n }\n // No threadTs: periodic events should always be top-level; keep sessionKey for task context\n return {\n ...base,\n type: \"periodic\",\n schedule: params.schedule,\n timezone: params.timezone,\n sessionKey: context.sessionKey,\n };\n}\n\nfunction sanitizeFileSegment(value: string): string {\n const sanitized = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"event\";\n}\n"]}
1
+ {"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/tools/event.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as log from \"../log.js\";\n\nconst eventSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the event you're scheduling (shown to user)\",\n }),\n type: Type.Union([Type.Literal(\"immediate\"), Type.Literal(\"one-shot\"), Type.Literal(\"periodic\")]),\n text: Type.String({ description: \"The reminder or event text to send when it fires\" }),\n at: Type.Optional(\n Type.String({\n description: \"ISO 8601 timestamp with offset, required for one-shot events\",\n }),\n ),\n schedule: Type.Optional(\n Type.String({\n description: \"Cron schedule, required for periodic events\",\n }),\n ),\n timezone: Type.Optional(\n Type.String({\n description: \"IANA timezone, required for periodic events\",\n }),\n ),\n filenamePrefix: Type.Optional(\n Type.String({\n description: \"Optional filename prefix for the event file\",\n }),\n ),\n});\n\ninterface EventToolContext {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n}\n\ntype EventToolParams = {\n label: string;\n type: \"immediate\" | \"one-shot\" | \"periodic\";\n text: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n filenamePrefix?: string;\n};\n\ntype EventPayload =\n | {\n type: \"immediate\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n sessionKey?: string;\n threadTs?: string;\n }\n | {\n type: \"one-shot\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n at: string;\n }\n | {\n type: \"periodic\";\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n text: string;\n schedule: string;\n timezone: string;\n sessionKey?: string;\n };\n\nexport function createEventTool(workspaceDir: string): {\n tool: AgentTool<typeof eventSchema>;\n setEventContext: (context: EventToolContext) => void;\n} {\n let eventContext: EventToolContext | null = null;\n\n const tool: AgentTool<typeof eventSchema> = {\n name: \"event\",\n label: \"event\",\n description:\n \"Schedule an immediate, one-shot, or periodic event for the current conversation. This automatically writes to the correct events directory and fills the current platform, conversation, conversation kind, and requester userId.\",\n parameters: eventSchema,\n execute: async (_toolCallId: string, params: EventToolParams, signal?: AbortSignal) => {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (!eventContext) {\n throw new Error(\"Event context not configured\");\n }\n\n const payload = buildEventPayload(params, eventContext);\n const eventsDir = join(workspaceDir, \"events\");\n await mkdir(eventsDir, { recursive: true });\n\n const prefix = sanitizeFileSegment(params.filenamePrefix || payload.type || \"event\");\n const filename = `${prefix}-${Date.now()}.json`;\n const filePath = join(eventsDir, filename);\n const content = JSON.stringify(payload) + \"\\n\";\n\n log.logInfo(\n `Writing event file: ${filePath} (type=${payload.type}, platform=${payload.platform}, conversation=${payload.conversationId})`,\n );\n await writeFile(filePath, content, \"utf-8\");\n\n try {\n const fileStat = await stat(filePath);\n log.logInfo(\n `Wrote event file: ${filePath} (${fileStat.size} bytes, mtime=${fileStat.mtime.toISOString()})`,\n );\n } catch (err) {\n log.logWarning(`Event file missing immediately after write: ${filePath}`, String(err));\n }\n\n return {\n content: [\n {\n type: \"text\",\n text:\n payload.type === \"periodic\"\n ? `Scheduled periodic event ${filename} for ${payload.platform}/${payload.conversationId} (${payload.schedule} ${payload.timezone})`\n : payload.type === \"one-shot\"\n ? `Scheduled one-shot event ${filename} for ${payload.platform}/${payload.conversationId} at ${payload.at}`\n : `Queued immediate event ${filename} for ${payload.platform}/${payload.conversationId}`,\n },\n ],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setEventContext: (context: EventToolContext) => {\n eventContext = context;\n },\n };\n}\n\nfunction buildEventPayload(params: EventToolParams, context: EventToolContext): EventPayload {\n const base = {\n platform: context.platform,\n conversationId: context.conversationId,\n conversationKind: context.conversationKind,\n userId: context.userId,\n text: params.text,\n };\n\n if (params.type === \"immediate\") {\n return {\n ...base,\n type: \"immediate\",\n sessionKey: context.sessionKey,\n ...(context.threadTs ? { threadTs: context.threadTs } : {}),\n };\n }\n\n if (params.type === \"one-shot\") {\n if (!params.at) {\n throw new Error(\"`at` is required for one-shot events\");\n }\n // No sessionKey or threadTs: reminders should fire as top-level messages, not buried in old threads\n return { ...base, type: \"one-shot\", at: params.at };\n }\n\n if (!params.schedule) {\n throw new Error(\"`schedule` is required for periodic events\");\n }\n if (!params.timezone) {\n throw new Error(\"`timezone` is required for periodic events\");\n }\n // No threadTs: periodic events should always be top-level; keep sessionKey for task context\n return {\n ...base,\n type: \"periodic\",\n schedule: params.schedule,\n timezone: params.timezone,\n sessionKey: context.sessionKey,\n };\n}\n\nfunction sanitizeFileSegment(value: string): string {\n const sanitized = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"event\";\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  export declare function createMamaTools(executor: Executor, workspaceDir: string): {
4
4
  tools: AgentTool<any>[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAO9C,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,GACnB;IACD,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IACrF,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,CAAC;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,IAAI,CAAC;CACZ,CAeA","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { createAttachTool } from \"../adapters/slack/tools/attach.js\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createEventTool } from \"./event.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createMamaTools(\n executor: Executor,\n workspaceDir: string,\n): {\n tools: AgentTool<any>[];\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n setEventContext: (context: {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n }) => void;\n} {\n const { tool: attachTool, setUploadFunction } = createAttachTool();\n const { tool: eventTool, setEventContext } = createEventTool(workspaceDir);\n return {\n tools: [\n createReadTool(executor),\n createBashTool(executor),\n createEditTool(executor),\n createWriteTool(executor),\n eventTool,\n attachTool,\n ],\n setUploadFunction,\n setEventContext,\n };\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAO9C,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,GACnB;IACD,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IACrF,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,CAAC;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,IAAI,CAAC;CACZ,CAeA","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { createAttachTool } from \"../adapters/slack/tools/attach.js\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createEventTool } from \"./event.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createMamaTools(\n executor: Executor,\n workspaceDir: string,\n): {\n tools: AgentTool<any>[];\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n setEventContext: (context: {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n }) => void;\n} {\n const { tool: attachTool, setUploadFunction } = createAttachTool();\n const { tool: eventTool, setEventContext } = createEventTool(workspaceDir);\n return {\n tools: [\n createReadTool(executor),\n createBashTool(executor),\n createEditTool(executor),\n createWriteTool(executor),\n eventTool,\n attachTool,\n ],\n setUploadFunction,\n setEventContext,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,eAAe,CAC7B,QAAkB,EAClB,YAAoB;IAapB,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3E,OAAO;QACL,KAAK,EAAE;YACL,cAAc,CAAC,QAAQ,CAAC;YACxB,cAAc,CAAC,QAAQ,CAAC;YACxB,cAAc,CAAC,QAAQ,CAAC;YACxB,eAAe,CAAC,QAAQ,CAAC;YACzB,SAAS;YACT,UAAU;SACX;QACD,iBAAiB;QACjB,eAAe;KAChB,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { createAttachTool } from \"../adapters/slack/tools/attach.js\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createEventTool } from \"./event.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createMamaTools(\n executor: Executor,\n workspaceDir: string,\n): {\n tools: AgentTool<any>[];\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n setEventContext: (context: {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n }) => void;\n} {\n const { tool: attachTool, setUploadFunction } = createAttachTool();\n const { tool: eventTool, setEventContext } = createEventTool(workspaceDir);\n return {\n tools: [\n createReadTool(executor),\n createBashTool(executor),\n createEditTool(executor),\n createWriteTool(executor),\n eventTool,\n attachTool,\n ],\n setUploadFunction,\n setEventContext,\n };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,eAAe,CAC7B,QAAkB,EAClB,YAAoB;IAapB,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3E,OAAO;QACL,KAAK,EAAE;YACL,cAAc,CAAC,QAAQ,CAAC;YACxB,cAAc,CAAC,QAAQ,CAAC;YACxB,cAAc,CAAC,QAAQ,CAAC;YACxB,eAAe,CAAC,QAAQ,CAAC;YACzB,SAAS;YACT,UAAU;SACX;QACD,iBAAiB;QACjB,eAAe;KAChB,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { createAttachTool } from \"../adapters/slack/tools/attach.js\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createEventTool } from \"./event.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createMamaTools(\n executor: Executor,\n workspaceDir: string,\n): {\n tools: AgentTool<any>[];\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n setEventContext: (context: {\n platform: string;\n conversationId: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n sessionKey: string;\n threadTs?: string;\n }) => void;\n} {\n const { tool: attachTool, setUploadFunction } = createAttachTool();\n const { tool: eventTool, setEventContext } = createEventTool(workspaceDir);\n return {\n tools: [\n createReadTool(executor),\n createBashTool(executor),\n createEditTool(executor),\n createWriteTool(executor),\n eventTool,\n attachTool,\n ],\n setUploadFunction,\n setEventContext,\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const readSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAI7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA4B9C,QAAA,MAAM,UAAU;;;;;EASd,CAAC;AAMH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CA0H/E","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { extname } from \"path\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateHead,\n} from \"./truncate.js\";\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n const ext = extname(filePath).toLowerCase();\n return IMAGE_MIME_TYPES[ext] || null;\n}\n\nconst readSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what you're reading and why (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n offset: Type.Optional(\n Type.Number({ description: \"Line number to start reading from (1-indexed)\" }),\n ),\n limit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\ninterface ReadToolDetails {\n truncation?: TruncationResult;\n}\n\nexport function createReadTool(executor: Executor): AgentTool<typeof readSchema> {\n return {\n name: \"read\",\n label: \"read\",\n description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files.`,\n parameters: readSchema,\n execute: async (\n _toolCallId: string,\n { path, offset, limit }: { label: string; path: string; offset?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<{\n content: (TextContent | ImageContent)[];\n details: ReadToolDetails | undefined;\n }> => {\n const mimeType = isImageFile(path);\n\n if (mimeType) {\n // Read as image (binary) - use base64\n const result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n const base64 = result.stdout.replace(/\\s/g, \"\"); // Remove whitespace from base64\n\n return {\n content: [\n { type: \"text\", text: `Read image file [${mimeType}]` },\n { type: \"image\", data: base64, mimeType },\n ],\n details: undefined,\n };\n }\n\n // Get total line count first\n const countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });\n if (countResult.code !== 0) {\n throw new Error(countResult.stderr || `Failed to read file: ${path}`);\n }\n const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10) + 1; // wc -l counts newlines, not lines\n\n // Apply offset if specified (1-indexed)\n const startLine = offset ? Math.max(1, offset) : 1;\n const startLineDisplay = startLine;\n\n // Check if offset is out of bounds\n if (startLine > totalFileLines) {\n throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n }\n\n // Read content with offset\n let cmd: string;\n if (startLine === 1) {\n cmd = `cat ${shellEscape(path)}`;\n } else {\n cmd = `tail -n +${startLine} ${shellEscape(path)}`;\n }\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n\n let selectedContent = result.stdout;\n let userLimitedLines: number | undefined;\n\n // Apply user limit if specified\n if (limit !== undefined) {\n const lines = selectedContent.split(\"\\n\");\n const endLine = Math.min(limit, lines.length);\n selectedContent = lines.slice(0, endLine).join(\"\\n\");\n userLimitedLines = endLine;\n }\n\n // Apply truncation (respects both line and byte limits)\n const truncation = truncateHead(selectedContent);\n\n let outputText: string;\n let details: ReadToolDetails | undefined;\n\n if (truncation.firstLineExceedsLimit) {\n // First line at offset exceeds 50KB - tell model to use bash\n const firstLineSize = formatSize(\n Buffer.byteLength(selectedContent.split(\"\\n\")[0], \"utf-8\"),\n );\n outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n details = { truncation };\n } else if (truncation.truncated) {\n // Truncation occurred - build actionable notice\n const endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n const nextOffset = endLineDisplay + 1;\n\n outputText = truncation.content;\n\n if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue]`;\n }\n details = { truncation };\n } else if (userLimitedLines !== undefined) {\n // User specified limit, check if there's more content\n const linesFromStart = startLine - 1 + userLimitedLines;\n if (linesFromStart < totalFileLines) {\n const remaining = totalFileLines - linesFromStart;\n const nextOffset = startLine + userLimitedLines;\n\n outputText = truncation.content;\n outputText += `\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;\n } else {\n outputText = truncation.content;\n }\n } else {\n // No truncation, no user limit exceeded\n outputText = truncation.content;\n }\n\n return {\n content: [{ type: \"text\", text: outputText }],\n details,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA4B9C,QAAA,MAAM,UAAU;;;;;EASd,CAAC;AAMH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CA0H/E","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { extname } from \"path\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateHead,\n} from \"./truncate.js\";\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n const ext = extname(filePath).toLowerCase();\n return IMAGE_MIME_TYPES[ext] || null;\n}\n\nconst readSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what you're reading and why (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n offset: Type.Optional(\n Type.Number({ description: \"Line number to start reading from (1-indexed)\" }),\n ),\n limit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\ninterface ReadToolDetails {\n truncation?: TruncationResult;\n}\n\nexport function createReadTool(executor: Executor): AgentTool<typeof readSchema> {\n return {\n name: \"read\",\n label: \"read\",\n description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files.`,\n parameters: readSchema,\n execute: async (\n _toolCallId: string,\n { path, offset, limit }: { label: string; path: string; offset?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<{\n content: (TextContent | ImageContent)[];\n details: ReadToolDetails | undefined;\n }> => {\n const mimeType = isImageFile(path);\n\n if (mimeType) {\n // Read as image (binary) - use base64\n const result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n const base64 = result.stdout.replace(/\\s/g, \"\"); // Remove whitespace from base64\n\n return {\n content: [\n { type: \"text\", text: `Read image file [${mimeType}]` },\n { type: \"image\", data: base64, mimeType },\n ],\n details: undefined,\n };\n }\n\n // Get total line count first\n const countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });\n if (countResult.code !== 0) {\n throw new Error(countResult.stderr || `Failed to read file: ${path}`);\n }\n const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10) + 1; // wc -l counts newlines, not lines\n\n // Apply offset if specified (1-indexed)\n const startLine = offset ? Math.max(1, offset) : 1;\n const startLineDisplay = startLine;\n\n // Check if offset is out of bounds\n if (startLine > totalFileLines) {\n throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n }\n\n // Read content with offset\n let cmd: string;\n if (startLine === 1) {\n cmd = `cat ${shellEscape(path)}`;\n } else {\n cmd = `tail -n +${startLine} ${shellEscape(path)}`;\n }\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n\n let selectedContent = result.stdout;\n let userLimitedLines: number | undefined;\n\n // Apply user limit if specified\n if (limit !== undefined) {\n const lines = selectedContent.split(\"\\n\");\n const endLine = Math.min(limit, lines.length);\n selectedContent = lines.slice(0, endLine).join(\"\\n\");\n userLimitedLines = endLine;\n }\n\n // Apply truncation (respects both line and byte limits)\n const truncation = truncateHead(selectedContent);\n\n let outputText: string;\n let details: ReadToolDetails | undefined;\n\n if (truncation.firstLineExceedsLimit) {\n // First line at offset exceeds 50KB - tell model to use bash\n const firstLineSize = formatSize(\n Buffer.byteLength(selectedContent.split(\"\\n\")[0], \"utf-8\"),\n );\n outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n details = { truncation };\n } else if (truncation.truncated) {\n // Truncation occurred - build actionable notice\n const endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n const nextOffset = endLineDisplay + 1;\n\n outputText = truncation.content;\n\n if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue]`;\n }\n details = { truncation };\n } else if (userLimitedLines !== undefined) {\n // User specified limit, check if there's more content\n const linesFromStart = startLine - 1 + userLimitedLines;\n if (linesFromStart < totalFileLines) {\n const remaining = totalFileLines - linesFromStart;\n const nextOffset = startLine + userLimitedLines;\n\n outputText = truncation.content;\n outputText += `\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;\n } else {\n outputText = truncation.content;\n }\n } else {\n // No truncation, no user limit exceeded\n outputText = truncation.content;\n }\n\n return {\n content: [{ type: \"text\", text: outputText }],\n details,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { extname } from \"path\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateHead,\n} from \"./truncate.js\";\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n const ext = extname(filePath).toLowerCase();\n return IMAGE_MIME_TYPES[ext] || null;\n}\n\nconst readSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what you're reading and why (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n offset: Type.Optional(\n Type.Number({ description: \"Line number to start reading from (1-indexed)\" }),\n ),\n limit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\ninterface ReadToolDetails {\n truncation?: TruncationResult;\n}\n\nexport function createReadTool(executor: Executor): AgentTool<typeof readSchema> {\n return {\n name: \"read\",\n label: \"read\",\n description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files.`,\n parameters: readSchema,\n execute: async (\n _toolCallId: string,\n { path, offset, limit }: { label: string; path: string; offset?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<{\n content: (TextContent | ImageContent)[];\n details: ReadToolDetails | undefined;\n }> => {\n const mimeType = isImageFile(path);\n\n if (mimeType) {\n // Read as image (binary) - use base64\n const result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n const base64 = result.stdout.replace(/\\s/g, \"\"); // Remove whitespace from base64\n\n return {\n content: [\n { type: \"text\", text: `Read image file [${mimeType}]` },\n { type: \"image\", data: base64, mimeType },\n ],\n details: undefined,\n };\n }\n\n // Get total line count first\n const countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });\n if (countResult.code !== 0) {\n throw new Error(countResult.stderr || `Failed to read file: ${path}`);\n }\n const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10) + 1; // wc -l counts newlines, not lines\n\n // Apply offset if specified (1-indexed)\n const startLine = offset ? Math.max(1, offset) : 1;\n const startLineDisplay = startLine;\n\n // Check if offset is out of bounds\n if (startLine > totalFileLines) {\n throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n }\n\n // Read content with offset\n let cmd: string;\n if (startLine === 1) {\n cmd = `cat ${shellEscape(path)}`;\n } else {\n cmd = `tail -n +${startLine} ${shellEscape(path)}`;\n }\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n\n let selectedContent = result.stdout;\n let userLimitedLines: number | undefined;\n\n // Apply user limit if specified\n if (limit !== undefined) {\n const lines = selectedContent.split(\"\\n\");\n const endLine = Math.min(limit, lines.length);\n selectedContent = lines.slice(0, endLine).join(\"\\n\");\n userLimitedLines = endLine;\n }\n\n // Apply truncation (respects both line and byte limits)\n const truncation = truncateHead(selectedContent);\n\n let outputText: string;\n let details: ReadToolDetails | undefined;\n\n if (truncation.firstLineExceedsLimit) {\n // First line at offset exceeds 50KB - tell model to use bash\n const firstLineSize = formatSize(\n Buffer.byteLength(selectedContent.split(\"\\n\")[0], \"utf-8\"),\n );\n outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n details = { truncation };\n } else if (truncation.truncated) {\n // Truncation occurred - build actionable notice\n const endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n const nextOffset = endLineDisplay + 1;\n\n outputText = truncation.content;\n\n if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue]`;\n }\n details = { truncation };\n } else if (userLimitedLines !== undefined) {\n // User specified limit, check if there's more content\n const linesFromStart = startLine - 1 + userLimitedLines;\n if (linesFromStart < totalFileLines) {\n const remaining = totalFileLines - linesFromStart;\n const nextOffset = startLine + userLimitedLines;\n\n outputText = truncation.content;\n outputText += `\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;\n } else {\n outputText = truncation.content;\n }\n } else {\n // No truncation, no user limit exceeded\n outputText = truncation.content;\n }\n\n return {\n content: [{ type: \"text\", text: outputText }],\n details,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { extname } from \"path\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateHead,\n} from \"./truncate.js\";\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n const ext = extname(filePath).toLowerCase();\n return IMAGE_MIME_TYPES[ext] || null;\n}\n\nconst readSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what you're reading and why (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n offset: Type.Optional(\n Type.Number({ description: \"Line number to start reading from (1-indexed)\" }),\n ),\n limit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\ninterface ReadToolDetails {\n truncation?: TruncationResult;\n}\n\nexport function createReadTool(executor: Executor): AgentTool<typeof readSchema> {\n return {\n name: \"read\",\n label: \"read\",\n description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files.`,\n parameters: readSchema,\n execute: async (\n _toolCallId: string,\n { path, offset, limit }: { label: string; path: string; offset?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<{\n content: (TextContent | ImageContent)[];\n details: ReadToolDetails | undefined;\n }> => {\n const mimeType = isImageFile(path);\n\n if (mimeType) {\n // Read as image (binary) - use base64\n const result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n const base64 = result.stdout.replace(/\\s/g, \"\"); // Remove whitespace from base64\n\n return {\n content: [\n { type: \"text\", text: `Read image file [${mimeType}]` },\n { type: \"image\", data: base64, mimeType },\n ],\n details: undefined,\n };\n }\n\n // Get total line count first\n const countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });\n if (countResult.code !== 0) {\n throw new Error(countResult.stderr || `Failed to read file: ${path}`);\n }\n const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10) + 1; // wc -l counts newlines, not lines\n\n // Apply offset if specified (1-indexed)\n const startLine = offset ? Math.max(1, offset) : 1;\n const startLineDisplay = startLine;\n\n // Check if offset is out of bounds\n if (startLine > totalFileLines) {\n throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);\n }\n\n // Read content with offset\n let cmd: string;\n if (startLine === 1) {\n cmd = `cat ${shellEscape(path)}`;\n } else {\n cmd = `tail -n +${startLine} ${shellEscape(path)}`;\n }\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to read file: ${path}`);\n }\n\n let selectedContent = result.stdout;\n let userLimitedLines: number | undefined;\n\n // Apply user limit if specified\n if (limit !== undefined) {\n const lines = selectedContent.split(\"\\n\");\n const endLine = Math.min(limit, lines.length);\n selectedContent = lines.slice(0, endLine).join(\"\\n\");\n userLimitedLines = endLine;\n }\n\n // Apply truncation (respects both line and byte limits)\n const truncation = truncateHead(selectedContent);\n\n let outputText: string;\n let details: ReadToolDetails | undefined;\n\n if (truncation.firstLineExceedsLimit) {\n // First line at offset exceeds 50KB - tell model to use bash\n const firstLineSize = formatSize(\n Buffer.byteLength(selectedContent.split(\"\\n\")[0], \"utf-8\"),\n );\n outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n details = { truncation };\n } else if (truncation.truncated) {\n // Truncation occurred - build actionable notice\n const endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n const nextOffset = endLineDisplay + 1;\n\n outputText = truncation.content;\n\n if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue]`;\n }\n details = { truncation };\n } else if (userLimitedLines !== undefined) {\n // User specified limit, check if there's more content\n const linesFromStart = startLine - 1 + userLimitedLines;\n if (linesFromStart < totalFileLines) {\n const remaining = totalFileLines - linesFromStart;\n const nextOffset = startLine + userLimitedLines;\n\n outputText = truncation.content;\n outputText += `\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;\n } else {\n outputText = truncation.content;\n }\n } else {\n // No truncation, no user limit exceeded\n outputText = truncation.content;\n }\n\n return {\n content: [{ type: \"text\", text: outputText }],\n details,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const writeSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;