Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0d15382
📦 new: add update checker functionality and context injection
warengonzaga Feb 18, 2026
ec863b1
📦 new: implement update checker with caching and runtime detection
warengonzaga Feb 18, 2026
054b7c8
🧪 test: add comprehensive tests for update checker module
warengonzaga Feb 18, 2026
59c79d9
📦 new: add updateContext to AgentContext for software updates
warengonzaga Feb 18, 2026
c0a9883
📦 new: add software update check and context to start command
warengonzaga Feb 18, 2026
9398d21
📦 new: update package versions to 1.0.1 across all packages
warengonzaga Feb 18, 2026
4e31c8a
⚙️ setup: update CI workflows for release process and permissions
warengonzaga Feb 18, 2026
1cd2fca
🔧 update: enhance version parsing in isNewerVersion function
warengonzaga Feb 18, 2026
3f91a71
📦 new: add build step for all packages in CI workflow
warengonzaga Feb 18, 2026
080224e
⚙️ setup (ci): update package-build-flow-action to v2.0.1
warengonzaga Feb 19, 2026
6e46e61
⚙️ setup: enforce clean commit convention with husky and ci
warengonzaga Feb 19, 2026
57d3d0f
⚙️ setup (husky): add clean commit validation hook
warengonzaga Feb 19, 2026
d71d865
⚙️ setup (ci): fix security, guards, and validation issues
warengonzaga Feb 19, 2026
0407858
⚙️ setup (ci): remove redundant permissions from reusable workflow calls
warengonzaga Feb 19, 2026
a240bb6
🔧 update (ci): handle initial push and improve update-checker test
warengonzaga Feb 19, 2026
283a069
📦 new (landing): add landing page with svelte and tailwindcss
warengonzaga Feb 19, 2026
cbe57e2
⚙️ setup (ci): add deploy workflow for landing page
warengonzaga Feb 19, 2026
01135d6
⚙️ setup (landing): add build:landing script and update lockfile
warengonzaga Feb 19, 2026
af268be
⚙️ setup (husky): add error handling and allow revert commits
warengonzaga Feb 19, 2026
cf70b85
🔒 security (update-checker): sanitize version and url for prompt inje…
warengonzaga Feb 19, 2026
7094636
🧪 test (update-checker): add sanitizeForPrompt tests and improve mocking
warengonzaga Feb 19, 2026
858ff22
⚙️ setup (ci): pin action shas and scope deploy permissions
warengonzaga Feb 19, 2026
a5c1305
🔒 security (core): harden update checker cache and fetch logic
warengonzaga Feb 19, 2026
ea45a35
📦 new (landing): extract GitHubIcon component and improve accessibility
warengonzaga Feb 19, 2026
c142513
🔧 update (landing): improve scrollbar styling and Firefox support
warengonzaga Feb 19, 2026
302f44b
🔧 update (landing): clarify QuickStart step 3 description
warengonzaga Feb 19, 2026
99c228a
⚙️ setup (ci): streamline CI workflows by removing unused jobs
warengonzaga Feb 19, 2026
bab10e0
🗑️ remove: delete pre-commit script for bun test
warengonzaga Feb 19, 2026
3004a38
🔧 update (ci): add build step for workspace packages
warengonzaga Feb 19, 2026
67f0a76
🔧 update (dockerfile): upgrade bun version for builder and production…
warengonzaga Feb 19, 2026
8dc6789
🚀 release: bump version to 1.1.0 for all packages
warengonzaga Feb 19, 2026
4422bf3
🔧 update (dockerfile): remove frozen-lockfile option from bun install
warengonzaga Feb 19, 2026
958e520
📦 new: add dev:landing script for development of landing page
warengonzaga Feb 19, 2026
61699d8
📖 docs: update README with model names and licensing information
warengonzaga Feb 19, 2026
6e3e0bd
⚙️ setup: update release action to v1.2.1 and change token secret
warengonzaga Feb 20, 2026
8bb8382
📖 docs: update commit message guidelines for breaking changes
warengonzaga Feb 20, 2026
7c8f470
🔧 update: enhance commit message validation for breaking changes
warengonzaga Feb 20, 2026
c42053d
⚙️ setup: update release action to v1.2.2
warengonzaga Feb 20, 2026
0fee582
Merge branch 'main' into dev
warengonzaga Feb 20, 2026
64855aa
⚙️ setup: update actions/checkout version in CI workflow
warengonzaga Feb 20, 2026
1c4b39e
⚙️ setup: update actions/checkout version in CI workflow
warengonzaga Feb 20, 2026
a4b2676
⚙️ setup: update actions/checkout version in CI workflow
warengonzaga Feb 20, 2026
0a6b4ff
⚙️ setup: update actions/checkout version in CI workflow
warengonzaga Feb 20, 2026
a710869
⚙️ setup: remove version check job and update release action to v1.3.0
warengonzaga Feb 20, 2026
3cf25ab
☕ chore: merge branch 'dev' of https://github.com/warengonzaga/tinycl…
warengonzaga Feb 20, 2026
bb0bb6d
🔧 update (dev): merge main into dev
warengonzaga Feb 21, 2026
90b5742
🔧 update (ci): upgrade release-build-flow-action to v1.4.3
warengonzaga Feb 21, 2026
47a3fd7
🔧 update: change shebang from node to bun
warengonzaga Feb 21, 2026
96838af
🔧 update (web): correct path references in dev notice fallback page (#9)
youhanasheriff Feb 21, 2026
f58c5e1
🔧 update (cli): add auto-build for web ui in setup --web
warengonzaga Feb 21, 2026
104c9cb
🔧 update (cli): change build command to use process.execPath
warengonzaga Feb 21, 2026
4c07100
🔧 update (cli): add webRoot to setupWebUI configuration
warengonzaga Feb 21, 2026
1dacabf
🔧 update (cli): add webRoot to createWebUI configuration
warengonzaga Feb 21, 2026
eb92852
🔧 update (web): allow override of webRoot in resolveUiPaths and findS…
warengonzaga Feb 21, 2026
d761952
📦 new: outbound gateway with SSE push and channel routing
warengonzaga Feb 21, 2026
01a4a78
📦 new: wire nudge engine, agent tools, and update check into start
warengonzaga Feb 21, 2026
4f88443
📦 new: nudge engine with intercom wiring and delivery queue
warengonzaga Feb 21, 2026
8e33f61
📦 new: nudge user preferences with config and API endpoints
warengonzaga Feb 21, 2026
53683ac
📦 new: outbound messaging for Discord and Friends channels
warengonzaga Feb 21, 2026
601267a
📦 new: add gateway and nudge package.json to Dockerfile
warengonzaga Feb 21, 2026
9fdf3e3
📦 new: companion nudge system with mood roulette
warengonzaga Feb 21, 2026
40b4cf6
📦 new: add companion jobs and touch activity mocks
warengonzaga Feb 21, 2026
94781c6
🔧 update (agentloop): prevent feedback loop by refining tool call han…
warengonzaga Feb 21, 2026
79d8429
🔧 update: fix regex for commit message scope to allow uppercase letters
warengonzaga Feb 21, 2026
ce23d43
📦 new: add SSE push channel for receiving nudges and proactive messages
warengonzaga Feb 21, 2026
47f3e7c
🔧 update: enhance prompt injection defense for internal system users
warengonzaga Feb 21, 2026
f8bd089
🔧 update: re-check auth status periodically to handle session expiry
warengonzaga Feb 21, 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/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
fetch-depth: 0

- name: Create Release
uses: wgtechlabs/release-build-flow-action@140fc1588bdbf4a9e6fc992592f1f37a85a5404e # v1.4.2
uses: wgtechlabs/release-build-flow-action@5c5896446fd0631b74a3c94d589ad733a94dbfae # v1.4.3
with:
github-token: ${{ secrets.GH_PAT }}
monorepo: 'true'
2 changes: 1 addition & 1 deletion .husky/validate-commit-msg.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if (/^Merge /.test(firstLine) || /^Revert /.test(firstLine)) process.exit(0);
// Clean Commit convention pattern
// Format: <emoji> <type>[!][(<scope>)]: <description>
const pattern =
/^(📦|🔧|🗑\uFE0F?|🔒|⚙\uFE0F?|☕|🧪|📖|🚀) (new|update|remove|security|setup|chore|test|docs|release)(!?)( \([a-z0-9][a-z0-9-]*\))?: .{1,72}$/u;
/^(📦|🔧|🗑\uFE0F?|🔒|⚙\uFE0F?|☕|🧪|📖|🚀) (new|update|remove|security|setup|chore|test|docs|release)(!?)( \([a-zA-Z0-9][a-zA-Z0-9-]*\))?: .{1,72}$/u;

// Only new, update, remove, security may use the breaking change marker
const breakingMatch = firstLine.match(pattern);
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ COPY packages/compactor/package.json ./packages/compactor/
COPY packages/config/package.json ./packages/config/
COPY packages/core/package.json ./packages/core/
COPY packages/delegation/package.json ./packages/delegation/
COPY packages/gateway/package.json ./packages/gateway/
COPY packages/heartware/package.json ./packages/heartware/
COPY packages/intercom/package.json ./packages/intercom/
COPY packages/learning/package.json ./packages/learning/
COPY packages/logger/package.json ./packages/logger/
COPY packages/matcher/package.json ./packages/matcher/
COPY packages/memory/package.json ./packages/memory/
COPY packages/nudge/package.json ./packages/nudge/
COPY packages/plugins/package.json ./packages/plugins/
COPY packages/pulse/package.json ./packages/pulse/
COPY packages/queue/package.json ./packages/queue/
Expand Down
23 changes: 23 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions packages/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ export const TinyClawConfigSchema = z.object({
removeDuplicateLines: z.boolean().optional(),
}).optional(),
}).optional(),

