@hasna/assistants 1.1.3 → 1.1.5

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.
Files changed (3) hide show
  1. package/dist/cli.js +62 -0
  2. package/dist/lib.js +218 -0
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -162160,6 +162160,15 @@ class AssistantLoop {
162160
162160
  registerConnectorsListTool(this.toolRegistry, {
162161
162161
  getConnectorBridge: () => this.connectorBridge
162162
162162
  });
162163
+ registerConnectorsSearchTool(this.toolRegistry, {
162164
+ getConnectorBridge: () => this.connectorBridge,
162165
+ onConnectorSelected: (connectorName) => {
162166
+ this.connectorBridge.registerConnector(this.toolRegistry, connectorName);
162167
+ }
162168
+ });
162169
+ registerConnectorExecuteTool(this.toolRegistry, {
162170
+ getConnectorBridge: () => this.connectorBridge
162171
+ });
162163
162172
  registerConnectorAutoRefreshTool(this.toolRegistry);
162164
162173
  registerConfigTools(this.toolRegistry, {
162165
162174
  cwd: this.cwd
@@ -162293,6 +162302,48 @@ class AssistantLoop {
162293
162302
  if (this.extraSystemPrompt) {
162294
162303
  this.context.addSystemMessage(this.extraSystemPrompt);
162295
162304
  }
162305
+ if (this.config.heartbeat?.autonomous) {
162306
+ const maxSleepMin = Math.round((this.config.heartbeat.maxSleepMs ?? 1800000) / 60000);
162307
+ this.context.addSystemMessage(`## Autonomous Heartbeat System
162308
+
162309
+ You are running in **autonomous mode**. You manage your own wakeup schedule.
162310
+
162311
+ ### How it works
162312
+ - After every turn, a safety-net hook ensures a heartbeat schedule exists
162313
+ - The heartbeat fires \`/main-loop\` which runs your autonomous check-in skill
162314
+ - A watchdog monitors your health and forces a wakeup if you're overdue
162315
+
162316
+ ### Your responsibilities at the END of every turn
162317
+ 1. **Save state** to memory before the turn ends:
162318
+ - \`memory_save agent.heartbeat.intention "what you plan to do next"\`
162319
+ - \`memory_save agent.state.pending "items waiting for follow-up"\`
162320
+ - \`memory_save agent.state.lastActions "what you just did"\`
162321
+ 2. **Schedule your next heartbeat**:
162322
+ - Delete old: \`schedule_delete heartbeat-${this.sessionId}\`
162323
+ - Create new: \`schedule_create\` with \`kind: "once"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and \`at\` set to your chosen time
162324
+ 3. **Save goals** when they change: \`memory_save agent.goals "..."\`
162325
+
162326
+ ### Timing guidelines
162327
+ | Situation | Wake up in |
162328
+ |-----------|-----------|
162329
+ | Active jobs running or tasks pending | 1\u20133 minutes |
162330
+ | Goals exist but nothing urgent | 5\u201315 minutes |
162331
+ | Nothing pending, user idle | 15\u2013${maxSleepMin} minutes (max) |
162332
+
162333
+ ### Key memory keys
162334
+ - \`agent.heartbeat.last\` \u2014 when you last ran (save ISO timestamp)
162335
+ - \`agent.heartbeat.next\` \u2014 when you plan to run next
162336
+ - \`agent.heartbeat.intention\` \u2014 why you're waking up
162337
+ - \`agent.goals\` \u2014 your active goals
162338
+ - \`agent.state.pending\` \u2014 items waiting
162339
+ - \`agent.state.lastActions\` \u2014 what you did recently
162340
+
162341
+ ### Rules
162342
+ - **Stay fast** \u2014 if work takes >30s, delegate to a subassistant
162343
+ - **Never sleep longer than ${maxSleepMin} minutes** \u2014 the system enforces this cap
162344
+ - **Always schedule your next heartbeat** \u2014 if you forget, the safety net creates a default one
162345
+ `);
162346
+ }
162296
162347
  this.contextManager?.refreshState(this.context.getMessages());
162297
162348
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
162298
162349
  session_id: this.sessionId,
@@ -162563,6 +162614,17 @@ class AssistantLoop {
162563
162614
  hook_event_name: "Stop",
162564
162615
  cwd: this.cwd
162565
162616
  });
162617
+ try {
162618
+ await nativeHookRegistry.execute("Stop", {
162619
+ session_id: this.sessionId,
162620
+ hook_event_name: "Stop",
162621
+ cwd: this.cwd
162622
+ }, {
162623
+ sessionId: this.sessionId,
162624
+ cwd: this.cwd,
162625
+ messages: this.context.getMessages()
162626
+ });
162627
+ } catch {}
162566
162628
  const shouldSkipVerification = this.shouldStop || streamError !== null;
162567
162629
  if (shouldSkipVerification) {
162568
162630
  this.scopeContextManager.clear();
package/dist/lib.js CHANGED
@@ -113513,6 +113513,162 @@ Use job_status or job_result to check progress.`;
113513
113513
  }
113514
113514
  }
113515
113515
  }
113516
+ var connectorExecuteTool = {
113517
+ name: "connector_execute",
113518
+ description: "Execute a command on any discovered connector. Use connectors_list or connectors_search first to discover available connectors and their commands.",
113519
+ parameters: {
113520
+ type: "object",
113521
+ properties: {
113522
+ connector: {
113523
+ type: "string",
113524
+ description: 'Name of the connector to use (e.g., "notion", "gmail", "googledrive")'
113525
+ },
113526
+ command: {
113527
+ type: "string",
113528
+ description: "The command to run on the connector"
113529
+ },
113530
+ args: {
113531
+ type: "array",
113532
+ description: "Arguments to pass to the command",
113533
+ items: { type: "string", description: "Argument value" }
113534
+ },
113535
+ options: {
113536
+ type: "object",
113537
+ description: "Options to pass to the command (key-value pairs)"
113538
+ }
113539
+ },
113540
+ required: ["connector", "command"]
113541
+ }
113542
+ };
113543
+ function createConnectorExecuteExecutor(context) {
113544
+ return async (input) => {
113545
+ const bridge = context.getConnectorBridge();
113546
+ if (!bridge) {
113547
+ return JSON.stringify({
113548
+ error: "Connector system not available",
113549
+ suggestion: "Ensure connectors are configured and discovered"
113550
+ });
113551
+ }
113552
+ const connectorName = input.connector;
113553
+ if (!connectorName) {
113554
+ return JSON.stringify({
113555
+ error: "Missing required parameter: connector",
113556
+ suggestion: "Use connectors_list to find available connectors"
113557
+ });
113558
+ }
113559
+ const connector = bridge.getConnector(connectorName);
113560
+ if (!connector) {
113561
+ const available = bridge.getConnectors().map((c) => c.name);
113562
+ return JSON.stringify({
113563
+ error: `Connector '${connectorName}' not found`,
113564
+ available: available.slice(0, 10),
113565
+ totalAvailable: available.length,
113566
+ suggestion: "Use connectors_list to see all available connectors"
113567
+ });
113568
+ }
113569
+ const executor = bridge["createExecutor"](connector);
113570
+ return executor({
113571
+ command: input.command,
113572
+ args: input.args,
113573
+ options: input.options,
113574
+ cwd: input.cwd
113575
+ });
113576
+ };
113577
+ }
113578
+ function registerConnectorExecuteTool(registry, context) {
113579
+ const executor = createConnectorExecuteExecutor(context);
113580
+ registry.register(connectorExecuteTool, executor);
113581
+ }
113582
+ var connectorsSearchTool = {
113583
+ name: "connectors_search",
113584
+ description: "Search for connectors by name, description, or command. Use this to find the right connector for a task when many are available.",
113585
+ parameters: {
113586
+ type: "object",
113587
+ properties: {
113588
+ query: {
113589
+ type: "string",
113590
+ description: "Search query to match against connector names, descriptions, and commands"
113591
+ },
113592
+ limit: {
113593
+ type: "number",
113594
+ description: "Maximum number of results to return (default: 5, max: 20)"
113595
+ }
113596
+ },
113597
+ required: ["query"]
113598
+ }
113599
+ };
113600
+ function createConnectorsSearchExecutor(context) {
113601
+ return async (input) => {
113602
+ const bridge = context.getConnectorBridge();
113603
+ if (!bridge) {
113604
+ return JSON.stringify({
113605
+ error: "Connector discovery not available",
113606
+ results: []
113607
+ });
113608
+ }
113609
+ const query = (input.query || "").toLowerCase();
113610
+ const limit = Math.min(Math.max(1, Number(input.limit) || 5), 20);
113611
+ if (!query) {
113612
+ return JSON.stringify({
113613
+ error: "Search query is required",
113614
+ suggestion: 'Provide a query like "email", "storage", or "calendar"'
113615
+ });
113616
+ }
113617
+ const connectors = bridge.getConnectors();
113618
+ const results = [];
113619
+ for (const connector of connectors) {
113620
+ let score = 0;
113621
+ const matchedCommands = [];
113622
+ if (connector.name.toLowerCase().includes(query)) {
113623
+ score += 10;
113624
+ if (connector.name.toLowerCase() === query) {
113625
+ score += 5;
113626
+ }
113627
+ }
113628
+ if (connector.description.toLowerCase().includes(query)) {
113629
+ score += 5;
113630
+ }
113631
+ for (const cmd of connector.commands) {
113632
+ if (cmd.name.toLowerCase().includes(query)) {
113633
+ score += 2;
113634
+ matchedCommands.push(cmd.name);
113635
+ }
113636
+ if (cmd.description.toLowerCase().includes(query)) {
113637
+ score += 1;
113638
+ if (!matchedCommands.includes(cmd.name)) {
113639
+ matchedCommands.push(cmd.name);
113640
+ }
113641
+ }
113642
+ }
113643
+ if (score > 0) {
113644
+ results.push({
113645
+ name: connector.name,
113646
+ description: connector.description,
113647
+ matchedCommands: matchedCommands.slice(0, 5),
113648
+ score
113649
+ });
113650
+ }
113651
+ }
113652
+ results.sort((a, b) => b.score - a.score);
113653
+ const topResults = results.slice(0, limit);
113654
+ if (context.onConnectorSelected && topResults.length > 0) {
113655
+ for (const result of topResults) {
113656
+ context.onConnectorSelected(result.name);
113657
+ }
113658
+ }
113659
+ return JSON.stringify({
113660
+ query,
113661
+ count: topResults.length,
113662
+ totalMatches: results.length,
113663
+ results: topResults.map(({ score, ...rest }) => rest),
113664
+ suggestion: topResults.length > 0 ? `Use connector_execute with connector="${topResults[0].name}" to run commands` : "Try a different search query or use connectors_list to see all available connectors"
113665
+ }, null, 2);
113666
+ };
113667
+ }
113668
+ function registerConnectorsSearchTool(registry, context) {
113669
+ const executor = createConnectorsSearchExecutor(context);
113670
+ registry.register(connectorsSearchTool, executor);
113671
+ }
113516
113672
  var connectorsListTool = {
113517
113673
  name: "connectors_list",
113518
113674
  description: "List all discovered connectors and their available commands. Use this to discover what connectors are available and what operations they support.",
@@ -155185,6 +155341,15 @@ class AssistantLoop {
155185
155341
  registerConnectorsListTool(this.toolRegistry, {
155186
155342
  getConnectorBridge: () => this.connectorBridge
155187
155343
  });
155344
+ registerConnectorsSearchTool(this.toolRegistry, {
155345
+ getConnectorBridge: () => this.connectorBridge,
155346
+ onConnectorSelected: (connectorName) => {
155347
+ this.connectorBridge.registerConnector(this.toolRegistry, connectorName);
155348
+ }
155349
+ });
155350
+ registerConnectorExecuteTool(this.toolRegistry, {
155351
+ getConnectorBridge: () => this.connectorBridge
155352
+ });
155188
155353
  registerConnectorAutoRefreshTool(this.toolRegistry);
155189
155354
  registerConfigTools(this.toolRegistry, {
155190
155355
  cwd: this.cwd
@@ -155318,6 +155483,48 @@ class AssistantLoop {
155318
155483
  if (this.extraSystemPrompt) {
155319
155484
  this.context.addSystemMessage(this.extraSystemPrompt);
155320
155485
  }
155486
+ if (this.config.heartbeat?.autonomous) {
155487
+ const maxSleepMin = Math.round((this.config.heartbeat.maxSleepMs ?? 1800000) / 60000);
155488
+ this.context.addSystemMessage(`## Autonomous Heartbeat System
155489
+
155490
+ You are running in **autonomous mode**. You manage your own wakeup schedule.
155491
+
155492
+ ### How it works
155493
+ - After every turn, a safety-net hook ensures a heartbeat schedule exists
155494
+ - The heartbeat fires \`/main-loop\` which runs your autonomous check-in skill
155495
+ - A watchdog monitors your health and forces a wakeup if you're overdue
155496
+
155497
+ ### Your responsibilities at the END of every turn
155498
+ 1. **Save state** to memory before the turn ends:
155499
+ - \`memory_save agent.heartbeat.intention "what you plan to do next"\`
155500
+ - \`memory_save agent.state.pending "items waiting for follow-up"\`
155501
+ - \`memory_save agent.state.lastActions "what you just did"\`
155502
+ 2. **Schedule your next heartbeat**:
155503
+ - Delete old: \`schedule_delete heartbeat-${this.sessionId}\`
155504
+ - Create new: \`schedule_create\` with \`kind: "once"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and \`at\` set to your chosen time
155505
+ 3. **Save goals** when they change: \`memory_save agent.goals "..."\`
155506
+
155507
+ ### Timing guidelines
155508
+ | Situation | Wake up in |
155509
+ |-----------|-----------|
155510
+ | Active jobs running or tasks pending | 1\u20133 minutes |
155511
+ | Goals exist but nothing urgent | 5\u201315 minutes |
155512
+ | Nothing pending, user idle | 15\u2013${maxSleepMin} minutes (max) |
155513
+
155514
+ ### Key memory keys
155515
+ - \`agent.heartbeat.last\` \u2014 when you last ran (save ISO timestamp)
155516
+ - \`agent.heartbeat.next\` \u2014 when you plan to run next
155517
+ - \`agent.heartbeat.intention\` \u2014 why you're waking up
155518
+ - \`agent.goals\` \u2014 your active goals
155519
+ - \`agent.state.pending\` \u2014 items waiting
155520
+ - \`agent.state.lastActions\` \u2014 what you did recently
155521
+
155522
+ ### Rules
155523
+ - **Stay fast** \u2014 if work takes >30s, delegate to a subassistant
155524
+ - **Never sleep longer than ${maxSleepMin} minutes** \u2014 the system enforces this cap
155525
+ - **Always schedule your next heartbeat** \u2014 if you forget, the safety net creates a default one
155526
+ `);
155527
+ }
155321
155528
  this.contextManager?.refreshState(this.context.getMessages());
155322
155529
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
155323
155530
  session_id: this.sessionId,
@@ -155588,6 +155795,17 @@ class AssistantLoop {
155588
155795
  hook_event_name: "Stop",
155589
155796
  cwd: this.cwd
155590
155797
  });
155798
+ try {
155799
+ await nativeHookRegistry.execute("Stop", {
155800
+ session_id: this.sessionId,
155801
+ hook_event_name: "Stop",
155802
+ cwd: this.cwd
155803
+ }, {
155804
+ sessionId: this.sessionId,
155805
+ cwd: this.cwd,
155806
+ messages: this.context.getMessages()
155807
+ });
155808
+ } catch {}
155591
155809
  const shouldSkipVerification = this.shouldStop || streamError !== null;
155592
155810
  if (shouldSkipVerification) {
155593
155811
  this.scopeContextManager.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/assistants",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "AI assistant that runs in your terminal - powered by Claude",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "prepublishOnly": "pnpm run clean && pnpm run build"
54
54
  },
55
55
  "dependencies": {
56
- "@hasna/assistants": "^1.1.2",
56
+ "@hasna/assistants": "1.1.3",
57
57
  "chalk": "^5.3.0",
58
58
  "ink": "^6.0.0",
59
59
  "ink-scroll-view": "^0.3.5",