@gajae-code/coding-agent 0.7.7 → 0.7.9
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 +38 -1
- package/README.md +14 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +19 -2
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +9 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +45 -0
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +14 -5
- package/dist/types/gjc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +3 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-input.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-selector.d.ts +4 -1
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +7 -3
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +21 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon.d.ts +1 -10
- package/dist/types/tools/ask.d.ts +24 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/package.json +7 -7
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +5 -3
- package/src/cli/update-cli.ts +155 -13
- package/src/cli.ts +19 -0
- package/src/commands/launch.ts +46 -1
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +92 -11
- package/src/config/model-profiles.ts +15 -2
- package/src/config/model-registry.ts +136 -6
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +48 -0
- package/src/config/settings.ts +11 -0
- package/src/coordinator-mcp/server.ts +280 -36
- package/src/deep-interview/render-middleware.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -6
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
- package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +321 -38
- package/src/gjc-runtime/launch-worktree.ts +16 -2
- package/src/gjc-runtime/team-runtime.ts +87 -7
- package/src/gjc-runtime/tmux-common.ts +16 -0
- package/src/gjc-runtime/tmux-sessions.ts +13 -9
- package/src/gjc-runtime/ultragoal-guard.ts +11 -5
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/modes/components/agent-dashboard.ts +2 -1
- package/src/modes/components/custom-model-preset-wizard.ts +31 -192
- package/src/modes/components/hook-editor.ts +7 -4
- package/src/modes/components/hook-input.ts +4 -0
- package/src/modes/components/model-selector.ts +260 -12
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/settings-selector.ts +506 -5
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +70 -35
- package/src/modes/controllers/event-controller.ts +98 -4
- package/src/modes/controllers/extension-ui-controller.ts +5 -0
- package/src/modes/controllers/input-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +142 -11
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/prompt-action-autocomplete.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +11 -4
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +6 -1
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon.ts +6 -21
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/sdk.ts +10 -1
- package/src/session/agent-session.ts +84 -6
- package/src/slash-commands/builtin-registry.ts +106 -13
- package/src/tools/ask.ts +11 -1
- package/src/tools/eval.ts +2 -2
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
13
13
|
type GjcModelAssignmentTargetId,
|
|
14
14
|
} from "../config/model-registry";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
extractExplicitThinkingSelector,
|
|
17
|
+
formatModelSelectorValue,
|
|
18
|
+
parseModelPattern,
|
|
19
|
+
parseModelString,
|
|
20
|
+
} from "../config/model-resolver";
|
|
16
21
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../discovery/helpers.js";
|
|
17
22
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
18
23
|
import { DynamicBorder } from "../modes/components/dynamic-border";
|
|
@@ -169,11 +174,32 @@ function splitExplicitThinkingSelector(selector: string): { baseSelector: string
|
|
|
169
174
|
return thinkingLevel ? { baseSelector: trimmed.slice(0, colonIndex), thinkingLevel } : { baseSelector: trimmed };
|
|
170
175
|
}
|
|
171
176
|
|
|
172
|
-
|
|
177
|
+
interface ModelCommandSelection {
|
|
178
|
+
model: Model;
|
|
179
|
+
selector: string;
|
|
180
|
+
thinkingLevel?: ThinkingLevel;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface ModelCommandResolutionFailure {
|
|
184
|
+
message: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
type ModelCommandResolution =
|
|
188
|
+
| { ok: true; selection: ModelCommandSelection }
|
|
189
|
+
| { ok: false; failure: ModelCommandResolutionFailure };
|
|
190
|
+
|
|
191
|
+
function parseProviderQualifiedSelector(selector: string): { provider: string; modelId: string } | undefined {
|
|
192
|
+
const splitSelector = splitExplicitThinkingSelector(selector);
|
|
193
|
+
const parsed = parseModelString(splitSelector.baseSelector);
|
|
194
|
+
if (!parsed) return undefined;
|
|
195
|
+
return { provider: parsed.provider, modelId: parsed.id };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function resolveModelCommandSelectionFromAvailable(
|
|
173
199
|
runtime: SlashCommandRuntime,
|
|
174
200
|
selector: string,
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
availableModels: Model[],
|
|
202
|
+
): ModelCommandSelection | undefined {
|
|
177
203
|
const matchPreferences = { usageOrder: runtime.settings.getStorage()?.getModelUsageOrder() };
|
|
178
204
|
const resolved = parseModelPattern(selector, availableModels, matchPreferences, {
|
|
179
205
|
modelRegistry: runtime.session.modelRegistry,
|
|
@@ -198,6 +224,78 @@ function resolveModelCommandSelection(
|
|
|
198
224
|
};
|
|
199
225
|
}
|
|
200
226
|
|
|
227
|
+
function formatDiscoverableProviderFailure(
|
|
228
|
+
selector: string,
|
|
229
|
+
provider: string,
|
|
230
|
+
modelId: string,
|
|
231
|
+
runtime: SlashCommandRuntime,
|
|
232
|
+
): string {
|
|
233
|
+
const state = runtime.session.modelRegistry.getProviderDiscoveryState?.(provider);
|
|
234
|
+
const discovered = state?.models ?? [];
|
|
235
|
+
const base = `Unknown model: ${selector}.`;
|
|
236
|
+
if (!modelId.trim()) {
|
|
237
|
+
return `${base} Local provider model selectors must use provider/model-id syntax with a non-empty model id.`;
|
|
238
|
+
}
|
|
239
|
+
if (!state) {
|
|
240
|
+
return `${base} Provider ${provider} is configured for discovery but has not reported models yet.`;
|
|
241
|
+
}
|
|
242
|
+
if (state.status === "unavailable") {
|
|
243
|
+
const details = state.error ? ` (${state.error})` : "";
|
|
244
|
+
return `${base} Provider ${provider} discovery is unavailable${details}. Check the local endpoint and run /model again.`;
|
|
245
|
+
}
|
|
246
|
+
if (state.status === "unauthenticated") {
|
|
247
|
+
return `${base} Provider ${provider} requires authentication before model discovery.`;
|
|
248
|
+
}
|
|
249
|
+
if (state.status === "empty") {
|
|
250
|
+
return `${base} Provider ${provider} discovery succeeded but returned no models.`;
|
|
251
|
+
}
|
|
252
|
+
if (discovered.length > 0) {
|
|
253
|
+
const preview = discovered.slice(0, 8).join(", ");
|
|
254
|
+
const suffix = discovered.length > 8 ? ", …" : "";
|
|
255
|
+
return `${base} Provider ${provider} did not report model ${modelId}. Available local models: ${preview}${suffix}.`;
|
|
256
|
+
}
|
|
257
|
+
return `${base} Provider ${provider} did not report model ${modelId}.`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function resolveModelCommandSelection(
|
|
261
|
+
runtime: SlashCommandRuntime,
|
|
262
|
+
selector: string,
|
|
263
|
+
): Promise<ModelCommandResolution> {
|
|
264
|
+
let availableModels = runtime.session.getAvailableModels?.() ?? [];
|
|
265
|
+
const initialSelection = resolveModelCommandSelectionFromAvailable(runtime, selector, availableModels as Model[]);
|
|
266
|
+
if (initialSelection) {
|
|
267
|
+
return { ok: true, selection: initialSelection };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const providerRef = parseProviderQualifiedSelector(selector);
|
|
271
|
+
const discoverableProviders = runtime.session.modelRegistry?.getDiscoverableProviders?.() ?? [];
|
|
272
|
+
if (providerRef && discoverableProviders.includes(providerRef.provider)) {
|
|
273
|
+
await runtime.session.modelRegistry.refreshProvider?.(providerRef.provider, "online");
|
|
274
|
+
availableModels = runtime.session.getAvailableModels?.() ?? [];
|
|
275
|
+
const refreshedSelection = resolveModelCommandSelectionFromAvailable(
|
|
276
|
+
runtime,
|
|
277
|
+
selector,
|
|
278
|
+
availableModels as Model[],
|
|
279
|
+
);
|
|
280
|
+
if (refreshedSelection) {
|
|
281
|
+
return { ok: true, selection: refreshedSelection };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ok: false,
|
|
285
|
+
failure: {
|
|
286
|
+
message: formatDiscoverableProviderFailure(selector, providerRef.provider, providerRef.modelId, runtime),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
failure: {
|
|
294
|
+
message: `Unknown model: ${selector}. Configure or login to a provider first, then list/select models with /model.`,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
201
299
|
function modelSelectionUsage(runtime: SlashCommandRuntime, currentModelLine?: string): string {
|
|
202
300
|
return [
|
|
203
301
|
currentModelLine,
|
|
@@ -282,16 +380,11 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
282
380
|
runtime,
|
|
283
381
|
);
|
|
284
382
|
}
|
|
285
|
-
const
|
|
286
|
-
if (!
|
|
287
|
-
return usage(
|
|
288
|
-
modelSelectionUsage(
|
|
289
|
-
runtime,
|
|
290
|
-
`Unknown model: ${modelId}. Configure or login to a provider first, then list/select models with /model.`,
|
|
291
|
-
),
|
|
292
|
-
runtime,
|
|
293
|
-
);
|
|
383
|
+
const resolution = await resolveModelCommandSelection(runtime, modelId);
|
|
384
|
+
if (!resolution.ok) {
|
|
385
|
+
return usage(modelSelectionUsage(runtime, resolution.failure.message), runtime);
|
|
294
386
|
}
|
|
387
|
+
const { selection } = resolution;
|
|
295
388
|
try {
|
|
296
389
|
const persistedSelector = formatModelSelectorValue(selection.selector, selection.thinkingLevel);
|
|
297
390
|
if (parsedArgs.targetId === "default") {
|
package/src/tools/ask.ts
CHANGED
|
@@ -62,6 +62,11 @@ const DeepInterviewMeta = z.object({
|
|
|
62
62
|
ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
const WorkflowGateMeta = z.object({
|
|
66
|
+
stage: z.enum(["deep-interview", "ralplan", "ultragoal"]).describe("workflow gate stage"),
|
|
67
|
+
kind: z.enum(["question", "approval", "execution"]).describe("workflow gate kind"),
|
|
68
|
+
});
|
|
69
|
+
|
|
65
70
|
const QuestionItem = z.object({
|
|
66
71
|
id: z.string().describe("question id"),
|
|
67
72
|
question: z.string().describe("question text"),
|
|
@@ -69,6 +74,7 @@ const QuestionItem = z.object({
|
|
|
69
74
|
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
70
75
|
recommended: z.number().describe("recommended option index").optional(),
|
|
71
76
|
deepInterview: DeepInterviewMeta.describe("optional deep-interview round metadata").optional(),
|
|
77
|
+
workflowGate: WorkflowGateMeta.describe("optional workflow gate stage/kind override").optional(),
|
|
72
78
|
});
|
|
73
79
|
|
|
74
80
|
export const askSchema = z.object({
|
|
@@ -551,7 +557,10 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
551
557
|
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
552
558
|
context?: AgentToolContext,
|
|
553
559
|
): Promise<AgentToolResult<AskToolDetails>> {
|
|
554
|
-
await assertUltragoalAskAllowed(this.session.cwd
|
|
560
|
+
await assertUltragoalAskAllowed(this.session.cwd, {
|
|
561
|
+
activeSkillState: this.session.getActiveSkillState?.(),
|
|
562
|
+
sessionId: this.session.getSessionId?.() ?? null,
|
|
563
|
+
});
|
|
555
564
|
const gateEmitter = this.session.getWorkflowGateEmitter?.();
|
|
556
565
|
const canUseWorkflowGate = gateEmitter?.isUnattended() === true;
|
|
557
566
|
|
|
@@ -665,6 +674,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
665
674
|
multi: q.multi,
|
|
666
675
|
recommended: q.recommended,
|
|
667
676
|
deepInterview: q.deepInterview,
|
|
677
|
+
workflowGate: q.workflowGate,
|
|
668
678
|
};
|
|
669
679
|
const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
|
|
670
680
|
const decoded = gateAnswerToResult(gateQuestion, answer);
|
package/src/tools/eval.ts
CHANGED
|
@@ -754,8 +754,8 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
|
|
|
754
754
|
case "git_log":
|
|
755
755
|
if (data.entries) {
|
|
756
756
|
addItems(data.entries as unknown[], e => {
|
|
757
|
-
const entry = e as { sha
|
|
758
|
-
return `${entry.sha} ${truncateToWidth(entry.subject, 50)}`;
|
|
757
|
+
const entry = e as { sha?: string; subject?: string };
|
|
758
|
+
return `${String(entry.sha ?? "")} ${truncateToWidth(String(entry.subject ?? ""), 50)}`;
|
|
759
759
|
});
|
|
760
760
|
}
|
|
761
761
|
break;
|
|
@@ -86,6 +86,7 @@ export const EXPAND_HINT = "(Ctrl+O for more)";
|
|
|
86
86
|
* Get first N lines of text as preview, with each line truncated.
|
|
87
87
|
*/
|
|
88
88
|
export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis?: Ellipsis): string[] {
|
|
89
|
+
if (typeof text !== "string") return [];
|
|
89
90
|
const lines = text.split("\n").filter(l => l.trim());
|
|
90
91
|
return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis));
|
|
91
92
|
}
|
|
@@ -568,6 +569,7 @@ export function truncateDiffByHunk(
|
|
|
568
569
|
// =============================================================================
|
|
569
570
|
|
|
570
571
|
export function shortenPath(filePath: string, homeDir?: string): string {
|
|
572
|
+
if (typeof filePath !== "string") return "";
|
|
571
573
|
const home = homeDir ?? os.homedir();
|
|
572
574
|
if (home && filePath.startsWith(home)) {
|
|
573
575
|
return `~${filePath.slice(home.length)}`;
|
|
@@ -10,6 +10,29 @@ const ULTRAGOAL_ASK_GUARD = Symbol.for("gajae-code.ultragoalAskGuard");
|
|
|
10
10
|
|
|
11
11
|
type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
|
|
12
12
|
|
|
13
|
+
export interface UltragoalAskGuardContext {
|
|
14
|
+
activeSkillState?: { skill?: string; session_id?: string } | null;
|
|
15
|
+
sessionId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const UPSTREAM_PLANNING_ASK_SKILLS = new Set(["deep-interview", "ralplan"]);
|
|
19
|
+
|
|
20
|
+
function normalizedActiveSkill(context?: UltragoalAskGuardContext): string | undefined {
|
|
21
|
+
const skill = context?.activeSkillState?.skill?.trim();
|
|
22
|
+
return skill || undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sessionScopedAskGuardId(
|
|
26
|
+
context: UltragoalAskGuardContext,
|
|
27
|
+
activeSkill: string | undefined,
|
|
28
|
+
): string | undefined {
|
|
29
|
+
if (activeSkill !== "ultragoal" && !UPSTREAM_PLANNING_ASK_SKILLS.has(activeSkill ?? "")) return undefined;
|
|
30
|
+
const activeSessionId = context.activeSkillState?.session_id?.trim();
|
|
31
|
+
if (activeSessionId) return activeSessionId;
|
|
32
|
+
const sessionId = context.sessionId?.trim();
|
|
33
|
+
return sessionId || undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
|
|
14
37
|
return [
|
|
15
38
|
diagnostic.message,
|
|
@@ -18,15 +41,26 @@ export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiag
|
|
|
18
41
|
].join("\n");
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
export async function assertUltragoalAskAllowed(cwd: string): Promise<void> {
|
|
22
|
-
const
|
|
44
|
+
export async function assertUltragoalAskAllowed(cwd: string, context: UltragoalAskGuardContext = {}): Promise<void> {
|
|
45
|
+
const activeSkill = normalizedActiveSkill(context);
|
|
46
|
+
// Deep-interview and ralplan are upstream planning workflows whose core gates
|
|
47
|
+
// are `ask` calls. Scope their Ultragoal check to the current session so stale
|
|
48
|
+
// or ambiguous Ultragoal durable state from another session cannot hijack those
|
|
49
|
+
// prompts; same-session active Ultragoal state still falls through to the
|
|
50
|
+
// blocker/nudge checks below.
|
|
51
|
+
const sessionId = sessionScopedAskGuardId(context, activeSkill);
|
|
52
|
+
const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
|
|
23
53
|
if (!diagnostic.active) return;
|
|
24
|
-
const nudge = await consumeUltragoalAskNudge(cwd);
|
|
54
|
+
const nudge = await consumeUltragoalAskNudge(cwd, sessionId);
|
|
25
55
|
if (nudge.nudged) throw new ToolError(nudge.message);
|
|
26
56
|
throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
59
|
+
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
60
|
+
tool: T,
|
|
61
|
+
getCwd: () => string,
|
|
62
|
+
getContext: () => UltragoalAskGuardContext = () => ({}),
|
|
63
|
+
): T {
|
|
30
64
|
if (tool.name !== "ask") return tool;
|
|
31
65
|
const candidate = tool as GuardedTool;
|
|
32
66
|
if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
|
|
@@ -35,7 +69,7 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: (
|
|
|
35
69
|
if (prop === ULTRAGOAL_ASK_GUARD) return true;
|
|
36
70
|
if (prop !== "execute") return Reflect.get(target, prop, receiver);
|
|
37
71
|
return async (...args: unknown[]): Promise<unknown> => {
|
|
38
|
-
await assertUltragoalAskAllowed(getCwd());
|
|
72
|
+
await assertUltragoalAskAllowed(getCwd(), getContext());
|
|
39
73
|
return Reflect.apply(target.execute, target, args);
|
|
40
74
|
};
|
|
41
75
|
},
|