@gajae-code/coding-agent 0.9.1 → 0.9.2
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 +24 -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 +7 -3
- package/dist/types/config/model-resolver.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +9 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -2
- package/dist/types/modes/components/thinking-selector.d.ts +6 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +29 -0
- package/dist/types/notifications/index.d.ts +12 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/threaded-render.d.ts +2 -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/tools/index.d.ts +1 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/package.json +7 -7
- package/src/cli/args.ts +8 -0
- package/src/cli/notify-cli.ts +27 -1
- package/src/cli.ts +5 -0
- package/src/commands/launch.ts +5 -0
- package/src/commands/notify.ts +2 -2
- package/src/config/model-profile-activation.ts +23 -7
- package/src/config/model-registry.ts +21 -4
- package/src/config/model-resolver.ts +5 -16
- package/src/config/settings-schema.ts +3 -2
- package/src/config/settings.ts +24 -1
- package/src/extensibility/runtime-skill-discovery.ts +229 -0
- package/src/gjc-runtime/launch-tmux.ts +72 -12
- package/src/gjc-runtime/psmux-detect.ts +17 -2
- package/src/gjc-runtime/team-runtime.ts +35 -3
- package/src/main.ts +26 -3
- package/src/modes/components/settings-selector.ts +3 -3
- package/src/modes/components/thinking-selector.ts +79 -18
- package/src/modes/controllers/input-controller.ts +50 -9
- package/src/modes/controllers/selector-controller.ts +7 -5
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +48 -15
- package/src/notifications/config-commands.ts +70 -0
- package/src/notifications/index.ts +156 -9
- package/src/notifications/rich-render.ts +1 -0
- package/src/notifications/telegram-daemon-cli.ts +13 -5
- package/src/notifications/telegram-daemon.ts +87 -4
- package/src/notifications/threaded-render.ts +14 -0
- package/src/notifications/topic-registry.ts +9 -8
- package/src/prompts/tools/skill-discovery.md +13 -0
- package/src/runtime-credential-selector.ts +35 -0
- package/src/sdk.ts +68 -5
- package/src/session/agent-session.ts +42 -18
- 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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Discover project and user runtime skills without loading full skill content.
|
|
2
|
+
|
|
3
|
+
<instruction>
|
|
4
|
+
- Searches only custom runtime skill locations: project `.gjc/skills` and user `~/.gjc/skills`.
|
|
5
|
+
- Built-in, bundled, and internal workflow skills are intentionally excluded.
|
|
6
|
+
- Returns thin metadata only: name, description, source scope, path, and use conditions when present.
|
|
7
|
+
- To load a selected skill's full `SKILL.md`, invoke it through the existing `skill` tool with the exact `name` returned here.
|
|
8
|
+
</instruction>
|
|
9
|
+
|
|
10
|
+
Input:
|
|
11
|
+
- `query` (optional): words to match against skill name, description, source, or use conditions.
|
|
12
|
+
- `source` (optional): `all`, `project`, or `user`.
|
|
13
|
+
- `limit` (optional): maximum results, 1-50.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AuthCredentialSelector, AuthCredentialSelectorKind } from "@gajae-code/ai";
|
|
2
|
+
|
|
3
|
+
export interface CliCredentialSelector {
|
|
4
|
+
provider?: string;
|
|
5
|
+
selector: AuthCredentialSelector;
|
|
6
|
+
raw: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SELECTOR_KINDS = new Set<AuthCredentialSelectorKind>(["id", "email", "account", "project"]);
|
|
10
|
+
|
|
11
|
+
function parseSelectorBody(body: string): AuthCredentialSelector | undefined {
|
|
12
|
+
const separator = body.indexOf(":");
|
|
13
|
+
if (separator === -1) {
|
|
14
|
+
if (body.includes("@")) return { kind: "email", value: body };
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const rawKind = body.slice(0, separator);
|
|
18
|
+
const value = body.slice(separator + 1).trim();
|
|
19
|
+
if (!SELECTOR_KINDS.has(rawKind as AuthCredentialSelectorKind) || value.length === 0) return undefined;
|
|
20
|
+
return { kind: rawKind as AuthCredentialSelectorKind, value };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseCliCredentialSelector(raw: string): CliCredentialSelector {
|
|
24
|
+
const trimmed = raw.trim();
|
|
25
|
+
const slash = trimmed.indexOf("/");
|
|
26
|
+
const provider = slash === -1 ? undefined : trimmed.slice(0, slash).trim();
|
|
27
|
+
const body = slash === -1 ? trimmed : trimmed.slice(slash + 1).trim();
|
|
28
|
+
const selector = parseSelectorBody(body);
|
|
29
|
+
if (!selector || provider === "") {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid --credential selector "${raw}". Use email:name@example.com, id:123, account:<id>, project:<id>, or provider/email:name@example.com.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return { ...(provider ? { provider } : {}), selector, raw };
|
|
35
|
+
}
|
package/src/sdk.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ThinkingLevel,
|
|
10
10
|
} from "@gajae-code/agent-core";
|
|
11
11
|
import {
|
|
12
|
+
type AuthCredentialSelector,
|
|
12
13
|
type CredentialDisabledEvent,
|
|
13
14
|
type Message,
|
|
14
15
|
type Model,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "./async";
|
|
40
41
|
import { loadCapability } from "./capability";
|
|
41
42
|
import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
|
|
42
|
-
import { ModelRegistry } from "./config/model-registry";
|
|
43
|
+
import { kNoAuth, ModelRegistry } from "./config/model-registry";
|
|
43
44
|
import {
|
|
44
45
|
formatModelString,
|
|
45
46
|
parseModelPattern,
|
|
@@ -272,6 +273,8 @@ export interface CreateAgentSessionOptions {
|
|
|
272
273
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
273
274
|
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
274
275
|
providerSessionId?: string;
|
|
276
|
+
/** Runtime credential selector for multi-account auth pools. */
|
|
277
|
+
credentialSelector?: { provider?: string; selector: AuthCredentialSelector; raw: string };
|
|
275
278
|
|
|
276
279
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
277
280
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
@@ -903,10 +906,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
903
906
|
startupCredentialDisabledEvents.push(event);
|
|
904
907
|
}
|
|
905
908
|
});
|
|
909
|
+
let runtimeCredentialSelectorInstalled = false;
|
|
910
|
+
const installRuntimeCredentialSelector = (provider: string): void => {
|
|
911
|
+
if (!options.credentialSelector || runtimeCredentialSelectorInstalled) return;
|
|
912
|
+
authStorage.setRuntimeCredentialSelector(provider, options.credentialSelector.selector);
|
|
913
|
+
runtimeCredentialSelectorInstalled = true;
|
|
914
|
+
};
|
|
915
|
+
const earlyCredentialSelectorProvider = options.credentialSelector?.provider ?? options.model?.provider;
|
|
916
|
+
if (earlyCredentialSelectorProvider) {
|
|
917
|
+
installRuntimeCredentialSelector(earlyCredentialSelectorProvider);
|
|
918
|
+
}
|
|
906
919
|
const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
|
|
907
920
|
modelRegistry.applyConfiguredModelBindings(settings);
|
|
908
921
|
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
909
|
-
|
|
922
|
+
const canRefreshModelsBeforeCredentialSelector =
|
|
923
|
+
!options.credentialSelector || runtimeCredentialSelectorInstalled || options.modelRegistry !== undefined;
|
|
924
|
+
if (!options.modelRegistry && canRefreshModelsBeforeCredentialSelector) {
|
|
910
925
|
modelRegistry.refreshInBackground();
|
|
911
926
|
}
|
|
912
927
|
// Kick off workspace tree discovery early. The native workspace scan returns
|
|
@@ -972,7 +987,29 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
972
987
|
return cached;
|
|
973
988
|
}
|
|
974
989
|
|
|
975
|
-
const
|
|
990
|
+
const credentialSelector =
|
|
991
|
+
options.credentialSelector && !runtimeCredentialSelectorInstalled
|
|
992
|
+
? options.credentialSelector.provider === undefined ||
|
|
993
|
+
options.credentialSelector.provider === candidate.provider
|
|
994
|
+
? options.credentialSelector.selector
|
|
995
|
+
: undefined
|
|
996
|
+
: undefined;
|
|
997
|
+
if (options.credentialSelector?.provider && options.credentialSelector.provider !== candidate.provider) {
|
|
998
|
+
modelApiKeyAvailability.set(availabilityKey, false);
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
const key = await modelRegistry.getApiKey(candidate, providerSessionId, { credentialSelector }).catch(error => {
|
|
1002
|
+
if (credentialSelector) {
|
|
1003
|
+
logger.debug("Credential selector did not match model availability candidate", {
|
|
1004
|
+
provider: candidate.provider,
|
|
1005
|
+
model: candidate.id,
|
|
1006
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1007
|
+
});
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
throw error;
|
|
1011
|
+
});
|
|
1012
|
+
const hasKey = Boolean(key) && (!credentialSelector || key !== kNoAuth);
|
|
976
1013
|
modelApiKeyAvailability.set(availabilityKey, hasKey);
|
|
977
1014
|
return hasKey;
|
|
978
1015
|
};
|
|
@@ -1047,6 +1084,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1047
1084
|
const taskDepth = options.taskDepth ?? 0;
|
|
1048
1085
|
|
|
1049
1086
|
let thinkingLevel = options.thinkingLevel;
|
|
1087
|
+
const hasExplicitDefaultThinkingLevel = settings.has("defaultThinkingLevel");
|
|
1088
|
+
let thinkingLevelFromSchemaDefault = false;
|
|
1050
1089
|
|
|
1051
1090
|
// If session has data and includes a thinking entry, restore it
|
|
1052
1091
|
if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
|
|
@@ -1057,13 +1096,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1057
1096
|
thinkingLevel = defaultRoleSpec.thinkingLevel;
|
|
1058
1097
|
}
|
|
1059
1098
|
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1099
|
+
// An explicit user/project default should win over the model's bundled
|
|
1100
|
+
// defaultLevel. The schema default is only a final fallback so model metadata
|
|
1101
|
+
// can keep driving first-run behavior until the user chooses "Set as default".
|
|
1102
|
+
if (thinkingLevel === undefined && hasExplicitDefaultThinkingLevel) {
|
|
1103
|
+
thinkingLevel = settings.get("defaultThinkingLevel");
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1062
1106
|
if (thinkingLevel === undefined && model?.thinking?.defaultLevel !== undefined) {
|
|
1063
1107
|
thinkingLevel = model.thinking.defaultLevel;
|
|
1064
1108
|
}
|
|
1109
|
+
|
|
1065
1110
|
if (thinkingLevel === undefined) {
|
|
1066
1111
|
thinkingLevel = settings.get("defaultThinkingLevel");
|
|
1112
|
+
thinkingLevelFromSchemaDefault = true;
|
|
1067
1113
|
}
|
|
1068
1114
|
if (model) {
|
|
1069
1115
|
const resolvedModel = model;
|
|
@@ -1608,6 +1654,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1608
1654
|
if (resolved) {
|
|
1609
1655
|
model = resolved;
|
|
1610
1656
|
modelFallbackMessage = undefined;
|
|
1657
|
+
if (thinkingLevelFromSchemaDefault && resolved.thinking?.defaultLevel !== undefined) {
|
|
1658
|
+
thinkingLevel = resolved.thinking.defaultLevel;
|
|
1659
|
+
thinkingLevelFromSchemaDefault = false;
|
|
1660
|
+
}
|
|
1661
|
+
thinkingLevel = resolveThinkingLevelForModel(resolved, thinkingLevel);
|
|
1611
1662
|
} else {
|
|
1612
1663
|
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
1613
1664
|
}
|
|
@@ -1639,6 +1690,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1639
1690
|
}
|
|
1640
1691
|
}
|
|
1641
1692
|
|
|
1693
|
+
if (options.credentialSelector && !runtimeCredentialSelectorInstalled) {
|
|
1694
|
+
const credentialProvider = options.credentialSelector.provider ?? model?.provider;
|
|
1695
|
+
if (!credentialProvider) {
|
|
1696
|
+
throw new Error(
|
|
1697
|
+
`--credential ${options.credentialSelector.raw} requires a resolved model or an explicit provider prefix`,
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
installRuntimeCredentialSelector(credentialProvider);
|
|
1701
|
+
if (!options.modelRegistry && !canRefreshModelsBeforeCredentialSelector) {
|
|
1702
|
+
modelRegistry.refreshInBackground();
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1642
1705
|
const customCommandsResult: CustomCommandsLoadResult = { commands: [], errors: [] };
|
|
1643
1706
|
|
|
1644
1707
|
let extensionRunner: ExtensionRunner | undefined;
|
|
@@ -473,6 +473,8 @@ export interface PromptOptions {
|
|
|
473
473
|
images?: ImageContent[];
|
|
474
474
|
/** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). */
|
|
475
475
|
streamingBehavior?: "steer" | "followUp";
|
|
476
|
+
/** When set to "sequential", this follow-up is delivered one prompt at a time even if followUpMode is "all". */
|
|
477
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
476
478
|
/** Optional tool choice override for the next LLM call. */
|
|
477
479
|
toolChoice?: ToolChoice;
|
|
478
480
|
/** Send as developer/system message instead of user. Providers that support it use the developer role; others fall back to user. */
|
|
@@ -5304,7 +5306,9 @@ export class AgentSession {
|
|
|
5304
5306
|
throw new AgentBusyError();
|
|
5305
5307
|
}
|
|
5306
5308
|
if (options.streamingBehavior === "followUp") {
|
|
5307
|
-
await this.#queueFollowUp(expandedText, options?.images
|
|
5309
|
+
await this.#queueFollowUp(expandedText, options?.images, {
|
|
5310
|
+
forceOneAtATime: options.followUpQueuePolicy === "sequential",
|
|
5311
|
+
});
|
|
5308
5312
|
} else {
|
|
5309
5313
|
await this.#queueSteer(expandedText, options?.images);
|
|
5310
5314
|
}
|
|
@@ -5422,7 +5426,7 @@ export class AgentSession {
|
|
|
5422
5426
|
|
|
5423
5427
|
async promptCustomMessage<T = unknown>(
|
|
5424
5428
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
5425
|
-
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice">,
|
|
5429
|
+
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy">,
|
|
5426
5430
|
): Promise<void> {
|
|
5427
5431
|
const textContent =
|
|
5428
5432
|
typeof message.content === "string"
|
|
@@ -5436,7 +5440,10 @@ export class AgentSession {
|
|
|
5436
5440
|
if (!options?.streamingBehavior) {
|
|
5437
5441
|
throw new AgentBusyError();
|
|
5438
5442
|
}
|
|
5439
|
-
await this.sendCustomMessage(message, {
|
|
5443
|
+
await this.sendCustomMessage(message, {
|
|
5444
|
+
deliverAs: options.streamingBehavior,
|
|
5445
|
+
followUpQueuePolicy: options.followUpQueuePolicy,
|
|
5446
|
+
});
|
|
5440
5447
|
return;
|
|
5441
5448
|
}
|
|
5442
5449
|
|
|
@@ -5783,13 +5790,19 @@ export class AgentSession {
|
|
|
5783
5790
|
/**
|
|
5784
5791
|
* Queue a follow-up message to process after the agent would otherwise stop.
|
|
5785
5792
|
*/
|
|
5786
|
-
async followUp(
|
|
5793
|
+
async followUp(
|
|
5794
|
+
text: string,
|
|
5795
|
+
images?: ImageContent[],
|
|
5796
|
+
options?: Pick<PromptOptions, "followUpQueuePolicy">,
|
|
5797
|
+
): Promise<void> {
|
|
5787
5798
|
if (text.startsWith("/")) {
|
|
5788
5799
|
this.#throwIfExtensionCommand(text);
|
|
5789
5800
|
}
|
|
5790
5801
|
|
|
5791
5802
|
const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
|
|
5792
|
-
await this.#queueFollowUp(expandedText, images
|
|
5803
|
+
await this.#queueFollowUp(expandedText, images, {
|
|
5804
|
+
forceOneAtATime: options?.followUpQueuePolicy === "sequential",
|
|
5805
|
+
});
|
|
5793
5806
|
}
|
|
5794
5807
|
|
|
5795
5808
|
/**
|
|
@@ -5826,19 +5839,22 @@ export class AgentSession {
|
|
|
5826
5839
|
/**
|
|
5827
5840
|
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
5828
5841
|
*/
|
|
5829
|
-
async #queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
|
|
5842
|
+
async #queueFollowUp(text: string, images?: ImageContent[], options?: { forceOneAtATime?: boolean }): Promise<void> {
|
|
5830
5843
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5831
5844
|
this.#followUpMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5832
5845
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5833
5846
|
if (images && images.length > 0) {
|
|
5834
5847
|
content.push(...images);
|
|
5835
5848
|
}
|
|
5836
|
-
this.agent.followUp(
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5849
|
+
this.agent.followUp(
|
|
5850
|
+
{
|
|
5851
|
+
role: "user",
|
|
5852
|
+
content,
|
|
5853
|
+
attribution: "user",
|
|
5854
|
+
timestamp: Date.now(),
|
|
5855
|
+
},
|
|
5856
|
+
options?.forceOneAtATime ? { forceOneAtATime: true } : undefined,
|
|
5857
|
+
);
|
|
5842
5858
|
// When fully idle AND the session is in a resumable assistant-ended state,
|
|
5843
5859
|
// schedule an immediate continue so the queued follow-up is delivered
|
|
5844
5860
|
// without waiting for the next user turn. We gate on isStreaming (model
|
|
@@ -5986,7 +6002,11 @@ export class AgentSession {
|
|
|
5986
6002
|
*/
|
|
5987
6003
|
async sendCustomMessage<T = unknown>(
|
|
5988
6004
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
5989
|
-
options?: {
|
|
6005
|
+
options?: {
|
|
6006
|
+
triggerTurn?: boolean;
|
|
6007
|
+
deliverAs?: "steer" | "followUp" | "nextTurn";
|
|
6008
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
6009
|
+
},
|
|
5990
6010
|
): Promise<void> {
|
|
5991
6011
|
const appMessage: CustomMessage<T> = {
|
|
5992
6012
|
role: "custom",
|
|
@@ -6004,7 +6024,10 @@ export class AgentSession {
|
|
|
6004
6024
|
}
|
|
6005
6025
|
|
|
6006
6026
|
if (options?.deliverAs === "followUp") {
|
|
6007
|
-
this.agent.followUp(
|
|
6027
|
+
this.agent.followUp(
|
|
6028
|
+
appMessage,
|
|
6029
|
+
options.followUpQueuePolicy === "sequential" ? { forceOneAtATime: true } : undefined,
|
|
6030
|
+
);
|
|
6008
6031
|
} else {
|
|
6009
6032
|
this.agent.steer(appMessage);
|
|
6010
6033
|
}
|
|
@@ -6930,7 +6953,7 @@ export class AgentSession {
|
|
|
6930
6953
|
|
|
6931
6954
|
/**
|
|
6932
6955
|
* Set thinking level.
|
|
6933
|
-
* Saves the effective metadata-clamped level to session and settings
|
|
6956
|
+
* Saves the effective metadata-clamped level to the session, and to settings when requested.
|
|
6934
6957
|
*/
|
|
6935
6958
|
setThinkingLevel(level: ThinkingLevel | undefined, persist: boolean = false): void {
|
|
6936
6959
|
const effectiveLevel = resolveThinkingLevelForModel(this.model, level);
|
|
@@ -6939,11 +6962,12 @@ export class AgentSession {
|
|
|
6939
6962
|
this.#thinkingLevel = effectiveLevel;
|
|
6940
6963
|
this.agent.setThinkingLevel(toReasoningEffort(effectiveLevel));
|
|
6941
6964
|
|
|
6965
|
+
if (persist && effectiveLevel !== undefined) {
|
|
6966
|
+
this.settings.set("defaultThinkingLevel", effectiveLevel);
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6942
6969
|
if (isChanging) {
|
|
6943
6970
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
6944
|
-
if (persist && effectiveLevel !== undefined && effectiveLevel !== ThinkingLevel.Off) {
|
|
6945
|
-
this.settings.set("defaultThinkingLevel", effectiveLevel);
|
|
6946
|
-
}
|
|
6947
6971
|
this.#emit({ type: "thinking_level_changed", thinkingLevel: effectiveLevel });
|
|
6948
6972
|
}
|
|
6949
6973
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -507,7 +507,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
507
507
|
? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
|
|
508
508
|
: Promise.resolve([]);
|
|
509
509
|
|
|
510
|
-
const [resolvedCustomPrompt, resolvedAppendPrompt, systemPromptCustomization, contextFiles,
|
|
510
|
+
const [resolvedCustomPrompt, resolvedAppendPrompt, systemPromptCustomization, contextFiles, , workspaceTree] =
|
|
511
511
|
await Promise.all([
|
|
512
512
|
withDeadline(
|
|
513
513
|
"customPrompt",
|
|
@@ -581,11 +581,9 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
581
581
|
description: tools?.get(name)?.description ?? "",
|
|
582
582
|
}));
|
|
583
583
|
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
const hasRead = tools?.has("read");
|
|
588
|
-
const filteredSkills = hasRead ? skills.filter(skill => skill.hide !== true) : [];
|
|
584
|
+
// Runtime skills are discovered by `skill_discovery` and loaded narrowly through
|
|
585
|
+
// `skill`; do not dump the full custom skill catalog into the core prompt.
|
|
586
|
+
const filteredSkills: Skill[] = [];
|
|
589
587
|
|
|
590
588
|
const effectiveSystemPromptCustomization = dedupePromptSource(systemPromptCustomization, [
|
|
591
589
|
resolvedCustomPrompt,
|
package/src/tools/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { reportFindingTool } from "./review";
|
|
|
56
56
|
import { SearchTool } from "./search";
|
|
57
57
|
import { SearchToolBm25Tool } from "./search-tool-bm25";
|
|
58
58
|
import { SkillTool } from "./skill";
|
|
59
|
+
import { SkillDiscoveryTool } from "./skill-discovery";
|
|
59
60
|
import { loadSshTool } from "./ssh";
|
|
60
61
|
import { SubagentTool } from "./subagent";
|
|
61
62
|
import { TelegramSendTool } from "./telegram-send";
|
|
@@ -95,6 +96,7 @@ export * from "./review";
|
|
|
95
96
|
export * from "./search";
|
|
96
97
|
export * from "./search-tool-bm25";
|
|
97
98
|
export * from "./skill";
|
|
99
|
+
export * from "./skill-discovery";
|
|
98
100
|
export * from "./ssh";
|
|
99
101
|
export * from "./subagent";
|
|
100
102
|
export * from "./telegram-send";
|
|
@@ -411,6 +413,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
411
413
|
todo_write: s => new TodoWriteTool(s),
|
|
412
414
|
web_search: s => new WebSearchTool(s),
|
|
413
415
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
416
|
+
skill_discovery: SkillDiscoveryTool.createIf,
|
|
414
417
|
telegram_send: TelegramSendTool.createIf,
|
|
415
418
|
write: s => new WriteTool(s),
|
|
416
419
|
skill: SkillTool.createIf,
|
|
@@ -570,6 +573,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
570
573
|
if (name === "search_tool_bm25") return discoveryActive;
|
|
571
574
|
if (name === "calc") return session.settings.get("calc.enabled");
|
|
572
575
|
if (name === "skill") return session.settings.get("skill.enabled");
|
|
576
|
+
if (name === "skill_discovery") return session.settings.get("skill.enabled");
|
|
573
577
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
574
578
|
if (name === "computer") return isComputerCallable(session);
|
|
575
579
|
if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
2
|
+
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
3
|
+
import * as z from "zod/v4";
|
|
4
|
+
import { discoverRuntimeSkills, type RuntimeSkillDiscoveryCandidate } from "../extensibility/runtime-skill-discovery";
|
|
5
|
+
import skillDiscoveryDescription from "../prompts/tools/skill-discovery.md" with { type: "text" };
|
|
6
|
+
import type { ToolSession } from ".";
|
|
7
|
+
|
|
8
|
+
const skillDiscoverySchema = z
|
|
9
|
+
.object({
|
|
10
|
+
query: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("words to match against skill name, description, source, or use conditions"),
|
|
14
|
+
source: z.enum(["all", "project", "user"]).default("all").optional().describe("skill source scope to search"),
|
|
15
|
+
limit: z.number().min(1).max(50).default(20).optional().describe("maximum results"),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
|
|
19
|
+
export type SkillDiscoveryToolInput = z.infer<typeof skillDiscoverySchema>;
|
|
20
|
+
|
|
21
|
+
export interface SkillDiscoveryToolDetails {
|
|
22
|
+
candidates: RuntimeSkillDiscoveryCandidate[];
|
|
23
|
+
count: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema, SkillDiscoveryToolDetails> {
|
|
27
|
+
readonly name = "skill_discovery";
|
|
28
|
+
readonly label = "SkillDiscovery";
|
|
29
|
+
readonly summary = "Discover project and user runtime skills by thin metadata";
|
|
30
|
+
readonly loadMode = "essential";
|
|
31
|
+
readonly description: string;
|
|
32
|
+
readonly parameters = skillDiscoverySchema;
|
|
33
|
+
readonly strict = true;
|
|
34
|
+
|
|
35
|
+
readonly #session: ToolSession;
|
|
36
|
+
|
|
37
|
+
constructor(session: ToolSession) {
|
|
38
|
+
this.#session = session;
|
|
39
|
+
this.description = prompt.render(skillDiscoveryDescription);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#getRuntimeSkillPolicy() {
|
|
43
|
+
return {
|
|
44
|
+
...this.#session.settings.getGroup("skills"),
|
|
45
|
+
disabledExtensions: this.#session.settings.get("disabledExtensions"),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static createIf(session: ToolSession): SkillDiscoveryTool | null {
|
|
50
|
+
if (session.settings.get("skill.enabled") === false) return null;
|
|
51
|
+
return new SkillDiscoveryTool(session);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async execute(
|
|
55
|
+
_toolCallId: string,
|
|
56
|
+
input: SkillDiscoveryToolInput,
|
|
57
|
+
signal?: AbortSignal,
|
|
58
|
+
): Promise<AgentToolResult<SkillDiscoveryToolDetails>> {
|
|
59
|
+
return untilAborted(signal, async () => {
|
|
60
|
+
const candidates = await discoverRuntimeSkills({
|
|
61
|
+
cwd: this.#session.cwd,
|
|
62
|
+
query: input.query,
|
|
63
|
+
source: input.source ?? "all",
|
|
64
|
+
limit: input.limit,
|
|
65
|
+
policy: this.#getRuntimeSkillPolicy(),
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify({ candidates, count: candidates.length }, null, 2) }],
|
|
69
|
+
details: { candidates, count: candidates.length },
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/tools/skill.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
|
19
19
|
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
20
20
|
import * as z from "zod/v4";
|
|
21
21
|
import { resolveSubskillActivationForSkillInvocation } from "../extensibility/gjc-plugins";
|
|
22
|
+
import { findRuntimeSkillByName } from "../extensibility/runtime-skill-discovery";
|
|
22
23
|
import { buildSkillPromptMessage } from "../extensibility/skills";
|
|
23
24
|
import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
|
|
24
25
|
import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
|
|
@@ -64,6 +65,13 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
64
65
|
this.description = prompt.render(skillDescription);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
#getRuntimeSkillPolicy() {
|
|
69
|
+
return {
|
|
70
|
+
...this.#session.settings.getGroup("skills"),
|
|
71
|
+
disabledExtensions: this.#session.settings.get("disabledExtensions"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
static createIf(session: ToolSession): SkillTool | null {
|
|
68
76
|
// The tool can only chain when the session can deliver the same-turn
|
|
69
77
|
// custom message. Without `sendCustomMessage` (e.g. minimal tool
|
|
@@ -110,10 +118,15 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
110
118
|
);
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
const skill =
|
|
121
|
+
const skill =
|
|
122
|
+
skills.find(s => s.name === requestedName) ??
|
|
123
|
+
(await findRuntimeSkillByName(this.#session.cwd, requestedName, this.#getRuntimeSkillPolicy()));
|
|
114
124
|
if (!skill) {
|
|
115
125
|
const available = skills.map(s => s.name).sort();
|
|
116
|
-
const hint =
|
|
126
|
+
const hint =
|
|
127
|
+
available.length > 0
|
|
128
|
+
? ` Available: ${available.join(", ")}. Use skill_discovery to find project/user runtime skills.`
|
|
129
|
+
: " Use skill_discovery to find project/user runtime skills.";
|
|
117
130
|
throw new ToolError(`skill tool: unknown skill "${requestedName}".${hint}`);
|
|
118
131
|
}
|
|
119
132
|
|