pikiclaw 0.3.39 → 0.3.40

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.
@@ -1618,6 +1618,63 @@ function getClaudeUsageFromTelemetry(home, model) {
1618
1618
  const windows = [{ label: ageLabel, usedPercent: null, remainingPercent: null, resetAt, resetAfterSeconds, status }];
1619
1619
  return { ok: true, agent: 'claude', source: 'telemetry', capturedAt: chosen.capturedAt, status, windows, error: null };
1620
1620
  }
1621
+ function claudeSessionTranscriptPath(workdir, sessionId) {
1622
+ const home = getHome();
1623
+ if (!home || !workdir || !sessionId)
1624
+ return '';
1625
+ return path.join(home, '.claude', 'projects', encodePathAsDirName(workdir), `${sessionId}.jsonl`);
1626
+ }
1627
+ /**
1628
+ * Scan a claude session transcript for the latest native /goal state. Returns
1629
+ * null when no `goal_status` attachment is present.
1630
+ */
1631
+ export function getClaudeNativeGoal(workdir, sessionId) {
1632
+ const file = claudeSessionTranscriptPath(workdir, sessionId);
1633
+ if (!file || !fs.existsSync(file))
1634
+ return null;
1635
+ // Goal status lines are tiny attachments. Walk the tail (1 MB) to find the
1636
+ // last one — tail covers all realistic session sizes without parsing every
1637
+ // line of a long transcript.
1638
+ const lines = readTailLines(file, 1024 * 1024);
1639
+ let latest = null;
1640
+ for (const raw of lines) {
1641
+ if (!raw || raw[0] !== '{')
1642
+ continue;
1643
+ // Cheap pre-filter so we only JSON.parse the relevant subset.
1644
+ if (!raw.includes('"goal_status"'))
1645
+ continue;
1646
+ try {
1647
+ const ev = JSON.parse(raw);
1648
+ const att = ev?.attachment;
1649
+ if (!att || att.type !== 'goal_status')
1650
+ continue;
1651
+ const condition = typeof att.condition === 'string' ? att.condition : '';
1652
+ const met = !!att.met;
1653
+ const ts = typeof ev.timestamp === 'string' ? Date.parse(ev.timestamp) : NaN;
1654
+ latest = {
1655
+ condition,
1656
+ met,
1657
+ status: met || !condition ? 'complete' : 'active',
1658
+ updatedAtMs: Number.isFinite(ts) ? ts : Date.now(),
1659
+ };
1660
+ }
1661
+ catch { /* skip */ }
1662
+ }
1663
+ // After auto-clear (met:true) claude still leaves the goal_status line in the
1664
+ // transcript; pikiclaw treats "no active goal" as null so the bridge mirrors
1665
+ // the codex semantics where `goal_get` returns null after a clear.
1666
+ if (latest && latest.met)
1667
+ return null;
1668
+ return latest;
1669
+ }
1670
+ /** Build the user-prompt that triggers claude's native `/goal <condition>` slash command. */
1671
+ export function buildClaudeSetGoalPrompt(objective) {
1672
+ return `/goal ${objective.trim()}`;
1673
+ }
1674
+ /** Build the user-prompt that triggers claude's native `/goal clear` slash command. */
1675
+ export function buildClaudeClearGoalPrompt() {
1676
+ return '/goal clear';
1677
+ }
1621
1678
  // ---------------------------------------------------------------------------
1622
1679
  // Driver
1623
1680
  // ---------------------------------------------------------------------------
@@ -115,7 +115,13 @@ export class CodexAppServer {
115
115
  }
116
116
  this.pending.clear();
117
117
  });
