@gajae-code/coding-agent 0.8.1 → 0.8.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.
Files changed (112) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/types/cli/completion-cli.d.ts +41 -0
  3. package/dist/types/cli/config-cli.d.ts +1 -0
  4. package/dist/types/cli/notify-cli.d.ts +7 -0
  5. package/dist/types/cli.d.ts +124 -1
  6. package/dist/types/commands/completion.d.ts +29 -0
  7. package/dist/types/commands/config.d.ts +3 -0
  8. package/dist/types/commands/deep-interview.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +7 -6
  10. package/dist/types/config/keybindings.d.ts +2 -2
  11. package/dist/types/config/model-profile-activation.d.ts +6 -0
  12. package/dist/types/config/settings.d.ts +8 -0
  13. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  14. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  15. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +39 -0
  16. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  17. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -1
  18. package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
  19. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  21. package/dist/types/main.d.ts +4 -1
  22. package/dist/types/modes/components/model-selector.d.ts +4 -0
  23. package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
  24. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  25. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  26. package/dist/types/modes/print-mode.d.ts +13 -1
  27. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
  28. package/dist/types/notifications/config.d.ts +8 -1
  29. package/dist/types/notifications/daemon-paths.d.ts +1 -0
  30. package/dist/types/notifications/index.d.ts +6 -3
  31. package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
  32. package/dist/types/notifications/telegram-daemon.d.ts +15 -3
  33. package/dist/types/notifications/telegram-reference.d.ts +0 -5
  34. package/dist/types/runtime-api-key.d.ts +7 -0
  35. package/dist/types/session/agent-session.d.ts +10 -0
  36. package/dist/types/session/agent-storage.d.ts +1 -0
  37. package/dist/types/task/executor.d.ts +2 -0
  38. package/dist/types/tools/computer.d.ts +2 -2
  39. package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
  40. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
  41. package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
  42. package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
  43. package/package.json +7 -7
  44. package/src/cli/completion-cli.ts +234 -0
  45. package/src/cli/config-cli.ts +37 -9
  46. package/src/cli/fast-help.ts +1 -0
  47. package/src/cli/notify-cli.ts +91 -7
  48. package/src/cli.ts +3 -1
  49. package/src/commands/completion.ts +84 -0
  50. package/src/commands/config.ts +2 -0
  51. package/src/commands/deep-interview.ts +2 -1
  52. package/src/config/file-lock.ts +73 -34
  53. package/src/config/keybindings.ts +2 -2
  54. package/src/config/model-profile-activation.ts +65 -0
  55. package/src/config/model-registry.ts +14 -2
  56. package/src/config/settings.ts +22 -0
  57. package/src/coordinator-mcp/server.ts +34 -8
  58. package/src/defaults/gjc/skills/deep-interview/SKILL.md +38 -5
  59. package/src/defaults/gjc/skills/ralplan/SKILL.md +15 -11
  60. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  61. package/src/extensibility/extensions/runner.ts +2 -0
  62. package/src/extensibility/extensions/types.ts +9 -0
  63. package/src/gjc-runtime/deep-interview-ambiguity.ts +206 -0
  64. package/src/gjc-runtime/deep-interview-recorder.ts +62 -8
  65. package/src/gjc-runtime/deep-interview-runtime.ts +210 -2
  66. package/src/gjc-runtime/deep-interview-state.ts +13 -0
  67. package/src/gjc-runtime/session-state-sidecar.ts +59 -11
  68. package/src/gjc-runtime/state-runtime.ts +12 -2
  69. package/src/gjc-runtime/team-runtime.ts +23 -0
  70. package/src/gjc-runtime/ultragoal-runtime.ts +73 -19
  71. package/src/hooks/native-skill-hook.ts +29 -0
  72. package/src/internal-urls/docs-index.generated.ts +2 -2
  73. package/src/main.ts +52 -41
  74. package/src/modes/components/model-selector.ts +54 -11
  75. package/src/modes/components/queued-message-selector.ts +92 -0
  76. package/src/modes/components/settings-selector.ts +4 -0
  77. package/src/modes/components/welcome.ts +160 -44
  78. package/src/modes/controllers/command-controller.ts +1 -2
  79. package/src/modes/controllers/extension-ui-controller.ts +4 -10
  80. package/src/modes/controllers/input-controller.ts +179 -17
  81. package/src/modes/controllers/selector-controller.ts +122 -27
  82. package/src/modes/interactive-mode.ts +43 -16
  83. package/src/modes/print-mode.ts +43 -4
  84. package/src/modes/prompt-action-autocomplete.ts +21 -20
  85. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  86. package/src/modes/utils/ui-helpers.ts +2 -2
  87. package/src/notifications/config.ts +9 -1
  88. package/src/notifications/daemon-paths.ts +2 -0
  89. package/src/notifications/index.ts +39 -9
  90. package/src/notifications/lifecycle-commands.ts +37 -7
  91. package/src/notifications/lifecycle-orchestrator.ts +19 -6
  92. package/src/notifications/telegram-daemon-control.ts +3 -1
  93. package/src/notifications/telegram-daemon.ts +217 -43
  94. package/src/notifications/telegram-reference.ts +0 -16
  95. package/src/prompts/system/system-prompt.md +3 -0
  96. package/src/prompts/tools/computer.md +2 -2
  97. package/src/runtime-api-key.ts +16 -0
  98. package/src/sdk.ts +20 -6
  99. package/src/session/agent-session.ts +225 -31
  100. package/src/session/agent-storage.ts +4 -0
  101. package/src/session/session-manager.ts +44 -2
  102. package/src/slash-commands/builtin-registry.ts +153 -71
  103. package/src/task/executor.ts +7 -4
  104. package/src/task/index.ts +9 -3
  105. package/src/task/output-manager.ts +16 -6
  106. package/src/task/receipt.ts +26 -7
  107. package/src/tools/computer.ts +246 -43
  108. package/src/tools/telegram-send.ts +6 -0
  109. package/src/workflow/workflow-intent-diff.ts +29 -6
  110. package/src/workflow/workflow-intent-report-consumer.ts +221 -0
  111. package/src/workflow/workflow-intent-report-schema.ts +129 -0
  112. package/src/workflow/workflow-intent-report.ts +135 -0
