pi-revit 0.2.5 → 0.2.7
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
CHANGED
|
@@ -7,6 +7,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/); version headers
|
|
|
7
7
|
Every published version gets an entry with **Added** / **Changed** / **Fixed** sections
|
|
8
8
|
describing what the user will notice — not internal refactors.
|
|
9
9
|
|
|
10
|
+
## [0.2.7] - 2026-07-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Update announcements: after a pi-revit update, the next pi session shows a one-time
|
|
14
|
+
"What's new in pi-revit" note listing the changelog entries for every version since the
|
|
15
|
+
last one announced — the same mechanism pi uses for its own updates. The last-announced
|
|
16
|
+
version is remembered in `%APPDATA%\pi-revit\state.json` (kept outside the package
|
|
17
|
+
folder so npm updates can't erase it). Fresh installs stay silent.
|
|
18
|
+
|
|
19
|
+
Extension-only change: no Revit add-in redeploy or Revit restart needed.
|
|
20
|
+
|
|
21
|
+
## [0.2.6] - 2026-07-20
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Write transactions (`set_parameters`, `execute_csharp`, and the temporary-isolate
|
|
25
|
+
branch of `manage_selection`) now register a failures preprocessor. Previously Revit
|
|
26
|
+
handled commit failures interactively: warnings popped the transient toast and spammed
|
|
27
|
+
the journal, and an error-severity failure showed the modal resolution dialog, blocking
|
|
28
|
+
the bridge until a human clicked. Now warnings are auto-dismissed and reported back
|
|
29
|
+
(`commitWarnings` in the result, e.g. duplicate Mark values), and errors roll the
|
|
30
|
+
transaction back with the actual Revit failure text in the error message.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- `set_parameters` tool description tells the model to relay `commitWarnings` to the user.
|
|
34
|
+
|
|
35
|
+
Requires redeploying the Revit add-in (`scripts\deploy.ps1` + Revit restart).
|
|
36
|
+
|
|
10
37
|
## [0.2.5] - 2026-07-20
|
|
11
38
|
|
|
12
39
|
### Fixed
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type, type TSchema } from "typebox";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { version as packageVersion } from "../../package.json";
|
|
7
8
|
|
|
8
9
|
interface BridgeInfo {
|
|
@@ -207,6 +208,73 @@ function versionMismatch(addinVersion: unknown): string | null {
|
|
|
207
208
|
return `pi-revit ${packageVersion} is installed, but the Revit add-in ${state} — the update is incomplete. Close Revit and run: npx.cmd -y pi-revit (or ask the agent to run scripts\\deploy.ps1 from the installed package, then restart Revit).`;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
// ------------------------------------------------------------- what's new
|
|
212
|
+
|
|
213
|
+
/** Stable per-user state file. Deliberately NOT under node_modules: npm wipes
|
|
214
|
+
* the package folder on every update, which is exactly when the last-announced
|
|
215
|
+
* version must survive. */
|
|
216
|
+
function announcerStatePath(): string {
|
|
217
|
+
const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
218
|
+
return path.join(appData, "pi-revit", "state.json");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function compareVersions(a: string, b: string): number {
|
|
222
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
223
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
224
|
+
for (let i = 0; i < 3; i++) {
|
|
225
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
226
|
+
}
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Parses `## [x.y.z]` entries from the packaged CHANGELOG.md (the same header
|
|
231
|
+
* format pi's own changelog parser reads) and returns the ones newer than
|
|
232
|
+
* sinceVersion, newest first — so a 0.2.5 -> 0.2.9 jump shows all four. */
|
|
233
|
+
async function newChangelogEntries(sinceVersion: string): Promise<{ version: string; body: string }[]> {
|
|
234
|
+
const changelogPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "CHANGELOG.md");
|
|
235
|
+
const markdown = await readFile(changelogPath, "utf8");
|
|
236
|
+
const headers = [...markdown.matchAll(/^##\s+\[?(\d+\.\d+\.\d+)\]?[^\n]*$/gm)];
|
|
237
|
+
const entries: { version: string; body: string }[] = [];
|
|
238
|
+
for (let i = 0; i < headers.length; i++) {
|
|
239
|
+
const version = headers[i][1];
|
|
240
|
+
if (compareVersions(version, sinceVersion) <= 0) continue;
|
|
241
|
+
const start = (headers[i].index ?? 0) + headers[i][0].length;
|
|
242
|
+
const end = i + 1 < headers.length ? headers[i + 1].index : markdown.length;
|
|
243
|
+
entries.push({ version, body: markdown.slice(start, end).trim() });
|
|
244
|
+
}
|
|
245
|
+
return entries;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Shows the changelog entries between the last announced version and the
|
|
249
|
+
* current one, once per update, then records the current version. A fresh
|
|
250
|
+
* install records silently (nothing is "new" yet). Every failure is swallowed:
|
|
251
|
+
* the announcer must never break a session. */
|
|
252
|
+
async function announceUpdateOnce(notify: (message: string, level: "info") => void): Promise<void> {
|
|
253
|
+
try {
|
|
254
|
+
const statePath = announcerStatePath();
|
|
255
|
+
let lastVersion: string | null = null;
|
|
256
|
+
try {
|
|
257
|
+
const state = JSON.parse(await readFile(statePath, "utf8")) as { lastAnnouncedVersion?: string };
|
|
258
|
+
if (typeof state.lastAnnouncedVersion === "string") lastVersion = state.lastAnnouncedVersion;
|
|
259
|
+
} catch {
|
|
260
|
+
// First run: no state yet.
|
|
261
|
+
}
|
|
262
|
+
if (lastVersion === packageVersion) return;
|
|
263
|
+
|
|
264
|
+
if (lastVersion) {
|
|
265
|
+
const entries = await newChangelogEntries(lastVersion);
|
|
266
|
+
if (entries.length > 0) {
|
|
267
|
+
const text = entries.map((entry) => `pi-revit ${entry.version}\n${entry.body}`).join("\n\n");
|
|
268
|
+
notify(`What's new in pi-revit (updated from ${lastVersion}):\n\n${text}`, "info");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
272
|
+
await writeFile(statePath, JSON.stringify({ lastAnnouncedVersion: packageVersion }, null, 2), "utf8");
|
|
273
|
+
} catch {
|
|
274
|
+
// Never let the announcer break a session.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
210
278
|
function registerPing(pi: ExtensionAPI) {
|
|
211
279
|
pi.registerTool({
|
|
212
280
|
name: "ping",
|
|
@@ -236,6 +304,7 @@ export default async function revitConnector(pi: ExtensionAPI) {
|
|
|
236
304
|
// where the user lands after running `pi update --extensions`. Bridge down at
|
|
237
305
|
// session start is the normal Revit-closed case: stay quiet.
|
|
238
306
|
pi.on("session_start", async (_event, ctx) => {
|
|
307
|
+
await announceUpdateOnce((message, level) => ctx.ui.notify(message, level));
|
|
239
308
|
try {
|
|
240
309
|
const payload = (await bridgeRequest("/ping", { method: "GET" }, undefined, 3_000)) as { addinVersion?: string };
|
|
241
310
|
const warning = versionMismatch(payload.addinVersion);
|
package/package.json
CHANGED
|
@@ -113,6 +113,7 @@ namespace RevitBridge.Tools
|
|
|
113
113
|
|
|
114
114
|
using var dialogGuard = new DialogGuard(uiapp);
|
|
115
115
|
using var transaction = new Transaction(doc, "execute_csharp");
|
|
116
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
116
117
|
if (transaction.Start() != TransactionStatus.Started)
|
|
117
118
|
throw new InvalidOperationException("Unable to start the execute_csharp transaction.");
|
|
118
119
|
|
|
@@ -142,6 +143,7 @@ namespace RevitBridge.Tools
|
|
|
142
143
|
if (transaction.Commit() != TransactionStatus.Committed)
|
|
143
144
|
throw new InvalidOperationException(
|
|
144
145
|
"Revit rolled back the execute_csharp transaction during commit (failure processing rejected the changes); no model changes were saved."
|
|
146
|
+
+ failureGuard.DescribeErrors()
|
|
145
147
|
+ DescribeDialogs(dialogGuard.Suppressed));
|
|
146
148
|
|
|
147
149
|
stopwatch.Stop();
|
|
@@ -154,6 +156,8 @@ namespace RevitBridge.Tools
|
|
|
154
156
|
};
|
|
155
157
|
if (dialogGuard.Suppressed.Count > 0)
|
|
156
158
|
payload["suppressedDialogs"] = dialogGuard.Suppressed;
|
|
159
|
+
if (failureGuard.Warnings.Count > 0)
|
|
160
|
+
payload["commitWarnings"] = failureGuard.Warnings;
|
|
157
161
|
return payload;
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools
|
|
4
|
+
{
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// Deterministic commit-time failure handling for tool-owned transactions. Without a
|
|
7
|
+
/// preprocessor, Revit resolves commit failures interactively: warnings pop the
|
|
8
|
+
/// transient toast (and spam the journal), and error-severity failures block the
|
|
9
|
+
/// Revit API thread behind the modal resolution dialog until a human clicks — past
|
|
10
|
+
/// the bridge's call budget. Attach() deletes warnings (recorded for the tool
|
|
11
|
+
/// result) and turns errors into a rollback, so a headless caller always gets an
|
|
12
|
+
/// answer instead of a hang.
|
|
13
|
+
/// </summary>
|
|
14
|
+
internal sealed class FailureGuard : IFailuresPreprocessor
|
|
15
|
+
{
|
|
16
|
+
public List<string> Warnings { get; } = new();
|
|
17
|
+
public List<string> Errors { get; } = new();
|
|
18
|
+
|
|
19
|
+
public static FailureGuard Attach(Transaction transaction)
|
|
20
|
+
{
|
|
21
|
+
var guard = new FailureGuard();
|
|
22
|
+
var options = transaction.GetFailureHandlingOptions();
|
|
23
|
+
options.SetFailuresPreprocessor(guard);
|
|
24
|
+
options.SetClearAfterRollback(true);
|
|
25
|
+
transaction.SetFailureHandlingOptions(options);
|
|
26
|
+
return guard;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public FailureProcessingResult PreprocessFailures(FailuresAccessor accessor)
|
|
30
|
+
{
|
|
31
|
+
bool hasError = false;
|
|
32
|
+
foreach (FailureMessageAccessor failure in accessor.GetFailureMessages())
|
|
33
|
+
{
|
|
34
|
+
string description;
|
|
35
|
+
try
|
|
36
|
+
{
|
|
37
|
+
description = failure.GetDescriptionText();
|
|
38
|
+
}
|
|
39
|
+
catch
|
|
40
|
+
{
|
|
41
|
+
description = "(failure without description)";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (failure.GetSeverity() == FailureSeverity.Warning)
|
|
45
|
+
{
|
|
46
|
+
Warnings.Add(description);
|
|
47
|
+
accessor.DeleteWarning(failure);
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
{
|
|
51
|
+
hasError = true;
|
|
52
|
+
Errors.Add(description);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return hasError ? FailureProcessingResult.ProceedWithRollBack : FailureProcessingResult.Continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// <summary>Formats the recorded errors for a commit-failure message.</summary>
|
|
59
|
+
public string DescribeErrors()
|
|
60
|
+
=> Errors.Count > 0 ? $" Revit failure(s): {string.Join("; ", Errors)}." : string.Empty;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -192,6 +192,7 @@ namespace RevitBridge.Tools
|
|
|
192
192
|
private static void ApplyTemporaryIsolate(Document doc, View view, ICollection<ElementId> elementIds)
|
|
193
193
|
{
|
|
194
194
|
using var transaction = new Transaction(doc, "manage_selection: temporary isolate");
|
|
195
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
195
196
|
if (transaction.Start() != TransactionStatus.Started)
|
|
196
197
|
throw new InvalidOperationException("Unable to start the temporary-isolate transaction.");
|
|
197
198
|
try
|
|
@@ -201,7 +202,7 @@ namespace RevitBridge.Tools
|
|
|
201
202
|
else
|
|
202
203
|
view.IsolateElementsTemporary(elementIds);
|
|
203
204
|
if (transaction.Commit() != TransactionStatus.Committed)
|
|
204
|
-
throw new InvalidOperationException("The temporary-isolate transaction failed to commit.");
|
|
205
|
+
throw new InvalidOperationException("The temporary-isolate transaction failed to commit." + failureGuard.DescribeErrors());
|
|
205
206
|
}
|
|
206
207
|
catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
|
|
207
208
|
{
|
|
@@ -19,7 +19,7 @@ namespace RevitBridge.Tools
|
|
|
19
19
|
|
|
20
20
|
public string Name => "set_parameters";
|
|
21
21
|
public string Label => "Set Parameters";
|
|
22
|
-
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). updates apply in ONE transaction: partial success commits and lists the failures; if every update fails the transaction is rolled back and nothing changes. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given.";
|
|
22
|
+
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). updates apply in ONE transaction: partial success commits and lists the failures; if every update fails the transaction is rolled back and nothing changes. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given. Revit warnings raised at commit (e.g. duplicate Mark values) are auto-dismissed and listed in commitWarnings — mention them to the user; error-severity failures roll the whole transaction back.";
|
|
23
23
|
public bool Write => true;
|
|
24
24
|
|
|
25
25
|
public object ParametersSchema => new
|
|
@@ -64,6 +64,7 @@ namespace RevitBridge.Tools
|
|
|
64
64
|
var failed = new List<Dictionary<string, object?>>();
|
|
65
65
|
|
|
66
66
|
using var transaction = new Transaction(doc, "set_parameters");
|
|
67
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
67
68
|
if (transaction.Start() != TransactionStatus.Started)
|
|
68
69
|
throw new InvalidOperationException("Unable to start the set_parameters transaction.");
|
|
69
70
|
|
|
@@ -95,7 +96,9 @@ namespace RevitBridge.Tools
|
|
|
95
96
|
if (succeeded.Count > 0)
|
|
96
97
|
{
|
|
97
98
|
if (transaction.Commit() != TransactionStatus.Committed)
|
|
98
|
-
throw new InvalidOperationException(
|
|
99
|
+
throw new InvalidOperationException(
|
|
100
|
+
"Revit rejected the set_parameters commit and the transaction was rolled back; no changes were saved."
|
|
101
|
+
+ failureGuard.DescribeErrors());
|
|
99
102
|
}
|
|
100
103
|
else
|
|
101
104
|
{
|
|
@@ -118,6 +121,8 @@ namespace RevitBridge.Tools
|
|
|
118
121
|
: succeeded.Count == 0
|
|
119
122
|
? $"No updates applied; all {failed.Count} failed — transaction rolled back.{failureSample}"
|
|
120
123
|
: $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s); {failed.Count} failed.{failureSample}";
|
|
124
|
+
if (failureGuard.Warnings.Count > 0)
|
|
125
|
+
compact += $" {failureGuard.Warnings.Count} Revit warning(s) auto-dismissed at commit (see commitWarnings), e.g.: {failureGuard.Warnings[0]}";
|
|
121
126
|
|
|
122
127
|
return new ToolOutput(new
|
|
123
128
|
{
|
|
@@ -125,6 +130,7 @@ namespace RevitBridge.Tools
|
|
|
125
130
|
committed = succeeded.Count > 0,
|
|
126
131
|
succeeded,
|
|
127
132
|
failed,
|
|
133
|
+
commitWarnings = failureGuard.Warnings,
|
|
128
134
|
}, compact);
|
|
129
135
|
}
|
|
130
136
|
|