@sayknow-cli/coding-agent 0.3.8 → 0.3.10
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 +13 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/model-profile-activation.d.ts +7 -7
- package/dist/types/config/model-registry.d.ts +11 -3
- package/dist/types/config/model-resolver.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +15 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -2
- package/dist/types/modes/components/thinking-selector.d.ts +7 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/notifications/config-commands.d.ts +38 -0
- package/dist/types/notifications/config.d.ts +16 -0
- package/dist/types/notifications/index.d.ts +14 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon.d.ts +22 -0
- package/dist/types/notifications/telegram-reference.d.ts +7 -0
- package/dist/types/notifications/threaded-render.d.ts +10 -0
- package/dist/types/notifications/topic-registry.d.ts +4 -5
- package/dist/types/runtime-credential-selector.d.ts +7 -0
- package/dist/types/sdk.d.ts +7 -1
- package/dist/types/session/agent-session.d.ts +6 -3
- package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/package.json +7 -7
- package/src/cli/args.ts +8 -0
- package/src/cli/notify-cli.ts +38 -10
- package/src/cli.ts +5 -0
- package/src/commands/launch.ts +5 -0
- package/src/commands/notify.ts +2 -2
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +23 -7
- package/src/config/model-registry.ts +107 -11
- package/src/config/model-resolver.ts +5 -16
- package/src/config/settings-schema.ts +11 -2
- package/src/config/settings.ts +24 -1
- package/src/extensibility/runtime-skill-discovery.ts +229 -0
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +26 -3
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/settings-selector.ts +3 -3
- package/src/modes/components/thinking-selector.ts +83 -20
- package/src/modes/controllers/event-controller.ts +11 -2
- package/src/modes/controllers/extension-ui-controller.ts +8 -1
- package/src/modes/controllers/input-controller.ts +62 -26
- package/src/modes/controllers/selector-controller.ts +43 -0
- package/src/modes/interactive-mode.ts +5 -0
- package/src/modes/rpc/rpc-mode.ts +3 -9
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +48 -15
- package/src/notifications/config-commands.ts +90 -0
- package/src/notifications/config.ts +27 -0
- package/src/notifications/index.ts +184 -14
- package/src/notifications/lifecycle-commands.ts +37 -16
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +143 -0
- package/src/notifications/telegram-daemon.ts +431 -91
- package/src/notifications/telegram-reference.ts +17 -0
- package/src/notifications/threaded-render.ts +42 -1
- package/src/notifications/topic-registry.ts +9 -8
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/skill-discovery.md +13 -0
- package/src/runtime-credential-selector.ts +35 -0
- package/src/sdk.ts +104 -13
- package/src/session/agent-session.ts +48 -22
- package/src/skc-runtime/psmux-detect.ts +17 -2
- package/src/skc-runtime/tmux-common.ts +1 -0
- package/src/skc-runtime/ultragoal-runtime.ts +10 -1
- package/src/slash-commands/builtin-registry.ts +78 -1
- package/src/system-prompt.ts +4 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/skill-discovery.ts +73 -0
- package/src/tools/skill.ts +15 -2
- package/src/utils/pasted-image-path.ts +170 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich-message promotion for stable non-editable Telegram text sends.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, the daemon promotes eligible finalized `sendMessage` payloads
|
|
5
|
+
* carrying raw markdown to the Bot API `sendRichMessage` method. On any miss or
|
|
6
|
+
* failure the daemon keeps the unchanged HTML `sendMessage` path, so the
|
|
7
|
+
* off-state request bodies are byte-identical.
|
|
8
|
+
*/
|
|
9
|
+
import type { BotApi } from "./telegram-daemon";
|
|
10
|
+
import type { ThreadedSend } from "./threaded-render";
|
|
11
|
+
/**
|
|
12
|
+
* Telegram's hard per-message character ceiling (4096). Surfaced here purely as
|
|
13
|
+
* documentation and a marker for a future native rich-message splitter — it is
|
|
14
|
+
* intentionally NON-BEHAVIORAL and MUST stay that way: nothing in the rich path
|
|
15
|
+
* branches on this value.
|
|
16
|
+
*
|
|
17
|
+
* Overflow is already safe without it. The production final-answer text is capped
|
|
18
|
+
* at 3500 chars upstream (`summaryFromMessage(..., 3500)`), so a promoted
|
|
19
|
+
* `sendRichMessage` never approaches this ceiling; and if the Bot API ever rejects
|
|
20
|
+
* an oversized rich payload it returns `{ ok: false }`, which
|
|
21
|
+
* `deliverRichWithFallback` (below) turns into the chunked HTML `splitTelegramHtml`
|
|
22
|
+
* fallback (each chunk ≤ TELEGRAM_MESSAGE_LIMIT). This constant only marks where a
|
|
23
|
+
* future rich splitter would read its ceiling; wiring it into a branch would change
|
|
24
|
+
* byte-for-byte behavior and is out of scope.
|
|
25
|
+
*/
|
|
26
|
+
export declare const RICH_MESSAGE_LIMIT = 4096;
|
|
27
|
+
/** Wrap raw markdown in the `sendRichMessage` request payload shape. */
|
|
28
|
+
export declare function buildRichMessage(raw: string, extras?: {
|
|
29
|
+
reply_markup?: unknown;
|
|
30
|
+
}): {
|
|
31
|
+
rich_message: {
|
|
32
|
+
markdown: string;
|
|
33
|
+
};
|
|
34
|
+
reply_markup?: unknown;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether a granted send should be promoted to `sendRichMessage`. Fail-closed
|
|
38
|
+
* and class-aware: every clause must hold, otherwise the daemon keeps the HTML path.
|
|
39
|
+
*/
|
|
40
|
+
export declare function shouldPromoteRich(input: {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
send: ThreadedSend;
|
|
43
|
+
}): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Deliver the promoted rich message, falling back to `fallbackDeliver` (the
|
|
46
|
+
* unchanged HTML `sendMessage` loop) on any failure. A failure is either a
|
|
47
|
+
* thrown transport error or a `{ ok: false }` JSON response (the transport
|
|
48
|
+
* returns `res.json()` for JSON methods, so `ok:false` does not throw). On
|
|
49
|
+
* failure exactly one diagnostic is logged before the fallback runs; on success
|
|
50
|
+
* the fallback never runs.
|
|
51
|
+
*
|
|
52
|
+
* Returns the sent message's `message_id` on success (when the response carries
|
|
53
|
+
* one), otherwise `undefined` — including every failure/fallback path and a
|
|
54
|
+
* success whose response omits `result.message_id`. Callers that ignore the
|
|
55
|
+
* return value are unaffected.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deliverRichWithFallback(botApi: BotApi, base: {
|
|
58
|
+
chat_id: string | number;
|
|
59
|
+
message_thread_id?: number;
|
|
60
|
+
}, send: ThreadedSend, fallbackDeliver: () => Promise<void>, log?: {
|
|
61
|
+
warn(msg: string): void;
|
|
62
|
+
}): Promise<number | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Deliver an action-needed (ask/idle) message via `sendRichMessage`, falling
|
|
65
|
+
* back to the unchanged HTML chunk loop on any failure. Mirrors
|
|
66
|
+
* {@link deliverRichWithFallback} but takes an explicit markdown body plus an
|
|
67
|
+
* optional top-level `reply_markup` (probe-confirmed: `sendRichMessage` accepts
|
|
68
|
+
* `reply_markup` alongside `rich_message`), and surfaces a structured outcome so
|
|
69
|
+
* the daemon can route inbound replies to the resulting message id.
|
|
70
|
+
*
|
|
71
|
+
* On rich success: returns `{ messageId, usedRich: true, usedFallback: false }`
|
|
72
|
+
* where `messageId` is `res.result.message_id` when present. On a `{ ok:false }`
|
|
73
|
+
* response or a thrown transport error: warns exactly once, runs `htmlFallback`,
|
|
74
|
+
* and returns `{ messageId, usedRich: false, usedFallback: true }` where
|
|
75
|
+
* `messageId` is the fallback's return value (the last HTML chunk's id).
|
|
76
|
+
*/
|
|
77
|
+
export declare function deliverRichActionWithFallback(botApi: BotApi, base: {
|
|
78
|
+
chat_id: string | number;
|
|
79
|
+
message_thread_id?: number;
|
|
80
|
+
}, opts: {
|
|
81
|
+
markdown: string;
|
|
82
|
+
replyMarkup?: unknown;
|
|
83
|
+
requireMessageId?: boolean;
|
|
84
|
+
}, htmlFallback: () => Promise<number | undefined>, log?: {
|
|
85
|
+
warn(msg: string): void;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
messageId?: number;
|
|
88
|
+
usedRich: boolean;
|
|
89
|
+
usedFallback: boolean;
|
|
90
|
+
}>;
|
|
@@ -231,6 +231,14 @@ export interface TelegramDaemonOptions {
|
|
|
231
231
|
* default applies (e.g. lifecycle control disabled), no control server starts.
|
|
232
232
|
*/
|
|
233
233
|
createLifecycleControlServer?: LifecycleControlServerFactory | null;
|
|
234
|
+
/** Rich text promotion (enabled by default; see rich-render.ts). */
|
|
235
|
+
rich?: {
|
|
236
|
+
enabled: boolean;
|
|
237
|
+
};
|
|
238
|
+
/** Opt-in rich-draft streaming of live turn previews (off by default; see rich-draft.ts). */
|
|
239
|
+
richDraft?: {
|
|
240
|
+
enabled: boolean;
|
|
241
|
+
};
|
|
234
242
|
}
|
|
235
243
|
interface SessionSocket {
|
|
236
244
|
sessionId: string;
|
|
@@ -268,6 +276,10 @@ export declare class TelegramNotificationDaemon {
|
|
|
268
276
|
private readonly pool;
|
|
269
277
|
private readonly poller;
|
|
270
278
|
private readonly dispatchState;
|
|
279
|
+
/** Original markdown of rich messages we sent (chat+message_id), for restoring reply context on inbound replies. */
|
|
280
|
+
private readonly replyStore;
|
|
281
|
+
/** Per-session debounce + monotonic draft-id state for opt-in draft streaming. */
|
|
282
|
+
private readonly draftStream;
|
|
271
283
|
/** Identity-bearing sessions by repo/branch surface, used to avoid transient duplicate topics. */
|
|
272
284
|
private readonly topicOwnerByIdentity;
|
|
273
285
|
/** Non-identity frames held until identity creates the correct thread. */
|
|
@@ -376,7 +388,17 @@ export declare class TelegramNotificationDaemon {
|
|
|
376
388
|
private ensureAttachmentDir;
|
|
377
389
|
private cleanupAllAttachmentDirs;
|
|
378
390
|
private resolveInboundAttachment;
|
|
391
|
+
/**
|
|
392
|
+
* Serialize all pool flushes. Every caller (`submitThreadedFrame`, the flat
|
|
393
|
+
* fallback, the drain timer's `void this.flushPool()`, topic teardown) goes
|
|
394
|
+
* through one promise chain, so two flushes never interleave — a live send can
|
|
395
|
+
* never be in-flight while a finalized flush reads `liveMessages` and decides
|
|
396
|
+
* to post a fresh (duplicate) final. Errors are swallowed so one failed flush
|
|
397
|
+
* never poisons the queue (each flush is already best-effort internally).
|
|
398
|
+
*/
|
|
399
|
+
private flushChain;
|
|
379
400
|
private flushPool;
|
|
401
|
+
private flushPoolInner;
|
|
380
402
|
/**
|
|
381
403
|
* Track the Telegram message id backing a streamed `(sessionId, coalesceKey)`
|
|
382
404
|
* so later live/finalized frames edit it in place. Evicts this session's stale
|
|
@@ -59,6 +59,13 @@ export declare function buildActionMessage(action: {
|
|
|
59
59
|
options?: string[];
|
|
60
60
|
summary?: string;
|
|
61
61
|
}): RenderedMessage;
|
|
62
|
+
/** Render an `action_needed` body as raw markdown (rich-message source; the HTML fallback stays on buildActionMessage). */
|
|
63
|
+
export declare function buildActionMarkdown(action: {
|
|
64
|
+
kind: "ask" | "idle";
|
|
65
|
+
question?: string;
|
|
66
|
+
options?: string[];
|
|
67
|
+
summary?: string;
|
|
68
|
+
}): string;
|
|
62
69
|
/** Send Telegram HTML text chunks sequentially so long messages preserve order. */
|
|
63
70
|
export declare function sendTelegramHtmlChunks(send: TelegramSend, chatId: string, text: string, inlineKeyboard?: InlineButton[][]): Promise<void>;
|
|
64
71
|
/** A protocol `reply` frame the client should send to the server. */
|
|
@@ -34,6 +34,13 @@ export interface ThreadedSend {
|
|
|
34
34
|
* message. Set for streamed turn frames so live + finalized share one message.
|
|
35
35
|
*/
|
|
36
36
|
editable?: boolean;
|
|
37
|
+
/** Rich message class metadata. Only finalized final-answer sends are ever
|
|
38
|
+
* rich-promoted; the daemon gate (`shouldPromoteRich`) requires exactly this. */
|
|
39
|
+
richClass?: "final";
|
|
40
|
+
/** Rich final-answer markdown (raw). Delivery marker derived ONLY from a frame's `finalAnswer` bit; never inferred from `phase`. */
|
|
41
|
+
richMarkdown?: string;
|
|
42
|
+
/** Live-turn raw markdown for opt-in draft streaming (set ONLY on non-finalized turn frames; never triggers rich-final promotion, which requires `lane === "finalized"`). */
|
|
43
|
+
richDraftMarkdown?: string;
|
|
37
44
|
}
|
|
38
45
|
interface ThreadedFrame {
|
|
39
46
|
type?: unknown;
|
|
@@ -50,6 +57,7 @@ interface ThreadedFrame {
|
|
|
50
57
|
diff?: unknown;
|
|
51
58
|
cwd?: unknown;
|
|
52
59
|
phase?: unknown;
|
|
60
|
+
finalAnswer?: boolean;
|
|
53
61
|
text?: unknown;
|
|
54
62
|
messageRef?: unknown;
|
|
55
63
|
source?: unknown;
|
|
@@ -59,6 +67,8 @@ interface ThreadedFrame {
|
|
|
59
67
|
name?: unknown;
|
|
60
68
|
verbosity?: unknown;
|
|
61
69
|
redact?: unknown;
|
|
70
|
+
status?: unknown;
|
|
71
|
+
message?: unknown;
|
|
62
72
|
}
|
|
63
73
|
/** Format the one-time identity header as pinned bullets. */
|
|
64
74
|
export declare function formatIdentityHeader(frame: {
|
|
@@ -58,11 +58,10 @@ export declare class TopicRegistry {
|
|
|
58
58
|
markIdentitySent(sessionId: string): void;
|
|
59
59
|
/** Whether the identity header still needs sending for this session. */
|
|
60
60
|
needsIdentity(sessionId: string): boolean;
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
applyName(sessionId: string, name: string): boolean;
|
|
61
|
+
/** Whether a known session topic's applied title differs from `name`. */
|
|
62
|
+
needsRename(sessionId: string, name: string): boolean;
|
|
63
|
+
/** Commit a successfully-applied Telegram topic title. */
|
|
64
|
+
markNameApplied(sessionId: string, name: string): void;
|
|
66
65
|
/** Remove a session topic record after Telegram deletes the topic. */
|
|
67
66
|
delete(sessionId: string): boolean;
|
|
68
67
|
/** Serialise for atomic persistence beside the daemon state. */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthCredentialSelector } from "@sayknow-cli/ai";
|
|
2
|
+
export interface CliCredentialSelector {
|
|
3
|
+
provider?: string;
|
|
4
|
+
selector: AuthCredentialSelector;
|
|
5
|
+
raw: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseCliCredentialSelector(raw: string): CliCredentialSelector;
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AgentTelemetryConfig, type ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
2
|
-
import { type Model, type ProviderSessionState } from "@sayknow-cli/ai";
|
|
2
|
+
import { type AuthCredentialSelector, type Model, type ProviderSessionState } from "@sayknow-cli/ai";
|
|
3
3
|
import { createAppendOnlyContextManager, providerSupportsAppendOnlyAuto, resolveAppendOnlyMode } from "./append-only-mode";
|
|
4
4
|
import { type Rule } from "./capability/rule";
|
|
5
5
|
import { ModelRegistry } from "./config/model-registry";
|
|
@@ -55,6 +55,12 @@ export interface CreateAgentSessionOptions {
|
|
|
55
55
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
56
56
|
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
57
57
|
providerSessionId?: string;
|
|
58
|
+
/** Runtime credential selector for multi-account auth pools. */
|
|
59
|
+
credentialSelector?: {
|
|
60
|
+
provider?: string;
|
|
61
|
+
selector: AuthCredentialSelector;
|
|
62
|
+
raw: string;
|
|
63
|
+
};
|
|
58
64
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
59
65
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
60
66
|
/** Explicit parent/phase used to load active SKC sub-skill tools for this session. */
|
|
@@ -270,6 +270,8 @@ export interface PromptOptions {
|
|
|
270
270
|
images?: ImageContent[];
|
|
271
271
|
/** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). */
|
|
272
272
|
streamingBehavior?: "steer" | "followUp";
|
|
273
|
+
/** When set to "sequential", this follow-up is delivered one prompt at a time even if followUpMode is "all". */
|
|
274
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
273
275
|
/** Optional tool choice override for the next LLM call. */
|
|
274
276
|
toolChoice?: ToolChoice;
|
|
275
277
|
/** Send as developer/system message instead of user. Providers that support it use the developer role; others fall back to user. */
|
|
@@ -657,7 +659,7 @@ export declare class AgentSession {
|
|
|
657
659
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
658
660
|
*/
|
|
659
661
|
prompt(text: string, options?: PromptOptions): Promise<void>;
|
|
660
|
-
promptCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice">): Promise<void>;
|
|
662
|
+
promptCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy">): Promise<void>;
|
|
661
663
|
/**
|
|
662
664
|
* Queue a steering message to interrupt the agent mid-run.
|
|
663
665
|
*/
|
|
@@ -665,7 +667,7 @@ export declare class AgentSession {
|
|
|
665
667
|
/**
|
|
666
668
|
* Queue a follow-up message to process after the agent would otherwise stop.
|
|
667
669
|
*/
|
|
668
|
-
followUp(text: string, images?: ImageContent[]): Promise<void>;
|
|
670
|
+
followUp(text: string, images?: ImageContent[], options?: Pick<PromptOptions, "followUpQueuePolicy">): Promise<void>;
|
|
669
671
|
queueDeferredMessage(message: CustomMessage): void;
|
|
670
672
|
queueDeferredMessageForTests(message: CustomMessage, triggerTurn?: boolean): void;
|
|
671
673
|
/**
|
|
@@ -679,6 +681,7 @@ export declare class AgentSession {
|
|
|
679
681
|
sendCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: {
|
|
680
682
|
triggerTurn?: boolean;
|
|
681
683
|
deliverAs?: "steer" | "followUp" | "nextTurn";
|
|
684
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
682
685
|
}): Promise<void>;
|
|
683
686
|
/** Remove undelivered queued custom messages matching `predicate` from executable queues and tagged display mirrors. */
|
|
684
687
|
purgeQueuedCustomMessages(predicate: (message: CustomMessage) => boolean): PurgeQueuedCustomMessagesResult;
|
|
@@ -834,7 +837,7 @@ export declare class AgentSession {
|
|
|
834
837
|
getAvailableModels(): Model[];
|
|
835
838
|
/**
|
|
836
839
|
* Set thinking level.
|
|
837
|
-
* Saves the effective metadata-clamped level to session and settings
|
|
840
|
+
* Saves the effective metadata-clamped level to the session, and to settings when requested.
|
|
838
841
|
*/
|
|
839
842
|
setThinkingLevel(level: ThinkingLevel | undefined, persist?: boolean): void;
|
|
840
843
|
/**
|
|
@@ -2,6 +2,7 @@ import type { ResolvedTmuxBinary } from "./psmux-detect";
|
|
|
2
2
|
export declare const SKC_DEFAULT_TMUX_SESSION = "sayknow_cli";
|
|
3
3
|
export declare const SKC_TMUX_SESSION_PREFIX = "sayknow_cli_";
|
|
4
4
|
export declare const SKC_TMUX_COMMAND_ENV = "SKC_TMUX_COMMAND";
|
|
5
|
+
export declare const SKC_TMUX_ACTIVE_SESSION_ENV = "SKC_TMUX_ACTIVE_SESSION";
|
|
5
6
|
export declare const SKC_TMUX_PROFILE_ENV = "SKC_TMUX_PROFILE";
|
|
6
7
|
export declare const SKC_TMUX_MOUSE_ENV = "SKC_MOUSE";
|
|
7
8
|
export declare const SKC_TMUX_PROFILE_OPTION = "@skc-profile";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@sayknow-cli/agent-core";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import { type RuntimeSkillDiscoveryCandidate } from "../extensibility/runtime-skill-discovery";
|
|
4
|
+
import type { ToolSession } from ".";
|
|
5
|
+
declare const skillDiscoverySchema: z.ZodObject<{
|
|
6
|
+
query: z.ZodOptional<z.ZodString>;
|
|
7
|
+
source: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
8
|
+
all: "all";
|
|
9
|
+
project: "project";
|
|
10
|
+
user: "user";
|
|
11
|
+
}>>>;
|
|
12
|
+
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
export type SkillDiscoveryToolInput = z.infer<typeof skillDiscoverySchema>;
|
|
15
|
+
export interface SkillDiscoveryToolDetails {
|
|
16
|
+
candidates: RuntimeSkillDiscoveryCandidate[];
|
|
17
|
+
count: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema, SkillDiscoveryToolDetails> {
|
|
20
|
+
#private;
|
|
21
|
+
readonly name = "skill_discovery";
|
|
22
|
+
readonly label = "SkillDiscovery";
|
|
23
|
+
readonly summary = "Discover project and user runtime skills by thin metadata";
|
|
24
|
+
readonly loadMode = "essential";
|
|
25
|
+
readonly description: string;
|
|
26
|
+
readonly parameters: z.ZodObject<{
|
|
27
|
+
query: z.ZodOptional<z.ZodString>;
|
|
28
|
+
source: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
29
|
+
all: "all";
|
|
30
|
+
project: "project";
|
|
31
|
+
user: "user";
|
|
32
|
+
}>>>;
|
|
33
|
+
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
readonly strict = true;
|
|
36
|
+
constructor(session: ToolSession);
|
|
37
|
+
static createIf(session: ToolSession): SkillDiscoveryTool | null;
|
|
38
|
+
execute(_toolCallId: string, input: SkillDiscoveryToolInput, signal?: AbortSignal): Promise<AgentToolResult<SkillDiscoveryToolDetails>>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface DecodePastedPathOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Platform whose path semantics apply when decoding (shell unescaping,
|
|
4
|
+
* `file://` drive letters / UNC hosts). Defaults to `process.platform`;
|
|
5
|
+
* injectable so tests can pin the win32 contract from any host.
|
|
6
|
+
*/
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
/** Home directory for `~/` expansion. Defaults to `os.homedir()`. */
|
|
9
|
+
homedir?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvePastedImagePathOptions extends DecodePastedPathOptions {
|
|
12
|
+
/** Base directory for relative paths. Defaults to `process.cwd()`. */
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Decode pasted text into a filesystem path candidate. No filesystem access.
|
|
17
|
+
*
|
|
18
|
+
* Handles terminal drag-drop shell escaping (`\ `, `\(`, ...; skipped on
|
|
19
|
+
* win32 where `\` is the path separator), quoted paths, `file://` URIs
|
|
20
|
+
* (drive-letter, `file://localhost`, and UNC forms on win32), and `~/`
|
|
21
|
+
* expansion. Returns `undefined` when the text is empty, spans multiple
|
|
22
|
+
* lines, or is an invalid `file://` URI.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decodePastedPathCandidate(text: string, options?: DecodePastedPathOptions): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the resolved path when the whole pasted text is a single path to an
|
|
27
|
+
* existing image file (verified by extension AND content signature),
|
|
28
|
+
* otherwise `undefined` (the paste is inserted as text).
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolvePastedImagePath(text: string, options?: ResolvePastedImagePathOptions): string | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.10",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -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
|
-
"@sayknow-cli/stats": "0.3.
|
|
56
|
-
"@sayknow-cli/agent-core": "0.3.
|
|
57
|
-
"@sayknow-cli/ai": "0.3.
|
|
58
|
-
"@sayknow-cli/natives": "0.3.
|
|
59
|
-
"@sayknow-cli/tui": "0.3.
|
|
60
|
-
"@sayknow-cli/utils": "0.3.
|
|
55
|
+
"@sayknow-cli/stats": "0.3.10",
|
|
56
|
+
"@sayknow-cli/agent-core": "0.3.10",
|
|
57
|
+
"@sayknow-cli/ai": "0.3.10",
|
|
58
|
+
"@sayknow-cli/natives": "0.3.10",
|
|
59
|
+
"@sayknow-cli/tui": "0.3.10",
|
|
60
|
+
"@sayknow-cli/utils": "0.3.10",
|
|
61
61
|
"@puppeteer/browsers": "^2.13.0",
|
|
62
62
|
"@types/turndown": "5.0.6",
|
|
63
63
|
"@xterm/headless": "^6.0.0",
|
package/src/cli/args.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { type Effort, THINKING_EFFORTS } from "@sayknow-cli/ai";
|
|
5
5
|
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@sayknow-cli/utils";
|
|
6
|
+
import { CliParseError } from "@sayknow-cli/utils/cli";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
import { parseEffort } from "../thinking";
|
|
8
9
|
import { BUILTIN_TOOLS } from "../tools";
|
|
@@ -20,6 +21,7 @@ export interface Args {
|
|
|
20
21
|
mpreset?: string;
|
|
21
22
|
default?: boolean;
|
|
22
23
|
apiKey?: string;
|
|
24
|
+
credential?: string;
|
|
23
25
|
systemPrompt?: string;
|
|
24
26
|
appendSystemPrompt?: string;
|
|
25
27
|
thinking?: Effort;
|
|
@@ -136,6 +138,12 @@ export function parseArgs(args: string[]): Args {
|
|
|
136
138
|
result.default = true;
|
|
137
139
|
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
138
140
|
result.apiKey = args[++i];
|
|
141
|
+
} else if (arg === "--credential") {
|
|
142
|
+
const next = args[i + 1];
|
|
143
|
+
if (!next || next.startsWith("-")) {
|
|
144
|
+
throw new CliParseError("--credential requires <selector>");
|
|
145
|
+
}
|
|
146
|
+
result.credential = args[++i];
|
|
139
147
|
} else if (arg === "--system-prompt" && i + 1 < args.length) {
|
|
140
148
|
result.systemPrompt = args[++i];
|
|
141
149
|
} else if (arg === "--append-system-prompt" && i + 1 < args.length) {
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -31,6 +31,9 @@ export interface NotifyCommandDeps {
|
|
|
31
31
|
setupRedact?: boolean;
|
|
32
32
|
setupInteractive?: boolean;
|
|
33
33
|
threadedModePrompt?: (message: string) => Promise<string>;
|
|
34
|
+
tokenPrompt?: () => Promise<string>;
|
|
35
|
+
setExitCode?: (code: number) => void;
|
|
36
|
+
exitProcess?: (code: number) => void;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
interface TelegramApiResponse<T> {
|
|
@@ -126,6 +129,27 @@ export async function runNotifyCommand(cmd: NotifyCommandArgs, deps: NotifyComma
|
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
export async function runNotifyCliCommand(cmd: NotifyCommandArgs, deps: NotifyCommandDeps = {}): Promise<void> {
|
|
133
|
+
try {
|
|
134
|
+
await runNotifyCommand(cmd, deps);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (cmd.action !== "setup" || !(error instanceof Error)) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const cancelled = error.message === "Telegram bot token prompt cancelled.";
|
|
141
|
+
process.stderr.write(cancelled ? "Telegram notify setup cancelled.\n" : `Error: ${error.message}\n`);
|
|
142
|
+
const code = cancelled ? 130 : 1;
|
|
143
|
+
if (deps.setExitCode) {
|
|
144
|
+
deps.setExitCode(code);
|
|
145
|
+
} else {
|
|
146
|
+
process.exitCode = code;
|
|
147
|
+
}
|
|
148
|
+
const exitProcess = deps.exitProcess ?? (deps.setExitCode ? undefined : process.exit);
|
|
149
|
+
exitProcess?.(code);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
129
153
|
async function getSettings(deps: NotifyCommandDeps): Promise<Settings> {
|
|
130
154
|
if (deps.settings) return deps.settings;
|
|
131
155
|
const { Settings } = await import("../config/settings");
|
|
@@ -136,7 +160,7 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
136
160
|
const settings = await getSettings(deps);
|
|
137
161
|
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
138
162
|
const apiBase = deps.apiBase ?? DEFAULT_API_BASE;
|
|
139
|
-
const token = deps.setupToken ?? (await promptForToken());
|
|
163
|
+
const token = deps.setupToken ?? (await (deps.tokenPrompt ?? promptForToken)());
|
|
140
164
|
if (!token.trim()) {
|
|
141
165
|
throw new Error("Telegram bot token is required.");
|
|
142
166
|
}
|
|
@@ -179,6 +203,7 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
179
203
|
type TokenPromptInput = NodeJS.ReadStream & {
|
|
180
204
|
isRaw?: boolean;
|
|
181
205
|
setRawMode?: (mode: boolean) => unknown;
|
|
206
|
+
pause?: () => unknown;
|
|
182
207
|
};
|
|
183
208
|
|
|
184
209
|
type TokenPromptOutput = Pick<NodeJS.WriteStream, "write">;
|
|
@@ -206,6 +231,7 @@ export async function promptForToken(
|
|
|
206
231
|
input.off("data", onData);
|
|
207
232
|
input.off("error", onError);
|
|
208
233
|
input.setRawMode?.(wasRaw);
|
|
234
|
+
input.pause?.();
|
|
209
235
|
output.write("\n");
|
|
210
236
|
};
|
|
211
237
|
|
|
@@ -249,18 +275,18 @@ export async function promptForToken(
|
|
|
249
275
|
}
|
|
250
276
|
|
|
251
277
|
const THREADED_ENABLED_SUCCESS =
|
|
252
|
-
"Telegram Threaded Mode capability verified for this bot. SKC will request a private-chat topic per session; if Telegram ever refuses topic creation, notifications fall back to this flat chat with a one-time nudge.\n";
|
|
278
|
+
"Telegram Threaded Mode capability verified for this bot. SKC will request a private-chat topic per session; if Telegram ever refuses topic creation, notifications fall back to this flat chat with inline ask buttons only and a one-time Threaded Mode nudge.\n";
|
|
253
279
|
|
|
254
280
|
const THREADED_MISSING_WARNING =
|
|
255
|
-
"Warning: Telegram getMe did not include has_topics_enabled, so SKC cannot verify private-chat Threaded Mode capability for this bot. Setup will continue;
|
|
281
|
+
"Warning: Telegram getMe did not include has_topics_enabled, so SKC cannot verify private-chat Threaded Mode capability for this bot. Setup will continue; flat private-chat fallback supports outbound notifications and inline ask buttons only. Free-text replies and session commands require Threaded Mode/topic routing.\n";
|
|
256
282
|
|
|
257
283
|
const THREADED_NONINTERACTIVE_WARNING =
|
|
258
|
-
"Warning: Telegram Threaded Mode capability is OFF for this bot. Setup will be saved because this run is non-interactive
|
|
284
|
+
"Warning: Telegram Threaded Mode capability is OFF for this bot. Setup will be saved because this run is non-interactive. Flat private-chat fallback supports outbound notifications and inline ask buttons only; free-text replies and session commands require enabling Threaded Mode in @BotFather > Bot Settings > Threads Settings.\n";
|
|
259
285
|
|
|
260
286
|
const THREADED_DISABLED_GUIDANCE =
|
|
261
287
|
"Telegram Threaded Mode is OFF for this bot. SKC needs Telegram private-chat topics so each session can use its own thread.\n" +
|
|
262
|
-
"SKC cannot enable this through the Bot API. Open @BotFather
|
|
263
|
-
"
|
|
288
|
+
"SKC cannot enable this through the Bot API. Open @BotFather > Bot Settings > Threads Settings for this bot, enable Threaded Mode / forum topics for private chats, then return here.\n" +
|
|
289
|
+
"Without Threaded Mode, flat private-chat fallback supports outbound notifications and inline ask buttons only; free-text replies and session commands require topic routing.\n";
|
|
264
290
|
|
|
265
291
|
const THREADED_DISABLED_PROMPT =
|
|
266
292
|
"Press Enter after enabling Threaded Mode, or type skip to finish setup with a warning: ";
|
|
@@ -270,7 +296,7 @@ const THREADED_STILL_OFF = "Telegram still reports Threaded Mode OFF for this bo
|
|
|
270
296
|
const THREADED_RETRY_PROMPT = "Press Enter to check again, or type skip to finish setup with a warning: ";
|
|
271
297
|
|
|
272
298
|
const THREADED_SKIP_WARNING =
|
|
273
|
-
"Warning: continuing without verified Telegram Threaded Mode capability. Setup will be saved
|
|
299
|
+
"Warning: continuing without verified Telegram Threaded Mode capability. Setup will be saved. Flat private-chat fallback supports outbound notifications and inline ask buttons only; free-text replies and session commands require enabling Threaded Mode in BotFather.\n";
|
|
274
300
|
|
|
275
301
|
const THREADED_INVALID_INPUT = "Type Enter to retry or skip to continue with a warning.\n";
|
|
276
302
|
|
|
@@ -500,8 +526,10 @@ ${chalk.bold("Examples:")}
|
|
|
500
526
|
|
|
501
527
|
${chalk.bold("Threaded Mode:")}
|
|
502
528
|
SKC uses Telegram private-chat topics for per-session threads. Setup verifies the bot
|
|
503
|
-
capability via getMe.has_topics_enabled.
|
|
504
|
-
bots cannot toggle it through the Bot API. If Telegram refuses topic
|
|
505
|
-
SKC delivers flat to the paired private chat
|
|
529
|
+
capability via getMe.has_topics_enabled. Enable Threaded Mode in @BotFather > Bot Settings
|
|
530
|
+
> Threads Settings; bots cannot toggle it through the Bot API. If Telegram refuses topic
|
|
531
|
+
creation at runtime, SKC delivers flat to the paired private chat with outbound notifications
|
|
532
|
+
and inline ask buttons only, then nudges you to enable Threaded Mode for free-text replies
|
|
533
|
+
and session commands.
|
|
506
534
|
`);
|
|
507
535
|
}
|
package/src/cli.ts
CHANGED
|
@@ -146,6 +146,10 @@ export class RootHelpCommand extends Command {
|
|
|
146
146
|
default: Flags.boolean({ description: "Persist --mpreset as the default model profile" }),
|
|
147
147
|
provider: Flags.string({ description: "Provider to use (legacy; prefer --model)" }),
|
|
148
148
|
"api-key": Flags.string({ description: "API key (defaults to env vars)" }),
|
|
149
|
+
credential: Flags.string({
|
|
150
|
+
description:
|
|
151
|
+
"Stored credential selector: email:<addr>, id:<n>, account:<id>, project:<id>, or provider/email:<addr>",
|
|
152
|
+
}),
|
|
149
153
|
"system-prompt": Flags.string({ description: "System prompt (default: coding assistant prompt)" }),
|
|
150
154
|
"append-system-prompt": Flags.string({ description: "Append text or file contents to the system prompt" }),
|
|
151
155
|
"allow-home": Flags.boolean({ description: "Allow starting in ~ without auto-switching to a temp dir" }),
|
|
@@ -191,6 +195,7 @@ export class RootHelpCommand extends Command {
|
|
|
191
195
|
`# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
|
|
192
196
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
193
197
|
`# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
|
|
198
|
+
`# Pin a stored credential for this session\n ${APP_NAME} --credential email:me@example.com`,
|
|
194
199
|
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-medium`,
|
|
195
200
|
`# Persist a model profile as the default\n ${APP_NAME} --mpreset opencodego --default`,
|
|
196
201
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.skc/agent/sessions/--path--/session.jsonl`,
|
package/src/commands/launch.ts
CHANGED
|
@@ -87,6 +87,10 @@ export default class Index extends Command {
|
|
|
87
87
|
"api-key": Flags.string({
|
|
88
88
|
description: "API key (defaults to env vars)",
|
|
89
89
|
}),
|
|
90
|
+
credential: Flags.string({
|
|
91
|
+
description:
|
|
92
|
+
"Stored credential selector: email:<addr>, id:<n>, account:<id>, project:<id>, or provider/email:<addr>",
|
|
93
|
+
}),
|
|
90
94
|
"system-prompt": Flags.string({
|
|
91
95
|
description: "System prompt (default: coding assistant prompt)",
|
|
92
96
|
}),
|
|
@@ -181,6 +185,7 @@ export default class Index extends Command {
|
|
|
181
185
|
`# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
|
|
182
186
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
183
187
|
`# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
|
|
188
|
+
`# Pin a stored credential for this session\n ${APP_NAME} --credential email:me@example.com`,
|
|
184
189
|
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-medium`,
|
|
185
190
|
`# Persist a model profile as the default\n ${APP_NAME} --mpreset opencodego --default`,
|
|
186
191
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.skc/agent/sessions/--path--/session.jsonl`,
|
package/src/commands/notify.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Configure Telegram notifications.
|
|
3
3
|
*/
|
|
4
4
|
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
|
-
import { type NotifyAction, type NotifyCommandArgs,
|
|
5
|
+
import { type NotifyAction, type NotifyCommandArgs, runNotifyCliCommand } from "../cli/notify-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
8
8
|
const ACTIONS: NotifyAction[] = ["setup", "status", "daemon-internal"];
|
|
@@ -56,6 +56,6 @@ export default class Notify extends Command {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
if (action !== "daemon-internal") await initTheme();
|
|
59
|
-
await
|
|
59
|
+
await runNotifyCliCommand(cmd);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -61,7 +61,7 @@ declare module "@sayknow-cli/tui" {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function defaultMessageQueueKeysForPlatform(platform: NodeJS.Platform = process.platform): KeyId {
|
|
64
|
-
return platform === "win32" ? "alt+q" : "alt+enter";
|
|
64
|
+
return platform === "win32" || platform === "darwin" ? "alt+q" : "alt+enter";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|