@@ -23,6 +23,12 @@ export declare function isGloballyConfigured(cfg: NotificationConfig): boolean;
23
23
  export declare function shouldRegisterNotificationsExtension(input: {
24
24
  env: NodeJS.ProcessEnv;
25
25
  cfg?: NotificationConfig;
26
+ /** Task recursion depth; helper/subagent sessions must not spawn remote surfaces. */
27
+ taskDepth?: number;
28
+ /** Parent subagent id/prefix; present for helper/subagent sessions even when depth is omitted. */
29
+ parentTaskPrefix?: string;
30
+ /** Role-agent type/name; present for task sessions even if depth metadata is lost. */
31
+ currentAgentType?: string;
26
32
  }): boolean;
27
33
  /**
28
34
  * Resolve whether THIS session should run notifications.
@@ -61,7 +67,8 @@ export interface RedactableAction {
61
67
  * When redact is false, return the action unchanged.
62
68
  *
63
69
  * Redaction still applies to streamed content frames (turn_stream, context_update,
64
- * image_attachment) which are suppressed at their emit sites, not here.
70
+ * image_attachment) which are suppressed at their emit sites, not here. Explicit
71
+ * `telegram_send` file attachments are rejected before the file is read or forwarded.
65
72
  */
66
73
  export declare function buildRedactedAction(action: RedactableAction, opts: {
67
74
  redact: boolean;
@@ -5,5 +5,6 @@ export interface DaemonPaths {
5
5
  roots: string;
6
6
  steal: string;
7
7
  aliases: string;
8
+ seenUpdates: string;
8
9
  }
9
10
  export declare function daemonPaths(agentDir: string): DaemonPaths;
@@ -17,7 +17,8 @@
17
17
  * Enable with Settings notifications config, `GJC_NOTIFICATIONS=1` (a token is
18
18
  * generated), or `GJC_NOTIFICATIONS_TOKEN`.
19
19
  */
20
- import type { ExtensionFactory } from "../extensibility/extensions";
20
+ import { Settings } from "../config/settings";
21
+ import type { ExtensionAPI } from "../extensibility/extensions";
21
22
  /** Where a `session_create` should run. Discriminated by `kind`. */
22
23
  export type SessionCreateTarget = {
23
24
  kind: "existing_path";
@@ -69,7 +70,7 @@ export interface SessionCloseFrame {
69
70
  chatId: string;
70
71
  token: string;
71
72
  target: SessionCloseTarget;
72
- /** Hard-kill even if a live pane is attached (GJC-managed only). */
73
+ /** Required force-only close flag; false/omitted is rejected by daemon policy. */
73
74
  force?: boolean;
74
75
  }
75
76
  /** Resume a session (reattach if alive, else cold-restart from history). */
@@ -173,4 +174,6 @@ export interface SessionReadyFrame {
173
174
  */
174
175
  export declare function readGitRepoName(cwd: string): string | undefined;
175
176
  export declare function notificationsEnabled(): boolean;
176
- export declare const createNotificationsExtension: ExtensionFactory;
177
+ export declare function createNotificationsExtension(api: ExtensionAPI, options?: {
178
+ settings?: Settings;
179
+ }): void;
@@ -23,8 +23,13 @@ export type ParsedLifecycleCommand = {
23
23
  } | {
24
24
  kind: "none";
25
25
  };
26
- /** True when the text begins a /session_* command (cheap pre-gate). */
27
- export declare function isLifecycleCommandText(text: string | undefined): boolean;
26
+ /** True when the text begins with any /session_* token, regardless of addressability. */
27
+ export declare function isLifecycleCommandLikeText(text: string | undefined): boolean;
28
+ /** True when the text begins an addressable /session_* command (cheap pre-gate). */
29
+ export declare function isLifecycleCommandText(text: string | undefined, ctx?: {
30
+ chatType?: string;
31
+ botUsername?: string;
32
+ }): boolean;
28
33
  /**
29
34
  * Parse a paired-chat message into a lifecycle command. Returns `none` for
30
35
  * non-lifecycle text, `usage`/`reject` for malformed input (no side effect), or
@@ -33,7 +38,10 @@ export declare function isLifecycleCommandText(text: string | undefined): boolea
33
38
  * The caller MUST have already enforced paired-chat authorization; this function
34
39
  * performs grammar + target validation only.
35
40
  */
36
- export declare function parseLifecycleCommand(text: string | undefined): ParsedLifecycleCommand;
41
+ export declare function parseLifecycleCommand(text: string | undefined, ctx?: {
42
+ chatType?: string;
43
+ botUsername?: string;
44
+ }): ParsedLifecycleCommand;
37
45
  /** The canonical usage text (exported for the daemon's help replies). */
38
46
  export declare function lifecycleUsage(): string;
39
47
  /**
@@ -3,8 +3,8 @@ import type { Settings } from "../config/settings";
3
3
  import type { DaemonRuntimeInfo } from "../daemon/control-types";
4
4
  import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
5
5
  import { NotificationOperatorRuntime, OperatorBackoffPolicy } from "./operator-runtime";
6
- import { type AliasTable, type CallbackRoute, type PendingAsk } from "./telegram-reference";
7
- export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled";
6
+ import { type AliasTable, type CallbackRoute } from "./telegram-reference";
7
+ export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
8
8
  export interface DaemonState {
9
9
  pid: number;
10
10
  ownerId: string;
@@ -95,6 +95,8 @@ export declare function acquireDaemonOwnership(input: {
95
95
  acquired: boolean;
96
96
  ownerId?: string;
97
97
  attached?: boolean;
98
+ blocked?: boolean;
99
+ reason?: "identity_mismatch";
98
100
  }>;
99
101
  export declare function renewDaemonHeartbeat(input: {
100
102
  settings: Settings;
@@ -276,6 +278,8 @@ export declare class TelegramNotificationDaemon {
276
278
  private readonly flatIdentitySent;
277
279
  /** Cached result of whether the paired chat is a private chat (flat-fallback gate). */
278
280
  private pairedChatPrivate;
281
+ /** Bot username from getMe, cached once at owner startup for group/forum command targeting. */
282
+ private botUsername;
279
283
  /** Sessions whose agent loop is currently busy (drives the typing indicator). */
280
284
  private get busy();
281
285
  /** Inbound update id → originating Telegram message, for delivery reactions. */
@@ -317,12 +321,17 @@ export declare class TelegramNotificationDaemon {
317
321
  /** Build an authenticated lifecycle frame from a parsed command + identity. */
318
322
  private buildLifecycleFrame;
319
323
  private handleLifecycleCommand;
324
+ private refreshBotIdentity;
320
325
  /** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
321
326
  private formatLifecycleResponse;
322
327
  constructor(opts: TelegramDaemonOptions);
323
328
  private createSessionRouter;
324
329
  loadAliases(): Promise<void>;
325
330
  persistAliases(): Promise<void>;
331
+ loadSeenUpdateIds(): Promise<void>;
332
+ persistSeenUpdateIds(): Promise<void>;
333
+ private pruneSeenUpdateIds;
334
+ private rememberSeenUpdateId;
326
335
  scanRoots(): Promise<void>;
327
336
  connectSession(sessionId: string, url: string, token: string): void;
328
337
  /**
@@ -340,12 +349,15 @@ export declare class TelegramNotificationDaemon {
340
349
  * the socket. `scanRoots()` then reconnects the session.
341
350
  */
342
351
  private dropSession;
352
+ private deleteMessageRoutes;
343
353
  private static readonly THREADED_FRAMES;
344
354
  private topicNameFor;
345
355
  private topicIdentityKey;
346
356
  private topicIdentityBase;
347
357
  private topicOwnerForIdentity;
358
+ private sessionCanClaimIdentity;
348
359
  private submitThreadedFrame;
360
+ private existingTopicForPrivateChat;
349
361
  private rememberPendingThreadedFrame;
350
362
  private flushPendingThreadedFrames;
351
363
  private ensureTopic;
@@ -376,7 +388,7 @@ export declare class TelegramNotificationDaemon {
376
388
  private startTypingTimer;
377
389
  private stopTypingTimer;
378
390
  handleSessionMessage(session: SessionSocket, msg: any): Promise<void>;
379
- pendingBySession: (sessionId?: string) => PendingAsk[];
391
+ private answerCallbackQueryBestEffort;
380
392
  private sendStaleGuidance;
381
393
  handleTelegramUpdate(update: unknown): Promise<void>;
382
394
  pollOnce(signal?: AbortSignal): Promise<number>;
@@ -81,14 +81,9 @@ export type RouteDecision = ({
81
81
  } | {
82
82
  kind: "ignore";
83
83
  };
84
- export interface PendingAsk {
85
- sessionId: string;
86
- actionId: string;
87
- }
88
84
  export interface RouteInboundContext {
89
85
  aliasTable: Pick<AliasTable, "get">;
90
86
  messageRoutes: Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>;
91
- pendingBySession: (sessionId?: string) => PendingAsk[];
92
87
  pairedChatId: string;
93
88
  }
94
89
  /** Route a Telegram update to a session/action without I/O. Fail closed under ambiguity. */
@@ -0,0 +1,7 @@
1
+ export interface RuntimeApiKeyTarget {
2
+ setRuntimeApiKey(provider: string, apiKey: string): void;
3
+ }
4
+ export interface RuntimeApiKeyModel {
5
+ provider: string;
6
+ }
7
+ export declare function applyCliRuntimeApiKeyOverride(target: RuntimeApiKeyTarget, apiKey: string | undefined, model: RuntimeApiKeyModel | undefined): void;
@@ -317,6 +317,13 @@ export interface SessionStats {
317
317
  premiumRequests: number;
318
318
  cost: number;
319
319
  }
320
+ export type QueuedMessageEditMode = "steer" | "followUp";
321
+ export interface QueuedMessageEditEntry {
322
+ id: string;
323
+ text: string;
324
+ mode: QueuedMessageEditMode;
325
+ label: string;
326
+ }
320
327
  /** A custom message contributed at the before-agent-start point. */
321
328
  export type BeforeAgentStartInternalMessage = Pick<CustomMessage, "customType" | "content" | "display" | "details" | "attribution">;
322
329
  /**
@@ -670,6 +677,9 @@ export declare class AgentSession {
670
677
  steering: readonly string[];
671
678
  followUp: readonly string[];
672
679
  };
680
+ getQueuedMessageEntries(): QueuedMessageEditEntry[];
681
+ removeQueuedMessageForEditing(id: string): string | undefined;
682
+ moveQueuedMessageForEditing(id: string, direction: "up" | "down"): boolean;
673
683
  /**
674
684
  * Pop the newest queued message across steering and follow-up queues.
675
685
  * Used by dequeue keybinding to restore messages to editor one at a time.
@@ -93,6 +93,7 @@ export declare class AgentStorage {
93
93
  * Sets a cached value with expiry time (unix seconds).
94
94
  */
95
95
  setCache(key: string, value: string, expiresAtSec: number): void;
96
+ deleteCachePrefix(prefix: string): void;
96
97
  /**
97
98
  * Deletes expired cache entries. Call periodically for cleanup.
98
99
  */
@@ -51,6 +51,8 @@ export interface ExecutorOptions {
51
51
  artifactsDir?: string;
52
52
  /** Path to parent conversation context file */
53
53
  contextFile?: string;
54
+ /** Whether the parent runtime actually exposes IRC coordination. */
55
+ ircAvailable?: boolean;
54
56
  eventBus?: EventBus;
55
57
  contextFiles?: ContextFileEntry[];
56
58
  skills?: Skill[];
@@ -238,7 +238,7 @@ export interface ComputerScreenshotDetails {
238
238
  scaleY?: number;
239
239
  originX?: number;
240
240
  originY?: number;
241
- displayEpoch?: string;
241
+ displayEpoch?: number;
242
242
  captureId?: string;
243
243
  pngBytes?: number;
244
244
  path?: string;
@@ -281,7 +281,7 @@ type NativeScreenshot = {
281
281
  scaleY?: number;
282
282
  originX?: number;
283
283
  originY?: number;
284
- displayEpoch?: string;
284
+ displayEpoch?: number;
285
285
  captureId?: string;
286
286
  };
287
287
  export type ComputerControllerFactory = () => NativeController;
@@ -1,5 +1,9 @@
1
+ import { type WorkflowClaimsLedger, type WorkflowConsensusReport } from "./workflow-intent-report";
1
2
  export declare const WORKFLOW_INTENT_DIFF_CUSTOM_TYPE = "workflow-intent-diff";
2
- export type WorkflowIntentRoute = "direct" | "deep-interview" | "ralplan" | "ultragoal" | "team";
3
+ export declare const WORKFLOW_INTENT_ROUTES: readonly ["direct", "deep-interview", "ralplan", "ultragoal", "team"];
4
+ export declare const WORKFLOW_ESCALATION_ROUTES: readonly ["deep-interview", "ralplan", "ultragoal", "team"];
5
+ export type WorkflowIntentRoute = (typeof WORKFLOW_INTENT_ROUTES)[number];
6
+ export type WorkflowEscalationRoute = (typeof WORKFLOW_ESCALATION_ROUTES)[number];
3
7
  export type DirectTrackingMode = "custom-entry-only" | "not-direct";
4
8
  export type RootCausePhaseStatus = "active" | "inactive";
5
9
  export interface WorkflowIntentDiff {
@@ -7,13 +11,15 @@ export interface WorkflowIntentDiff {
7
11
  readonly route: WorkflowIntentRoute;
8
12
  readonly reason: string;
9
13
  readonly directTracking: DirectTrackingMode;
10
- readonly recommendedSkill?: Exclude<WorkflowIntentRoute, "direct">;
14
+ readonly recommendedSkill?: WorkflowEscalationRoute;
11
15
  readonly recommendedInvocation?: string;
12
16
  readonly triggers: readonly string[];
13
17
  readonly rootCausePhase: {
14
18
  readonly status: RootCausePhaseStatus;
15
19
  readonly triggers: readonly string[];
16
20
  };
21
+ readonly claimsLedger: WorkflowClaimsLedger;
22
+ readonly consensusReport: WorkflowConsensusReport;
17
23
  readonly promptPreview: string;
18
24
  }
19
25
  export declare function buildWorkflowIntentDiff(text: string): WorkflowIntentDiff | null;
@@ -0,0 +1,50 @@
1
+ import type { SessionEntry } from "../session/session-manager";
2
+ import { type WorkflowIntentDiff, type WorkflowIntentRoute } from "./workflow-intent-diff";
3
+ import type { WorkflowSignalObserver } from "./workflow-intent-report";
4
+ export type WorkflowConsumerConfidence = "none" | "low" | "medium" | "high";
5
+ export interface WorkflowRouteCounts {
6
+ readonly direct: number;
7
+ readonly "deep-interview": number;
8
+ readonly ralplan: number;
9
+ readonly ultragoal: number;
10
+ readonly team: number;
11
+ }
12
+ export interface WorkflowIntentReportEntry {
13
+ readonly entryId: string;
14
+ readonly timestamp: string;
15
+ readonly intent: WorkflowIntentDiff;
16
+ }
17
+ export interface WorkflowIntentReportTotals {
18
+ readonly total: number;
19
+ readonly byRoute: WorkflowRouteCounts;
20
+ readonly escalationRequired: number;
21
+ readonly rootCauseActive: number;
22
+ }
23
+ export interface WorkflowIntentReportCollection {
24
+ readonly entries: readonly WorkflowIntentReportEntry[];
25
+ readonly latest: WorkflowIntentReportEntry | undefined;
26
+ readonly totals: WorkflowIntentReportTotals;
27
+ }
28
+ export interface WorkflowObserverSignalSummary {
29
+ readonly observer: WorkflowSignalObserver;
30
+ readonly conclusion: string;
31
+ readonly count: number;
32
+ }
33
+ export interface WorkflowConsensusSummary {
34
+ readonly total: number;
35
+ readonly dominantRoute: WorkflowIntentRoute | undefined;
36
+ readonly confidence: WorkflowConsumerConfidence;
37
+ readonly escalationGate: {
38
+ readonly required: boolean;
39
+ readonly count: number;
40
+ readonly latestReason: string | undefined;
41
+ };
42
+ readonly rootCausePhase: {
43
+ readonly active: boolean;
44
+ readonly count: number;
45
+ readonly triggers: readonly string[];
46
+ };
47
+ readonly observerSignals: readonly WorkflowObserverSignalSummary[];
48
+ }
49
+ export declare function collectWorkflowIntentReports(entries: readonly SessionEntry[]): WorkflowIntentReportCollection;
50
+ export declare function summarizeWorkflowConsensus(intents: readonly WorkflowIntentDiff[]): WorkflowConsensusSummary;
@@ -0,0 +1,2 @@
1
+ import { type WorkflowIntentDiff } from "./workflow-intent-diff";
2
+ export declare function parseWorkflowIntentDiffPayload(value: unknown): WorkflowIntentDiff | undefined;
@@ -0,0 +1,48 @@
1
+ import type { RootCausePhaseStatus, WorkflowIntentRoute } from "./workflow-intent-diff";
2
+ export type WorkflowConfidence = "high";
3
+ export type WorkflowClaimStatus = "confirmed";
4
+ export type WorkflowEscalationGateStatus = "required" | "not-required";
5
+ export type WorkflowSignalObserver = "intent-router" | "root-cause-schema" | "escalation-gate";
6
+ export interface WorkflowClaim {
7
+ readonly id: "workflow-route" | "root-cause-phase" | "escalation-gate";
8
+ readonly statement: string;
9
+ readonly status: WorkflowClaimStatus;
10
+ readonly confidence: WorkflowConfidence;
11
+ readonly evidence: readonly string[];
12
+ }
13
+ export interface WorkflowClaimsLedger {
14
+ readonly version: 1;
15
+ readonly claims: readonly WorkflowClaim[];
16
+ }
17
+ export interface WorkflowObserverSignal {
18
+ readonly observer: WorkflowSignalObserver;
19
+ readonly conclusion: string;
20
+ readonly evidence: readonly string[];
21
+ }
22
+ export interface WorkflowConsensusReport {
23
+ readonly version: 1;
24
+ readonly route: WorkflowIntentRoute;
25
+ readonly confidence: WorkflowConfidence;
26
+ readonly summary: string;
27
+ readonly observerSignals: readonly WorkflowObserverSignal[];
28
+ readonly escalationGate: {
29
+ readonly status: WorkflowEscalationGateStatus;
30
+ readonly reason: string;
31
+ };
32
+ }
33
+ export interface WorkflowIntentReport {
34
+ readonly claimsLedger: WorkflowClaimsLedger;
35
+ readonly consensusReport: WorkflowConsensusReport;
36
+ }
37
+ export interface WorkflowIntentReportInput {
38
+ readonly route: WorkflowIntentRoute;
39
+ readonly reason: string;
40
+ readonly direct: boolean;
41
+ readonly recommendedInvocation?: string;
42
+ readonly triggers: readonly string[];
43
+ readonly rootCausePhase: {
44
+ readonly status: RootCausePhaseStatus;
45
+ readonly triggers: readonly string[];
46
+ };
47
+ }
48
+ export declare function buildWorkflowIntentReport(input: WorkflowIntentReportInput): WorkflowIntentReport;
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.1",
4
+ "version": "0.8.2",
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.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",
55
+ "@gajae-code/stats": "0.8.2",
56
+ "@gajae-code/agent-core": "0.8.2",
57
+ "@gajae-code/ai": "0.8.2",
58
+ "@gajae-code/natives": "0.8.2",
59
+ "@gajae-code/tui": "0.8.2",
60
+ "@gajae-code/utils": "0.8.2",
61
61
  "@puppeteer/browsers": "^2.13.0",
62
62
  "@types/turndown": "5.0.6",
63
63
  "@xterm/headless": "^6.0.0",
@@ -0,0 +1,234 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import type { ArgDescriptor, CommandCtor, CommandEntry, FlagDescriptor } from "@gajae-code/utils/cli";
5
+
6
+ export type FigName = string | string[];
7
+
8
+ export interface FigArg {
9
+ name: string;
10
+ description?: string;
11
+ isOptional?: boolean;
12
+ isVariadic?: boolean;
13
+ suggestions?: string[];
14
+ }
15
+
16
+ export interface FigOption {
17
+ name: FigName;
18
+ description?: string;
19
+ args?: FigArg;
20
+ isRepeatable?: boolean;
21
+ }
22
+
23
+ export interface FigSubcommand {
24
+ name: FigName;
25
+ description?: string;
26
+ options?: FigOption[];
27
+ args?: FigArg | FigArg[];
28
+ }
29
+
30
+ export interface FigSpec extends FigSubcommand {
31
+ name: string;
32
+ subcommands?: FigSubcommand[];
33
+ }
34
+
35
+ export type InshellisenseIndexStatus = "created" | "updated" | "already-compatible" | "overwritten";
36
+
37
+ export interface InstallGjcInshellisenseSpecOptions {
38
+ dir?: string;
39
+ force?: boolean;
40
+ }
41
+
42
+ export interface InstallGjcInshellisenseSpecResult {
43
+ directory: string;
44
+ specPath: string;
45
+ indexPath: string;
46
+ indexStatus: InshellisenseIndexStatus;
47
+ }
48
+
49
+ const GENERATED_INDEX_MARKER = "// Generated by gjc completion inshellisense";
50
+ const BUILTIN_ROOT_OPTIONS: FigOption[] = [
51
+ { name: ["-h", "--help"], description: "Show help" },
52
+ { name: ["-v", "--version"], description: "Show version" },
53
+ ];
54
+ const BUILTIN_COMMAND_OPTIONS: FigOption[] = [{ name: ["-h", "--help"], description: "Show help" }];
55
+
56
+ export function defaultInshellisenseSpecDir(homeDir: string = os.homedir()): string {
57
+ return path.join(homeDir, ".fig", "autocomplete", "build");
58
+ }
59
+
60
+ function nameList(name: FigName): string[] {
61
+ return Array.isArray(name) ? name : [name];
62
+ }
63
+
64
+ function optionKey(option: FigOption): string {
65
+ return nameList(option.name).join("\u0000");
66
+ }
67
+
68
+ function combineOptions(primary: FigOption[], secondary: FigOption[]): FigOption[] {
69
+ const seen = new Set<string>();
70
+ const combined: FigOption[] = [];
71
+ for (const option of [...primary, ...secondary]) {
72
+ const key = optionKey(option);
73
+ if (seen.has(key)) continue;
74
+ seen.add(key);
75
+ combined.push(option);
76
+ }
77
+ return combined;
78
+ }
79
+
80
+ function commandEntryName(entry: CommandEntry): FigName {
81
+ const names = [entry.name, ...(entry.aliases ?? [])];
82
+ return names.length === 1 ? names[0]! : names;
83
+ }
84
+
85
+ function flagName(name: string, descriptor: FlagDescriptor): FigName {
86
+ const longName = `--${name}`;
87
+ if (!descriptor.char) return longName;
88
+ return [`-${descriptor.char}`, longName];
89
+ }
90
+
91
+ function argSuggestions(options: readonly string[] | undefined): string[] | undefined {
92
+ return options && options.length > 0 ? [...options] : undefined;
93
+ }
94
+
95
+ function optionArgName(name: string, descriptor: FlagDescriptor): string {
96
+ if (descriptor.options && descriptor.options.length > 0) return name;
97
+ return descriptor.kind === "integer" ? "int" : "value";
98
+ }
99
+
100
+ function figOptionFromFlag(name: string, descriptor: FlagDescriptor): FigOption {
101
+ const option: FigOption = {
102
+ name: flagName(name, descriptor),
103
+ };
104
+ if (descriptor.description) option.description = descriptor.description;
105
+ if (descriptor.multiple) option.isRepeatable = true;
106
+ if (descriptor.kind !== "boolean") {
107
+ const arg: FigArg = { name: optionArgName(name, descriptor) };
108
+ const suggestions = argSuggestions(descriptor.options);
109
+ if (suggestions) arg.suggestions = suggestions;
110
+ option.args = arg;
111
+ }
112
+ return option;
113
+ }
114
+
115
+ function figOptionsFromFlags(flags: Record<string, FlagDescriptor> | undefined): FigOption[] {
116
+ if (!flags) return [];
117
+ return Object.entries(flags).map(([name, descriptor]) => figOptionFromFlag(name, descriptor));
118
+ }
119
+
120
+ function figArgFromDescriptor(name: string, descriptor: ArgDescriptor): FigArg {
121
+ const arg: FigArg = { name };
122
+ if (descriptor.description) arg.description = descriptor.description;
123
+ if (!descriptor.required) arg.isOptional = true;
124
+ if (descriptor.multiple) arg.isVariadic = true;
125
+ const suggestions = argSuggestions(descriptor.options);
126
+ if (suggestions) arg.suggestions = suggestions;
127
+ return arg;
128
+ }
129
+
130
+ function figArgsFromDescriptors(args: Record<string, ArgDescriptor> | undefined): FigArg | FigArg[] | undefined {
131
+ if (!args) return undefined;
132
+ const values = Object.entries(args).map(([name, descriptor]) => figArgFromDescriptor(name, descriptor));
133
+ if (values.length === 0) return undefined;
134
+ return values.length === 1 ? values[0] : values;
135
+ }
136
+
137
+ function commandToSubcommand(entry: CommandEntry, command: CommandCtor): FigSubcommand {
138
+ const subcommand: FigSubcommand = { name: commandEntryName(entry) };
139
+ if (command.description) subcommand.description = command.description;
140
+ const options = combineOptions(BUILTIN_COMMAND_OPTIONS, figOptionsFromFlags(command.flags));
141
+ if (options.length > 0) subcommand.options = options;
142
+ const args = figArgsFromDescriptors(command.args);
143
+ if (args) subcommand.args = args;
144
+ return subcommand;
145
+ }
146
+
147
+ export async function buildGjcFigSpec(entries: CommandEntry[], rootCommandOverride?: CommandCtor): Promise<FigSpec> {
148
+ const subcommands: FigSubcommand[] = [];
149
+ let rootCommand: CommandCtor | undefined = rootCommandOverride;
150
+
151
+ for (const entry of entries) {
152
+ if (entry.name === "launch" && rootCommandOverride) {
153
+ continue;
154
+ }
155
+ let command: CommandCtor;
156
+ try {
157
+ command = await entry.load();
158
+ } catch {
159
+ if (entry.name !== "launch") {
160
+ subcommands.push({ name: commandEntryName(entry) });
161
+ }
162
+ continue;
163
+ }
164
+ if (entry.name === "launch") {
165
+ rootCommand = command;
166
+ continue;
167
+ }
168
+ if (command.hidden) continue;
169
+ subcommands.push(commandToSubcommand(entry, command));
170
+ }
171
+
172
+ const spec: FigSpec = {
173
+ name: "gjc",
174
+ description: rootCommand?.description ?? "Gajae Code CLI",
175
+ };
176
+ const rootOptions = combineOptions(BUILTIN_ROOT_OPTIONS, figOptionsFromFlags(rootCommand?.flags));
177
+ if (rootOptions.length > 0) spec.options = rootOptions;
178
+ const rootArgs = figArgsFromDescriptors(rootCommand?.args);
179
+ if (rootArgs) spec.args = rootArgs;
180
+ if (subcommands.length > 0) spec.subcommands = subcommands;
181
+ return spec;
182
+ }
183
+
184
+ export function renderFigSpecModule(spec: FigSpec): string {
185
+ return `const completionSpec = ${JSON.stringify(spec, null, "\t")};\n\nexport default completionSpec;\n`;
186
+ }
187
+
188
+ export function renderInshellisenseIndexModule(): string {
189
+ return `${GENERATED_INDEX_MARKER}\n\nexport default ["gjc"];\nexport const diffVersionedCompletions = [];\n`;
190
+ }
191
+
192
+ async function fileTextIfExists(filePath: string): Promise<string | undefined> {
193
+ try {
194
+ return await Bun.file(filePath).text();
195
+ } catch (error) {
196
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ function indexDeclaresGjc(content: string): boolean {
202
+ return /["']gjc["']/.test(content);
203
+ }
204
+
205
+ async function resolveIndexStatus(indexPath: string, force: boolean | undefined): Promise<InshellisenseIndexStatus> {
206
+ const existing = await fileTextIfExists(indexPath);
207
+ if (existing === undefined) return "created";
208
+ if (existing.includes(GENERATED_INDEX_MARKER)) {
209
+ return existing === renderInshellisenseIndexModule() ? "already-compatible" : "updated";
210
+ }
211
+ if (indexDeclaresGjc(existing)) return "already-compatible";
212
+ if (force) return "overwritten";
213
+ throw new Error(
214
+ `inshellisense index already exists at ${indexPath} and does not list gjc; rerun with --force to overwrite it or pass --dir to install into a GJC-only specs directory`,
215
+ );
216
+ }
217
+
218
+ export async function installGjcInshellisenseSpec(
219
+ spec: FigSpec,
220
+ options: InstallGjcInshellisenseSpecOptions = {},
221
+ ): Promise<InstallGjcInshellisenseSpecResult> {
222
+ const directory = path.resolve(options.dir ?? defaultInshellisenseSpecDir());
223
+ const specPath = path.join(directory, "gjc.js");
224
+ const indexPath = path.join(directory, "index.js");
225
+ const indexStatus = await resolveIndexStatus(indexPath, options.force);
226
+
227
+ await fs.mkdir(directory, { recursive: true });
228
+ await Bun.write(specPath, renderFigSpecModule(spec));
229
+ if (indexStatus !== "already-compatible") {
230
+ await Bun.write(indexPath, renderInshellisenseIndexModule());
231
+ }
232
+
233
+ return { directory, specPath, indexPath, indexStatus };
234
+ }