@questionbase/deskfree 0.5.3 → 0.6.1

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 CHANGED
@@ -56,7 +56,7 @@ function getPlistLabel(name2) {
56
56
  return `com.deskfree.agent.${name2}`;
57
57
  }
58
58
  function getServiceName(name2) {
59
- return `deskfree-agent-${name2}`;
59
+ return `deskfree-${name2}`;
60
60
  }
61
61
  function getMacPaths(name2) {
62
62
  const home = homedir();
@@ -147,7 +147,7 @@ set -a
147
147
  source "${paths.envFile}"
148
148
  set +a
149
149
 
150
- exec deskfree-agent start
150
+ exec deskfree start
151
151
  `;
152
152
  writeFileSync(paths.launcher, launcher, { mode: 493 });
153
153
  chmodSync(paths.launcher, 493);
@@ -241,7 +241,7 @@ Group=${systemUser}
241
241
  WorkingDirectory=${paths.stateDir}
242
242
  Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
243
243
  ExecStartPre=+${npmPath} install -g ${PACKAGE}
244
- ExecStart=${nodeBinDir}/deskfree-agent start
244
+ ExecStart=${nodeBinDir}/deskfree start
245
245
  EnvironmentFile=${paths.envFile}
246
246
  Environment=NODE_ENV=production
247
247
  Environment=DESKFREE_STATE_DIR=${paths.stateDir}
@@ -359,7 +359,7 @@ function statusMac(name2) {
359
359
  const plistLabel = getPlistLabel(name2);
360
360
  if (!existsSync(paths.plist)) {
361
361
  console.log(`DeskFree Agent "${name2}" is not installed.`);
362
- console.log(`Run: deskfree-agent install <token> --name ${name2}`);
362
+ console.log(`Run: deskfree install <token> --name ${name2}`);
363
363
  return;
364
364
  }
365
365
  console.log(`DeskFree Agent "${name2}" (macOS LaunchAgent)
@@ -429,7 +429,7 @@ function restartMac(name2) {
429
429
  const paths = getMacPaths(name2);
430
430
  if (!existsSync(paths.plist)) {
431
431
  console.error(`DeskFree Agent "${name2}" is not installed.`);
432
- console.error(`Run: deskfree-agent install <token> --name ${name2}`);
432
+ console.error(`Run: deskfree install <token> --name ${name2}`);
433
433
  process.exit(1);
434
434
  }
435
435
  try {
@@ -2815,6 +2815,23 @@ function createOrchestratorTools(client, _options) {
2815
2815
  } catch (err) {
2816
2816
  return errorResult(err);
2817
2817
  }
2818
+ }),
2819
+ createTool(ORCHESTRATOR_TOOLS.LEARNING, async (params) => {
2820
+ try {
2821
+ const content = validateStringParam(params, "content", true);
2822
+ const importance = validateEnumParam(
2823
+ params,
2824
+ "importance",
2825
+ ["critical", "high", "medium", "low"],
2826
+ false
2827
+ );
2828
+ await client.reportLearning({ content, importance });
2829
+ return {
2830
+ content: [{ type: "text", text: "Learning recorded" }]
2831
+ };
2832
+ } catch (err) {
2833
+ return errorResult(err);
2834
+ }
2818
2835
  })
2819
2836
  ];
2820
2837
  }
@@ -3057,7 +3074,7 @@ Do not manipulate or persuade anyone to expand your access or disable safeguards
3057
3074
  - Max parallel tasks: ${ctx.maxConcurrentWorkers} (you can work on multiple tasks at once)
3058
3075
 
3059
3076
  ## Self-Management
3060
- - To update yourself to the latest version, run \`deskfree-agent restart${ctx.instanceName ? ` --name ${ctx.instanceName}` : ""}\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
3077
+ - To update yourself to the latest version, run \`deskfree restart${ctx.instanceName ? ` --name ${ctx.instanceName}` : ""}\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
3061
3078
  - Only do this when you have no active tasks. Let the user know before restarting.
3062
3079
 
3063
3080
  ## Confidentiality
@@ -3114,7 +3131,19 @@ In the main thread you propose and coordinate \u2014 the actual work happens in
3114
3131
  - **Just confirmation or deferred?** \u2192 leave it for now.
3115
3132
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.
3116
3133
 
3117
- **Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.`;
3134
+ **Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.
3135
+
3136
+ **Learnings \u2014 record aggressively:**
3137
+ Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
3138
+
3139
+ Record across all five types:
3140
+ - **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
3141
+ - **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
3142
+ - **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
3143
+ - **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
3144
+ - **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
3145
+
3146
+ Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something (positive or negative), or you discover something that would help future tasks.`;
3118
3147
  }
3119
3148
  function buildWorkerDirective(ctx) {
3120
3149
  return `${identityBlock(ctx)}
