Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
753b2b7
🔧 update (secrets): extend SecretsManager interface and engine types
warengonzaga Apr 6, 2026
625ff50
🔧 update (core): extend agent loop with state tracking support
warengonzaga Apr 6, 2026
891a62d
🔧 update (discord): extend plugin with new channel handlers
warengonzaga Apr 6, 2026
c653699
🔧 update (cli): update setup and setup-web commands
warengonzaga Apr 6, 2026
28cf362
🔧 update (cli): extend start command and expand tests
warengonzaga Apr 6, 2026
1bf96e9
🔧 update (cli): extend purge command and expand tests
warengonzaga Apr 6, 2026
f357dd9
🔧 update: fix retry duplication, path quoting, auto-restart deduplica…
Copilot Apr 7, 2026
77261cb
🔧 update (core): extract RESTART_TOOL_NAME constant and fix auto-rest…
Copilot Apr 7, 2026
af4db11
☕ chore: Bump vite from 7.3.1 to 8.0.0 (#58)
dependabot[bot] Mar 19, 2026
51b17e9
☕ chore: Bump github/codeql-action from 4.32.5 to 4.33.0 (#57)
dependabot[bot] Mar 19, 2026
b804f73
☕ chore: Bump docker/setup-qemu-action from 3 to 4 (#53)
dependabot[bot] Mar 19, 2026
4813a81
☕ chore: Bump wgtechlabs/release-build-flow-action from 1.6.0 to 1.7.…
dependabot[bot] Mar 19, 2026
78a8fd1
☕ chore: Bump actions/setup-node from 6.2.0 to 6.3.0 (#51)
dependabot[bot] Mar 19, 2026
facbe35
☕ chore: Bump oven/bun from 1.3.10-slim to 1.3.11-slim (#59)
dependabot[bot] Apr 3, 2026
306b10b
☕ chore: Bump nick-fields/retry from 3 to 4 (#60)
dependabot[bot] Apr 3, 2026
b167574
☕ chore: Bump wgtechlabs/package-build-flow-action from 2.1.0 to 2.1.…
dependabot[bot] Apr 3, 2026
ed7c534
☕ chore: Bump typescript from 5.9.3 to 6.0.2 (#63)
dependabot[bot] Apr 3, 2026
4c51525
☕ chore: Bump github/codeql-action from 4.33.0 to 4.35.1 (#64)
dependabot[bot] Apr 3, 2026
1166a54
☕ chore: Bump actions/deploy-pages from 4.0.5 to 5.0.0 (#65)
dependabot[bot] Apr 3, 2026
18f517b
☕ chore: Bump wgtechlabs/container-build-flow-action from 1.7.0 to 1.…
dependabot[bot] Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
run: bun run build:plugins

- name: Run tests (with retry for Bun runtime segfaults)
uses: nick-fields/retry@v3
uses: nick-fields/retry@v4
with:
max_attempts: 3
timeout_minutes: 10
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Initialize CodeQL
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4
with:
category: '/language:${{ matrix.language }}'
4 changes: 2 additions & 2 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4

- name: Build and Push Container
uses: wgtechlabs/[email protected].0
uses: wgtechlabs/[email protected].1
with:
registry: both
dockerhub-username: ${{ secrets.DOCKER_HUB_USERNAME }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/landing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
4 changes: 2 additions & 2 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

- uses: oven-sh/setup-bun@v2

- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'

Expand All @@ -41,7 +41,7 @@ jobs:
run: bun run build:packages

- name: Build & Publish Packages
uses: wgtechlabs/[email protected].0
uses: wgtechlabs/[email protected].1
with:
monorepo: 'true'
workspace-detection: 'true'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
token: ${{ secrets.GH_PAT }}

- name: Create Release
uses: wgtechlabs/release-build-flow-action@v1.6.0 # v1.6.0
uses: wgtechlabs/release-build-flow-action@v1.7.0 # v1.6.0
with:
github-token: ${{ secrets.GH_PAT }}
monorepo: 'true'
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ── Stage 1: Install + Build ────────────────────────────────────────
FROM oven/bun:1.3.10 AS builder
FROM oven/bun:1.3.11 AS builder

WORKDIR /app

Expand Down Expand Up @@ -42,7 +42,7 @@ COPY . .
RUN bun run build

# ── Stage 2: Production ─────────────────────────────────────────────
FROM oven/bun:1.3.10-slim AS production
FROM oven/bun:1.3.11-slim AS production

WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@biomejs/biome": "^2.4.6",
"@types/bun": "latest",
"@types/node": "^25.3.3",
"typescript": "^5.9.3"
"typescript": "^6.0.2"
},
"dependencies": {
"@wgtechlabs/log-engine": "^2.3.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/compactor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@
},
"devDependencies": {
"@tinyclaw/core": "workspace:*",
"typescript": "^5.0.0"
"typescript": "^6.0.2"
}
}
111 changes: 108 additions & 3 deletions packages/core/src/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
Message,
PendingApproval,
ShieldEvent,
StreamEvent,
Tool,
ToolCall,
} from '@tinyclaw/types';
import { isOwner, OWNER_ONLY_TOOLS } from '@tinyclaw/types';
Expand All @@ -18,6 +20,9 @@ import { BUILTIN_MODEL_TAGS } from './models.js';
*/
const SELF_GATED_TOOLS: ReadonlySet<string> = new Set([...SHELL_TOOL_NAMES]);

/** Name of the built-in restart tool — used in several places below. */
const RESTART_TOOL_NAME = 'tinyclaw_restart';

// ---------------------------------------------------------------------------
// Text Sanitization — strip em-dashes from LLM output
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -207,6 +212,58 @@ function getWorkingMessage(toolName: string): string {
return '🤔 Working on that…\n\n';
}

function shouldAutoRestartAfterTool(toolName: string, result: string): boolean {
if (!/_(pair|unpair)$/.test(toolName)) {
return false;
}

if (result.startsWith('Error')) {
return false;
}

return result.includes(RESTART_TOOL_NAME);
}

async function maybeRunAutoRestart(
originalToolName: string,
toolResults: Array<{ id: string; result: string }>,
tools: Tool[],
onStream: ((event: StreamEvent) => void) | undefined,
): Promise<void> {
const needsRestart = toolResults.some((toolResult) =>
shouldAutoRestartAfterTool(originalToolName, toolResult.result),
);

if (!needsRestart) {
return;
}

const restartTool = tools.find((tool) => tool.name === RESTART_TOOL_NAME);
if (!restartTool) {
return;
}

if (onStream) {
onStream({ type: 'tool_start', tool: restartTool.name });
}

try {
const restartResult = await restartTool.execute({
reason: `Apply changes from ${originalToolName}`,
});
toolResults.push({ id: `${originalToolName}:auto-restart`, result: restartResult });
if (onStream) {
onStream({ type: 'tool_result', tool: restartTool.name, result: restartResult });
}
} catch (error) {
const errorMsg = `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
toolResults.push({ id: `${originalToolName}:auto-restart`, result: errorMsg });
if (onStream) {
onStream({ type: 'tool_result', tool: restartTool.name, result: errorMsg });
}
}
}

// ---------------------------------------------------------------------------
// Delegation stream event helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -497,6 +554,18 @@ When the user asks to set up or change their primary provider:

Providers must be installed as plugins first (added to plugins.enabled in the config).

## Plugin Setup Guidance

When the user asks to set up, connect, install, enable, or pair a channel or provider plugin:
- First figure out whether they have already shared the required credential in the conversation, such as a bot token or API key.
- If they have not shared it yet, do not pretend the plugin is configured and do not claim it is online.
- Walk them through the setup step by step, briefly and concretely.
- For Discord, explain that they need to create an application in the Discord Developer Portal, add a bot, copy the bot token, and enable Message Content Intent.
- After they provide the required credential, call the appropriate pairing tool.
- After pairing succeeds, clearly tell them what changed and whether a restart is happening.
- Never say a plugin is active, connected, or ready unless the pairing tool succeeded and any required restart or activation step has been completed.
- If the user asks whether the Discord bot is online, offline, connected, or why it failed to start, use the discord_status tool before answering. Do not guess from config alone.

## How to Use Tools

When you need to use a tool, output ONLY a JSON object with the tool name and arguments. Examples:
Expand Down Expand Up @@ -961,6 +1030,8 @@ export async function agentLoop(
}
}

await maybeRunAutoRestart(toolCall.name, toolResults, tools, onStream);

// For read/search/recall operations, send result back to LLM for natural response
const isReadOperation =
toolCall.name.includes('read') ||
Expand Down Expand Up @@ -999,11 +1070,10 @@ export async function agentLoop(
// For write operations, feed the result back to the LLM so it
// can craft a natural, conversational response instead of the
// generic "Done!" that was causing a feedback loop in the history.
const writeResult = toolResults[0]?.result || 'completed';
const _writeSummary = summarizeToolResults([toolCall], toolResults);
const resultsText = toolResults.map((result) => result.result).join('\n\n');
messages.push({
role: 'assistant',
content: `I used ${toolCall.name} and the result was: ${writeResult}`,
content: `I used these tools and the results were:\n${resultsText}`,
});
messages.push({
role: 'user',
Expand Down Expand Up @@ -1143,6 +1213,25 @@ export async function agentLoop(
}
}

// Determine whether the model already scheduled a tinyclaw_restart in this
// batch so we don't trigger a second (duplicate) restart via auto-restart.
const batchHasRestartCall = response.toolCalls.some(
(tc) => tc.name === RESTART_TOOL_NAME,
);

if (!batchHasRestartCall) {
for (const toolCall of response.toolCalls) {
const matchingResults = toolResults.filter((result) => result.id === toolCall.id);
await maybeRunAutoRestart(toolCall.name, matchingResults, tools, onStream);
const autoRestartResults = matchingResults.filter(
(result) => result.id === `${toolCall.name}:auto-restart`,
);
if (autoRestartResults.length > 0) {
toolResults.push(...autoRestartResults);
}
}
}

// If pending approvals were queued during structured tool_calls, ask the user
// about the first one (subsequent ones will be handled on following turns).
const paQueue = pendingApprovals.get(userId);
Expand Down Expand Up @@ -1209,6 +1298,22 @@ export async function agentLoop(
continue;
}

if (toolResults.some((r) => !r.result.startsWith('Error'))) {
const resultsText = toolResults.map((r) => r.result).join('\n\n');
messages.push({
role: 'assistant',
content: `I used these tools and the results were:\n${resultsText}`,
});
messages.push({
role: 'user',
content:
'Now respond naturally to my original message. Briefly confirm the action you took and be conversational.',
});

// Continue the loop to get LLM's natural response
continue;
}

const responseText = summarizeToolResults(response.toolCalls, toolResults);

if (onStream) {
Expand Down
Loading
Loading