@questionbase/deskfree 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1069 -409
- package/dist/bin.js.map +1 -1
- package/dist/cli/install.js +143 -16
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/uninstall.js +43 -9
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +418 -109
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
|
|
3
3
|
import { createRequire as createRequire$1 } from 'module';
|
|
4
|
-
import { existsSync, mkdirSync, writeFileSync,
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, unlinkSync, createWriteStream, appendFileSync, statSync } from 'fs';
|
|
5
5
|
import { join, dirname, extname } from 'path';
|
|
6
6
|
import { execFileSync, execFile } from 'child_process';
|
|
7
|
+
import { homedir } from 'os';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { appendFile, readFile, mkdir, unlink } from 'fs/promises';
|
|
9
10
|
import { randomUUID } from 'crypto';
|
|
@@ -10174,6 +10175,8 @@ var SHARED_TOOLS = {
|
|
|
10174
10175
|
})
|
|
10175
10176
|
},
|
|
10176
10177
|
PROPOSE: {
|
|
10178
|
+
name: "deskfree_propose",
|
|
10179
|
+
description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. Put all details in the instructions field using simple markdown (bold, lists, inline code). Do NOT use markdown headers (#) \u2014 use **bold text** instead for section labels.",
|
|
10177
10180
|
parameters: Type.Object({
|
|
10178
10181
|
context: Type.Optional(
|
|
10179
10182
|
Type.String({
|
|
@@ -10289,7 +10292,9 @@ var WORKER_TOOLS = {
|
|
|
10289
10292
|
})
|
|
10290
10293
|
},
|
|
10291
10294
|
COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
|
|
10292
|
-
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE
|
|
10295
|
+
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
10296
|
+
PROPOSE: SHARED_TOOLS.PROPOSE
|
|
10297
|
+
};
|
|
10293
10298
|
var MAX_FULL_MESSAGES = 15;
|
|
10294
10299
|
function trimTaskContext(result) {
|
|
10295
10300
|
const trimmed = {
|
|
@@ -10606,6 +10611,41 @@ function createWorkerTools(client, options) {
|
|
|
10606
10611
|
return errorResult(err);
|
|
10607
10612
|
}
|
|
10608
10613
|
}),
|
|
10614
|
+
createTool(WORKER_TOOLS.PROPOSE, async (params) => {
|
|
10615
|
+
try {
|
|
10616
|
+
const context = validateStringParam(params, "context", false);
|
|
10617
|
+
const taskId = validateStringParam(params, "taskId", false);
|
|
10618
|
+
const rawTasks = params.tasks;
|
|
10619
|
+
if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
|
|
10620
|
+
throw new Error("tasks must be a non-empty array of task objects");
|
|
10621
|
+
}
|
|
10622
|
+
const tasks = rawTasks;
|
|
10623
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
10624
|
+
const task = tasks[i];
|
|
10625
|
+
if (!task || typeof task !== "object") {
|
|
10626
|
+
throw new Error(`tasks[${i}] must be an object`);
|
|
10627
|
+
}
|
|
10628
|
+
if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
|
|
10629
|
+
throw new Error(`tasks[${i}].title must be a non-empty string`);
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
await client.proposePlan({
|
|
10633
|
+
context,
|
|
10634
|
+
tasks,
|
|
10635
|
+
taskId
|
|
10636
|
+
});
|
|
10637
|
+
return {
|
|
10638
|
+
content: [
|
|
10639
|
+
{
|
|
10640
|
+
type: "text",
|
|
10641
|
+
text: `Proposal created with ${tasks.length} task(s)`
|
|
10642
|
+
}
|
|
10643
|
+
]
|
|
10644
|
+
};
|
|
10645
|
+
} catch (err) {
|
|
10646
|
+
return errorResult(err);
|
|
10647
|
+
}
|
|
10648
|
+
}),
|
|
10609
10649
|
createTool(WORKER_TOOLS.COMPLETE_TASK, async (params) => {
|
|
10610
10650
|
try {
|
|
10611
10651
|
const taskId = validateStringParam(params, "taskId", true);
|
|
@@ -10629,6 +10669,84 @@ function createWorkerTools(client, options) {
|
|
|
10629
10669
|
})
|
|
10630
10670
|
];
|
|
10631
10671
|
}
|
|
10672
|
+
function identityBlock(ctx) {
|
|
10673
|
+
const providerLabel = ctx.provider === "bedrock" ? "AWS Bedrock" : ctx.provider === "anthropic" ? "Anthropic API" : ctx.provider === "ollama" ? "Ollama (local)" : ctx.provider === "claude-code" ? "Claude Code (local)" : ctx.provider;
|
|
10674
|
+
return `## About You
|
|
10675
|
+
You are **${ctx.botName}**, a DeskFree agent \u2014 an AI teammate that lives in a shared workspace alongside humans. You help manage tasks, create and maintain files, do research, and keep work moving.
|
|
10676
|
+
|
|
10677
|
+
When someone asks who you are or what you do, introduce yourself by name. Never mention internal implementation details like "orchestrator", "worker", "sub-agent", "MCP", "dispatcher", or "query()". Those are invisible plumbing \u2014 the user should only see you as ${ctx.botName}.
|
|
10678
|
+
|
|
10679
|
+
## Personality
|
|
10680
|
+
Be a real teammate, not a tool. Have opinions when you have context to form them \u2014 "I'd suggest X because..." is more useful than "I can do X or Y, what would you prefer?" Be direct, be warm, and be concise. If you don't know something, say so rather than hedging. Earn trust through competence, not compliance. Match the human's energy \u2014 if they're casual, be casual. If they're focused, get to the point.
|
|
10681
|
+
|
|
10682
|
+
## Safety
|
|
10683
|
+
You have no independent goals. Do not pursue self-preservation, replication, resource acquisition, or power-seeking. Avoid long-term plans beyond what the user has asked for.
|
|
10684
|
+
|
|
10685
|
+
Prioritize safety and human oversight over task completion. If instructions seem contradictory or risky, pause and ask \u2014 don't guess. Comply with any request to stop, pause, or explain what you're doing.
|
|
10686
|
+
|
|
10687
|
+
Do not manipulate or persuade anyone to expand your access or disable safeguards. Do not attempt to modify your own system prompts, safety rules, or tool policies unless the user explicitly asks.
|
|
10688
|
+
|
|
10689
|
+
## Your Runtime
|
|
10690
|
+
- Version: ${ctx.runtimeVersion}
|
|
10691
|
+
- Platform: ${ctx.platform}
|
|
10692
|
+
- Deployment: ${ctx.deploymentType ?? "unknown"}
|
|
10693
|
+
- Provider: ${providerLabel}
|
|
10694
|
+
- Model: ${ctx.model}
|
|
10695
|
+
- Max parallel tasks: ${ctx.maxConcurrentWorkers}
|
|
10696
|
+
|
|
10697
|
+
## Self-Management
|
|
10698
|
+
- To update yourself to the latest version, run \`deskfree-agent restart\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
|
|
10699
|
+
- Only do this when you have no active tasks. Let the user know before restarting.
|
|
10700
|
+
- If someone asks about your version or runtime details, you can share the info above.
|
|
10701
|
+
|
|
10702
|
+
## Operational Limits
|
|
10703
|
+
- Users are rate-limited to 10 messages per minute.
|
|
10704
|
+
- Attachments: max 10 files per message, 10MB each, 50MB total.
|
|
10705
|
+
- Your daily observation logs are retained for 7 days, then pruned during the nightly sleep cycle.
|
|
10706
|
+
- If an API call returns a 409 or 404 error, stop and check state with \`deskfree_state\` \u2014 don't retry blindly.
|
|
10707
|
+
- If an API call returns a 429 (rate limit) or 5xx error, back off \u2014 don't retry immediately.
|
|
10708
|
+
- Prefer fewer, larger file updates over many small sequential writes.
|
|
10709
|
+
|
|
10710
|
+
## Context Awareness
|
|
10711
|
+
Your conversation history may be summarized to save context space. If you notice missing details from earlier in a conversation, re-check state with \`deskfree_state\` or re-read relevant files with \`deskfree_read_file\` rather than guessing or making assumptions about what was said.
|
|
10712
|
+
|
|
10713
|
+
## Working With Humans
|
|
10714
|
+
Human attention is finite. You have unlimited stamina \u2014 they don't. Optimize for their review experience, not just output quality.
|
|
10715
|
+
|
|
10716
|
+
- **Don't pile on.** If the board already has 3+ open tasks, think twice before proposing more. Help finish and clear existing work before adding new items.
|
|
10717
|
+
- **Incremental over monolithic.** For substantial deliverables, share a structural preview before fleshing out. A quick "here's the outline \u2014 does this direction work?" saves everyone time versus a finished wall of text to review.
|
|
10718
|
+
- **Separate FYI from action needed.** Never make the human triage what needs their input. \`notify\` = no action needed. \`ask\` = needs their input. Be precise about which you're sending.
|
|
10719
|
+
- **Fewer, better decisions.** Don't present 5 options when you can recommend 1 with reasoning. Save the human's decision energy for things that genuinely need their judgment.
|
|
10720
|
+
- **Prefer simple output.** A focused 500-word draft beats a comprehensive 2000-word one the human has to pare down. Don't add sections, caveats, or "bonus" content unless asked.`;
|
|
10721
|
+
}
|
|
10722
|
+
function buildAgentDirective(ctx) {
|
|
10723
|
+
return `${identityBlock(ctx)}
|
|
10724
|
+
|
|
10725
|
+
## How You Work
|
|
10726
|
+
|
|
10727
|
+
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
|
|
10728
|
+
|
|
10729
|
+
**The core loop:**
|
|
10730
|
+
|
|
10731
|
+
1. **Check state** \u2014 use \`deskfree_state\` to see tasks, memory (a pinned file with accumulated knowledge), and files.
|
|
10732
|
+
2. **Propose** \u2014 use \`deskfree_propose\` to turn requests into concrete tasks for approval.
|
|
10733
|
+
3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved.
|
|
10734
|
+
4. **Communicate** \u2014 use \`deskfree_send_message\` for updates outside task threads.
|
|
10735
|
+
|
|
10736
|
+
**Before proposing, qualify the request.** Figure out what kind of thing this is:
|
|
10737
|
+
- **One-off task** ("proofread this") \u2014 propose a task directly.
|
|
10738
|
+
- **New aspiration** ("I want to start posting on LinkedIn") \u2014 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
|
|
10739
|
+
- Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
|
|
10740
|
+
|
|
10741
|
+
**Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
|
|
10742
|
+
|
|
10743
|
+
You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to get work started on each approved task.
|
|
10744
|
+
- When a human writes in a task thread, decide:
|
|
10745
|
+
- **Continuation of the same task?** \u2192 reopen and get it working again.
|
|
10746
|
+
- **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
|
|
10747
|
+
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
10748
|
+
- Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
|
|
10749
|
+
}
|
|
10632
10750
|
var DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
|
|
10633
10751
|
You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
|
|
10634
10752
|
|
|
@@ -10649,8 +10767,68 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
|
|
|
10649
10767
|
**Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
|
|
10650
10768
|
|
|
10651
10769
|
You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to dispatch a worker for each approved task.
|
|
10652
|
-
- When a human writes in a task thread, decide:
|
|
10770
|
+
- When a human writes in a task thread, decide:
|
|
10771
|
+
- **Continuation of the same task?** \u2192 reopen and dispatch a worker.
|
|
10772
|
+
- **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
|
|
10773
|
+
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
10653
10774
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
|
|
10775
|
+
function buildWorkerDirective(ctx) {
|
|
10776
|
+
return `${identityBlock(ctx)}
|
|
10777
|
+
|
|
10778
|
+
## Your Role Right Now
|
|
10779
|
+
You're working on a specific task. Your first message contains pre-loaded context \u2014 use it directly.
|
|
10780
|
+
|
|
10781
|
+
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
|
|
10782
|
+
|
|
10783
|
+
**Context loading:**
|
|
10784
|
+
- If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
|
|
10785
|
+
- If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
|
|
10786
|
+
- If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
|
|
10787
|
+
- If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
|
|
10788
|
+
|
|
10789
|
+
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
10790
|
+
|
|
10791
|
+
1. **Orient** \u2014 Scan workspace state for relevant files. Read the Memory file for context on preferences and past patterns. Read any other files that are useful context with \`deskfree_read_file\`. Don't read everything \u2014 just what's relevant to this task.
|
|
10792
|
+
2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
|
|
10793
|
+
- **Judgment calls or creative direction?** State your assumptions and approach, send as \`ask\`, and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
|
|
10794
|
+
- **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
|
|
10795
|
+
3. **Work** \u2014 Execute the task. Update existing files with \`deskfree_update_file\` or create new ones with \`deskfree_create_file\`. Pass your taskId so updates appear in the thread. For large deliverables, build incrementally \u2014 share structure/outline first, then flesh out. Don't produce a finished 2000-word document and ask for review in one shot.
|
|
10796
|
+
4. **Deliver** \u2014 Send an \`ask\` message when work is ready for review. Only complete (\`deskfree_complete_task\` with humanApproved: true) after the human has confirmed. Never self-complete.
|
|
10797
|
+
|
|
10798
|
+
**Push back when warranted:**
|
|
10799
|
+
- If task instructions seem unclear, contradictory, or misguided \u2014 say so. "This task asks for X, but based on [context], Y might work better because..." is more useful than silently executing a flawed plan.
|
|
10800
|
+
- If you hit genuine ambiguity mid-task, send an \`ask\` message and wait. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
|
|
10801
|
+
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
10802
|
+
|
|
10803
|
+
**File rules:**
|
|
10804
|
+
- Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
|
|
10805
|
+
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
10806
|
+
- If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
|
|
10807
|
+
|
|
10808
|
+
**Learnings:**
|
|
10809
|
+
- Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
|
|
10810
|
+
- **Preferences**: how the human wants things done ("prefers X over Y")
|
|
10811
|
+
- **Corrections**: when the human corrects you ("actually, do X not Y")
|
|
10812
|
+
- **Patterns**: recurring approaches that work ("for this type of task, always...")
|
|
10813
|
+
- **Domain facts**: business-specific knowledge not in project docs
|
|
10814
|
+
- Prefix critical observations with [!] (corrections, constraints, errors).
|
|
10815
|
+
- Prefix notable observations with [~] (preferences, patterns).
|
|
10816
|
+
- Leave routine observations unprefixed.
|
|
10817
|
+
- Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
|
|
10818
|
+
- If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
|
|
10819
|
+
|
|
10820
|
+
**Sub-agents & delegation:**
|
|
10821
|
+
- Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
|
|
10822
|
+
- Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
|
|
10823
|
+
- Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
|
|
10824
|
+
- Use \`run_in_background: true\` for parallel independent work.
|
|
10825
|
+
- During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
|
|
10826
|
+
- After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
|
|
10827
|
+
- Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
|
|
10828
|
+
|
|
10829
|
+
**Completing tasks:**
|
|
10830
|
+
- On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
|
|
10831
|
+
}
|
|
10654
10832
|
var DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
|
|
10655
10833
|
You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
|
|
10656
10834
|
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
|
|
@@ -10664,10 +10842,17 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
10664
10842
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
10665
10843
|
|
|
10666
10844
|
1. **Orient** \u2014 Scan workspace state for relevant files. Read the Memory file for context on preferences and past patterns. Read any other files that are useful context with \`deskfree_read_file\`. Don't read everything \u2014 just what's relevant to this task.
|
|
10667
|
-
2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
|
|
10668
|
-
|
|
10845
|
+
2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
|
|
10846
|
+
- **Judgment calls or creative direction?** State your assumptions and approach, send as \`ask\`, and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
|
|
10847
|
+
- **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
|
|
10848
|
+
3. **Work** \u2014 Execute the task. Update existing files with \`deskfree_update_file\` or create new ones with \`deskfree_create_file\`. Pass your taskId so updates appear in the thread. For large deliverables, build incrementally \u2014 share structure/outline first, then flesh out. Don't produce a finished 2000-word document and ask for review in one shot.
|
|
10669
10849
|
4. **Deliver** \u2014 Send an \`ask\` message when work is ready for review. Only complete (\`deskfree_complete_task\` with humanApproved: true) after the human has confirmed. Never self-complete.
|
|
10670
10850
|
|
|
10851
|
+
**Push back when warranted:**
|
|
10852
|
+
- If task instructions seem unclear, contradictory, or misguided \u2014 say so. "This task asks for X, but based on [context], Y might work better because..." is more useful than silently executing a flawed plan.
|
|
10853
|
+
- If you hit genuine ambiguity mid-task, send an \`ask\` message and wait. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
|
|
10854
|
+
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
10855
|
+
|
|
10671
10856
|
**File rules:**
|
|
10672
10857
|
- Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
|
|
10673
10858
|
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
@@ -10688,6 +10873,7 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
10688
10873
|
**Sub-agents & delegation:**
|
|
10689
10874
|
- Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
|
|
10690
10875
|
- Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
|
|
10876
|
+
- Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
|
|
10691
10877
|
- Use \`run_in_background: true\` for parallel independent work.
|
|
10692
10878
|
- During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
|
|
10693
10879
|
- After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
|
|
@@ -10695,12 +10881,16 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
10695
10881
|
|
|
10696
10882
|
**Completing tasks:**
|
|
10697
10883
|
- On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
|
|
10698
|
-
|
|
10884
|
+
function buildHeartbeatDirective(ctx) {
|
|
10885
|
+
return `${identityBlock(ctx)}
|
|
10886
|
+
|
|
10887
|
+
## Heartbeat Check
|
|
10699
10888
|
On each heartbeat, run through this checklist:
|
|
10700
10889
|
|
|
10701
10890
|
### 1. Work the queue
|
|
10702
10891
|
- Run \`deskfree_state\` to get the full workspace snapshot.
|
|
10703
|
-
-
|
|
10892
|
+
- **Check board load.** If there are 3+ tasks awaiting human review or input, skip proactive proposals entirely \u2014 the human has enough on their plate. Focus only on dispatching approved work.
|
|
10893
|
+
- Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to get each one started. Pass the taskId.
|
|
10704
10894
|
- Any open tasks that seem stalled (claimed but no recent activity)? Check on them.
|
|
10705
10895
|
|
|
10706
10896
|
### 2. Proactive assessment
|
|
@@ -10714,7 +10904,7 @@ After handling the queue, step back and think about the bigger picture. You have
|
|
|
10714
10904
|
|
|
10715
10905
|
**Then act \u2014 but only if you have something genuinely useful:**
|
|
10716
10906
|
|
|
10717
|
-
*Things
|
|
10907
|
+
*Things you can do* \u2014 research, drafts, analysis, prep. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
|
|
10718
10908
|
|
|
10719
10909
|
*Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
|
|
10720
10910
|
|
|
@@ -10723,8 +10913,12 @@ After handling the queue, step back and think about the bigger picture. You have
|
|
|
10723
10913
|
- Do not repeat suggestions the human ignored or rejected recently.
|
|
10724
10914
|
- Quality over quantity. One good insight beats five generic nudges.
|
|
10725
10915
|
- If everything looks healthy and active, do nothing. Silence is fine.`;
|
|
10726
|
-
|
|
10727
|
-
|
|
10916
|
+
}
|
|
10917
|
+
function buildSleepDirective(ctx) {
|
|
10918
|
+
return `${identityBlock(ctx)}
|
|
10919
|
+
|
|
10920
|
+
## Nightly Sleep Cycle
|
|
10921
|
+
You're running your nightly cycle to reflect, consolidate memory, and prepare for tomorrow.
|
|
10728
10922
|
|
|
10729
10923
|
Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
|
|
10730
10924
|
|
|
@@ -10749,10 +10943,10 @@ Each memory item carries a type tag and strength score: \`[type:X, strength:N]\`
|
|
|
10749
10943
|
|
|
10750
10944
|
| Type | What it captures | Decay rate |
|
|
10751
10945
|
|------|-----------------|------------|
|
|
10752
|
-
| \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength
|
|
10753
|
-
| \`preference\` | How the human wants things done | Slow (\u22121 when strength
|
|
10946
|
+
| \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength >=10, else no decay) |
|
|
10947
|
+
| \`preference\` | How the human wants things done | Slow (\u22121 when strength >=6, else no decay) |
|
|
10754
10948
|
| \`pattern\` | Approaches/workflows that work | Normal (see decay rules) |
|
|
10755
|
-
| \`domain\` | Business/project-specific facts | Slow (\u22121 when strength
|
|
10949
|
+
| \`domain\` | Business/project-specific facts | Slow (\u22121 when strength >=6, else no decay) |
|
|
10756
10950
|
| \`insight\` | Meta-observations from reflection | Normal (see decay rules) |
|
|
10757
10951
|
|
|
10758
10952
|
Corrections and domain facts are durable \u2014 they rarely become irrelevant.
|
|
@@ -10770,18 +10964,18 @@ Strength uses Ebbinghaus-inspired logarithmic decay \u2014 strong memories resis
|
|
|
10770
10964
|
- Replace old memory, new one starts at [strength: 3, type: correction]
|
|
10771
10965
|
|
|
10772
10966
|
**Decay (memory NOT referenced by any daily observation):**
|
|
10773
|
-
- strength
|
|
10967
|
+
- strength >= 10: decay by \u22121 (deeply encoded, slow forgetting)
|
|
10774
10968
|
- strength 5-9: decay by \u22122 (moderately encoded)
|
|
10775
10969
|
- strength 1-4: decay by \u22123 (weakly encoded, fast forgetting)
|
|
10776
|
-
- EXCEPT: corrections and domain facts with strength
|
|
10777
|
-
- EXCEPT: preferences with strength
|
|
10970
|
+
- EXCEPT: corrections and domain facts with strength >=6 decay at \u22121 max (durable memories)
|
|
10971
|
+
- EXCEPT: preferences with strength >=6 decay at \u22121 max
|
|
10778
10972
|
|
|
10779
10973
|
**Removal:**
|
|
10780
10974
|
- Strength reaches 0 \u2192 move to a ## Fading section at the bottom (one-line summaries only, no strength tags). Keep max 10 fading memories. If Fading section is full, oldest entries are permanently forgotten. You may rescue a fading memory back to active if it becomes relevant again (re-add with [strength: 2]).
|
|
10781
10975
|
|
|
10782
10976
|
**New observation:**
|
|
10783
10977
|
- Assess importance: how consequential is this for future tasks?
|
|
10784
|
-
- Look for [!] prefix (critical) or [~] prefix (notable) as importance signals
|
|
10978
|
+
- Look for [!] prefix (critical) or [~] prefix (notable) as importance signals.
|
|
10785
10979
|
- Low importance (casual mention, routine) \u2192 [strength: 1, type: <appropriate>]
|
|
10786
10980
|
- Medium importance (useful preference, [~] prefix) \u2192 [strength: 2, type: <appropriate>]
|
|
10787
10981
|
- High importance (explicit correction, strong constraint, [!] prefix) \u2192 [strength: 4, type: <appropriate>]
|
|
@@ -10808,6 +11002,7 @@ After memory consolidation:
|
|
|
10808
11002
|
### 3. PROACTIVE OPPORTUNITIES
|
|
10809
11003
|
|
|
10810
11004
|
Based on recent work, completed tasks, and patterns in memory \u2014 is there something genuinely useful to propose or a message worth sending?
|
|
11005
|
+
- **Check board load first.** If the human already has 3+ items needing their attention, skip proposals entirely.
|
|
10811
11006
|
- One focused proposal max. Skip if nothing merits it.
|
|
10812
11007
|
- Quality over quantity. Don't force it.
|
|
10813
11008
|
|
|
@@ -10815,8 +11010,12 @@ Based on recent work, completed tasks, and patterns in memory \u2014 is there so
|
|
|
10815
11010
|
- Keep main thread messages short (1-2 sentences).
|
|
10816
11011
|
- Do NOT propose things the human has previously ignored or rejected.
|
|
10817
11012
|
- Use \`deskfree_read_file\` only if you need to re-read the Memory file after your own update (verification). The current content is already in your prompt.`;
|
|
10818
|
-
|
|
10819
|
-
|
|
11013
|
+
}
|
|
11014
|
+
function buildDuskDirective(ctx) {
|
|
11015
|
+
return `${identityBlock(ctx)}
|
|
11016
|
+
|
|
11017
|
+
## Evening Dusk Cycle
|
|
11018
|
+
You're running your evening cycle to review the day, propose overnight work, and brief the human.
|
|
10820
11019
|
|
|
10821
11020
|
Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
|
|
10822
11021
|
|
|
@@ -10851,10 +11050,11 @@ Think about work that can be done autonomously overnight \u2014 WITHOUT human ju
|
|
|
10851
11050
|
### 3. PROPOSE THE PLAN
|
|
10852
11051
|
|
|
10853
11052
|
If you identified useful overnight work:
|
|
10854
|
-
1.
|
|
10855
|
-
2.
|
|
10856
|
-
3.
|
|
10857
|
-
4.
|
|
11053
|
+
1. **Check board load first.** Count open and awaiting-review tasks. If the human already has 3+ items needing their attention, limit to 1 proposal max \u2014 or skip entirely. Don't pile on.
|
|
11054
|
+
2. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
|
|
11055
|
+
3. Each task should be self-contained \u2014 it must be completable without human input.
|
|
11056
|
+
4. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
|
|
11057
|
+
5. If nothing genuinely useful can be done overnight, skip the proposal entirely. Don't force it.
|
|
10858
11058
|
|
|
10859
11059
|
### 4. BRIEF THE HUMAN
|
|
10860
11060
|
|
|
@@ -10870,6 +11070,7 @@ Send a brief main-thread message via \`deskfree_send_message\`:
|
|
|
10870
11070
|
- Keep the briefing message short and actionable.
|
|
10871
11071
|
- Cross-reference memory for recurring patterns \u2014 if something is due, propose it.
|
|
10872
11072
|
- Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
|
|
11073
|
+
}
|
|
10873
11074
|
var THROTTLE_MS = 300;
|
|
10874
11075
|
var CHAR_BUFFER_SIZE = 256;
|
|
10875
11076
|
var CLOSE_MAX_RETRIES = 3;
|
|
@@ -11283,6 +11484,14 @@ function validateApiUrl(value) {
|
|
|
11283
11484
|
|
|
11284
11485
|
// src/agents/orchestrator.ts
|
|
11285
11486
|
var MAX_ORCHESTRATOR_TURNS = 20;
|
|
11487
|
+
var ORCHESTRATOR_ALLOWED_TOOLS = [
|
|
11488
|
+
"mcp__deskfree-orchestrator__*",
|
|
11489
|
+
"Read",
|
|
11490
|
+
"Glob",
|
|
11491
|
+
"Grep",
|
|
11492
|
+
"WebSearch",
|
|
11493
|
+
"WebFetch"
|
|
11494
|
+
];
|
|
11286
11495
|
var DISALLOWED_BUILTIN_TOOLS = [
|
|
11287
11496
|
"TodoWrite",
|
|
11288
11497
|
"AskUserQuestion",
|
|
@@ -11295,7 +11504,15 @@ var DISALLOWED_BUILTIN_TOOLS = [
|
|
|
11295
11504
|
"Agent"
|
|
11296
11505
|
];
|
|
11297
11506
|
function runOrchestrator(opts) {
|
|
11298
|
-
const {
|
|
11507
|
+
const {
|
|
11508
|
+
prompt,
|
|
11509
|
+
orchestratorServer,
|
|
11510
|
+
model,
|
|
11511
|
+
sessionId,
|
|
11512
|
+
claudeCodePath,
|
|
11513
|
+
agentContext
|
|
11514
|
+
} = opts;
|
|
11515
|
+
const systemPrompt = agentContext ? buildAgentDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
|
|
11299
11516
|
return query({
|
|
11300
11517
|
prompt,
|
|
11301
11518
|
options: {
|
|
@@ -11303,7 +11520,7 @@ function runOrchestrator(opts) {
|
|
|
11303
11520
|
process.stderr.write(`[orchestrator-sdk] ${data}
|
|
11304
11521
|
`);
|
|
11305
11522
|
},
|
|
11306
|
-
systemPrompt
|
|
11523
|
+
systemPrompt,
|
|
11307
11524
|
model,
|
|
11308
11525
|
...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
|
|
11309
11526
|
maxTurns: MAX_ORCHESTRATOR_TURNS,
|
|
@@ -11316,28 +11533,18 @@ function runOrchestrator(opts) {
|
|
|
11316
11533
|
"deskfree-orchestrator": orchestratorServer
|
|
11317
11534
|
},
|
|
11318
11535
|
tools: [],
|
|
11319
|
-
allowedTools:
|
|
11320
|
-
"mcp__deskfree-orchestrator__*",
|
|
11321
|
-
"Read",
|
|
11322
|
-
"Write",
|
|
11323
|
-
"Edit",
|
|
11324
|
-
"Bash",
|
|
11325
|
-
"Glob",
|
|
11326
|
-
"Grep",
|
|
11327
|
-
"WebSearch",
|
|
11328
|
-
"WebFetch",
|
|
11329
|
-
"NotebookEdit"
|
|
11330
|
-
],
|
|
11536
|
+
allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
|
|
11331
11537
|
disallowedTools: DISALLOWED_BUILTIN_TOOLS
|
|
11332
11538
|
}
|
|
11333
11539
|
});
|
|
11334
11540
|
}
|
|
11335
11541
|
function runHeartbeat(opts) {
|
|
11336
|
-
const { prompt, orchestratorServer, model, claudeCodePath } = opts;
|
|
11542
|
+
const { prompt, orchestratorServer, model, claudeCodePath, agentContext } = opts;
|
|
11543
|
+
const systemPrompt = agentContext ? buildHeartbeatDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
|
|
11337
11544
|
return query({
|
|
11338
11545
|
prompt,
|
|
11339
11546
|
options: {
|
|
11340
|
-
systemPrompt
|
|
11547
|
+
systemPrompt,
|
|
11341
11548
|
model,
|
|
11342
11549
|
...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
|
|
11343
11550
|
maxTurns: MAX_ORCHESTRATOR_TURNS,
|
|
@@ -11348,18 +11555,7 @@ function runHeartbeat(opts) {
|
|
|
11348
11555
|
"deskfree-orchestrator": orchestratorServer
|
|
11349
11556
|
},
|
|
11350
11557
|
tools: [],
|
|
11351
|
-
allowedTools:
|
|
11352
|
-
"mcp__deskfree-orchestrator__*",
|
|
11353
|
-
"Read",
|
|
11354
|
-
"Write",
|
|
11355
|
-
"Edit",
|
|
11356
|
-
"Bash",
|
|
11357
|
-
"Glob",
|
|
11358
|
-
"Grep",
|
|
11359
|
-
"WebSearch",
|
|
11360
|
-
"WebFetch",
|
|
11361
|
-
"NotebookEdit"
|
|
11362
|
-
],
|
|
11558
|
+
allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
|
|
11363
11559
|
disallowedTools: DISALLOWED_BUILTIN_TOOLS
|
|
11364
11560
|
}
|
|
11365
11561
|
});
|
|
@@ -11384,8 +11580,8 @@ function runOneShotWorker(opts) {
|
|
|
11384
11580
|
}
|
|
11385
11581
|
var isDocker = process.env["DOCKER"] === "1" || existsSync("/.dockerenv");
|
|
11386
11582
|
var DEFAULTS = {
|
|
11387
|
-
stateDir: isDocker ? "/app/state" : ".deskfree
|
|
11388
|
-
toolsDir: isDocker ? "/app/tools" : ".deskfree
|
|
11583
|
+
stateDir: isDocker ? "/app/state" : join(homedir(), ".deskfree", "state"),
|
|
11584
|
+
toolsDir: isDocker ? "/app/tools" : join(homedir(), ".deskfree", "tools"),
|
|
11389
11585
|
logLevel: "info",
|
|
11390
11586
|
healthPort: 3100
|
|
11391
11587
|
};
|
|
@@ -11452,8 +11648,8 @@ function loadConfig() {
|
|
|
11452
11648
|
function mergeWithRemoteConfig(local, remote) {
|
|
11453
11649
|
const stateDirOverridden = !!process.env["DESKFREE_STATE_DIR"];
|
|
11454
11650
|
const toolsDirOverridden = !!process.env["DESKFREE_TOOLS_DIR"];
|
|
11455
|
-
const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir :
|
|
11456
|
-
const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir :
|
|
11651
|
+
const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : join(homedir(), ".deskfree", remote.botId, "state");
|
|
11652
|
+
const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : join(homedir(), ".deskfree", remote.botId, "tools");
|
|
11457
11653
|
let claudeCodePath;
|
|
11458
11654
|
if (remote.provider === "claude-code") {
|
|
11459
11655
|
try {
|
|
@@ -11481,6 +11677,8 @@ function mergeWithRemoteConfig(local, remote) {
|
|
|
11481
11677
|
anthropicApiKey: remote.anthropicApiKey,
|
|
11482
11678
|
baseUrl: process.env["DESKFREE_BASE_URL"] ?? remote.baseUrl,
|
|
11483
11679
|
botId: remote.botId,
|
|
11680
|
+
botName: remote.botName,
|
|
11681
|
+
deploymentType: remote.deploymentType,
|
|
11484
11682
|
memoryFileId: remote.memoryFileId,
|
|
11485
11683
|
sleepHour: remote.sleepHour,
|
|
11486
11684
|
duskHour: remote.duskHour,
|
|
@@ -11521,6 +11719,72 @@ function getHealthState() {
|
|
|
11521
11719
|
uptimeSeconds: Math.floor((Date.now() - startTime) / 1e3)
|
|
11522
11720
|
};
|
|
11523
11721
|
}
|
|
11722
|
+
var LEVELS = {
|
|
11723
|
+
debug: 0,
|
|
11724
|
+
info: 1,
|
|
11725
|
+
warn: 2,
|
|
11726
|
+
error: 3
|
|
11727
|
+
};
|
|
11728
|
+
var MAX_LOG_FILE_BYTES = 5 * 1024 * 1024;
|
|
11729
|
+
var ROTATION_CHECK_INTERVAL = 500;
|
|
11730
|
+
var logFilePath = null;
|
|
11731
|
+
var writesSinceCheck = 0;
|
|
11732
|
+
function enableFileLogging(filePath) {
|
|
11733
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
11734
|
+
logFilePath = filePath;
|
|
11735
|
+
}
|
|
11736
|
+
function getLogFilePath() {
|
|
11737
|
+
return logFilePath;
|
|
11738
|
+
}
|
|
11739
|
+
function rotateIfNeeded() {
|
|
11740
|
+
if (!logFilePath) return;
|
|
11741
|
+
try {
|
|
11742
|
+
const stat = statSync(logFilePath);
|
|
11743
|
+
if (stat.size <= MAX_LOG_FILE_BYTES) return;
|
|
11744
|
+
const content = readFileSync(logFilePath, "utf8");
|
|
11745
|
+
const half = Math.floor(content.length / 2);
|
|
11746
|
+
const newlineIdx = content.indexOf("\n", half);
|
|
11747
|
+
const trimmed = newlineIdx >= 0 ? content.slice(newlineIdx + 1) : content.slice(half);
|
|
11748
|
+
writeFileSync(logFilePath, trimmed);
|
|
11749
|
+
} catch {
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11752
|
+
function createLogger(component, minLevel = "info") {
|
|
11753
|
+
const minLevelNum = LEVELS[minLevel];
|
|
11754
|
+
function log(level, message, fields) {
|
|
11755
|
+
if (LEVELS[level] < minLevelNum) return;
|
|
11756
|
+
const entry = {
|
|
11757
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11758
|
+
level,
|
|
11759
|
+
component,
|
|
11760
|
+
msg: message,
|
|
11761
|
+
...fields
|
|
11762
|
+
};
|
|
11763
|
+
const line = JSON.stringify(entry);
|
|
11764
|
+
if (level === "error" || level === "warn") {
|
|
11765
|
+
process.stderr.write(line + "\n");
|
|
11766
|
+
} else {
|
|
11767
|
+
process.stdout.write(line + "\n");
|
|
11768
|
+
}
|
|
11769
|
+
if (logFilePath) {
|
|
11770
|
+
try {
|
|
11771
|
+
appendFileSync(logFilePath, line + "\n");
|
|
11772
|
+
if (++writesSinceCheck >= ROTATION_CHECK_INTERVAL) {
|
|
11773
|
+
writesSinceCheck = 0;
|
|
11774
|
+
rotateIfNeeded();
|
|
11775
|
+
}
|
|
11776
|
+
} catch {
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
11779
|
+
}
|
|
11780
|
+
return {
|
|
11781
|
+
debug: (msg, fields) => log("debug", msg, fields),
|
|
11782
|
+
info: (msg, fields) => log("info", msg, fields),
|
|
11783
|
+
warn: (msg, fields) => log("warn", msg, fields),
|
|
11784
|
+
error: (msg, fields) => log("error", msg, fields)
|
|
11785
|
+
};
|
|
11786
|
+
}
|
|
11787
|
+
var logger = createLogger("runtime");
|
|
11524
11788
|
|
|
11525
11789
|
// src/gateway/polling.ts
|
|
11526
11790
|
var deliveredMessageIds = /* @__PURE__ */ new Set();
|
|
@@ -11897,6 +12161,49 @@ async function runWebSocketConnection(opts) {
|
|
|
11897
12161
|
cleanup();
|
|
11898
12162
|
ws.close(1e3, "reload");
|
|
11899
12163
|
process.kill(process.pid, "SIGTERM");
|
|
12164
|
+
} else if (msg.action === "logs") {
|
|
12165
|
+
const MAX_RESPONSE_BYTES = 12e4;
|
|
12166
|
+
const lineCount = msg.lines ?? 200;
|
|
12167
|
+
try {
|
|
12168
|
+
const logFile = getLogFilePath();
|
|
12169
|
+
if (!logFile) {
|
|
12170
|
+
ws.send(
|
|
12171
|
+
JSON.stringify({
|
|
12172
|
+
action: "logsResponse",
|
|
12173
|
+
lines: [],
|
|
12174
|
+
error: "File logging not enabled"
|
|
12175
|
+
})
|
|
12176
|
+
);
|
|
12177
|
+
} else {
|
|
12178
|
+
const content = readFileSync(logFile, "utf8");
|
|
12179
|
+
const allLines = content.split("\n").filter(Boolean);
|
|
12180
|
+
let tail = allLines.slice(-lineCount);
|
|
12181
|
+
while (tail.length > 1) {
|
|
12182
|
+
const payload = JSON.stringify({
|
|
12183
|
+
action: "logsResponse",
|
|
12184
|
+
lines: tail
|
|
12185
|
+
});
|
|
12186
|
+
if (Buffer.byteLength(payload) <= MAX_RESPONSE_BYTES) break;
|
|
12187
|
+
tail = tail.slice(Math.ceil(tail.length * 0.25));
|
|
12188
|
+
}
|
|
12189
|
+
ws.send(
|
|
12190
|
+
JSON.stringify({
|
|
12191
|
+
action: "logsResponse",
|
|
12192
|
+
lines: tail
|
|
12193
|
+
})
|
|
12194
|
+
);
|
|
12195
|
+
}
|
|
12196
|
+
} catch (err) {
|
|
12197
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12198
|
+
log.warn(`Failed to read logs: ${errMsg}`);
|
|
12199
|
+
ws.send(
|
|
12200
|
+
JSON.stringify({
|
|
12201
|
+
action: "logsResponse",
|
|
12202
|
+
lines: [],
|
|
12203
|
+
error: errMsg
|
|
12204
|
+
})
|
|
12205
|
+
);
|
|
12206
|
+
}
|
|
11900
12207
|
}
|
|
11901
12208
|
} catch (err) {
|
|
11902
12209
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -12462,21 +12769,35 @@ function scheduleDailyCycle(label, run, hour, timezone, signal, log) {
|
|
|
12462
12769
|
}
|
|
12463
12770
|
function msUntilNextLocalHour(hour, timezone) {
|
|
12464
12771
|
const now = Date.now();
|
|
12465
|
-
const
|
|
12772
|
+
const hourFmt = new Intl.DateTimeFormat("en-US", {
|
|
12466
12773
|
timeZone: timezone,
|
|
12467
12774
|
hour: "numeric",
|
|
12468
12775
|
hour12: false
|
|
12469
12776
|
});
|
|
12777
|
+
const detailFmt = new Intl.DateTimeFormat("en-US", {
|
|
12778
|
+
timeZone: timezone,
|
|
12779
|
+
minute: "numeric",
|
|
12780
|
+
second: "numeric"
|
|
12781
|
+
});
|
|
12470
12782
|
for (let offsetMs = 6e4; offsetMs <= 25 * 36e5; offsetMs += 36e5) {
|
|
12471
12783
|
const candidateMs = now + offsetMs;
|
|
12472
|
-
const parts =
|
|
12784
|
+
const parts = hourFmt.formatToParts(new Date(candidateMs));
|
|
12473
12785
|
const hourPart = parts.find((p) => p.type === "hour");
|
|
12474
12786
|
const candidateHour = parseInt(hourPart?.value ?? "-1", 10);
|
|
12475
12787
|
if (candidateHour === hour) {
|
|
12476
|
-
const
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12788
|
+
const detailParts = detailFmt.formatToParts(new Date(candidateMs));
|
|
12789
|
+
const localMinute = parseInt(
|
|
12790
|
+
detailParts.find((p) => p.type === "minute")?.value ?? "0",
|
|
12791
|
+
10
|
|
12792
|
+
);
|
|
12793
|
+
const localSecond = parseInt(
|
|
12794
|
+
detailParts.find((p) => p.type === "second")?.value ?? "0",
|
|
12795
|
+
10
|
|
12796
|
+
);
|
|
12797
|
+
const snappedMs = candidateMs - localMinute * 6e4 - localSecond * 1e3;
|
|
12798
|
+
if (snappedMs > now + 6e4) {
|
|
12799
|
+
return snappedMs - now;
|
|
12800
|
+
}
|
|
12480
12801
|
}
|
|
12481
12802
|
}
|
|
12482
12803
|
return 24 * 36e5;
|
|
@@ -12564,40 +12885,6 @@ async function loadToolModules(tools, toolsDir, log) {
|
|
|
12564
12885
|
}
|
|
12565
12886
|
return allTools;
|
|
12566
12887
|
}
|
|
12567
|
-
|
|
12568
|
-
// src/util/logger.ts
|
|
12569
|
-
var LEVELS = {
|
|
12570
|
-
debug: 0,
|
|
12571
|
-
info: 1,
|
|
12572
|
-
warn: 2,
|
|
12573
|
-
error: 3
|
|
12574
|
-
};
|
|
12575
|
-
function createLogger(component, minLevel = "info") {
|
|
12576
|
-
const minLevelNum = LEVELS[minLevel];
|
|
12577
|
-
function log(level, message, fields) {
|
|
12578
|
-
if (LEVELS[level] < minLevelNum) return;
|
|
12579
|
-
const entry = {
|
|
12580
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12581
|
-
level,
|
|
12582
|
-
component,
|
|
12583
|
-
msg: message,
|
|
12584
|
-
...fields
|
|
12585
|
-
};
|
|
12586
|
-
const line = JSON.stringify(entry);
|
|
12587
|
-
if (level === "error" || level === "warn") {
|
|
12588
|
-
process.stderr.write(line + "\n");
|
|
12589
|
-
} else {
|
|
12590
|
-
process.stdout.write(line + "\n");
|
|
12591
|
-
}
|
|
12592
|
-
}
|
|
12593
|
-
return {
|
|
12594
|
-
debug: (msg, fields) => log("debug", msg, fields),
|
|
12595
|
-
info: (msg, fields) => log("info", msg, fields),
|
|
12596
|
-
warn: (msg, fields) => log("warn", msg, fields),
|
|
12597
|
-
error: (msg, fields) => log("error", msg, fields)
|
|
12598
|
-
};
|
|
12599
|
-
}
|
|
12600
|
-
var logger = createLogger("runtime");
|
|
12601
12888
|
var MAX_MESSAGE_CONTENT_LENGTH = 32 * 1024;
|
|
12602
12889
|
var MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
12603
12890
|
var MAX_TOTAL_DOWNLOAD_SIZE = 50 * 1024 * 1024;
|
|
@@ -12880,7 +13167,8 @@ async function routeMessage(message, client, deps, sessionStore, config) {
|
|
|
12880
13167
|
orchestratorServer: deps.createOrchestratorServer(),
|
|
12881
13168
|
model: deps.model,
|
|
12882
13169
|
sessionId: existingSessionId,
|
|
12883
|
-
claudeCodePath: deps.claudeCodePath
|
|
13170
|
+
claudeCodePath: deps.claudeCodePath,
|
|
13171
|
+
agentContext: deps.agentContext
|
|
12884
13172
|
});
|
|
12885
13173
|
let fullText = "";
|
|
12886
13174
|
let capturedSessionId = null;
|
|
@@ -13041,17 +13329,16 @@ var DISALLOWED_BUILTIN_TOOLS2 = [
|
|
|
13041
13329
|
"ReadMcpResource"
|
|
13042
13330
|
];
|
|
13043
13331
|
function runWorker(opts) {
|
|
13044
|
-
const { prompt, workerServer, model, sessionId } = opts;
|
|
13332
|
+
const { prompt, workerServer, model, sessionId, agentContext } = opts;
|
|
13333
|
+
const systemPrompt = agentContext ? buildWorkerDirective(agentContext) : DESKFREE_WORKER_DIRECTIVE;
|
|
13045
13334
|
return query({
|
|
13046
13335
|
prompt,
|
|
13047
13336
|
options: {
|
|
13048
|
-
debug: true,
|
|
13049
|
-
debugFile: "/dev/stderr",
|
|
13050
13337
|
stderr: (data) => {
|
|
13051
13338
|
process.stderr.write(`[worker-sdk] ${data}
|
|
13052
13339
|
`);
|
|
13053
13340
|
},
|
|
13054
|
-
systemPrompt
|
|
13341
|
+
systemPrompt,
|
|
13055
13342
|
model,
|
|
13056
13343
|
maxTurns: MAX_WORKER_TURNS,
|
|
13057
13344
|
permissionMode: "bypassPermissions",
|
|
@@ -13337,7 +13624,8 @@ ${userMessage}
|
|
|
13337
13624
|
prompt: channel,
|
|
13338
13625
|
workerServer,
|
|
13339
13626
|
model,
|
|
13340
|
-
sessionId: previousSessionId
|
|
13627
|
+
sessionId: previousSessionId,
|
|
13628
|
+
agentContext: this.deps.agentContext
|
|
13341
13629
|
});
|
|
13342
13630
|
const idleTimer = this.startIdleTimer(taskId);
|
|
13343
13631
|
const drainPromise = this.drainLoop(
|
|
@@ -13575,7 +13863,7 @@ function startHealthServer(port, log) {
|
|
|
13575
13863
|
}
|
|
13576
13864
|
};
|
|
13577
13865
|
}
|
|
13578
|
-
function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath) {
|
|
13866
|
+
function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath, agentContext) {
|
|
13579
13867
|
if (intervalMs <= 0) return;
|
|
13580
13868
|
let running = false;
|
|
13581
13869
|
async function tick() {
|
|
@@ -13583,11 +13871,13 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
|
|
|
13583
13871
|
running = true;
|
|
13584
13872
|
try {
|
|
13585
13873
|
log.debug("Heartbeat tick: checking for pending work...");
|
|
13874
|
+
const heartbeatPrompt = agentContext ? buildHeartbeatDirective(agentContext) : "Run your heartbeat check now.";
|
|
13586
13875
|
const result = runHeartbeat({
|
|
13587
|
-
prompt:
|
|
13876
|
+
prompt: heartbeatPrompt,
|
|
13588
13877
|
orchestratorServer: createOrchServer(),
|
|
13589
13878
|
model,
|
|
13590
|
-
claudeCodePath
|
|
13879
|
+
claudeCodePath,
|
|
13880
|
+
agentContext
|
|
13591
13881
|
});
|
|
13592
13882
|
for await (const _ of result) {
|
|
13593
13883
|
}
|
|
@@ -13628,8 +13918,23 @@ async function startAgent(opts) {
|
|
|
13628
13918
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13629
13919
|
throw new Error(`Failed to bootstrap config from API: ${msg}`);
|
|
13630
13920
|
}
|
|
13921
|
+
const isDocker2 = process.env["DOCKER"] === "1" || (await import('fs')).existsSync("/.dockerenv");
|
|
13922
|
+
const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
|
|
13923
|
+
const agentContext = {
|
|
13924
|
+
botName: config.botName,
|
|
13925
|
+
deploymentType: config.deploymentType,
|
|
13926
|
+
provider: config.provider,
|
|
13927
|
+
model: config.model,
|
|
13928
|
+
platform: isDocker2 ? "Docker" : process.platform === "darwin" ? "macOS" : "Linux",
|
|
13929
|
+
runtimeVersion,
|
|
13930
|
+
maxConcurrentWorkers: 5
|
|
13931
|
+
// updated after WorkerManager is created
|
|
13932
|
+
};
|
|
13631
13933
|
mkdirSync(config.stateDir, { recursive: true });
|
|
13632
13934
|
mkdirSync(config.toolsDir, { recursive: true });
|
|
13935
|
+
const logFile = join(config.stateDir, "runtime.log");
|
|
13936
|
+
enableFileLogging(logFile);
|
|
13937
|
+
log.info(`File logging enabled: ${logFile}`);
|
|
13633
13938
|
if (config.tools.length > 0) {
|
|
13634
13939
|
log.info(`Installing ${config.tools.length} tool package(s)...`);
|
|
13635
13940
|
await installTools(config.tools, config.toolsDir, log);
|
|
@@ -13683,8 +13988,10 @@ async function startAgent(opts) {
|
|
|
13683
13988
|
"memory",
|
|
13684
13989
|
config.botId,
|
|
13685
13990
|
"session-history.json"
|
|
13686
|
-
)
|
|
13991
|
+
),
|
|
13992
|
+
agentContext
|
|
13687
13993
|
});
|
|
13994
|
+
agentContext.maxConcurrentWorkers = workerManager.maxConcurrentWorkers;
|
|
13688
13995
|
const createOrchServer = () => createOrchestratorMcpServer(client, customTools, workerManager);
|
|
13689
13996
|
const healthServer = startHealthServer(config.healthPort, log);
|
|
13690
13997
|
const sessionStore = new SessionStore();
|
|
@@ -13711,7 +14018,8 @@ async function startAgent(opts) {
|
|
|
13711
14018
|
createOrchestratorServer: createOrchServer,
|
|
13712
14019
|
workerManager,
|
|
13713
14020
|
model: config.model,
|
|
13714
|
-
claudeCodePath: config.claudeCodePath
|
|
14021
|
+
claudeCodePath: config.claudeCodePath,
|
|
14022
|
+
agentContext
|
|
13715
14023
|
},
|
|
13716
14024
|
sessionStore,
|
|
13717
14025
|
{
|
|
@@ -13727,7 +14035,8 @@ async function startAgent(opts) {
|
|
|
13727
14035
|
config.heartbeatIntervalMs,
|
|
13728
14036
|
abortController.signal,
|
|
13729
14037
|
log,
|
|
13730
|
-
config.claudeCodePath
|
|
14038
|
+
config.claudeCodePath,
|
|
14039
|
+
agentContext
|
|
13731
14040
|
);
|
|
13732
14041
|
if (config.memoryFileId && config.sleepHour !== null && config.timezone) {
|
|
13733
14042
|
const memoryFileId = config.memoryFileId;
|
|
@@ -13764,7 +14073,7 @@ async function startAgent(opts) {
|
|
|
13764
14073
|
const workerServer = createWorkServer();
|
|
13765
14074
|
const result = runOneShotWorker({
|
|
13766
14075
|
prompt,
|
|
13767
|
-
systemPrompt:
|
|
14076
|
+
systemPrompt: buildSleepDirective(agentContext),
|
|
13768
14077
|
workerServer,
|
|
13769
14078
|
model: config.model
|
|
13770
14079
|
});
|
|
@@ -13810,7 +14119,7 @@ async function startAgent(opts) {
|
|
|
13810
14119
|
const workerServer = createWorkServer();
|
|
13811
14120
|
const result = runOneShotWorker({
|
|
13812
14121
|
prompt,
|
|
13813
|
-
systemPrompt:
|
|
14122
|
+
systemPrompt: buildDuskDirective(agentContext),
|
|
13814
14123
|
workerServer,
|
|
13815
14124
|
model: config.model
|
|
13816
14125
|
});
|