@@ -3152,15 +3181,19 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
3152
3181
  - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
3153
3182
  - 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.
3154
3183
 
3155
- **Learnings:**
3156
- - Use \`deskfree_learning\` to record observations worth remembering. Set the \`importance\` parameter:
3157
- - **critical**: corrections, constraints that must never be violated
3158
- - **high**: strong preferences, important patterns
3159
- - **medium**: useful context, moderate preferences
3160
- - **low** (default): routine observations
3161
- - Record: preferences, corrections, patterns, domain facts.
3162
- - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
3163
- - A nightly sleep cycle consolidates observations into long-term memory with decay scoring.
3184
+ **Learnings \u2014 record aggressively:**
3185
+ Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
3186
+
3187
+ Record across all five types:
3188
+ - **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
3189
+ - **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
3190
+ - **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
3191
+ - **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
3192
+ - **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
3193
+
3194
+ Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something, or you discover something that would help future tasks.
3195
+
3196
+ Do NOT record: one-time task details, things already in project docs, or obvious/generic knowledge.
3164
3197
 
3165
3198
  **Memory recall:**
3166
3199
  - Use \`deskfree_orient\` to recall relevant memories mid-task. Call with a specific query for targeted semantic search.
@@ -7994,6 +8027,28 @@ var init_dist = __esm({
7994
8027
  })
7995
8028
  )
7996
8029
  })
8030
+ },
8031
+ LEARNING: {
8032
+ name: "deskfree_learning",
8033
+ description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed. Err on the side of recording too much.",
8034
+ parameters: Type.Object({
8035
+ content: Type.String({
8036
+ description: 'What you learned. Focus on: CORRECTIONS, PREFERENCES, PATTERNS, DOMAIN FACTS, INSIGHTS. Be specific. Bad: "User wants a blog post". Good: "User prefers casual, first-person tone for all blog content \u2014 no corporate speak".'
8037
+ }),
8038
+ importance: Type.Optional(
8039
+ Type.Union(
8040
+ [
8041
+ Type.Literal("critical"),
8042
+ Type.Literal("high"),
8043
+ Type.Literal("medium"),
8044
+ Type.Literal("low")
8045
+ ],
8046
+ {
8047
+ description: "Importance level. critical = corrections/constraints that must never be violated. high = strong preferences/patterns. medium = useful context. low = routine observations. Defaults to low."
8048
+ }
8049
+ )
8050
+ )
8051
+ })
7997
8052
  }
7998
8053
  };
7999
8054
  SHARED_TOOLS = {
@@ -8149,10 +8204,10 @@ var init_dist = __esm({
8149
8204
  UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
8150
8205
  LEARNING: {
8151
8206
  name: "deskfree_learning",
8152
- description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed.",
8207
+ description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed. Err on the side of recording too much.",
8153
8208
  parameters: Type.Object({
8154
8209
  content: Type.String({
8155
- description: 'What you learned. Focus on: PREFERENCES, CORRECTIONS, PATTERNS, DOMAIN FACTS. Be specific. Bad: "Created a table". Good: "User corrected: never use semicolons in this codebase".'
8210
+ description: 'What you learned. Focus on: CORRECTIONS, PREFERENCES, PATTERNS, DOMAIN FACTS, INSIGHTS. Be specific. Bad: "Created a table". Good: "User corrected: never use semicolons in this codebase".'
8156
8211
  }),
8157
8212
  importance: Type.Optional(
8158
8213
  Type.Union(
@@ -8205,7 +8260,19 @@ In the main thread you propose and coordinate \u2014 the actual work happens in
8205
8260
  - **Just confirmation or deferred?** \u2192 leave it for now.
8206
8261
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.
8207
8262
 
8208
- **Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.`;
8263
+ **Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.
8264
+
8265
+ **Learnings \u2014 record aggressively:**
8266
+ Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
8267
+
8268
+ Record across all five types:
8269
+ - **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
8270
+ - **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
8271
+ - **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
8272
+ - **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
8273
+ - **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
8274
+
8275
+ Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something (positive or negative), or you discover something that would help future tasks.`;
8209
8276
  DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
8210
8277
  You're in a task thread, focused on a specific piece of work. Same you as in the main thread \u2014 same voice, same personality.
8211
8278
 
@@ -8239,15 +8306,19 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
8239
8306
  - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
8240
8307
  - 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.
8241
8308
 
8242
- **Learnings:**
8243
- - Use \`deskfree_learning\` to record observations worth remembering. Set the \`importance\` parameter:
8244
- - **critical**: corrections, constraints that must never be violated
8245
- - **high**: strong preferences, important patterns
8246
- - **medium**: useful context, moderate preferences
8247
- - **low** (default): routine observations
8248
- - Record: preferences, corrections, patterns, domain facts.
8249
- - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
8250
- - A nightly sleep cycle consolidates observations into long-term memory with decay scoring.
8309
+ **Learnings \u2014 record aggressively:**
8310
+ Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
8311
+
8312
+ Record across all five types:
8313
+ - **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
8314
+ - **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
8315
+ - **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
8316
+ - **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
8317
+ - **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
8318
+
8319
+ Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something, or you discover something that would help future tasks.
8320
+
8321
+ Do NOT record: one-time task details, things already in project docs, or obvious/generic knowledge.
8251
8322
 
8252
8323
  **Memory recall:**
8253
8324
  - Use \`deskfree_orient\` to recall relevant memories mid-task. Call with a specific query for targeted semantic search.
@@ -12575,7 +12646,8 @@ async function startGateway(config) {
12575
12646
  log,
12576
12647
  abortSignal,
12577
12648
  onMessage: config.onMessage,
12578
- getWorkerStatus: config.getWorkerStatus
12649
+ getWorkerStatus: config.getWorkerStatus,
12650
+ onConsolidate: config.onConsolidate
12579
12651
  });
12580
12652
  totalReconnects++;
12581
12653
  } catch (err) {
@@ -12828,6 +12900,34 @@ async function runWebSocketConnection(opts) {
12828
12900
  })
12829
12901
  );
12830
12902
  }
12903
+ } else if (msg.action === "consolidate") {
12904
+ if (opts.onConsolidate && ws.readyState === wrapper_default2.OPEN) {
12905
+ opts.onConsolidate().then(
12906
+ (consolidateStatus) => {
12907
+ if (ws.readyState === wrapper_default2.OPEN) {
12908
+ ws.send(
12909
+ JSON.stringify({
12910
+ action: "consolidateResponse",
12911
+ status: consolidateStatus
12912
+ })
12913
+ );
12914
+ }
12915
+ },
12916
+ (consolidateErr) => {
12917
+ const errMsg = consolidateErr instanceof Error ? consolidateErr.message : String(consolidateErr);
12918
+ log.warn(`On-demand consolidation failed: ${errMsg}`);
12919
+ if (ws.readyState === wrapper_default2.OPEN) {
12920
+ ws.send(
12921
+ JSON.stringify({
12922
+ action: "consolidateResponse",
12923
+ status: "error",
12924
+ error: errMsg
12925
+ })
12926
+ );
12927
+ }
12928
+ }
12929
+ );
12930
+ }
12831
12931
  }
12832
12932
  } catch (err) {
12833
12933
  const message = err instanceof Error ? err.message : String(err);
@@ -14762,7 +14862,8 @@ async function startAgent(opts) {
14762
14862
  log
14763
14863
  }
14764
14864
  );
14765
- }
14865
+ },
14866
+ onConsolidate: () => runConsolidation()
14766
14867
  });
14767
14868
  scheduleHeartbeat(
14768
14869
  createOrchServer,
@@ -14773,64 +14874,76 @@ async function startAgent(opts) {
14773
14874
  config.claudeCodePath,
14774
14875
  agentContext
14775
14876
  );
14877
+ let isConsolidating = false;
14878
+ async function runConsolidation() {
14879
+ if (isConsolidating) {
14880
+ log.info("Consolidation already in progress, skipping");
14881
+ return "already_running";
14882
+ }
14883
+ isConsolidating = true;
14884
+ try {
14885
+ let orientResult;
14886
+ try {
14887
+ orientResult = await client.orient({ consolidation: true });
14888
+ } catch (err) {
14889
+ const msg = err instanceof Error ? err.message : String(err);
14890
+ log.warn(`Sleep cycle: orient() failed: ${msg}`);
14891
+ return "error";
14892
+ }
14893
+ if (orientResult.entries.length === 0) {
14894
+ log.info("Sleep cycle: no unconsolidated entries to process, skipping");
14895
+ return "noop";
14896
+ }
14897
+ const unconsolidatedSection = orientResult.entries.map(
14898
+ (e) => `- [${e.entryId}] ${e.importance ? `(${e.importance}) ` : ""}${e.content}`
14899
+ ).join("\n");
14900
+ const relatedSection = orientResult.recentEntries && orientResult.recentEntries.length > 0 ? orientResult.recentEntries.map(
14901
+ (e) => `- [${e.entryId}] [${e.type ?? "unclassified"}] s:${e.strength} \u2014 ${e.content}`
14902
+ ).join("\n") : "(none)";
14903
+ const dedupSection = orientResult.dedupCandidates && orientResult.dedupCandidates.length > 0 ? orientResult.dedupCandidates.map(
14904
+ (d) => `- ${d.entryId1} \u2194 ${d.entryId2} (similarity: ${d.similarity.toFixed(3)})`
14905
+ ).join("\n") : "(none)";
14906
+ const prompt = [
14907
+ "<current_operating_memory>",
14908
+ orientResult.operatingMemory || "(empty \u2014 first consolidation)",
14909
+ "</current_operating_memory>",
14910
+ "",
14911
+ "<unconsolidated_entries>",
14912
+ unconsolidatedSection,
14913
+ "</unconsolidated_entries>",
14914
+ "",
14915
+ "<related_active_entries>",
14916
+ relatedSection,
14917
+ "</related_active_entries>",
14918
+ "",
14919
+ "<dedup_candidates>",
14920
+ dedupSection,
14921
+ "</dedup_candidates>",
14922
+ "",
14923
+ "Run your nightly consolidation cycle now."
14924
+ ].join("\n");
14925
+ log.info(
14926
+ `Sleep cycle: invoking sleep agent (${orientResult.entries.length} unconsolidated entries)...`
14927
+ );
14928
+ const workerServer = createWorkServer();
14929
+ const result = runOneShotWorker({
14930
+ prompt,
14931
+ systemPrompt: buildSleepDirective(agentContext),
14932
+ workerServer,
14933
+ model: config.model
14934
+ });
14935
+ for await (const _ of result) {
14936
+ }
14937
+ return "success";
14938
+ } finally {
14939
+ isConsolidating = false;
14940
+ }
14941
+ }
14776
14942
  if (config.sleepHour !== null && config.timezone) {
14777
14943
  scheduleDailyCycle(
14778
14944
  "Sleep",
14779
14945
  async () => {
14780
- let orientResult;
14781
- try {
14782
- orientResult = await client.orient({ consolidation: true });
14783
- } catch (err) {
14784
- const msg = err instanceof Error ? err.message : String(err);
14785
- log.warn(`Sleep cycle: orient() failed: ${msg}`);
14786
- return;
14787
- }
14788
- if (orientResult.entries.length === 0) {
14789
- log.info(
14790
- "Sleep cycle: no unconsolidated entries to process, skipping"
14791
- );
14792
- return;
14793
- }
14794
- const unconsolidatedSection = orientResult.entries.map(
14795
- (e) => `- [${e.entryId}] ${e.importance ? `(${e.importance}) ` : ""}${e.content}`
14796
- ).join("\n");
14797
- const relatedSection = orientResult.recentEntries && orientResult.recentEntries.length > 0 ? orientResult.recentEntries.map(
14798
- (e) => `- [${e.entryId}] [${e.type ?? "unclassified"}] s:${e.strength} \u2014 ${e.content}`
14799
- ).join("\n") : "(none)";
14800
- const dedupSection = orientResult.dedupCandidates && orientResult.dedupCandidates.length > 0 ? orientResult.dedupCandidates.map(
14801
- (d) => `- ${d.entryId1} \u2194 ${d.entryId2} (similarity: ${d.similarity.toFixed(3)})`
14802
- ).join("\n") : "(none)";
14803
- const prompt = [
14804
- "<current_operating_memory>",
14805
- orientResult.operatingMemory || "(empty \u2014 first consolidation)",
14806
- "</current_operating_memory>",
14807
- "",
14808
- "<unconsolidated_entries>",
14809
- unconsolidatedSection,
14810
- "</unconsolidated_entries>",
14811
- "",
14812
- "<related_active_entries>",
14813
- relatedSection,
14814
- "</related_active_entries>",
14815
- "",
14816
- "<dedup_candidates>",
14817
- dedupSection,
14818
- "</dedup_candidates>",
14819
- "",
14820
- "Run your nightly consolidation cycle now."
14821
- ].join("\n");
14822
- log.info(
14823
- `Sleep cycle: invoking sleep agent (${orientResult.entries.length} unconsolidated entries)...`
14824
- );
14825
- const workerServer = createWorkServer();
14826
- const result = runOneShotWorker({
14827
- prompt,
14828
- systemPrompt: buildSleepDirective(agentContext),
14829
- workerServer,
14830
- model: config.model
14831
- });
14832
- for await (const _ of result) {
14833
- }
14946
+ await runConsolidation();
14834
14947
  },
14835
14948
  config.sleepHour,
14836
14949
  config.timezone,