@gajae-code/coding-agent 0.8.0 → 0.8.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/CHANGELOG.md +14 -0
- package/dist/types/notifications/telegram-daemon.d.ts +3 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/package.json +7 -7
- package/src/coordinator-mcp/server.ts +33 -4
- package/src/extensibility/skills.ts +30 -2
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +7 -8
- package/src/modes/prompt-action-autocomplete.ts +17 -7
- package/src/notifications/telegram-daemon.ts +14 -0
- package/src/tools/skill.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.8.1] - 2026-07-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Skills can now be invoked inline within a prompt (for example, mid-message `/skill:*` references) instead of only as a standalone prompt, with matching autocomplete, input-controller queueing, and ACP dispatch support.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The Telegram notification daemon now tombstones a session endpoint generation after `session_closed`, preventing the scan loop from reconnecting to the still-live old endpoint and recreating an empty topic immediately after deleting the original topic.
|
|
14
|
+
- `/contribute-pr` in the interactive TUI now prepares the redacted manifest and worker prompt without spawning a second GJC process on the same terminal, avoiding competing TUI renderers that make the chat viewport jump around. Run the generated worker prompt from a separate terminal instead.
|
|
15
|
+
- The `skill` tool is now a default-registered essential tool instead of a discoverable one, so skill chaining is always available without first activating it through tool discovery.
|
|
16
|
+
- The coordinator MCP server now classifies vanished-but-acknowledged tmux turns correctly, avoiding spurious delivery failures when a delegated turn's pane disappears after its prompt was already acknowledged.
|
|
17
|
+
- Tool-call argument strings are now sanitized of invalid Unicode surrogate sequences across providers (Anthropic, Bedrock, Google, OpenAI chat/completions/responses, and Codex), including custom Responses tool input, preventing malformed tool calls from breaking requests.
|
|
18
|
+
|
|
5
19
|
## [0.8.0] - 2026-07-04
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -233,6 +233,7 @@ export interface TelegramDaemonOptions {
|
|
|
233
233
|
interface SessionSocket {
|
|
234
234
|
sessionId: string;
|
|
235
235
|
token: string;
|
|
236
|
+
endpointKey: string;
|
|
236
237
|
ws: WebSocket;
|
|
237
238
|
pending: Map<string, {
|
|
238
239
|
sessionId: string;
|
|
@@ -267,6 +268,8 @@ export declare class TelegramNotificationDaemon {
|
|
|
267
268
|
private readonly topicOwnerByIdentity;
|
|
268
269
|
/** Non-identity frames held until identity creates the correct thread. */
|
|
269
270
|
private readonly pendingThreadedFrames;
|
|
271
|
+
/** Endpoint generation tombstones for sessions that already sent session_closed. */
|
|
272
|
+
private readonly closedEndpointKeys;
|
|
270
273
|
/** True once the daemon has nudged the user to enable Threaded Mode. */
|
|
271
274
|
private threadedFallbackNoticeSent;
|
|
272
275
|
/** Sessions whose identity header was already sent flat (Threaded Mode off). */
|
|
@@ -33,7 +33,7 @@ export declare class SkillTool implements AgentTool<typeof skillSchema, SkillToo
|
|
|
33
33
|
readonly name = "skill";
|
|
34
34
|
readonly label = "Skill";
|
|
35
35
|
readonly summary = "Chain into another available skill in the current turn";
|
|
36
|
-
readonly loadMode = "
|
|
36
|
+
readonly loadMode = "essential";
|
|
37
37
|
readonly description: string;
|
|
38
38
|
readonly parameters: z.ZodObject<{
|
|
39
39
|
name: z.ZodString;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
53
53
|
"@babel/parser": "^7.29.3",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@gajae-code/stats": "0.8.
|
|
56
|
-
"@gajae-code/agent-core": "0.8.
|
|
57
|
-
"@gajae-code/ai": "0.8.
|
|
58
|
-
"@gajae-code/natives": "0.8.
|
|
59
|
-
"@gajae-code/tui": "0.8.
|
|
60
|
-
"@gajae-code/utils": "0.8.
|
|
55
|
+
"@gajae-code/stats": "0.8.1",
|
|
56
|
+
"@gajae-code/agent-core": "0.8.1",
|
|
57
|
+
"@gajae-code/ai": "0.8.1",
|
|
58
|
+
"@gajae-code/natives": "0.8.1",
|
|
59
|
+
"@gajae-code/tui": "0.8.1",
|
|
60
|
+
"@gajae-code/utils": "0.8.1",
|
|
61
61
|
"@puppeteer/browsers": "^2.13.0",
|
|
62
62
|
"@types/turndown": "5.0.6",
|
|
63
63
|
"@xterm/headless": "^6.0.0",
|
|
@@ -966,24 +966,53 @@ function hasTmuxIdentity(session: Record<string, unknown>): boolean {
|
|
|
966
966
|
);
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
function unavailableSessionReason(turn: TurnRecord, reason: string): string {
|
|
970
|
+
if (
|
|
971
|
+
reason === "tmux_session_missing" &&
|
|
972
|
+
turn.delivery.tmux_keys_sent === true &&
|
|
973
|
+
turn.delivery.prompt_acknowledged === true
|
|
974
|
+
) {
|
|
975
|
+
return "tmux_session_missing_after_prompt_acknowledgement";
|
|
976
|
+
}
|
|
977
|
+
return reason;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function unavailableSessionEvidence(turn: TurnRecord, reason: string, timestamp: string): Record<string, unknown>[] {
|
|
981
|
+
if (reason !== "tmux_session_missing_after_prompt_acknowledgement") return turn.evidence;
|
|
982
|
+
return [
|
|
983
|
+
...turn.evidence,
|
|
984
|
+
{
|
|
985
|
+
type: reason,
|
|
986
|
+
message:
|
|
987
|
+
"The tmux session disappeared after GJC runtime acknowledged the prompt, before any terminal final_response or error was recorded. Treat this as an in-flight vanished session and inspect/restart with recovery evidence rather than resubmitting blindly.",
|
|
988
|
+
tmux_keys_sent: true,
|
|
989
|
+
prompt_acknowledged: true,
|
|
990
|
+
prior_status: turn.status,
|
|
991
|
+
created_at: timestamp,
|
|
992
|
+
},
|
|
993
|
+
];
|
|
994
|
+
}
|
|
995
|
+
|
|
969
996
|
async function markTurnFailedForUnavailableSession(
|
|
970
997
|
namespaceDir: string,
|
|
971
998
|
turn: TurnRecord,
|
|
972
999
|
reason: string,
|
|
973
1000
|
): Promise<TurnRecord> {
|
|
974
1001
|
const timestamp = new Date().toISOString();
|
|
1002
|
+
const durableReason = unavailableSessionReason(turn, reason);
|
|
975
1003
|
const failed: TurnRecord = {
|
|
976
1004
|
...turn,
|
|
977
1005
|
status: "failed",
|
|
978
1006
|
final_response: {
|
|
979
|
-
text: `Coordinator session unavailable: ${
|
|
1007
|
+
text: `Coordinator session unavailable: ${durableReason}`,
|
|
980
1008
|
format: "markdown",
|
|
981
1009
|
source: "coordinator_liveness",
|
|
982
1010
|
artifact_path: null,
|
|
983
1011
|
truncated: false,
|
|
984
1012
|
},
|
|
985
|
-
|
|
986
|
-
|
|
1013
|
+
evidence: unavailableSessionEvidence(turn, durableReason, timestamp),
|
|
1014
|
+
error: { code: "session_unavailable", message: durableReason, recoverable: true },
|
|
1015
|
+
liveness: { checked_at: timestamp, live: false, reason: durableReason },
|
|
987
1016
|
updated_at: timestamp,
|
|
988
1017
|
completed_at: timestamp,
|
|
989
1018
|
};
|
|
@@ -992,7 +1021,7 @@ async function markTurnFailedForUnavailableSession(
|
|
|
992
1021
|
await writeSessionState(namespaceDir, failed.session_id, "stale", {
|
|
993
1022
|
lastTurnId: failed.turn_id,
|
|
994
1023
|
live: false,
|
|
995
|
-
reason,
|
|
1024
|
+
reason: durableReason,
|
|
996
1025
|
});
|
|
997
1026
|
return failed;
|
|
998
1027
|
}
|
|
@@ -326,12 +326,28 @@ interface SkillTokenMatch {
|
|
|
326
326
|
skill: Skill;
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
function buildInlineSkillInvocationArgs(text: string, matches: readonly SkillTokenMatch[]): string {
|
|
330
|
+
const pieces: string[] = [];
|
|
331
|
+
let cursor = 0;
|
|
332
|
+
for (const match of matches) {
|
|
333
|
+
pieces.push(text.slice(cursor, match.index));
|
|
334
|
+
cursor = match.end;
|
|
335
|
+
}
|
|
336
|
+
pieces.push(text.slice(cursor));
|
|
337
|
+
return pieces
|
|
338
|
+
.join("")
|
|
339
|
+
.replace(/[ \t]{2,}/g, " ")
|
|
340
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
341
|
+
.replace(/\n[ \t]+/g, "\n")
|
|
342
|
+
.trim();
|
|
343
|
+
}
|
|
344
|
+
|
|
329
345
|
export function parseSkillInvocations(
|
|
330
346
|
text: string,
|
|
331
347
|
skillsByCommandName: ReadonlyMap<string, Skill>,
|
|
332
348
|
): ParsedSkillInvocation[] {
|
|
333
349
|
const trimmedText = text.trim();
|
|
334
|
-
if (!trimmedText
|
|
350
|
+
if (!trimmedText) return [];
|
|
335
351
|
const canonicalSkillCommandPattern = /(^|\s)\/(skill:[^\s]+)/g;
|
|
336
352
|
const matches: SkillTokenMatch[] = [];
|
|
337
353
|
for (const match of trimmedText.matchAll(canonicalSkillCommandPattern)) {
|
|
@@ -348,7 +364,19 @@ export function parseSkillInvocations(
|
|
|
348
364
|
skill,
|
|
349
365
|
});
|
|
350
366
|
}
|
|
351
|
-
if (matches.length === 0
|
|
367
|
+
if (matches.length === 0) return [];
|
|
368
|
+
if (matches[0]?.index !== 0) {
|
|
369
|
+
// Preserve leading slash-command semantics: `/skill:unknown /skill:alpha`
|
|
370
|
+
// should remain plain text rather than silently skipping the unknown
|
|
371
|
+
// leading command and invoking the later known one.
|
|
372
|
+
if (trimmedText.startsWith("/")) return [];
|
|
373
|
+
const args = buildInlineSkillInvocationArgs(trimmedText, matches);
|
|
374
|
+
return matches.map(match => ({
|
|
375
|
+
commandName: match.commandName,
|
|
376
|
+
args,
|
|
377
|
+
skill: match.skill,
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
352
380
|
return matches.map((match, index) => {
|
|
353
381
|
const next = matches[index + 1];
|
|
354
382
|
return {
|
|
@@ -710,9 +710,6 @@ export class AcpAgent implements Agent {
|
|
|
710
710
|
text: string,
|
|
711
711
|
options: { directAliasMayCollide?: boolean } = {},
|
|
712
712
|
): Promise<boolean> {
|
|
713
|
-
if (!text.startsWith("/")) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
713
|
if (!record.session.skillsSettings?.enableSkillCommands) {
|
|
717
714
|
return false;
|
|
718
715
|
}
|
|
@@ -758,9 +755,13 @@ export class AcpAgent implements Agent {
|
|
|
758
755
|
}
|
|
759
756
|
return true;
|
|
760
757
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
758
|
+
const slashText = text.trimStart();
|
|
759
|
+
if (!slashText.startsWith("/")) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
const spaceIndex = slashText.indexOf(" ");
|
|
763
|
+
const commandName = spaceIndex === -1 ? slashText.slice(1) : slashText.slice(1, spaceIndex);
|
|
764
|
+
const args = spaceIndex === -1 ? "" : slashText.slice(spaceIndex + 1).trim();
|
|
764
765
|
if (
|
|
765
766
|
!isNamespacedSkillSlashCommandName(commandName) &&
|
|
766
767
|
options.directAliasMayCollide !== true &&
|
|
@@ -1276,17 +1276,18 @@ export class CommandController {
|
|
|
1276
1276
|
async handleContributionPrepCommand(customInstructions?: string): Promise<void> {
|
|
1277
1277
|
this.ctx.editor.setText("");
|
|
1278
1278
|
try {
|
|
1279
|
-
const result = await this.ctx.session.prepareContributionPrep({ customInstructions, spawnWorker:
|
|
1279
|
+
const result = await this.ctx.session.prepareContributionPrep({ customInstructions, spawnWorker: false });
|
|
1280
1280
|
this.ctx.showStatus(
|
|
1281
1281
|
[
|
|
1282
1282
|
"Contribution prep artifacts written.",
|
|
1283
1283
|
`Manifest: ${result.manifestPath}`,
|
|
1284
1284
|
`Worker prompt: ${result.workerPromptPath}`,
|
|
1285
|
+
"Open the worker prompt from a separate terminal to keep this TUI session stable.",
|
|
1285
1286
|
].join("\n"),
|
|
1286
1287
|
);
|
|
1287
1288
|
this.ctx.chatContainer.addChild(
|
|
1288
1289
|
new Text(
|
|
1289
|
-
`${theme.fg("accent", `${theme.status.success} Contribution prep ready`)}\
|
|
1290
|
+
`${theme.fg("accent", `${theme.status.success} Contribution prep ready`)}\nWorker prompt: ${result.workerPromptPath}`,
|
|
1290
1291
|
1,
|
|
1291
1292
|
1,
|
|
1292
1293
|
),
|
|
@@ -644,19 +644,18 @@ export class InputController {
|
|
|
644
644
|
|
|
645
645
|
/**
|
|
646
646
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
647
|
-
* using the supplied `streamingBehavior`. Returns true if the text
|
|
648
|
-
* recognised skill command chain and was dispatched. A
|
|
649
|
-
* file is surfaced via `showError` but still returns
|
|
650
|
-
* already cleared on the success path, so falling
|
|
651
|
-
* handling at that point would double-submit. Returns
|
|
652
|
-
*
|
|
653
|
-
*
|
|
647
|
+
* using the supplied `streamingBehavior`. Returns true if the text contains a
|
|
648
|
+
* recognised canonical skill command or command chain and was dispatched. A
|
|
649
|
+
* failure to load a skill file is surfaced via `showError` but still returns
|
|
650
|
+
* true — the editor was already cleared on the success path, so falling
|
|
651
|
+
* through to plain-text handling at that point would double-submit. Returns
|
|
652
|
+
* false when the text has no registered canonical skill invocation, so the
|
|
653
|
+
* caller can fall through to plain-text handling (this branch
|
|
654
654
|
* leaves the editor state untouched). `streamingBehavior` is only consulted
|
|
655
655
|
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
656
656
|
* ignores it.
|
|
657
657
|
*/
|
|
658
658
|
async #invokeSkillCommand(text: string, streamingBehavior: "steer" | "followUp"): Promise<boolean> {
|
|
659
|
-
if (!text.startsWith("/")) return false;
|
|
660
659
|
const invocations = parseSkillInvocations(text, this.ctx.skillCommands ?? new Map());
|
|
661
660
|
if (invocations.length === 0) return false;
|
|
662
661
|
this.ctx.editor.addToHistory(text);
|
|
@@ -177,6 +177,9 @@ function getSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
|
177
177
|
return token;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
function isLeadingSlashToken(textBeforeCursor: string, slashPrefix: string): boolean {
|
|
181
|
+
return textBeforeCursor.trimStart() === slashPrefix;
|
|
182
|
+
}
|
|
180
183
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
181
184
|
#baseProvider: CombinedAutocompleteProvider;
|
|
182
185
|
#actions: PromptActionDefinition[];
|
|
@@ -223,10 +226,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
223
226
|
|
|
224
227
|
const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
|
|
225
228
|
if (slashPrefix) {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const isLeading = isLeadingSlashToken(textBeforeCursor, slashPrefix);
|
|
230
|
+
const baseSuggestions = isLeading
|
|
231
|
+
? withoutSkillCommandSuggestions(await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol))
|
|
232
|
+
: null;
|
|
233
|
+
const skillCommandSuggestions =
|
|
234
|
+
isLeading || slashPrefix.startsWith("/skill")
|
|
235
|
+
? this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: isLeading })
|
|
236
|
+
: null;
|
|
230
237
|
return sortSlashCommandSuggestions(
|
|
231
238
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
232
239
|
this.#commands,
|
|
@@ -300,7 +307,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
300
307
|
const baseSuggestions = withoutSkillCommandSuggestions(
|
|
301
308
|
this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null,
|
|
302
309
|
);
|
|
303
|
-
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
|
|
310
|
+
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: false });
|
|
304
311
|
return sortSlashCommandSuggestions(
|
|
305
312
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
306
313
|
this.#commands,
|
|
@@ -311,11 +318,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
311
318
|
return tryEmojiInlineReplace(textBeforeCursor);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
#getSkillCommandSuggestions(
|
|
321
|
+
#getSkillCommandSuggestions(
|
|
322
|
+
textBeforeCursor: string,
|
|
323
|
+
options: { includeEmpty: boolean },
|
|
324
|
+
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
315
325
|
const prefix = getSlashTokenPrefix(textBeforeCursor);
|
|
316
326
|
if (!prefix) return null;
|
|
317
327
|
const query = prefix.slice(1).toLowerCase();
|
|
318
|
-
if (query.length === 0) return null;
|
|
328
|
+
if (query.length === 0 && !options.includeEmpty) return null;
|
|
319
329
|
const normalizedQuery = query.startsWith("skill-") ? `skill:${query.slice("skill-".length)}` : query;
|
|
320
330
|
const exactNonSkillCommand = this.#commands.some(
|
|
321
331
|
command => command.name === query && !command.name.startsWith("skill:"),
|
|
@@ -142,6 +142,9 @@ function splitTelegramPlainText(text: string, max = TELEGRAM_MESSAGE_LIMIT): str
|
|
|
142
142
|
if (out) chunks.push(out);
|
|
143
143
|
return chunks;
|
|
144
144
|
}
|
|
145
|
+
function endpointGenerationKey(url: string, token: string): string {
|
|
146
|
+
return `${url}\0${token}`;
|
|
147
|
+
}
|
|
145
148
|
|
|
146
149
|
/**
|
|
147
150
|
* Whether `err` is a transient network failure worth retrying. Telegram API
|
|
@@ -772,6 +775,7 @@ export interface TelegramDaemonOptions {
|
|
|
772
775
|
interface SessionSocket {
|
|
773
776
|
sessionId: string;
|
|
774
777
|
token: string;
|
|
778
|
+
endpointKey: string;
|
|
775
779
|
ws: WebSocket;
|
|
776
780
|
pending: Map<string, { sessionId: string; actionId: string }>;
|
|
777
781
|
/** True once the server advertised the `client_ping_pong` capability. */
|
|
@@ -808,6 +812,8 @@ export class TelegramNotificationDaemon {
|
|
|
808
812
|
private readonly topicOwnerByIdentity = new Map<string, string>();
|
|
809
813
|
/** Non-identity frames held until identity creates the correct thread. */
|
|
810
814
|
private readonly pendingThreadedFrames = new Map<string, PendingThreadedFrame[]>();
|
|
815
|
+
/** Endpoint generation tombstones for sessions that already sent session_closed. */
|
|
816
|
+
private readonly closedEndpointKeys = new Map<string, string>();
|
|
811
817
|
/** True once the daemon has nudged the user to enable Threaded Mode. */
|
|
812
818
|
private threadedFallbackNoticeSent = false;
|
|
813
819
|
/** Sessions whose identity header was already sent flat (Threaded Mode off). */
|
|
@@ -1223,7 +1229,9 @@ export class TelegramNotificationDaemon {
|
|
|
1223
1229
|
matches: msg => msg.type === "session_closed",
|
|
1224
1230
|
handle: async session => {
|
|
1225
1231
|
this.busy.delete(session.sessionId);
|
|
1232
|
+
this.closedEndpointKeys.set(session.sessionId, session.endpointKey);
|
|
1226
1233
|
await this.deleteTopic(session.sessionId);
|
|
1234
|
+
this.dropSession(session, "session_closed");
|
|
1227
1235
|
},
|
|
1228
1236
|
});
|
|
1229
1237
|
}
|
|
@@ -1260,6 +1268,9 @@ export class TelegramNotificationDaemon {
|
|
|
1260
1268
|
// would chase a dead, token-bearing record forever.
|
|
1261
1269
|
const pidAlive = this.opts.pidAlive ?? defaultPidAlive;
|
|
1262
1270
|
if (endpoint.stale || (endpoint.pid !== undefined && !pidAlive(endpoint.pid))) continue;
|
|
1271
|
+
const endpointKey = endpointGenerationKey(endpoint.url, endpoint.token);
|
|
1272
|
+
if (this.closedEndpointKeys.get(sessionId) === endpointKey) continue;
|
|
1273
|
+
this.closedEndpointKeys.delete(sessionId);
|
|
1263
1274
|
this.connectSession(sessionId, endpoint.url, endpoint.token);
|
|
1264
1275
|
} catch {}
|
|
1265
1276
|
}
|
|
@@ -1269,9 +1280,12 @@ export class TelegramNotificationDaemon {
|
|
|
1269
1280
|
connectSession(sessionId: string, url: string, token: string): void {
|
|
1270
1281
|
const WS = this.opts.WebSocketImpl ?? WebSocket;
|
|
1271
1282
|
const ws = new WS(`${url}/?token=${encodeURIComponent(token)}`);
|
|
1283
|
+
const endpointKey = endpointGenerationKey(url, token);
|
|
1284
|
+
this.closedEndpointKeys.delete(sessionId);
|
|
1272
1285
|
const session: SessionSocket = {
|
|
1273
1286
|
sessionId,
|
|
1274
1287
|
token,
|
|
1288
|
+
endpointKey,
|
|
1275
1289
|
ws,
|
|
1276
1290
|
pending: new Map(),
|
|
1277
1291
|
capable: false,
|
package/src/tools/skill.ts
CHANGED
|
@@ -50,7 +50,7 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
50
50
|
readonly name = "skill";
|
|
51
51
|
readonly label = "Skill";
|
|
52
52
|
readonly summary = "Chain into another available skill in the current turn";
|
|
53
|
-
readonly loadMode = "
|
|
53
|
+
readonly loadMode = "essential";
|
|
54
54
|
readonly description: string;
|
|
55
55
|
readonly parameters = skillSchema;
|
|
56
56
|
readonly strict = true;
|