118
- this.call('initialize', { clientInfo: { name: 'pikiclaw', version: '0.2.0' } })
118
+ // Declare experimentalApi so `thread/goal/*` is reachable. Codex 0.130+
119
+ // gates these RPCs behind that capability — without it, every goal call
120
+ // returns "requires experimentalApi capability".
121
+ this.call('initialize', {
122
+ clientInfo: { name: 'pikiclaw', version: '0.2.0' },
123
+ capabilities: { experimentalApi: true },
124
+ })
119
125
  .then(resp => {
120
126
  clearTimeout(timer);
121
127
  if (resp.error) {
@@ -32,6 +32,8 @@ export { getProjectSkillPaths, initializeProjectSkills, listSkills, getGlobalSki
32
32
  export { readGoal, writeGoal, clearGoal, setGoal, pauseGoal, resumeGoal, completeGoal, accountTurn, bumpContinuationCount, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, sessionGoalPath, DEFAULT_MAX_CONTINUATIONS, } from './goal.js';
33
33
  // ── Re-export: native codex goal bridge ──────────────────────────────────────
34
34
  export { setCodexGoal, getCodexGoal, clearCodexGoal, pauseCodexGoal, resumeCodexGoal, } from './drivers/codex.js';
35
+ // ── Re-export: native claude goal bridge ─────────────────────────────────────
36
+ export { getClaudeNativeGoal, buildClaudeSetGoalPrompt, buildClaudeClearGoalPrompt, } from './drivers/claude.js';
35
37
  // ── Re-export: MCP extensions ───────────────────────────────────────────────
36
38
  export { listAllMcpExtensions, addGlobalMcpExtension, removeGlobalMcpExtension, updateGlobalMcpExtension, addWorkspaceMcpExtension, removeWorkspaceMcpExtension, updateWorkspaceMcpExtension, loadGlobalMcpExtensions, loadWorkspaceMcpExtensions, getCatalogItems, getCatalogItem, buildInstalledConfigFromRecommended, checkMcpHealth, getCachedHealth, cacheHealth, } from './mcp/extensions.js';
37
39
  export { getRecommendedMcpServers, getRecommendedMcpServer, getRecommendedSkillRepos, searchMcpServers, searchSkills as searchSkillRepos, } from './mcp/registry.js';
package/dist/bot/bot.js CHANGED
@@ -7,7 +7,7 @@ import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import { execSync, spawn } from 'node:child_process';
9
9
  import { getActiveUserConfig, loadWorkspaces, onUserConfigChange, resolveUserWorkdir, setUserWorkdir, updateUserConfig } from '../core/config/user-config.js';
10
- import { doStream, ensureManagedSession, findManagedThreadSession, findThreadSessionAcrossAgents, getSessionStoredConfig, getUsage, initializeProjectSkills, listAgents, resolveAgentModels, listSkills, stageSessionFiles, reconcileOrphanedRunningSessions, getAgentBoundModelId, setAgentBoundModelId, collapseSkillPrompt, readGoal, accountTurn, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, bumpContinuationCount, pauseGoal, resumeGoal, setGoal as setGoalState, clearGoal as clearGoalState, setCodexGoal, getCodexGoal, clearCodexGoal, pauseCodexGoal, resumeCodexGoal, isPendingSessionId, } from '../agent/index.js';
10
+ import { doStream, ensureManagedSession, findManagedThreadSession, findThreadSessionAcrossAgents, getSessionStoredConfig, getUsage, initializeProjectSkills, listAgents, resolveAgentModels, listSkills, stageSessionFiles, reconcileOrphanedRunningSessions, getAgentBoundModelId, setAgentBoundModelId, collapseSkillPrompt, readGoal, accountTurn, shouldContinueAfterTurn, renderContinuationPrompt, renderBudgetLimitPrompt, bumpContinuationCount, pauseGoal, resumeGoal, setGoal as setGoalState, clearGoal as clearGoalState, setCodexGoal, getCodexGoal, clearCodexGoal, pauseCodexGoal, resumeCodexGoal, getClaudeNativeGoal, buildClaudeSetGoalPrompt, buildClaudeClearGoalPrompt, isPendingSessionId, } from '../agent/index.js';
11
11
  import { querySessions, querySessionTail, updateSession, } from './session-hub.js';
12
12
  import { getDriver, hasDriver, allDriverIds } from '../agent/driver.js';
13
13
  import { resolveGuiIntegrationConfig } from '../agent/mcp/bridge.js';
@@ -142,6 +142,18 @@ function normalizeFromCodex(goal) {
142
142
  continuationCount: null,
143
143
  };
144
144
  }
145
+ function normalizeFromClaudeNative(goal) {
146
+ return {
147
+ source: 'claude',
148
+ objective: goal.condition,
149
+ // Native /goal exposes no pause/budget — it's either active or absent.
150
+ status: 'active',
151
+ tokenBudget: null,
152
+ tokensUsed: 0,
153
+ timeUsedSeconds: 0,
154
+ continuationCount: null,
155
+ };
156
+ }
145
157
  // ---------------------------------------------------------------------------
146
158
  // Bot
147
159
  // ---------------------------------------------------------------------------
@@ -1333,14 +1345,14 @@ export class Bot {
1333
1345
  * tasks that get cancelled or errored auto-pause the goal so the loop does
1334
1346
  * not silently resume on the user's next message.
1335
1347
  *
1336
- * Codex sessions short-circuit: codex CLI runs its own native `/goal`
1337
- * lifecycle (state machine + continuation engine) inside its app-server, so
1338
- * pikiclaw stays out to avoid a double loop. See setSessionGoal et al — they
1339
- * bridge to codex's `thread/goal/*` RPC instead of writing pikiclaw's
1340
- * goal.json for codex sessions.
1348
+ * Codex and Claude sessions short-circuit: each runs its own native `/goal`
1349
+ * lifecycle (codex's app-server state machine; claude's in-process Stop
1350
+ * hook), so pikiclaw stays out to avoid a double loop. See setSessionGoal
1351
+ * et al — they bridge to codex's `thread/goal/*` RPC and to claude's
1352
+ * `/goal <condition>` slash command instead of writing pikiclaw's goal.json.
1341
1353
  */
1342
1354
  maybeEnqueueGoalContinuation(session, opts, result) {
1343
- if (session.agent === 'codex')
1355
+ if (session.agent === 'codex' || session.agent === 'claude')
1344
1356
  return;
1345
1357
  const sessionId = (result.sessionId || session.sessionId || '').trim();
1346
1358
  if (!sessionId || isPendingSessionId(sessionId))
@@ -1420,6 +1432,12 @@ export class Bot {
1420
1432
  const goal = await getCodexGoal(sessionId);
1421
1433
  return goal ? normalizeFromCodex(goal) : null;
1422
1434
  }
1435
+ if (agent === 'claude') {
1436
+ if (!sessionId || isPendingSessionId(sessionId))
1437
+ return null;
1438
+ const goal = getClaudeNativeGoal(workdir, sessionId);
1439
+ return goal ? normalizeFromClaudeNative(goal) : null;
1440
+ }
1423
1441
  const goal = readGoal(workdir, agent, sessionId);
1424
1442
  return goal ? normalizeFromPikiclaw(goal) : null;
1425
1443
  }
@@ -1448,6 +1466,37 @@ export class Bot {
1448
1466
  throw new Error('codex did not return a goal snapshot');
1449
1467
  return normalizeFromCodex(goal);
1450
1468
  }
1469
+ if (agent === 'claude') {
1470
+ if (!sessionId || isPendingSessionId(sessionId)) {
1471
+ throw new Error('claude session must exist before /goal — send a first message to create the transcript');
1472
+ }
1473
+ // Native /goal owns its own continuation engine (Stop hook). pikiclaw
1474
+ // just submits the slash command as the next task; claude internally
1475
+ // sets up the goal_status attachment, injects its meta directive, and
1476
+ // keeps looping until the Haiku completion check returns met. Token
1477
+ // budget is accepted in the API for shape parity with codex/portable
1478
+ // but ignored — claude native /goal has no budget concept.
1479
+ const objective = opts.objective.trim();
1480
+ if (!objective)
1481
+ throw new Error('objective must be non-empty');
1482
+ this.submitSessionTask({
1483
+ agent,
1484
+ sessionId,
1485
+ workdir,
1486
+ prompt: buildClaudeSetGoalPrompt(objective),
1487
+ chatId: opts.chatId,
1488
+ modelId: opts.modelId,
1489
+ thinkingEffort: opts.thinkingEffort,
1490
+ });
1491
+ // Return an optimistic snapshot — the actual goal_status attachment is
1492
+ // written by claude during the task; readers can poll getSessionGoal.
1493
+ return normalizeFromClaudeNative({
1494
+ condition: objective,
1495
+ status: 'active',
1496
+ met: false,
1497
+ updatedAtMs: Date.now(),
1498
+ });
1499
+ }
1451
1500
  const goal = setGoalState(workdir, agent, sessionId, {
1452
1501
  objective: opts.objective,
1453
1502
  tokenBudget: opts.tokenBudget ?? null,
@@ -1477,6 +1526,11 @@ export class Bot {
1477
1526
  const goal = resp.goal ?? (await getCodexGoal(sessionId));
1478
1527
  return goal ? normalizeFromCodex(goal) : null;
1479
1528
  }
1529
+ if (agent === 'claude') {
1530
+ // Claude's native /goal exposes no pause/resume — only set and clear.
1531
+ // Surface a clear error so the IM layer can render a friendly message.
1532
+ throw new Error('Claude native /goal does not support pause/resume — only `/goal clear`. Re-issue `/goal <objective>` to start fresh.');
1533
+ }
1480
1534
  const goal = pauseGoal(workdir, agent, sessionId);
1481
1535
  return goal ? normalizeFromPikiclaw(goal) : null;
1482
1536
  }
@@ -1490,6 +1544,9 @@ export class Bot {
1490
1544
  const goal = resp.goal ?? (await getCodexGoal(sessionId));
1491
1545
  return goal ? normalizeFromCodex(goal) : null;
1492
1546
  }
1547
+ if (agent === 'claude') {
1548
+ throw new Error('Claude native /goal does not support pause/resume — re-issue `/goal <objective>` to start fresh.');
1549
+ }
1493
1550
  const goal = resumeGoal(workdir, agent, sessionId);
1494
1551
  if (!goal || goal.status !== 'active')
1495
1552
  return goal ? normalizeFromPikiclaw(goal) : null;
@@ -1508,7 +1565,7 @@ export class Bot {
1508
1565
  }
1509
1566
  return normalizeFromPikiclaw(goal);
1510
1567
  }
1511
- async clearSessionGoal(workdir, agent, sessionId) {
1568
+ async clearSessionGoal(workdir, agent, sessionId, opts = {}) {
1512
1569
  if (agent === 'codex') {
1513
1570
  if (!sessionId || isPendingSessionId(sessionId))
1514
1571
  return;
@@ -1517,6 +1574,24 @@ export class Bot {
1517
1574
  throw new Error(resp.error);
1518
1575
  return;
1519
1576
  }
1577
+ if (agent === 'claude') {
1578
+ if (!sessionId || isPendingSessionId(sessionId))
1579
+ return;
1580
+ // Read goal-status first to avoid spawning a no-op turn when nothing is set.
1581
+ const existing = getClaudeNativeGoal(workdir, sessionId);
1582
+ if (!existing)
1583
+ return;
1584
+ this.submitSessionTask({
1585
+ agent,
1586
+ sessionId,
1587
+ workdir,
1588
+ prompt: buildClaudeClearGoalPrompt(),
1589
+ chatId: opts.chatId,
1590
+ modelId: opts.modelId,
1591
+ thinkingEffort: opts.thinkingEffort,
1592
+ });
1593
+ return;
1594
+ }
1520
1595
  clearGoalState(workdir, agent, sessionId);
1521
1596
  }
1522
1597
  cancelTask(taskId) {
@@ -72,8 +72,11 @@ export function getWorkspacesData(bot, chatId) {
72
72
  * the IM renderer to send back. Returns null when there is no active session
73
73
  * for the chat (caller renders its own "pick a session first" message).
74
74
  *
75
- * For codex sessions this awaits codex's native `thread/goal/*` RPC; for other
76
- * drivers it's effectively sync but stays async for a uniform call site.
75
+ * Per-agent routing:
76
+ * - codex native `thread/goal/*` RPC (state machine + budget + pause/resume)
77
+ * - claude → native `/goal <condition>` slash command (Stop hook continuation,
78
+ * auto-clear on completion, no budget / no pause/resume)
79
+ * - others → pikiclaw's portable goal.json with continuation injection
77
80
  */
78
81
  export async function handleGoalCommand(bot, chatId, rawArgs) {
79
82
  const session = bot.selectedSession(chatId);
@@ -104,12 +107,17 @@ export async function handleGoalCommand(bot, chatId, rawArgs) {
104
107
  return `Resumed goal: ${truncate(goal.objective, 80)}`;
105
108
  }
106
109
  if (lower === 'clear' || lower === 'cancel' || lower === 'stop') {
107
- await bot.clearSessionGoal(workdir, agent, sessionId);
108
- return 'Cleared goal.';
110
+ await bot.clearSessionGoal(workdir, agent, sessionId, { chatId });
111
+ return agent === 'claude'
112
+ ? 'Submitted `/goal clear` to claude. (Native /goal auto-clears once the condition is met, so this is only needed to stop early.)'
113
+ : 'Cleared goal.';
109
114
  }
110
115
  const { objective, tokenBudget } = parseObjective(args);
111
116
  if (!objective)
112
117
  return 'Usage: /goal <objective> (or pause / resume / clear)';
118
+ if (agent === 'claude' && tokenBudget != null) {
119
+ return 'Claude native /goal does not support `budget=N` — drop the budget prefix. (Use a codex session if you need a token budget.)';
120
+ }
113
121
  const goal = await bot.setSessionGoal(workdir, agent, sessionId, {
114
122
  objective,
115
123
  tokenBudget,
@@ -122,6 +130,12 @@ export async function handleGoalCommand(bot, chatId, rawArgs) {
122
130
  'Send any message to trigger codex\'s native continuation loop. Each message resumes the thread and codex audits / continues until it marks the goal complete or hits the budget.',
123
131
  ].join('\n');
124
132
  }
133
+ if (agent === 'claude') {
134
+ return [
135
+ `Goal set (claude native): ${truncate(goal.objective, 120)}`,
136
+ 'Claude\'s in-process Stop hook keeps working until a Haiku judge confirms the condition is met, then auto-clears. Send `/goal clear` to stop early; `/goal` to inspect.',
137
+ ].join('\n');
138
+ }
125
139
  return `Goal set${budgetLabel}: ${truncate(goal.objective, 120)}\nThe agent will keep working until it audits the objective complete${goal.tokenBudget != null ? ' or exhausts the budget' : ''}.`;
126
140
  }
127
141
  catch (e) {
@@ -131,6 +145,12 @@ export async function handleGoalCommand(bot, chatId, rawArgs) {
131
145
  function formatGoalStatusLine(goal, agent) {
132
146
  if (!goal)
133
147
  return 'No goal set for this session. Use `/goal <objective>` to set one.';
148
+ if (goal.source === 'claude') {
149
+ return [
150
+ `Goal: ${truncate(goal.objective, 200)}`,
151
+ `Status: ${goal.status} · claude native (Stop hook, auto-clears on completion)`,
152
+ ].join('\n');
153
+ }
134
154
  const budget = goal.tokenBudget != null
135
155
  ? `${goal.tokensUsed}/${goal.tokenBudget} tokens`
136
156
  : `${goal.tokensUsed} tokens (no budget)`;
@@ -437,7 +437,7 @@ app.get('/api/session-hub/skills', (c) => {
437
437
  app.post('/api/session-hub/session/send', async (c) => {
438
438
  try {
439
439
  const { workdir, agent, sessionId, prompt, model, effort, attachments, cleanup } = await parseSessionSendRequest(c);
440
- const queued = queueDashboardSessionTask({
440
+ const queued = await queueDashboardSessionTask({
441
441
  workdir,
442
442
  agent,
443
443
  sessionId,
@@ -2,10 +2,38 @@
2
2
  * Public session task control surface for dashboard and API routes.
3
3
  */
4
4
  import path from 'node:path';
5
- import { getProjectSkillPaths, listSkills, stageSessionFiles, ensureManagedSession, getDriverCapabilities } from '../agent/index.js';
5
+ import { getProjectSkillPaths, listSkills, stageSessionFiles, ensureManagedSession, getDriverCapabilities, isPendingSessionId } from '../agent/index.js';
6
6
  import { loadUserConfig } from '../core/config/user-config.js';
7
7
  import { runtime } from './runtime.js';
8
8
  const KNOWN_AGENTS = new Set(['claude', 'codex', 'gemini', 'hermes']);
9
+ /**
10
+ * Parse a `/goal[ args]` prompt typed in the dashboard chat box. Returns null
11
+ * when the prompt is not a goal slash command. Sub-commands mirror the IM
12
+ * `handleGoalCommand` semantics (set / clear / pause / resume / status).
13
+ *
14
+ * Routing /goal through the native bridge is the dashboard's analog of what
15
+ * channels/{telegram,feishu,weixin}/bot.ts do via `handleGoalCommand` — before
16
+ * this hook, dashboard /goal was matched by the legacy `goal` skill resolver
17
+ * and silently rewritten to "Read SKILL.md and execute", which bypassed both
18
+ * the claude native /goal slash command and codex's thread/goal RPC.
19
+ */
20
+ function parseGoalSlash(prompt) {
21
+ const trimmed = prompt.trim();
22
+ const m = trimmed.match(/^\/goal(?:\s+([\s\S]*))?$/);
23
+ if (!m)
24
+ return null;
25
+ const args = (m[1] || '').trim();
26
+ if (!args)
27
+ return { action: 'status', objective: '' };
28
+ const lower = args.toLowerCase();
29
+ if (lower === 'clear' || lower === 'cancel' || lower === 'stop')
30
+ return { action: 'clear', objective: '' };
31
+ if (lower === 'pause')
32
+ return { action: 'pause', objective: '' };
33
+ if (lower === 'resume')
34
+ return { action: 'resume', objective: '' };
35
+ return { action: 'set', objective: args };
36
+ }
9
37
  /**
10
38
  * Resolve a `/skill-name [args]` prompt into the full skill execution prompt.
11
39
  * Returns null if the prompt is not a skill invocation or the skill is not found.
@@ -33,7 +61,7 @@ function resolveSkillFromPrompt(workdir, prompt) {
33
61
  const resolvedPrompt = `${workdirHint}Read the skill definition at \`${targetPath}\` and execute the instructions defined there.${extra}`;
34
62
  return { resolvedPrompt, skillName: skill.name };
35
63
  }
36
- export function queueDashboardSessionTask(request) {
64
+ export async function queueDashboardSessionTask(request) {
37
65
  const bot = runtime.getBotRef();
38
66
  if (!bot)
39
67
  return { ok: false, error: 'Bot is not running' };
@@ -48,6 +76,14 @@ export function queueDashboardSessionTask(request) {
48
76
  const thinkingEffort = resolvedAgent === 'gemini'
49
77
  ? ''
50
78
  : (typeof request.effort === 'string' ? request.effort.trim().toLowerCase() : '');
79
+ // /goal — route directly to the goal bridge (claude native slash, codex RPC,
80
+ // or portable goal.json for gemini/hermes). Must run BEFORE skill resolution
81
+ // so the legacy `goal` skill doesn't grab the prompt and rewrite it into a
82
+ // "Read SKILL.md" instruction.
83
+ const goalCmd = parseGoalSlash(request.prompt || '');
84
+ if (goalCmd && request.sessionId && !isPendingSessionId(request.sessionId)) {
85
+ return runDashboardGoalSlash(bot, resolvedAgent, request, goalCmd, modelId, thinkingEffort);
86
+ }
51
87
  // Resolve /skill-name prompts into full skill execution prompts
52
88
  let prompt = request.prompt;
53
89
  const skillResult = prompt ? resolveSkillFromPrompt(request.workdir, prompt) : null;
@@ -84,6 +120,43 @@ export function queueDashboardSessionTask(request) {
84
120
  ...(thinkingEffort ? { thinkingEffort } : {}),
85
121
  });
86
122
  }
123
+ async function runDashboardGoalSlash(bot, agent, request, cmd, modelId, thinkingEffort) {
124
+ const opts = { chatId: 'dashboard', modelId: modelId || undefined, thinkingEffort: thinkingEffort || undefined };
125
+ const sessionKey = `${agent}:${request.sessionId}`;
126
+ // Synthetic task id — for set / clear / resume on agents that internally
127
+ // submit a follow-up task (claude native slash, portable continuation),
128
+ // the real task id is owned by submitSessionTask. The dashboard's SSE
129
+ // stream listener picks that up via session events; this id is just to
130
+ // give the HTTP caller a non-empty taskId field.
131
+ const taskId = `goal-${cmd.action}-${Date.now().toString(36)}`;
132
+ try {
133
+ if (cmd.action === 'status') {
134
+ const goal = await bot.getSessionGoal(request.workdir, agent, request.sessionId);
135
+ return { ok: true, taskId, sessionKey, queued: false, goal };
136
+ }
137
+ if (cmd.action === 'clear') {
138
+ await bot.clearSessionGoal(request.workdir, agent, request.sessionId, opts);
139
+ return { ok: true, taskId, sessionKey, queued: false };
140
+ }
141
+ if (cmd.action === 'pause') {
142
+ const goal = await bot.pauseSessionGoal(request.workdir, agent, request.sessionId);
143
+ return { ok: true, taskId, sessionKey, queued: false, goal };
144
+ }
145
+ if (cmd.action === 'resume') {
146
+ const goal = await bot.resumeSessionGoal(request.workdir, agent, request.sessionId, opts);
147
+ return { ok: true, taskId, sessionKey, queued: false, goal };
148
+ }
149
+ // set
150
+ const goal = await bot.setSessionGoal(request.workdir, agent, request.sessionId, {
151
+ objective: cmd.objective,
152
+ ...opts,
153
+ });
154
+ return { ok: true, taskId, sessionKey, queued: true, goal };
155
+ }
156
+ catch (e) {
157
+ return { ok: false, error: e?.message || String(e) };
158
+ }
159
+ }
87
160
  export function forkDashboardSessionTask(request) {
88
161
  const bot = runtime.getBotRef();
89
162
  if (!bot)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiclaw",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
4
4
  "description": "Put the world's smartest AI agents in your pocket. Command local Claude & Gemini via IM. | 让最好用的 IM 变成你电脑上的顶级 Agent 控制台",
5
5
  "type": "module",
6
6
  "bin": {