/** Nudge / proactive messaging settings */
nudge: z.object({
/** Master switch — disables all nudges when false. Default: true */
enabled: z.boolean().optional(),
/** Quiet hours start (24h format, e.g. '22:00'). */
quietHoursStart: z.string().regex(/^\d{2}:\d{2}$/).optional(),
/** Quiet hours end (24h format, e.g. '08:00'). */
quietHoursEnd: z.string().regex(/^\d{2}:\d{2}$/).optional(),
/** Max nudges per hour. Default: 5 */
maxPerHour: z.number().int().positive().optional(),
/** Categories to suppress (opt-out). */
suppressedCategories: z.array(z.string()).optional(),
}).optional(),
}).passthrough();

/**
Expand Down
50 changes: 33 additions & 17 deletions packages/core/src/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,28 @@ function containsInjectionPatterns(text: string): boolean {
return INJECTION_PATTERNS.some(pattern => pattern.test(text));
}

/**
* Internal system userIds (pulse, companion, etc.) are trusted and should
* never be subjected to prompt injection sanitization.
*/
const INTERNAL_USER_PREFIXES = ['pulse:', 'companion:', 'system:'];

function isInternalUser(userId: string): boolean {
return INTERNAL_USER_PREFIXES.some(prefix => userId.startsWith(prefix));
}

