@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-dj46zzrm.md} +110 -0
- package/dist/cli.js +10009 -9654
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-discovery.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +73 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +25 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/internal-urls/security-protocol.d.ts +15 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +18 -0
- package/dist/types/security/auth.d.ts +30 -0
- package/dist/types/security/cloud.d.ts +79 -0
- package/dist/types/security/comparison.d.ts +49 -0
- package/dist/types/security/contracts/ids.d.ts +15 -0
- package/dist/types/security/contracts/index.d.ts +4 -0
- package/dist/types/security/contracts/schemas.d.ts +660 -0
- package/dist/types/security/contracts/types.d.ts +234 -0
- package/dist/types/security/contracts/validation.d.ts +5 -0
- package/dist/types/security/coordinator.d.ts +100 -0
- package/dist/types/security/importers/codex-security.d.ts +7 -0
- package/dist/types/security/importers/index.d.ts +2 -0
- package/dist/types/security/importers/sarif.d.ts +9 -0
- package/dist/types/security/index.d.ts +13 -0
- package/dist/types/security/preflight.d.ts +61 -0
- package/dist/types/security/provenance.d.ts +24 -0
- package/dist/types/security/publication.d.ts +78 -0
- package/dist/types/security/remediation.d.ts +27 -0
- package/dist/types/security/resource-output.d.ts +8 -0
- package/dist/types/security/sarif.d.ts +2 -0
- package/dist/types/security/store.d.ts +40 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/slash-commands/helpers/security.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +4 -1
- package/dist/types/task/executor.d.ts +14 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +10 -2
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/security-scan.d.ts +96 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-discovery.ts +32 -5
- package/src/config/model-registry.ts +107 -25
- package/src/config/settings-schema.ts +77 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/eval/py/prelude.py +7 -3
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +65 -13
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/internal-urls/router.ts +4 -1
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/types.ts +1 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +7 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/host-uris.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/sdk.ts +212 -21
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +201 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +65 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +152 -3
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/agents.ts +2 -0
- package/src/task/executor.ts +20 -4
- package/src/task/render.ts +20 -4
- package/src/task/structured-subagent.ts +6 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +37 -5
- package/src/tools/path-utils.ts +91 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
package/src/tools/read.ts
CHANGED
|
@@ -735,6 +735,8 @@ export interface ReadToolDetails {
|
|
|
735
735
|
method?: string;
|
|
736
736
|
notes?: string[];
|
|
737
737
|
meta?: OutputMeta;
|
|
738
|
+
/** Full on-disk byte size recorded before applying a file range. */
|
|
739
|
+
fileSize?: number;
|
|
738
740
|
/** Raw text + start line for user-visible TUI rendering, set when content is text-like.
|
|
739
741
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
740
742
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
@@ -2959,6 +2961,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2959
2961
|
}
|
|
2960
2962
|
}
|
|
2961
2963
|
|
|
2964
|
+
details.fileSize = fileSize;
|
|
2962
2965
|
this.#markMarkdownContentType(details, absolutePath);
|
|
2963
2966
|
if (suffixResolution) {
|
|
2964
2967
|
details.suffixResolution = suffixResolution;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult, ToolTier } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
import securityScanDescription from "../prompts/tools/security-scan.md" with { type: "text" };
|
|
4
|
+
import { selectSecurityAccount } from "../security/auth";
|
|
5
|
+
import {
|
|
6
|
+
CodexSecurityCloudClient,
|
|
7
|
+
type CodexSecurityCloudConfiguration,
|
|
8
|
+
type CodexSecurityCloudStats,
|
|
9
|
+
pullCodexSecurityCloudResults,
|
|
10
|
+
} from "../security/cloud";
|
|
11
|
+
import { createSecurityEvidenceId, type SecurityEvidence, type SecurityValidationStatus } from "../security/contracts";
|
|
12
|
+
import type { SecurityOperationSnapshot } from "../security/coordinator";
|
|
13
|
+
import { getSecurityCoordinator } from "../security/coordinator";
|
|
14
|
+
import type { SecurityTargetRequest } from "../security/preflight";
|
|
15
|
+
import { SecurityStore } from "../security/store";
|
|
16
|
+
import type { ToolSession } from "./index";
|
|
17
|
+
import { ToolError } from "./tool-errors";
|
|
18
|
+
|
|
19
|
+
const securityScanSchema = type({
|
|
20
|
+
action:
|
|
21
|
+
"'preflight' | 'start' | 'status' | 'cancel' | 'validate' | 'cloud_scans' | 'cloud_start' | 'cloud_status' | 'cloud_pull'",
|
|
22
|
+
"plan_id?": "string",
|
|
23
|
+
"operation_id?": "string",
|
|
24
|
+
"target_kind?": "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree'",
|
|
25
|
+
"include_paths?": "string[]",
|
|
26
|
+
"exclude_paths?": "string[]",
|
|
27
|
+
"base_revision?": "string",
|
|
28
|
+
"head_revision?": "string",
|
|
29
|
+
"knowledge_base_paths?": "string[]",
|
|
30
|
+
"output_root?": "string",
|
|
31
|
+
"archive_existing?": "boolean",
|
|
32
|
+
"credential_id?": "number.integer >= 1",
|
|
33
|
+
"scan_id?": "string",
|
|
34
|
+
"finding_id?": "string",
|
|
35
|
+
"validation_status?": "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'",
|
|
36
|
+
"validation_summary?": "string",
|
|
37
|
+
"validation_evidence?": type({ label: "string > 0", explanation: "string" }).array(),
|
|
38
|
+
"cloud_configuration_id?": "string",
|
|
39
|
+
"repository_id?": "string",
|
|
40
|
+
"repository_url?": "string",
|
|
41
|
+
"environment_id?": "string",
|
|
42
|
+
"lookback_days?": "number.integer >= 1 | 'all'",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type SecurityScanParams = typeof securityScanSchema.infer;
|
|
46
|
+
|
|
47
|
+
export interface SecurityScanToolDetails {
|
|
48
|
+
action: SecurityScanParams["action"];
|
|
49
|
+
plan?: { id: string; fingerprint: string };
|
|
50
|
+
operation?: SecurityOperationSnapshot;
|
|
51
|
+
cancelled?: boolean;
|
|
52
|
+
finding?: { id: string; validationStatus: SecurityValidationStatus };
|
|
53
|
+
cloudConfigurations?: CodexSecurityCloudConfiguration[];
|
|
54
|
+
cloudStats?: CodexSecurityCloudStats;
|
|
55
|
+
cloudScan?: { id: string; repositoryUrl: string };
|
|
56
|
+
importedScan?: { id: string; findingCount: number };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function targetFromParams(params: SecurityScanParams): SecurityTargetRequest {
|
|
60
|
+
const common = { includePaths: params.include_paths, excludePaths: params.exclude_paths };
|
|
61
|
+
switch (params.target_kind ?? "repository") {
|
|
62
|
+
case "scoped_path": {
|
|
63
|
+
if (!params.include_paths?.some(value => value.trim().length > 0)) {
|
|
64
|
+
throw new ToolError("scoped_path security scans require at least one include path");
|
|
65
|
+
}
|
|
66
|
+
return { kind: "scoped_path", includePaths: params.include_paths, excludePaths: params.exclude_paths };
|
|
67
|
+
}
|
|
68
|
+
case "working_tree":
|
|
69
|
+
return { kind: "working_tree", ...common };
|
|
70
|
+
case "ref_diff":
|
|
71
|
+
if (!params.base_revision || !params.head_revision) {
|
|
72
|
+
throw new ToolError("ref_diff preflight requires base_revision and head_revision");
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
kind: "ref_diff",
|
|
76
|
+
baseRevision: params.base_revision,
|
|
77
|
+
headRevision: params.head_revision,
|
|
78
|
+
...common,
|
|
79
|
+
};
|
|
80
|
+
default:
|
|
81
|
+
return { kind: "repository", ...common };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function requireValue(value: string | undefined, label: string): string {
|
|
86
|
+
if (!value?.trim()) throw new ToolError(`${label} is required for this action`);
|
|
87
|
+
return value.trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cloudClientForSession(session: ToolSession, credentialId?: number): CodexSecurityCloudClient {
|
|
91
|
+
if (!session.authStorage) throw new ToolError("Codex Security cloud requires the authentication registry");
|
|
92
|
+
const account = selectSecurityAccount(
|
|
93
|
+
session.authStorage,
|
|
94
|
+
"openai-codex",
|
|
95
|
+
credentialId,
|
|
96
|
+
session.getSessionId?.() ?? undefined,
|
|
97
|
+
);
|
|
98
|
+
return new CodexSecurityCloudClient({ authStorage: session.authStorage, account });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function textResult(text: string, details: SecurityScanToolDetails): AgentToolResult<SecurityScanToolDetails> {
|
|
102
|
+
return { content: [{ type: "text", text }], details };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class SecurityScanTool implements AgentTool<typeof securityScanSchema, SecurityScanToolDetails> {
|
|
106
|
+
readonly name = "security_scan";
|
|
107
|
+
readonly approval: ToolTier = "exec";
|
|
108
|
+
readonly label = "Security Scan";
|
|
109
|
+
readonly loadMode = "discoverable";
|
|
110
|
+
readonly summary = "Run OMP-native scans and explicit Codex Security cloud operations";
|
|
111
|
+
readonly description = securityScanDescription.trim();
|
|
112
|
+
readonly parameters = securityScanSchema;
|
|
113
|
+
readonly strict = true;
|
|
114
|
+
|
|
115
|
+
constructor(readonly session: ToolSession) {}
|
|
116
|
+
|
|
117
|
+
async execute(
|
|
118
|
+
_toolCallId: string,
|
|
119
|
+
params: SecurityScanParams,
|
|
120
|
+
signal?: AbortSignal,
|
|
121
|
+
): Promise<AgentToolResult<SecurityScanToolDetails>> {
|
|
122
|
+
if (!this.session.settings.get("security.enabled")) {
|
|
123
|
+
throw new ToolError("Security is disabled. Enable security.enabled before using security_scan.");
|
|
124
|
+
}
|
|
125
|
+
const coordinatorForSession = () => {
|
|
126
|
+
if (!this.session.modelRegistry || !this.session.authStorage) {
|
|
127
|
+
throw new ToolError("Security scan requires the session model and authentication registries");
|
|
128
|
+
}
|
|
129
|
+
return getSecurityCoordinator({
|
|
130
|
+
cwd: this.session.cwd,
|
|
131
|
+
settings: this.session.settings,
|
|
132
|
+
authStorage: this.session.authStorage,
|
|
133
|
+
modelRegistry: this.session.modelRegistry,
|
|
134
|
+
activeModel: this.session.getActiveModel?.(),
|
|
135
|
+
sessionId: this.session.getSessionId?.() ?? undefined,
|
|
136
|
+
agentId: this.session.getAgentId?.() ?? undefined,
|
|
137
|
+
asyncJobManager: this.session.asyncJobManager,
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
switch (params.action) {
|
|
141
|
+
case "preflight": {
|
|
142
|
+
const model = this.session.getActiveModel?.();
|
|
143
|
+
const plan = await coordinatorForSession().preflight({
|
|
144
|
+
target: targetFromParams(params),
|
|
145
|
+
knowledgeBasePaths: params.knowledge_base_paths,
|
|
146
|
+
outputRoot: params.output_root,
|
|
147
|
+
archiveExisting: params.archive_existing,
|
|
148
|
+
credentialId: params.credential_id,
|
|
149
|
+
model,
|
|
150
|
+
signal,
|
|
151
|
+
});
|
|
152
|
+
return textResult(
|
|
153
|
+
[
|
|
154
|
+
`Security plan ${plan.id} is ready.`,
|
|
155
|
+
`Fingerprint: ${plan.fingerprint}.`,
|
|
156
|
+
`Start it with action=start and plan_id=${plan.id}.`,
|
|
157
|
+
].join(" "),
|
|
158
|
+
{ action: params.action, plan: { id: plan.id, fingerprint: plan.fingerprint } },
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
case "start": {
|
|
162
|
+
const operation = await coordinatorForSession().start({
|
|
163
|
+
planId: requireValue(params.plan_id, "plan_id"),
|
|
164
|
+
});
|
|
165
|
+
return textResult(`Security scan ${operation.scanId} started as ${operation.operationId}.`, {
|
|
166
|
+
action: params.action,
|
|
167
|
+
operation,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
case "status": {
|
|
171
|
+
const operationId = requireValue(params.operation_id, "operation_id");
|
|
172
|
+
const operation = await coordinatorForSession().status(operationId);
|
|
173
|
+
if (!operation) throw new ToolError(`Unknown security operation: ${operationId}`);
|
|
174
|
+
return textResult(
|
|
175
|
+
`Security scan ${operation.scanId}: ${operation.phase}; ${operation.findingCount} finding(s).`,
|
|
176
|
+
{ action: params.action, operation },
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
case "cancel": {
|
|
180
|
+
const operationId = requireValue(params.operation_id, "operation_id");
|
|
181
|
+
const cancelled = await coordinatorForSession().cancel(operationId);
|
|
182
|
+
return textResult(
|
|
183
|
+
cancelled ? `Cancellation requested for ${operationId}.` : `No running operation ${operationId}.`,
|
|
184
|
+
{
|
|
185
|
+
action: params.action,
|
|
186
|
+
cancelled,
|
|
187
|
+
operation: (await coordinatorForSession().status(operationId)) ?? undefined,
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
case "cloud_scans": {
|
|
192
|
+
const configurations = await cloudClientForSession(
|
|
193
|
+
this.session,
|
|
194
|
+
params.credential_id,
|
|
195
|
+
).listAllConfigurations(signal);
|
|
196
|
+
return textResult(
|
|
197
|
+
configurations.length === 0
|
|
198
|
+
? "No Codex Security cloud scan configurations are available."
|
|
199
|
+
: configurations
|
|
200
|
+
.map(
|
|
201
|
+
item =>
|
|
202
|
+
`${item.id} ${item.currentStep ?? "unknown"} repo=${item.repositoryId} environment=${item.environmentId} ${item.repositoryUrl}`,
|
|
203
|
+
)
|
|
204
|
+
.join("\n"),
|
|
205
|
+
{ action: params.action, cloudConfigurations: configurations },
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
case "cloud_start": {
|
|
209
|
+
const configuration = await cloudClientForSession(this.session, params.credential_id).startScan({
|
|
210
|
+
repositoryId: requireValue(params.repository_id, "repository_id"),
|
|
211
|
+
repositoryUrl: requireValue(params.repository_url, "repository_url"),
|
|
212
|
+
environmentId: requireValue(params.environment_id, "environment_id"),
|
|
213
|
+
lookbackDays: params.lookback_days,
|
|
214
|
+
signal,
|
|
215
|
+
});
|
|
216
|
+
return textResult(
|
|
217
|
+
`Codex Security cloud scan ${configuration.id} started for ${configuration.repositoryUrl}. This consumes cloud scan allowance.`,
|
|
218
|
+
{
|
|
219
|
+
action: params.action,
|
|
220
|
+
cloudScan: { id: configuration.id, repositoryUrl: configuration.repositoryUrl },
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
case "cloud_status": {
|
|
225
|
+
const stats = await cloudClientForSession(this.session, params.credential_id).getStats(
|
|
226
|
+
requireValue(params.cloud_configuration_id, "cloud_configuration_id"),
|
|
227
|
+
signal,
|
|
228
|
+
);
|
|
229
|
+
return textResult(
|
|
230
|
+
`Codex Security cloud scan ${stats.configurationId}: ${stats.currentStep ?? "unknown"}; ${stats.finishedCommits} finished commit(s), ${stats.pendingCommits} pending.`,
|
|
231
|
+
{ action: params.action, cloudStats: stats },
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
case "cloud_pull": {
|
|
235
|
+
const store = await SecurityStore.openForCwd(this.session.cwd, { signal });
|
|
236
|
+
const bundle = await pullCodexSecurityCloudResults({
|
|
237
|
+
client: cloudClientForSession(this.session, params.credential_id),
|
|
238
|
+
configurationId: requireValue(params.cloud_configuration_id, "cloud_configuration_id"),
|
|
239
|
+
store,
|
|
240
|
+
signal,
|
|
241
|
+
});
|
|
242
|
+
return textResult(
|
|
243
|
+
`Imported ${bundle.findings.length} Codex Security cloud finding(s) as security scan ${bundle.scan.id}.`,
|
|
244
|
+
{
|
|
245
|
+
action: params.action,
|
|
246
|
+
importedScan: { id: bundle.scan.id, findingCount: bundle.findings.length },
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
case "validate": {
|
|
251
|
+
const scanId = requireValue(params.scan_id, "scan_id");
|
|
252
|
+
const findingId = requireValue(params.finding_id, "finding_id");
|
|
253
|
+
const status = params.validation_status;
|
|
254
|
+
if (!status) throw new ToolError("validation_status is required for this action");
|
|
255
|
+
const summary = requireValue(params.validation_summary, "validation_summary");
|
|
256
|
+
const store = await SecurityStore.openForCwd(this.session.cwd, { signal });
|
|
257
|
+
const finding = await store.getFinding(scanId, findingId);
|
|
258
|
+
if (!finding) throw new ToolError(`Unknown security finding: ${findingId}`);
|
|
259
|
+
const evidence: SecurityEvidence[] = (params.validation_evidence ?? []).map((item, index) => ({
|
|
260
|
+
id: createSecurityEvidenceId(
|
|
261
|
+
finding.fingerprint,
|
|
262
|
+
`validation:${item.label}`,
|
|
263
|
+
finding.evidence.length + index,
|
|
264
|
+
),
|
|
265
|
+
kind: "validation",
|
|
266
|
+
label: item.label,
|
|
267
|
+
explanation: item.explanation,
|
|
268
|
+
}));
|
|
269
|
+
const updated = await store.updateValidation(
|
|
270
|
+
scanId,
|
|
271
|
+
findingId,
|
|
272
|
+
{
|
|
273
|
+
status,
|
|
274
|
+
summary,
|
|
275
|
+
evidenceIds: evidence.map(item => item.id),
|
|
276
|
+
validatedAt: new Date().toISOString(),
|
|
277
|
+
},
|
|
278
|
+
evidence,
|
|
279
|
+
);
|
|
280
|
+
return textResult(`Finding ${updated.id} validation is now ${updated.validation.status}.`, {
|
|
281
|
+
action: params.action,
|
|
282
|
+
finding: { id: updated.id, validationStatus: updated.validation.status },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
package/src/tools/write.ts
CHANGED
|
@@ -1267,9 +1267,15 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
1267
1267
|
|
|
1268
1268
|
// Try ACP bridge first for editor-visible filesystem paths. Internal
|
|
1269
1269
|
// artifacts such as local:// plans are owned by OMP, not the editor.
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1270
|
+
const bridgeWrite = await routeWriteThroughBridge(this.session, path, absolutePath, cleanContent, signal);
|
|
1271
|
+
if (bridgeWrite) {
|
|
1272
|
+
// `write` always replaces the whole file, so (unlike hashline's
|
|
1273
|
+
// hunk-scoped diff) there's no size cost to keying the header/
|
|
1274
|
+
// executable-bit check on the verified post-write content —
|
|
1275
|
+
// use it so a drifted write (e.g. client format-on-save) still
|
|
1276
|
+
// hands back a tag that matches what's actually on disk.
|
|
1277
|
+
const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, bridgeWrite.text);
|
|
1278
|
+
const header = maybeWriteSnapshotHeader(this.session, absolutePath, bridgeWrite.text);
|
|
1273
1279
|
const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
1274
1280
|
let resultText = header ? `${header}\n${writeLine}` : writeLine;
|
|
1275
1281
|
if (stripped) {
|
package/src/tools/xdev.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
|
35
35
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
36
36
|
import { XD_URL_PREFIX } from "../internal-urls/xd-protocol";
|
|
37
37
|
import type { Theme } from "../modes/theme/theme";
|
|
38
|
+
import { truncateHeadBytes } from "../session/streaming-output";
|
|
38
39
|
import { renderDefaultToolExecution } from "./default-renderer";
|
|
39
40
|
import type { Tool } from "./index";
|
|
40
41
|
import { replaceTabs } from "./render-utils";
|
|
@@ -178,14 +179,34 @@ function toolSummary(inst: Tool): string {
|
|
|
178
179
|
return firstLine?.trim() ?? inst.label ?? inst.name;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
/** C0/C1 controls and Unicode line/paragraph separators; summaries must remain one line. */
|
|
183
|
+
const SUMMARY_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g;
|
|
184
|
+
const SUMMARY_ELLIPSIS = "…";
|
|
185
|
+
const SUMMARY_ELLIPSIS_BYTES = Buffer.byteLength(SUMMARY_ELLIPSIS, "utf-8");
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Bound a catalog summary for prompt rendering. External summaries are
|
|
189
|
+
* third-party metadata inlined verbatim, so control characters are stripped
|
|
190
|
+
* first, then the result is bounded in UTF-8 BYTES rather than characters (a
|
|
191
|
+
* character bound is not a byte bound for multi-byte scripts). The cut lands
|
|
192
|
+
* on a code point boundary, so the prompt never carries a partial code point.
|
|
193
|
+
*/
|
|
194
|
+
function sanitizeCatalogSummary(summary: string, maxBytes?: number): string {
|
|
195
|
+
const cleaned = summary.replace(SUMMARY_CONTROL_CHARS, " ").trim();
|
|
196
|
+
if (maxBytes === undefined || Buffer.byteLength(cleaned, "utf-8") <= maxBytes) return cleaned;
|
|
197
|
+
if (maxBytes <= 0) return "";
|
|
198
|
+
if (maxBytes < SUMMARY_ELLIPSIS_BYTES) return truncateHeadBytes(cleaned, maxBytes).text;
|
|
199
|
+
const body = truncateHeadBytes(cleaned, maxBytes - SUMMARY_ELLIPSIS_BYTES).text.trimEnd();
|
|
200
|
+
return `${body}${SUMMARY_ELLIPSIS}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function promptCatalogSummary(inst: Tool, maxBytes?: number): string {
|
|
182
204
|
const summary =
|
|
183
205
|
toolSummary(inst)
|
|
184
206
|
.split("\n")
|
|
185
207
|
.find(line => line.trim().length > 0)
|
|
186
208
|
?.trim() ?? inst.name;
|
|
187
|
-
|
|
188
|
-
return `${summary.slice(0, maxLength).trimEnd()}…`;
|
|
209
|
+
return sanitizeCatalogSummary(summary, maxBytes) || inst.name;
|
|
189
210
|
}
|
|
190
211
|
|
|
191
212
|
/** Compile the `tools.xdevInlineDevices` allowlist once per render, dropping
|
|
@@ -257,15 +278,19 @@ export function listXdevTools(state: XdevState): Tool[] {
|
|
|
257
278
|
});
|
|
258
279
|
}
|
|
259
280
|
|
|
260
|
-
/** `{name, summary}`
|
|
261
|
-
export function xdevEntries(state: XdevState): Array<{ name: string; summary: string }> {
|
|
262
|
-
return listXdevTools(state).map(tool =>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
281
|
+
/** `{name, summary, dynamic}` triples for prompt templates and `/tools` display. */
|
|
282
|
+
export function xdevEntries(state: XdevState): Array<{ name: string; summary: string; dynamic: boolean }> {
|
|
283
|
+
return listXdevTools(state).map(tool => {
|
|
284
|
+
// Built-ins are first-party; anything else carries third-party metadata. One
|
|
285
|
+
// boolean drives both the description cap and the flag callers present, so
|
|
286
|
+
// the two can never disagree about which summaries are untrusted.
|
|
287
|
+
const dynamic = !state.builtInNames.has(tool.name);
|
|
288
|
+
return {
|
|
289
|
+
name: tool.name,
|
|
290
|
+
summary: promptCatalogSummary(tool, dynamic ? XDEV_EXTERNAL_DESCRIPTION_CAP : undefined),
|
|
291
|
+
dynamic,
|
|
292
|
+
};
|
|
293
|
+
});
|
|
269
294
|
}
|
|
270
295
|
|
|
271
296
|
/** `read xd://` listing with one device per line. */
|
|
@@ -313,8 +338,8 @@ export function xdevDocsAll(
|
|
|
313
338
|
[
|
|
314
339
|
"## Additional devices (docs on demand)",
|
|
315
340
|
...overflow.map(tool => {
|
|
316
|
-
const
|
|
317
|
-
return `- ${XD_URL_PREFIX}${tool.name} — ${promptCatalogSummary(tool,
|
|
341
|
+
const maxBytes = state.builtInNames.has(tool.name) ? undefined : XDEV_EXTERNAL_DESCRIPTION_CAP;
|
|
342
|
+
return `- ${XD_URL_PREFIX}${tool.name} — ${promptCatalogSummary(tool, maxBytes)}`;
|
|
318
343
|
}),
|
|
319
344
|
"",
|
|
320
345
|
`Read ${XD_URL_PREFIX}<tool> for full docs + JSON schema before first use.`,
|
package/src/utils/changelog.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { getLastChangelogVersionPath, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import bundledChangelogPath from "../../CHANGELOG.md" with { type: "file" };
|
|
4
|
+
import type { SettingValue } from "../config/settings";
|
|
4
5
|
|
|
5
6
|
export interface ChangelogEntry {
|
|
6
7
|
major: number;
|
|
@@ -28,6 +29,101 @@ export interface StartupChangelogSelection {
|
|
|
28
29
|
persistCurrentVersion: boolean;
|
|
29
30
|
truncated: boolean;
|
|
30
31
|
selectedEntries: number;
|
|
32
|
+
totalUnseenEntries: number;
|
|
33
|
+
latestVersion: string | undefined;
|
|
34
|
+
changeCount: number;
|
|
35
|
+
categoryCounts: Record<string, number>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const CHANGELOG_CATEGORY_ORDER = [
|
|
39
|
+
"Breaking Changes",
|
|
40
|
+
"Added",
|
|
41
|
+
"Changed",
|
|
42
|
+
"Deprecated",
|
|
43
|
+
"Removed",
|
|
44
|
+
"Fixed",
|
|
45
|
+
"Security",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
function emptyStartupSelection(persistCurrentVersion: boolean): StartupChangelogSelection {
|
|
49
|
+
return {
|
|
50
|
+
markdown: undefined,
|
|
51
|
+
persistCurrentVersion,
|
|
52
|
+
truncated: false,
|
|
53
|
+
selectedEntries: 0,
|
|
54
|
+
totalUnseenEntries: 0,
|
|
55
|
+
latestVersion: undefined,
|
|
56
|
+
changeCount: 0,
|
|
57
|
+
categoryCounts: {},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function summarizeChangelogEntries(entries: readonly ChangelogEntry[]): {
|
|
62
|
+
changeCount: number;
|
|
63
|
+
categoryCounts: Record<string, number>;
|
|
64
|
+
} {
|
|
65
|
+
const categoryCounts: Record<string, number> = {};
|
|
66
|
+
let changeCount = 0;
|
|
67
|
+
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
let category: string | undefined;
|
|
70
|
+
for (const line of entry.content.split("\n")) {
|
|
71
|
+
const heading = line.match(/^###\s+(.+?)\s*$/);
|
|
72
|
+
if (heading) {
|
|
73
|
+
category = heading[1];
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!category || !/^-\s+\S/.test(line)) continue;
|
|
77
|
+
categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;
|
|
78
|
+
changeCount++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { changeCount, categoryCounts };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function categoryLabel(category: string, count: number): string {
|
|
86
|
+
if (category === "Breaking Changes") {
|
|
87
|
+
return count === 1 ? "breaking change" : "breaking changes";
|
|
88
|
+
}
|
|
89
|
+
return category.toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Format the compact, deterministic startup update notice. */
|
|
93
|
+
export function formatStartupChangelogSummary(selection: StartupChangelogSelection): string {
|
|
94
|
+
const latestVersion = selection.latestVersion;
|
|
95
|
+
if (!latestVersion || selection.selectedEntries === 0) {
|
|
96
|
+
return "Updated omp. Use /changelog for recent changes.";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const releaseCount = selection.selectedEntries;
|
|
100
|
+
const changeCount = selection.changeCount;
|
|
101
|
+
const releaseWord = releaseCount === 1 ? "release" : "releases";
|
|
102
|
+
const changeWord = changeCount === 1 ? "change" : "changes";
|
|
103
|
+
const firstLine =
|
|
104
|
+
releaseCount === 1
|
|
105
|
+
? `Updated to v${latestVersion} · ${changeCount} ${changeWord} in 1 release`
|
|
106
|
+
: `Updated to v${latestVersion} · ${changeCount} ${changeWord} across ${releaseCount} ${releaseWord}`;
|
|
107
|
+
|
|
108
|
+
const orderedCategories = [
|
|
109
|
+
...CHANGELOG_CATEGORY_ORDER.filter(category => selection.categoryCounts[category]),
|
|
110
|
+
...Object.keys(selection.categoryCounts)
|
|
111
|
+
.filter(category => !(CHANGELOG_CATEGORY_ORDER as readonly string[]).includes(category))
|
|
112
|
+
.sort(),
|
|
113
|
+
];
|
|
114
|
+
const breakdown = orderedCategories
|
|
115
|
+
.map(
|
|
116
|
+
category =>
|
|
117
|
+
`${selection.categoryCounts[category]} ${categoryLabel(category, selection.categoryCounts[category])}`,
|
|
118
|
+
)
|
|
119
|
+
.join(" · ");
|
|
120
|
+
const omittedReleases = selection.totalUnseenEntries - selection.selectedEntries;
|
|
121
|
+
const detailHint =
|
|
122
|
+
omittedReleases > 0
|
|
123
|
+
? `+${omittedReleases} earlier ${omittedReleases === 1 ? "release" : "releases"} · Use /changelog full for history.`
|
|
124
|
+
: "Use /changelog for details.";
|
|
125
|
+
|
|
126
|
+
return breakdown ? `${firstLine}\n${breakdown} · ${detailHint}` : `${firstLine}\n${detailHint}`;
|
|
31
127
|
}
|
|
32
128
|
|
|
33
129
|
/**
|
|
@@ -177,16 +273,17 @@ export function selectStartupChangelog(
|
|
|
177
273
|
): StartupChangelogSelection {
|
|
178
274
|
const parsedLastVersion = parseChangelogVersion(lastVersion);
|
|
179
275
|
if (!parsedLastVersion) {
|
|
180
|
-
return
|
|
276
|
+
return emptyStartupSelection(true);
|
|
181
277
|
}
|
|
182
278
|
const markerVersion = lastVersion ?? "";
|
|
183
279
|
if (markerVersion === currentVersion) {
|
|
184
|
-
return
|
|
280
|
+
return emptyStartupSelection(false);
|
|
185
281
|
}
|
|
186
282
|
|
|
187
|
-
const
|
|
283
|
+
const allNewEntries = getNewEntries(entries, markerVersion);
|
|
284
|
+
const newEntries = allNewEntries.slice(0, RECENT_CHANGELOG_ENTRY_LIMIT);
|
|
188
285
|
if (newEntries.length === 0) {
|
|
189
|
-
return
|
|
286
|
+
return emptyStartupSelection(false);
|
|
190
287
|
}
|
|
191
288
|
|
|
192
289
|
const rendered = renderChangelogEntries(newEntries, {
|
|
@@ -194,14 +291,57 @@ export function selectStartupChangelog(
|
|
|
194
291
|
truncationHint: STARTUP_CHANGELOG_FULL_HINT,
|
|
195
292
|
oldestFirst: false,
|
|
196
293
|
});
|
|
294
|
+
const summary = summarizeChangelogEntries(newEntries);
|
|
295
|
+
const latestEntry = newEntries[0];
|
|
197
296
|
return {
|
|
198
297
|
markdown: rendered.markdown,
|
|
199
298
|
persistCurrentVersion: true,
|
|
200
299
|
truncated: rendered.truncated,
|
|
201
300
|
selectedEntries: newEntries.length,
|
|
301
|
+
totalUnseenEntries: allNewEntries.length,
|
|
302
|
+
latestVersion: latestEntry ? `${latestEntry.major}.${latestEntry.minor}.${latestEntry.patch}` : undefined,
|
|
303
|
+
...summary,
|
|
202
304
|
};
|
|
203
305
|
}
|
|
204
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Resolve and persist the automatic startup changelog decision.
|
|
309
|
+
*
|
|
310
|
+
* Hidden mode advances the marker only for an upgrade, so downgrades do not
|
|
311
|
+
* erase knowledge of a newer version the user has already seen.
|
|
312
|
+
*/
|
|
313
|
+
export async function resolveStartupChangelogForDisplay(options: {
|
|
314
|
+
mode: SettingValue<"startup.changelogMode">;
|
|
315
|
+
currentVersion: string;
|
|
316
|
+
changelogPath?: string;
|
|
317
|
+
agentDir?: string;
|
|
318
|
+
}): Promise<StartupChangelogSelection | undefined> {
|
|
319
|
+
const lastVersion = await readLastChangelogVersion(options.agentDir);
|
|
320
|
+
const parsedLastVersion = parseChangelogVersion(lastVersion);
|
|
321
|
+
if (!parsedLastVersion) {
|
|
322
|
+
await writeLastChangelogVersion(options.currentVersion, options.agentDir);
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
if (lastVersion === options.currentVersion) {
|
|
326
|
+
// Steady state: skip the changelog file read and parse.
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
if (options.mode === "hidden") {
|
|
330
|
+
const currentVersion = parseChangelogVersion(options.currentVersion);
|
|
331
|
+
if (currentVersion && compareVersions(currentVersion, parsedLastVersion) > 0) {
|
|
332
|
+
await writeLastChangelogVersion(options.currentVersion, options.agentDir);
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const entries = await parseChangelog(options.changelogPath);
|
|
338
|
+
const startupChangelog = selectStartupChangelog(entries, lastVersion, options.currentVersion);
|
|
339
|
+
if (startupChangelog.persistCurrentVersion) {
|
|
340
|
+
await writeLastChangelogVersion(options.currentVersion, options.agentDir);
|
|
341
|
+
}
|
|
342
|
+
return startupChangelog.markdown ? startupChangelog : undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
205
345
|
// Re-export getChangelogPath from paths.ts for convenience
|
|
206
346
|
export { getChangelogPath } from "../config";
|
|
207
347
|
|