@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.
- package/dist/cli.js +62 -0
- package/dist/lib.js +218 -0
- 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
|
+
"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": "
|
|
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",
|