/**
* Sanitize a user/friend message for prompt injection defense.
* - For **owner** messages: no sanitization (owner is trusted).
* - For **internal** messages (pulse, companion): no sanitization (system-generated).
* - For **friend** messages: if injection patterns are detected, wrap in
* boundary markers so the LLM treats it as untrusted content.
*/
function sanitizeMessage(text: string, userId: string, ownerId: string | undefined): string {
// Owner is fully trusted — no sanitization
if (ownerId && isOwner(userId, ownerId)) return text;
// Internal system users are trusted — no sanitization
if (isInternalUser(userId)) return text;
// No owner set yet — pre-claim, treat cautiously
if (!ownerId) return text;

Expand Down Expand Up @@ -787,7 +800,11 @@ export async function agentLoop(
logger.debug('LLM Response:', { type: response.type, contentLength: response.content?.length, content: response.content?.slice(0, 200) });

if (response.type === 'text') {
const toolCall = extractToolCallFromText(response.content || '');
const rawToolCall = extractToolCallFromText(response.content || '');
// Only treat as a tool call if the extracted name matches a registered tool.
// This prevents the LLM's stray JSON from being misinterpreted as tool
// invocations, which was causing a "Working on that... Done!" loop.
const toolCall = rawToolCall && tools.some(t => t.name === rawToolCall.name) ? rawToolCall : null;

if (toolCall) {
jsonToolReplies += 1;
Expand Down Expand Up @@ -945,23 +962,22 @@ export async function agentLoop(
continue;
}

// For write operations, just return the summary
const responseText = summarizeToolResults([toolCall], toolResults);

if (onStream) {
onStream({ type: 'text', content: responseText });
onStream({ type: 'done' });
}

db.saveMessage(userId, 'user', message);
db.saveMessage(userId, 'assistant', responseText);
recordEpisodic(responseText);

setTimeout(() => {
learning.analyze(message, responseText, history);
}, 100);
// 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);
messages.push({
role: 'assistant',
content: `I used ${toolCall.name} and the result was: ${writeResult}`,
});
messages.push({
role: 'user',
content: 'Now respond naturally to my original message. Briefly confirm the action you took and be conversational.',
});

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

// Stream the text response
Expand Down
24 changes: 23 additions & 1 deletion packages/delegation/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import type { Provider, Tool, Message } from '@tinyclaw/types';
import { logger } from '@tinyclaw/logger';
import type { DelegationStore, DelegationQueue } from './store.js';
import type { DelegationStore, DelegationQueue, DelegationIntercom } from './store.js';
import type {
BackgroundRunner,
BackgroundTaskRecord,
Expand Down Expand Up @@ -39,6 +39,7 @@ export function createBackgroundRunner(
queue: DelegationQueue,
timeoutEstimator?: TimeoutEstimator,
templates?: TemplateManager,
intercom?: DelegationIntercom,
): BackgroundRunner {
/** In-flight AbortControllers keyed by taskId. */
const controllers = new Map<string, AbortController>();
Expand Down Expand Up @@ -137,6 +138,13 @@ export function createBackgroundRunner(
db.updateBackgroundTask(taskId, 'completed', result.response, Date.now());
lifecycle.recordTaskResult(agentId, true);

// Emit intercom event for nudge system
intercom?.emit('task:completed', userId, {
taskId,
agentId,
summary: result.response?.slice(0, 200),
});

// Auto-create/update template on success
if (templates) {
try {
Expand All @@ -161,6 +169,13 @@ export function createBackgroundRunner(
} else {
db.updateBackgroundTask(taskId, 'failed', result.response, Date.now());
lifecycle.recordTaskResult(agentId, false);

// Emit intercom event for nudge system
intercom?.emit('task:failed', userId, {
taskId,
agentId,
error: result.response?.slice(0, 200),
});
}

// Auto-dismiss sub-agent once all its tasks are done — moves to History.
Expand All @@ -179,6 +194,13 @@ export function createBackgroundRunner(
lifecycle.recordTaskResult(agentId, false);
logger.error('Background task failed', { taskId, error: errorMsg });

// Emit intercom event for nudge system
intercom?.emit('task:failed', userId, {
taskId,
agentId,
error: errorMsg,
});

// Auto-dismiss sub-agent on failure — move to History
const allTasks = db.getUserBackgroundTasks(userId);
const hasRunningTasks = allTasks.some(
Expand Down
6 changes: 4 additions & 2 deletions packages/delegation/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import type { Tool, Provider, QueryTier } from '@tinyclaw/types';
import { logger } from '@tinyclaw/logger';
import type { DelegationStore, DelegationQueue } from './store.js';
import type { DelegationStore, DelegationQueue, DelegationIntercom } from './store.js';
import type {
DelegationV2Config,
LifecycleManager,
Expand Down Expand Up @@ -74,6 +74,8 @@ function filterToolsForSubAgent(

export interface DelegationToolsConfig extends DelegationV2Config {
queue: DelegationQueue;
/** Optional intercom for emitting task lifecycle events (nudge system). */
intercom?: DelegationIntercom;
}

export function createDelegationTools(config: DelegationToolsConfig): {
Expand All @@ -99,7 +101,7 @@ export function createDelegationTools(config: DelegationToolsConfig): {

const lifecycle = createLifecycleManager(db);
const templates = createTemplateManager(db);
const background = createBackgroundRunner(db, lifecycle, queue, timeoutEstimator, templates);
const background = createBackgroundRunner(db, lifecycle, queue, timeoutEstimator, templates, config.intercom);

// Helper: build orientation for the current user
function getOrientation(userId: string): OrientationContext {
Expand Down
35 changes: 35 additions & 0 deletions packages/gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# @tinyclaw/gateway

Outbound message gateway for proactive messaging across channels.

Routes messages from the agent system to users via their connected channel (Web UI, Discord, Friends Chat, etc.) using userId prefix-based routing.

## Usage

```ts
import { createGateway } from '@tinyclaw/gateway';

const gateway = createGateway();

// Register a channel sender
gateway.register('web', {
name: 'Web UI',
async send(userId, message) {
// Push via SSE to the browser
},
});

// Send a proactive message
await gateway.send('web:owner', {
content: 'Your background task is complete!',
priority: 'normal',
source: 'background_task',
});

// Broadcast to all channels
await gateway.broadcast({
content: 'System update available',
priority: 'low',
source: 'system',
});
```
31 changes: 31 additions & 0 deletions packages/gateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@tinyclaw/gateway",
"private": true,
"version": "1.1.0",
"description": "Outbound message gateway for proactive messaging across channels",
"license": "GPL-3.0",
"author": "Waren Gonzaga",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/warengonzaga/tinyclaw.git",
"directory": "packages/gateway"
},
"homepage": "https://github.com/warengonzaga/tinyclaw/tree/main/packages/gateway#readme",
"bugs": {
"url": "https://github.com/warengonzaga/tinyclaw/issues"
},
"keywords": ["tinyclaw", "gateway", "outbound", "messaging"],
"scripts": {
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@tinyclaw/types": "workspace:*",
"@tinyclaw/logger": "workspace:*"
}
}
Loading
Loading