@mulmoclaude/core 5.4.0 → 5.5.0
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/assets/helps/custom-view-remote.md +19 -10
- package/assets/helps/custom-view.md +2 -2
- package/dist/google/collectionPush.d.ts +29 -0
- package/dist/google/index.cjs +24 -5
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +1 -1
- package/dist/google/index.js +24 -6
- package/dist/google/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -60,7 +60,7 @@ window.__MC_VIEW = {
|
|
|
60
60
|
getItems: (opts) => Promise, // the ONLY way to read records — see below
|
|
61
61
|
updateItem: (id, patch) => Promise, // patch declared fields (see Writing records)
|
|
62
62
|
deleteItem: (id) => Promise, // remove a record (requires allowDelete)
|
|
63
|
-
startChat: (prompt, role) => void, // start a new chat
|
|
63
|
+
startChat: (prompt, role) => void, // start a new chat — draft, or sent when the view declares allowSendChat
|
|
64
64
|
t: (key, named) => string, // vue-i18n-style dict lookup (same as desktop)
|
|
65
65
|
};
|
|
66
66
|
```
|
|
@@ -220,16 +220,25 @@ only way a remote view "does" anything beyond display:
|
|
|
220
220
|
window.__MC_VIEW.startChat("Mark task " + item.id + " as done.");
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
-
**
|
|
224
|
-
|
|
225
|
-
would simply strand the work. (The desktop custom view drafts by default and
|
|
226
|
-
sends only when its `views[]` entry declares `allowSendChat: true`; the desktop
|
|
227
|
-
phone-frame preview follows that same declaration, so declare it there too if
|
|
228
|
-
you want the preview to behave the way the phone will.)
|
|
223
|
+
**The phone follows the view's `allowSendChat` declaration**, exactly as the
|
|
224
|
+
desktop does — it is read from the `views[]` entry, never from your code:
|
|
229
225
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
- **Not declared** — the prompt opens as an **editable draft** in the chat sheet,
|
|
227
|
+
and the user presses Send.
|
|
228
|
+
- **`"allowSendChat": true`** — **one press and the turn runs**.
|
|
229
|
+
|
|
230
|
+
```jsonc
|
|
231
|
+
{ "id": "tasks", "label": "Tasks", "file": "views/tasks.html", "target": "mobile", "allowSendChat": true }
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Declare it whenever a button is meant to start work: an undeclared view makes
|
|
235
|
+
the user open the sheet and send by hand on every press. The desktop
|
|
236
|
+
phone-frame preview reads the same declaration, so it behaves the way the phone
|
|
237
|
+
will.
|
|
238
|
+
|
|
239
|
+
With it declared, whatever you compose becomes an agent turn verbatim, with
|
|
240
|
+
every tool available. Build the prompt from your own records, and don't paste
|
|
241
|
+
text you fetched from somewhere else into it.
|
|
233
242
|
|
|
234
243
|
### Translations — `t`
|
|
235
244
|
|
|
@@ -442,8 +442,8 @@ to reword.
|
|
|
442
442
|
- Whatever you compose becomes an agent turn verbatim, with every tool
|
|
443
443
|
available. Build the prompt from your own records, and don't paste text you
|
|
444
444
|
fetched from somewhere else into it.
|
|
445
|
-
- **
|
|
446
|
-
|
|
445
|
+
- **The phone (`target: "mobile"`) follows the same declaration** — undeclared
|
|
446
|
+
drafts, declared sends. See `config/helps/custom-view-remote.md`.
|
|
447
447
|
|
|
448
448
|
Use this — not a hidden flag the user has to reconcile later — whenever a button
|
|
449
449
|
should _start backend work_: the user stays in the loop through trusted first-
|
|
@@ -39,6 +39,13 @@ export interface CalendarCollectionPushResult {
|
|
|
39
39
|
deletedInGoogle: number;
|
|
40
40
|
/** Records that cannot be pushed as they stand, each with the reason. */
|
|
41
41
|
skipped: string[];
|
|
42
|
+
/** Deletions the sweep left standing in Google, each with the reason.
|
|
43
|
+
*
|
|
44
|
+
* NOT merged into `skipped`: that list means "this push did not do what you
|
|
45
|
+
* asked", and a refused deletion is "it was deliberately not deleted" while
|
|
46
|
+
* the rest of the run carried. Merging them made one refusal hide every
|
|
47
|
+
* successful create and update from the caller (#3272). */
|
|
48
|
+
keptInGoogle: string[];
|
|
42
49
|
errors: string[];
|
|
43
50
|
/** Records whose local edit did NOT reach Google and would be destroyed by
|
|
44
51
|
* the pull that follows an automatic push (#2620).
|
|
@@ -108,9 +115,28 @@ type PushOutcome = {
|
|
|
108
115
|
message: string;
|
|
109
116
|
};
|
|
110
117
|
export type PushOutcomeKind = PushOutcome["kind"];
|
|
118
|
+
/** One record's push, kept with the id so the pull can be told which records to
|
|
119
|
+
* leave alone. */
|
|
120
|
+
export interface PushAttempt {
|
|
121
|
+
eventId: string;
|
|
122
|
+
outcome: PushOutcome;
|
|
123
|
+
}
|
|
111
124
|
/** Whether this outcome leaves a local edit that only exists locally, so the
|
|
112
125
|
* pull must not overwrite the record and the baseline must not advance. */
|
|
113
126
|
export declare const isUnpushed: (kind: PushOutcomeKind) => boolean;
|
|
127
|
+
/** The whole report one run produces, from what each record's push did and what
|
|
128
|
+
* the delete sweep did.
|
|
129
|
+
*
|
|
130
|
+
* A sweep's refusal goes to `keptInGoogle`, NEVER to `skipped`: that list means
|
|
131
|
+
* "this push did not do what you asked", and a refusal means "the event was
|
|
132
|
+
* deliberately left standing" while the rest of the run carried. Merged, one
|
|
133
|
+
* refusal took the caller's problem branch and hid every create and update
|
|
134
|
+
* (#3272).
|
|
135
|
+
*
|
|
136
|
+
* Exported because it is pure — a list of attempts and a sweep, no workspace,
|
|
137
|
+
* grant or clock — so the separation above is something a test drives rather
|
|
138
|
+
* than argues. */
|
|
139
|
+
export declare function pushResultFrom(slug: string, attempts: readonly PushAttempt[], deletes: DeleteSweep): CalendarCollectionPushResult;
|
|
114
140
|
/** Record ids whose local value no longer matches the baseline — edits that have
|
|
115
141
|
* not reached Google.
|
|
116
142
|
*
|
|
@@ -133,6 +159,9 @@ export declare function unsentLocalEdits(collection: LoadedCollection, workspace
|
|
|
133
159
|
export interface DeleteSweep {
|
|
134
160
|
seen: number;
|
|
135
161
|
deleted: string[];
|
|
162
|
+
/** Deletions left standing in Google, each with the reason. Reported apart
|
|
163
|
+
* from the push's own `skipped` on the result: that list means "could not be
|
|
164
|
+
* sent", and a refusal means "deliberately left alone" (#3272). */
|
|
136
165
|
skipped: string[];
|
|
137
166
|
errors: string[];
|
|
138
167
|
}
|
package/dist/google/index.cjs
CHANGED
|
@@ -2034,9 +2034,22 @@ var UNPUSHED_KINDS = ["conflict", "error"];
|
|
|
2034
2034
|
/** Whether this outcome leaves a local edit that only exists locally, so the
|
|
2035
2035
|
* pull must not overwrite the record and the baseline must not advance. */
|
|
2036
2036
|
var isUnpushed = (kind) => UNPUSHED_KINDS.includes(kind);
|
|
2037
|
-
|
|
2037
|
+
/** The whole report one run produces, from what each record's push did and what
|
|
2038
|
+
* the delete sweep did.
|
|
2039
|
+
*
|
|
2040
|
+
* A sweep's refusal goes to `keptInGoogle`, NEVER to `skipped`: that list means
|
|
2041
|
+
* "this push did not do what you asked", and a refusal means "the event was
|
|
2042
|
+
* deliberately left standing" while the rest of the run carried. Merged, one
|
|
2043
|
+
* refusal took the caller's problem branch and hid every create and update
|
|
2044
|
+
* (#3272).
|
|
2045
|
+
*
|
|
2046
|
+
* Exported because it is pure — a list of attempts and a sweep, no workspace,
|
|
2047
|
+
* grant or clock — so the separation above is something a test drives rather
|
|
2048
|
+
* than argues. */
|
|
2049
|
+
function pushResultFrom(slug, attempts, deletes) {
|
|
2038
2050
|
const outcomes = attempts.map((attempt) => attempt.outcome);
|
|
2039
2051
|
const count = (kind) => outcomes.filter((outcome) => outcome.kind === kind).length;
|
|
2052
|
+
const messagesOf = (kind) => outcomes.flatMap((outcome) => outcome.kind === kind ? [outcome.message] : []);
|
|
2040
2053
|
return {
|
|
2041
2054
|
slug,
|
|
2042
2055
|
created: count("created"),
|
|
@@ -2044,8 +2057,9 @@ function tally(slug, attempts, deletes) {
|
|
|
2044
2057
|
conflicts: count("conflict"),
|
|
2045
2058
|
localDeletes: deletes.seen,
|
|
2046
2059
|
deletedInGoogle: deletes.deleted.length,
|
|
2047
|
-
skipped:
|
|
2048
|
-
|
|
2060
|
+
skipped: messagesOf("skipped"),
|
|
2061
|
+
keptInGoogle: [...deletes.skipped],
|
|
2062
|
+
errors: [...messagesOf("error"), ...deletes.errors],
|
|
2049
2063
|
unpushedIds: attempts.filter((attempt) => isUnpushed(attempt.outcome.kind)).map((attempt) => attempt.eventId)
|
|
2050
2064
|
};
|
|
2051
2065
|
}
|
|
@@ -2175,7 +2189,7 @@ async function pushCollectionNow(collection, workspaceRoot, deps = liveDeps) {
|
|
|
2175
2189
|
});
|
|
2176
2190
|
await saveCalendarShadow(calendarId, pushedShadow([outcome]), workspaceRoot);
|
|
2177
2191
|
}
|
|
2178
|
-
const result =
|
|
2192
|
+
const result = pushResultFrom(slug, attempts, await sweepDeletes(locallyDeletedIds(shadow, records.map((record) => fieldText(record[schema.primaryKey]))), schema.googleCalendar?.propagateDeletes === true, {
|
|
2179
2193
|
fetchEvent: (eventId) => (deps.fetchEvent ?? getCalendarEvent)(accessToken, {
|
|
2180
2194
|
calendarId,
|
|
2181
2195
|
eventId
|
|
@@ -2315,7 +2329,7 @@ function pullableEvents(events, unpushed) {
|
|
|
2315
2329
|
* A conflict or an error means that record now diverges from Google until
|
|
2316
2330
|
* someone resolves it, which must not be silent. */
|
|
2317
2331
|
function reportAutoPush(slug, result) {
|
|
2318
|
-
const { created, updated, conflicts, skipped, errors, unpushedIds } = result;
|
|
2332
|
+
const { created, updated, conflicts, skipped, keptInGoogle, errors, unpushedIds } = result;
|
|
2319
2333
|
if (created + updated > 0) log.info("google", "auto-pushed local calendar edits", {
|
|
2320
2334
|
slug,
|
|
2321
2335
|
created,
|
|
@@ -2330,6 +2344,10 @@ function reportAutoPush(slug, result) {
|
|
|
2330
2344
|
slug,
|
|
2331
2345
|
skipped
|
|
2332
2346
|
});
|
|
2347
|
+
if (keptInGoogle.length > 0) log.warn("google", "deletions the auto push left standing in Google", {
|
|
2348
|
+
slug,
|
|
2349
|
+
keptInGoogle
|
|
2350
|
+
});
|
|
2333
2351
|
if (errors.length > 0) log.warn("google", "auto push errors", {
|
|
2334
2352
|
slug,
|
|
2335
2353
|
errors
|
|
@@ -3264,6 +3282,7 @@ exports.pullableEvents = pullableEvents;
|
|
|
3264
3282
|
exports.pushAndProtect = pushAndProtect;
|
|
3265
3283
|
exports.pushCalendarForCollection = pushCalendarForCollection;
|
|
3266
3284
|
exports.pushCollectionNow = pushCollectionNow;
|
|
3285
|
+
exports.pushResultFrom = pushResultFrom;
|
|
3267
3286
|
exports.pushableMap = pushableMap;
|
|
3268
3287
|
exports.readDriveFile = readDriveFile;
|
|
3269
3288
|
exports.releaseOrphanedCalendarToken = releaseOrphanedCalendarToken;
|