@shin1ohno/sage 0.8.8 → 0.9.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/README.md +31 -0
- package/dist/cli/http-server-with-config.d.ts +2 -0
- package/dist/cli/http-server-with-config.d.ts.map +1 -1
- package/dist/cli/http-server-with-config.js +70 -0
- package/dist/cli/http-server-with-config.js.map +1 -1
- package/dist/cli/main-entry.d.ts.map +1 -1
- package/dist/cli/main-entry.js +1 -0
- package/dist/cli/main-entry.js.map +1 -1
- package/dist/cli/parser.d.ts +2 -0
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +7 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/config/validation.d.ts +721 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +203 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/integrations/calendar-service.d.ts +14 -0
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/integrations/calendar-source-manager.d.ts +86 -3
- package/dist/integrations/calendar-source-manager.d.ts.map +1 -1
- package/dist/integrations/calendar-source-manager.js +146 -15
- package/dist/integrations/calendar-source-manager.js.map +1 -1
- package/dist/integrations/google-calendar-service.d.ts +55 -3
- package/dist/integrations/google-calendar-service.d.ts.map +1 -1
- package/dist/integrations/google-calendar-service.js +213 -4
- package/dist/integrations/google-calendar-service.js.map +1 -1
- package/dist/oauth/encryption-service.d.ts +104 -0
- package/dist/oauth/encryption-service.d.ts.map +1 -0
- package/dist/oauth/encryption-service.js +271 -0
- package/dist/oauth/encryption-service.js.map +1 -0
- package/dist/oauth/file-mutex.d.ts +68 -0
- package/dist/oauth/file-mutex.d.ts.map +1 -0
- package/dist/oauth/file-mutex.js +140 -0
- package/dist/oauth/file-mutex.js.map +1 -0
- package/dist/oauth/google-oauth-handler.d.ts +8 -9
- package/dist/oauth/google-oauth-handler.d.ts.map +1 -1
- package/dist/oauth/google-oauth-handler.js +30 -65
- package/dist/oauth/google-oauth-handler.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/oauth-server.d.ts +61 -1
- package/dist/oauth/oauth-server.d.ts.map +1 -1
- package/dist/oauth/oauth-server.js +181 -40
- package/dist/oauth/oauth-server.js.map +1 -1
- package/dist/oauth/persistent-client-store.d.ts +58 -0
- package/dist/oauth/persistent-client-store.d.ts.map +1 -0
- package/dist/oauth/persistent-client-store.js +187 -0
- package/dist/oauth/persistent-client-store.js.map +1 -0
- package/dist/oauth/persistent-refresh-token-store.d.ts +77 -0
- package/dist/oauth/persistent-refresh-token-store.d.ts.map +1 -0
- package/dist/oauth/persistent-refresh-token-store.js +225 -0
- package/dist/oauth/persistent-refresh-token-store.js.map +1 -0
- package/dist/oauth/persistent-session-store.d.ts +69 -0
- package/dist/oauth/persistent-session-store.d.ts.map +1 -0
- package/dist/oauth/persistent-session-store.js +154 -0
- package/dist/oauth/persistent-session-store.js.map +1 -0
- package/dist/oauth/session-store.d.ts +31 -0
- package/dist/oauth/session-store.d.ts.map +1 -0
- package/dist/oauth/session-store.js +47 -0
- package/dist/oauth/session-store.js.map +1 -0
- package/dist/services/working-cadence.d.ts +37 -1
- package/dist/services/working-cadence.d.ts.map +1 -1
- package/dist/services/working-cadence.js +151 -13
- package/dist/services/working-cadence.js.map +1 -1
- package/dist/tools/calendar/handlers.d.ts +82 -3
- package/dist/tools/calendar/handlers.d.ts.map +1 -1
- package/dist/tools/calendar/handlers.js +200 -16
- package/dist/tools/calendar/handlers.js.map +1 -1
- package/dist/types/google-calendar-types.d.ts +150 -3
- package/dist/types/google-calendar-types.d.ts.map +1 -1
- package/dist/types/google-calendar-types.js +79 -2
- package/dist/types/google-calendar-types.js.map +1 -1
- package/dist/types/task.d.ts +14 -0
- package/dist/types/task.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Session Store
|
|
3
|
+
* Requirements: 26 (Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Stores user sessions with encrypted filesystem persistence.
|
|
6
|
+
* Implements automatic cleanup of expired sessions and session limits.
|
|
7
|
+
*/
|
|
8
|
+
import { UserSession } from './types.js';
|
|
9
|
+
import { EncryptionService } from './encryption-service.js';
|
|
10
|
+
import { SessionStore } from './session-store.js';
|
|
11
|
+
/**
|
|
12
|
+
* Persistent Session Store Implementation
|
|
13
|
+
*
|
|
14
|
+
* Extends SessionStore interface with encrypted filesystem persistence.
|
|
15
|
+
* Maintains in-memory cache for fast access while persisting to disk.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PersistentSessionStore implements SessionStore {
|
|
18
|
+
private sessions;
|
|
19
|
+
private sessionExpiryMs;
|
|
20
|
+
private maxSessions;
|
|
21
|
+
private encryptionService;
|
|
22
|
+
private storagePath;
|
|
23
|
+
constructor(encryptionService: EncryptionService, storagePath?: string);
|
|
24
|
+
/**
|
|
25
|
+
* Load sessions from encrypted file
|
|
26
|
+
*
|
|
27
|
+
* Filters out expired sessions during load.
|
|
28
|
+
* Logs loaded and expired session counts.
|
|
29
|
+
*/
|
|
30
|
+
loadFromStorage(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Save sessions to encrypted file
|
|
33
|
+
*
|
|
34
|
+
* Enforces session limit by keeping only most recent 100 sessions.
|
|
35
|
+
*/
|
|
36
|
+
private saveToStorage;
|
|
37
|
+
/**
|
|
38
|
+
* Create new session
|
|
39
|
+
*
|
|
40
|
+
* Saves asynchronously (fire and forget) to avoid blocking.
|
|
41
|
+
*/
|
|
42
|
+
createSession(userId: string): UserSession;
|
|
43
|
+
/**
|
|
44
|
+
* Get session by ID
|
|
45
|
+
*
|
|
46
|
+
* Checks expiry and removes expired sessions automatically.
|
|
47
|
+
*/
|
|
48
|
+
getSession(sessionId: string): UserSession | null;
|
|
49
|
+
/**
|
|
50
|
+
* Delete session
|
|
51
|
+
*
|
|
52
|
+
* Saves asynchronously after deletion.
|
|
53
|
+
*/
|
|
54
|
+
deleteSession(sessionId: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Flush pending saves
|
|
57
|
+
*
|
|
58
|
+
* Call on server shutdown to ensure all data is persisted.
|
|
59
|
+
*/
|
|
60
|
+
flush(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Get metrics for monitoring
|
|
63
|
+
*/
|
|
64
|
+
getMetrics(): {
|
|
65
|
+
count: number;
|
|
66
|
+
expiredCount: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=persistent-session-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistent-session-store.d.ts","sourceRoot":"","sources":["../../src/oauth/persistent-session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,WAAW,CAAS;gBAEhB,iBAAiB,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,MAAM;IAKtE;;;;;OAKG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAgCtC;;;;OAIG;YACW,aAAa;IAuB3B;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAmB1C;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAgBjD;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAOtC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,UAAU,IAAI;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;KACtB;CAeF"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Session Store
|
|
3
|
+
* Requirements: 26 (Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Stores user sessions with encrypted filesystem persistence.
|
|
6
|
+
* Implements automatic cleanup of expired sessions and session limits.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { randomBytes } from 'crypto';
|
|
11
|
+
/**
|
|
12
|
+
* Persistent Session Store Implementation
|
|
13
|
+
*
|
|
14
|
+
* Extends SessionStore interface with encrypted filesystem persistence.
|
|
15
|
+
* Maintains in-memory cache for fast access while persisting to disk.
|
|
16
|
+
*/
|
|
17
|
+
export class PersistentSessionStore {
|
|
18
|
+
sessions = new Map();
|
|
19
|
+
sessionExpiryMs = 24 * 60 * 60 * 1000; // 24 hours
|
|
20
|
+
maxSessions = 100; // Limit to prevent unbounded growth
|
|
21
|
+
encryptionService;
|
|
22
|
+
storagePath;
|
|
23
|
+
constructor(encryptionService, storagePath) {
|
|
24
|
+
this.encryptionService = encryptionService;
|
|
25
|
+
this.storagePath = storagePath || join(homedir(), '.sage', 'oauth_sessions.enc');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load sessions from encrypted file
|
|
29
|
+
*
|
|
30
|
+
* Filters out expired sessions during load.
|
|
31
|
+
* Logs loaded and expired session counts.
|
|
32
|
+
*/
|
|
33
|
+
async loadFromStorage() {
|
|
34
|
+
const data = await this.encryptionService.decryptFromFile(this.storagePath);
|
|
35
|
+
if (!data) {
|
|
36
|
+
console.log('[OAuth] No existing sessions found, starting fresh');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const storage = JSON.parse(data);
|
|
41
|
+
// Load sessions and filter expired ones
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
let loadedCount = 0;
|
|
44
|
+
let expiredCount = 0;
|
|
45
|
+
for (const session of storage.sessions) {
|
|
46
|
+
if (now < session.expiresAt) {
|
|
47
|
+
this.sessions.set(session.sessionId, session);
|
|
48
|
+
loadedCount++;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
expiredCount++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log(`[OAuth] Loaded ${loadedCount} sessions (${expiredCount} expired sessions cleaned up)`);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('[OAuth] Failed to parse session storage, starting fresh:', error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Save sessions to encrypted file
|
|
62
|
+
*
|
|
63
|
+
* Enforces session limit by keeping only most recent 100 sessions.
|
|
64
|
+
*/
|
|
65
|
+
async saveToStorage() {
|
|
66
|
+
// Enforce session limit by keeping only most recent sessions
|
|
67
|
+
let sessions = Array.from(this.sessions.values());
|
|
68
|
+
if (sessions.length > this.maxSessions) {
|
|
69
|
+
sessions.sort((a, b) => b.createdAt - a.createdAt);
|
|
70
|
+
sessions = sessions.slice(0, this.maxSessions);
|
|
71
|
+
// Update map to reflect limit
|
|
72
|
+
this.sessions.clear();
|
|
73
|
+
for (const session of sessions) {
|
|
74
|
+
this.sessions.set(session.sessionId, session);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const storage = {
|
|
78
|
+
version: 1,
|
|
79
|
+
sessions,
|
|
80
|
+
};
|
|
81
|
+
const data = JSON.stringify(storage, null, 2);
|
|
82
|
+
await this.encryptionService.encryptToFile(data, this.storagePath);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create new session
|
|
86
|
+
*
|
|
87
|
+
* Saves asynchronously (fire and forget) to avoid blocking.
|
|
88
|
+
*/
|
|
89
|
+
createSession(userId) {
|
|
90
|
+
const sessionId = randomBytes(32).toString('hex');
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const session = {
|
|
93
|
+
sessionId,
|
|
94
|
+
userId,
|
|
95
|
+
createdAt: now,
|
|
96
|
+
expiresAt: now + this.sessionExpiryMs,
|
|
97
|
+
};
|
|
98
|
+
this.sessions.set(sessionId, session);
|
|
99
|
+
// Save asynchronously (don't wait)
|
|
100
|
+
this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save session:', err));
|
|
101
|
+
return session;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get session by ID
|
|
105
|
+
*
|
|
106
|
+
* Checks expiry and removes expired sessions automatically.
|
|
107
|
+
*/
|
|
108
|
+
getSession(sessionId) {
|
|
109
|
+
const session = this.sessions.get(sessionId);
|
|
110
|
+
if (!session)
|
|
111
|
+
return null;
|
|
112
|
+
// Check expiry
|
|
113
|
+
if (Date.now() > session.expiresAt) {
|
|
114
|
+
this.sessions.delete(sessionId);
|
|
115
|
+
this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save after session expiry:', err));
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return session;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Delete session
|
|
122
|
+
*
|
|
123
|
+
* Saves asynchronously after deletion.
|
|
124
|
+
*/
|
|
125
|
+
deleteSession(sessionId) {
|
|
126
|
+
this.sessions.delete(sessionId);
|
|
127
|
+
this.saveToStorage().catch((err) => console.error('[OAuth] Failed to save after session deletion:', err));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Flush pending saves
|
|
131
|
+
*
|
|
132
|
+
* Call on server shutdown to ensure all data is persisted.
|
|
133
|
+
*/
|
|
134
|
+
async flush() {
|
|
135
|
+
await this.saveToStorage();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get metrics for monitoring
|
|
139
|
+
*/
|
|
140
|
+
getMetrics() {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
let expiredCount = 0;
|
|
143
|
+
for (const session of this.sessions.values()) {
|
|
144
|
+
if (session.expiresAt < now) {
|
|
145
|
+
expiredCount++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
count: this.sessions.size,
|
|
150
|
+
expiredCount,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=persistent-session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistent-session-store.js","sourceRoot":"","sources":["../../src/oauth/persistent-session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Store
|
|
3
|
+
* Requirements: 26 (Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Manages user sessions for OAuth authentication flow.
|
|
6
|
+
* Sessions track authenticated users during the consent process.
|
|
7
|
+
*/
|
|
8
|
+
import { UserSession } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Session Store Interface
|
|
11
|
+
*/
|
|
12
|
+
export interface SessionStore {
|
|
13
|
+
createSession(userId: string): UserSession;
|
|
14
|
+
getSession(sessionId: string): UserSession | null;
|
|
15
|
+
deleteSession(sessionId: string): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* In-memory Session Store Implementation
|
|
19
|
+
*/
|
|
20
|
+
export declare class InMemorySessionStore implements SessionStore {
|
|
21
|
+
private sessions;
|
|
22
|
+
private sessionExpiryMs;
|
|
23
|
+
createSession(userId: string): UserSession;
|
|
24
|
+
getSession(sessionId: string): UserSession | null;
|
|
25
|
+
deleteSession(sessionId: string): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a Session Store instance
|
|
29
|
+
*/
|
|
30
|
+
export declare function createSessionStore(): SessionStore;
|
|
31
|
+
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/oauth/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,eAAe,CAAuB;IAE9C,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAa1C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAUjD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGvC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Store
|
|
3
|
+
* Requirements: 26 (Session Management)
|
|
4
|
+
*
|
|
5
|
+
* Manages user sessions for OAuth authentication flow.
|
|
6
|
+
* Sessions track authenticated users during the consent process.
|
|
7
|
+
*/
|
|
8
|
+
import { randomBytes } from 'crypto';
|
|
9
|
+
/**
|
|
10
|
+
* In-memory Session Store Implementation
|
|
11
|
+
*/
|
|
12
|
+
export class InMemorySessionStore {
|
|
13
|
+
sessions = new Map();
|
|
14
|
+
sessionExpiryMs = 24 * 60 * 60 * 1000; // 24 hours
|
|
15
|
+
createSession(userId) {
|
|
16
|
+
const sessionId = randomBytes(32).toString('hex');
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const session = {
|
|
19
|
+
sessionId,
|
|
20
|
+
userId,
|
|
21
|
+
createdAt: now,
|
|
22
|
+
expiresAt: now + this.sessionExpiryMs,
|
|
23
|
+
};
|
|
24
|
+
this.sessions.set(sessionId, session);
|
|
25
|
+
return session;
|
|
26
|
+
}
|
|
27
|
+
getSession(sessionId) {
|
|
28
|
+
const session = this.sessions.get(sessionId);
|
|
29
|
+
if (!session)
|
|
30
|
+
return null;
|
|
31
|
+
if (Date.now() > session.expiresAt) {
|
|
32
|
+
this.sessions.delete(sessionId);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
deleteSession(sessionId) {
|
|
38
|
+
this.sessions.delete(sessionId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a Session Store instance
|
|
43
|
+
*/
|
|
44
|
+
export function createSessionStore() {
|
|
45
|
+
return new InMemorySessionStore();
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/oauth/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAYrC;;GAEG;AACH,MAAM,OAAO,oBAAoB;IACvB,QAAQ,GAA6B,IAAI,GAAG,EAAE,CAAC;IAC/C,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;IAE1D,aAAa,CAAC,MAAc;QAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAgB;YAC3B,SAAS;YACT,MAAM;YACN,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG,GAAG,IAAI,CAAC,eAAe;SACtC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;YACnC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,aAAa,CAAC,SAAiB;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,oBAAoB,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - Scheduling recommendations
|
|
9
9
|
*/
|
|
10
10
|
import type { CalendarConfig, DeepWorkBlock } from '../types/config.js';
|
|
11
|
+
import type { CalendarSourceManager } from '../integrations/calendar-source-manager.js';
|
|
11
12
|
export interface GetWorkingCadenceRequest {
|
|
12
13
|
/** Get info for a specific day of week */
|
|
13
14
|
dayOfWeek?: 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday';
|
|
@@ -55,6 +56,18 @@ export interface WorkingCadenceResult {
|
|
|
55
56
|
recommendations: SchedulingRecommendation[];
|
|
56
57
|
/** Generated summary message */
|
|
57
58
|
summary: string;
|
|
59
|
+
/** Focus time statistics from calendar events (optional) */
|
|
60
|
+
focusTimeStats?: {
|
|
61
|
+
/** Total focus time blocks by day of week */
|
|
62
|
+
focusTimeBlocks: Array<{
|
|
63
|
+
day: string;
|
|
64
|
+
duration: number;
|
|
65
|
+
}>;
|
|
66
|
+
/** Days detected as deep work from focusTime events (>=4h) */
|
|
67
|
+
detectedDeepWorkDays: string[];
|
|
68
|
+
/** Whether deep work days were enhanced by focusTime analysis */
|
|
69
|
+
enhanced: boolean;
|
|
70
|
+
};
|
|
58
71
|
}
|
|
59
72
|
export interface DeepWorkBlockInfo {
|
|
60
73
|
day: string;
|
|
@@ -73,6 +86,12 @@ export interface SchedulingRecommendation {
|
|
|
73
86
|
reason: string;
|
|
74
87
|
}
|
|
75
88
|
export declare class WorkingCadenceService {
|
|
89
|
+
private calendarSourceManager?;
|
|
90
|
+
/**
|
|
91
|
+
* Constructor
|
|
92
|
+
* @param calendarSourceManager Optional CalendarSourceManager for focusTime analysis
|
|
93
|
+
*/
|
|
94
|
+
constructor(calendarSourceManager?: CalendarSourceManager);
|
|
76
95
|
/**
|
|
77
96
|
* Get working cadence information
|
|
78
97
|
*/
|
|
@@ -87,8 +106,10 @@ export declare class WorkingCadenceService {
|
|
|
87
106
|
getDayOfWeek(date: string): string;
|
|
88
107
|
/**
|
|
89
108
|
* Generate scheduling recommendations based on calendar config
|
|
109
|
+
* @param config Calendar configuration
|
|
110
|
+
* @param focusTimeStats Optional focus time statistics for enhanced recommendations
|
|
90
111
|
*/
|
|
91
|
-
generateRecommendations(config: CalendarConfig): SchedulingRecommendation[];
|
|
112
|
+
generateRecommendations(config: CalendarConfig, focusTimeStats?: WorkingCadenceResult['focusTimeStats']): SchedulingRecommendation[];
|
|
92
113
|
/**
|
|
93
114
|
* Calculate working hours info from config
|
|
94
115
|
*/
|
|
@@ -115,5 +136,20 @@ export declare class WorkingCadenceService {
|
|
|
115
136
|
private formatHour;
|
|
116
137
|
private isValidDate;
|
|
117
138
|
private errorResult;
|
|
139
|
+
/**
|
|
140
|
+
* Analyze focus time events from calendar
|
|
141
|
+
* Filters events with eventType='focusTime' and calculates total focus time per day
|
|
142
|
+
* @param events Calendar events to analyze
|
|
143
|
+
* @returns Object containing focus time blocks with day and duration
|
|
144
|
+
*/
|
|
145
|
+
private analyzeFocusTimeEvents;
|
|
146
|
+
/**
|
|
147
|
+
* Enhance deep work detection by combining config settings with focus time analysis
|
|
148
|
+
* Days with >=4h (240 minutes) of focusTime events are considered deep work days
|
|
149
|
+
* @param configDeepWorkDays Deep work days from configuration
|
|
150
|
+
* @param focusTimeBlocks Focus time blocks from calendar analysis
|
|
151
|
+
* @returns Combined list of deep work days (unique values)
|
|
152
|
+
*/
|
|
153
|
+
private enhanceDeepWorkDetection;
|
|
118
154
|
}
|
|
119
155
|
//# sourceMappingURL=working-cadence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"working-cadence.d.ts","sourceRoot":"","sources":["../../src/services/working-cadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAc,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"working-cadence.d.ts","sourceRoot":"","sources":["../../src/services/working-cadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAc,MAAM,oBAAoB,CAAC;AAEpF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAGxF,MAAM,WAAW,wBAAwB;IACvC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC/F,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,uBAAuB;IACvB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAEF,oBAAoB;IACpB,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,qBAAqB;IACrB,aAAa,EAAE;QACb,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;IAEF,4CAA4C;IAC5C,cAAc,EAAE,iBAAiB,EAAE,CAAC;IAEpC,6BAA6B;IAC7B,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF,oEAAoE;IACpE,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,WAAW,GAAG,eAAe,GAAG,QAAQ,CAAC;QAClD,cAAc,EAAE,iBAAiB,EAAE,CAAC;QACpC,eAAe,EAAE,MAAM,EAAE,CAAC;KAC3B,CAAC;IAEF,iCAAiC;IACjC,eAAe,EAAE,wBAAwB,EAAE,CAAC;IAE5C,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,4DAA4D;IAC5D,cAAc,CAAC,EAAE;QACf,6CAA6C;QAC7C,eAAe,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC1D,8DAA8D;QAC9D,oBAAoB,EAAE,MAAM,EAAE,CAAC;QAC/B,iEAAiE;QACjE,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,qBAAqB,CAAC,CAAwB;IAEtD;;;OAGG;gBACS,qBAAqB,CAAC,EAAE,qBAAqB;IAIzD;;OAEG;IACG,iBAAiB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA4H1F;;OAEG;IACH,UAAU,CACR,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC7B,WAAW,GAAG,eAAe,GAAG,QAAQ;IAU3C;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMlC;;;;OAIG;IACH,uBAAuB,CACrB,MAAM,EAAE,cAAc,EACtB,cAAc,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,GACtD,wBAAwB,EAAE;IA2D7B;;OAEG;IACH,qBAAqB,CAAC,YAAY,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QACnE,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;KACtB;IAYD;;OAEG;IACH,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,iBAAiB,EAAE;IAYrE;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM;IAOlC,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,oBAAoB;IAwD5B,OAAO,CAAC,eAAe;IA4CvB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAanB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAwB9B;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;CAajC"}
|
|
@@ -20,6 +20,14 @@ const DAY_MAP = {
|
|
|
20
20
|
Sunday: '日',
|
|
21
21
|
};
|
|
22
22
|
export class WorkingCadenceService {
|
|
23
|
+
calendarSourceManager;
|
|
24
|
+
/**
|
|
25
|
+
* Constructor
|
|
26
|
+
* @param calendarSourceManager Optional CalendarSourceManager for focusTime analysis
|
|
27
|
+
*/
|
|
28
|
+
constructor(calendarSourceManager) {
|
|
29
|
+
this.calendarSourceManager = calendarSourceManager;
|
|
30
|
+
}
|
|
23
31
|
/**
|
|
24
32
|
* Get working cadence information
|
|
25
33
|
*/
|
|
@@ -46,20 +54,56 @@ export class WorkingCadenceService {
|
|
|
46
54
|
}
|
|
47
55
|
// Calculate working hours
|
|
48
56
|
const workingHours = this.calculateWorkingHours(config.calendar.workingHours);
|
|
49
|
-
//
|
|
50
|
-
|
|
57
|
+
// Load calendar events and analyze focusTime (if CalendarSourceManager is available)
|
|
58
|
+
let focusTimeStats;
|
|
59
|
+
let enhancedDeepWorkDays = config.calendar.deepWorkDays;
|
|
60
|
+
if (this.calendarSourceManager) {
|
|
61
|
+
try {
|
|
62
|
+
// Get events from the past 4 weeks for better weekly pattern analysis
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const startDate = new Date(now);
|
|
65
|
+
startDate.setDate(startDate.getDate() - 28);
|
|
66
|
+
const endDate = new Date(now);
|
|
67
|
+
endDate.setDate(endDate.getDate() + 7); // Include next week for upcoming focusTime
|
|
68
|
+
const events = await this.calendarSourceManager.getEvents(startDate.toISOString(), endDate.toISOString());
|
|
69
|
+
// Analyze focusTime events
|
|
70
|
+
const { focusTimeBlocks } = this.analyzeFocusTimeEvents(events);
|
|
71
|
+
// Enhance deep work detection with focusTime analysis
|
|
72
|
+
const detectedDeepWorkDays = focusTimeBlocks
|
|
73
|
+
.filter((block) => block.duration >= 240) // >=4 hours
|
|
74
|
+
.map((block) => block.day);
|
|
75
|
+
enhancedDeepWorkDays = this.enhanceDeepWorkDetection(config.calendar.deepWorkDays, focusTimeBlocks);
|
|
76
|
+
// Check if any new days were detected from focusTime
|
|
77
|
+
const enhanced = detectedDeepWorkDays.some((day) => !config.calendar.deepWorkDays.includes(day));
|
|
78
|
+
focusTimeStats = {
|
|
79
|
+
focusTimeBlocks,
|
|
80
|
+
detectedDeepWorkDays,
|
|
81
|
+
enhanced,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// If calendar loading fails, continue without focusTime analysis
|
|
86
|
+
console.error('Failed to load calendar events for focusTime analysis:', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Build weekly pattern with enhanced deep work days
|
|
90
|
+
const enhancedCalendarConfig = {
|
|
91
|
+
...config.calendar,
|
|
92
|
+
deepWorkDays: enhancedDeepWorkDays,
|
|
93
|
+
};
|
|
94
|
+
const weeklyPattern = this.buildWeeklyPattern(enhancedCalendarConfig);
|
|
51
95
|
// Transform deep work blocks
|
|
52
96
|
const deepWorkBlocks = this.transformDeepWorkBlocks(config.calendar.deepWorkBlocks);
|
|
53
|
-
// Generate recommendations
|
|
54
|
-
const recommendations = this.generateRecommendations(
|
|
55
|
-
// Build specific day info if requested
|
|
97
|
+
// Generate recommendations (with enhanced config for focusTime-aware recommendations)
|
|
98
|
+
const recommendations = this.generateRecommendations(enhancedCalendarConfig, focusTimeStats);
|
|
99
|
+
// Build specific day info if requested (using enhanced deep work days)
|
|
56
100
|
let specificDay;
|
|
57
101
|
if (request?.dayOfWeek || request?.date) {
|
|
58
102
|
const dayOfWeek = request.dayOfWeek || this.getDayOfWeek(request.date);
|
|
59
|
-
specificDay = this.buildSpecificDayInfo(dayOfWeek, request.date,
|
|
103
|
+
specificDay = this.buildSpecificDayInfo(dayOfWeek, request.date, enhancedCalendarConfig, deepWorkBlocks, focusTimeStats);
|
|
60
104
|
}
|
|
61
|
-
// Generate summary
|
|
62
|
-
const summary = this.generateSummary(workingHours, weeklyPattern, config.reminders.weeklyReview);
|
|
105
|
+
// Generate summary (with focusTime info if available)
|
|
106
|
+
const summary = this.generateSummary(workingHours, weeklyPattern, config.reminders.weeklyReview, focusTimeStats);
|
|
63
107
|
return {
|
|
64
108
|
success: true,
|
|
65
109
|
user: {
|
|
@@ -73,6 +117,7 @@ export class WorkingCadenceService {
|
|
|
73
117
|
specificDay,
|
|
74
118
|
recommendations,
|
|
75
119
|
summary,
|
|
120
|
+
focusTimeStats,
|
|
76
121
|
};
|
|
77
122
|
}
|
|
78
123
|
/**
|
|
@@ -97,15 +142,25 @@ export class WorkingCadenceService {
|
|
|
97
142
|
}
|
|
98
143
|
/**
|
|
99
144
|
* Generate scheduling recommendations based on calendar config
|
|
145
|
+
* @param config Calendar configuration
|
|
146
|
+
* @param focusTimeStats Optional focus time statistics for enhanced recommendations
|
|
100
147
|
*/
|
|
101
|
-
generateRecommendations(config) {
|
|
148
|
+
generateRecommendations(config, focusTimeStats) {
|
|
102
149
|
const recommendations = [];
|
|
103
150
|
if (config.deepWorkDays.length > 0) {
|
|
151
|
+
let reason = 'これらの日はDeep Work日として設定されており、集中作業に適しています';
|
|
152
|
+
// Enhance reason if days were detected from focusTime events
|
|
153
|
+
if (focusTimeStats?.enhanced && focusTimeStats.detectedDeepWorkDays.length > 0) {
|
|
154
|
+
const detectedDays = focusTimeStats.detectedDeepWorkDays.filter((day) => !config.deepWorkDays.includes(day));
|
|
155
|
+
if (detectedDays.length > 0) {
|
|
156
|
+
reason += ` (${this.formatDays(detectedDays)}はFocus Timeイベントから検出)`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
104
159
|
recommendations.push({
|
|
105
160
|
type: 'deep-work',
|
|
106
161
|
recommendation: `複雑なタスクは${this.formatDays(config.deepWorkDays)}にスケジュールしてください`,
|
|
107
162
|
bestDays: config.deepWorkDays,
|
|
108
|
-
reason
|
|
163
|
+
reason,
|
|
109
164
|
});
|
|
110
165
|
}
|
|
111
166
|
if (config.meetingHeavyDays.length > 0) {
|
|
@@ -125,6 +180,17 @@ export class WorkingCadenceService {
|
|
|
125
180
|
reason: 'ミーティング日の合間は短いタスクに適しています',
|
|
126
181
|
});
|
|
127
182
|
}
|
|
183
|
+
// Focus time recommendation if focusTime events exist
|
|
184
|
+
if (focusTimeStats && focusTimeStats.focusTimeBlocks.length > 0) {
|
|
185
|
+
const focusTimeDays = focusTimeStats.focusTimeBlocks.map((block) => block.day);
|
|
186
|
+
const uniqueFocusTimeDays = [...new Set(focusTimeDays)];
|
|
187
|
+
recommendations.push({
|
|
188
|
+
type: 'deep-work',
|
|
189
|
+
recommendation: `${this.formatDays(uniqueFocusTimeDays)}には既存のFocus Timeブロックがあります。この時間を活用してください`,
|
|
190
|
+
bestDays: uniqueFocusTimeDays,
|
|
191
|
+
reason: 'カレンダーにFocus Timeイベントが登録されています。集中作業に最適な時間です',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
128
194
|
return recommendations;
|
|
129
195
|
}
|
|
130
196
|
/**
|
|
@@ -171,7 +237,7 @@ export class WorkingCadenceService {
|
|
|
171
237
|
normalDays,
|
|
172
238
|
};
|
|
173
239
|
}
|
|
174
|
-
buildSpecificDayInfo(dayOfWeek, date, calendarConfig, allDeepWorkBlocks) {
|
|
240
|
+
buildSpecificDayInfo(dayOfWeek, date, calendarConfig, allDeepWorkBlocks, focusTimeStats) {
|
|
175
241
|
const dayType = this.getDayType(dayOfWeek, calendarConfig);
|
|
176
242
|
const dayBlocks = allDeepWorkBlocks.filter((block) => block.day === dayOfWeek);
|
|
177
243
|
const recommendations = [];
|
|
@@ -181,12 +247,32 @@ export class WorkingCadenceService {
|
|
|
181
247
|
const times = dayBlocks.map((b) => `${b.startTime}-${b.endTime}`).join(', ');
|
|
182
248
|
recommendations.push(`Deep Workブロック: ${times}`);
|
|
183
249
|
}
|
|
250
|
+
// Add focusTime info if detected from calendar events
|
|
251
|
+
if (focusTimeStats?.detectedDeepWorkDays.includes(dayOfWeek)) {
|
|
252
|
+
const focusTimeBlock = focusTimeStats.focusTimeBlocks.find((b) => b.day === dayOfWeek);
|
|
253
|
+
if (focusTimeBlock) {
|
|
254
|
+
const hours = Math.floor(focusTimeBlock.duration / 60);
|
|
255
|
+
const minutes = Math.round(focusTimeBlock.duration % 60);
|
|
256
|
+
const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
|
|
257
|
+
recommendations.push(`Focus Timeイベント: 合計${durationStr}のFocus Timeが登録されています`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
184
260
|
}
|
|
185
261
|
else if (dayType === 'meeting-heavy') {
|
|
186
262
|
recommendations.push('この日はミーティングが多い日です。ミーティングの合間に短いタスクを処理してください。');
|
|
187
263
|
}
|
|
188
264
|
else {
|
|
189
265
|
recommendations.push('この日は特別な設定がありません。自由にスケジュールを組めます。');
|
|
266
|
+
// Check if there are focusTime blocks on this day
|
|
267
|
+
if (focusTimeStats) {
|
|
268
|
+
const focusTimeBlock = focusTimeStats.focusTimeBlocks.find((b) => b.day === dayOfWeek);
|
|
269
|
+
if (focusTimeBlock) {
|
|
270
|
+
const hours = Math.floor(focusTimeBlock.duration / 60);
|
|
271
|
+
const minutes = Math.round(focusTimeBlock.duration % 60);
|
|
272
|
+
const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
|
|
273
|
+
recommendations.push(`Focus Timeイベント: 合計${durationStr}のFocus Timeが登録されています`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
190
276
|
}
|
|
191
277
|
return {
|
|
192
278
|
date,
|
|
@@ -196,11 +282,16 @@ export class WorkingCadenceService {
|
|
|
196
282
|
recommendations,
|
|
197
283
|
};
|
|
198
284
|
}
|
|
199
|
-
generateSummary(workingHours, weeklyPattern, weeklyReview) {
|
|
285
|
+
generateSummary(workingHours, weeklyPattern, weeklyReview, focusTimeStats) {
|
|
200
286
|
const lines = [];
|
|
201
287
|
lines.push(`勤務時間: ${workingHours.start}-${workingHours.end} (${Math.floor(workingHours.totalMinutes / 60)}時間)`);
|
|
202
288
|
if (weeklyPattern.deepWorkDays.length > 0) {
|
|
203
|
-
|
|
289
|
+
let deepWorkLine = `Deep Work日: ${this.formatDays(weeklyPattern.deepWorkDays)}`;
|
|
290
|
+
// Add indicator if days were enhanced by focusTime analysis
|
|
291
|
+
if (focusTimeStats?.enhanced) {
|
|
292
|
+
deepWorkLine += ' (Focus Time分析により強化)';
|
|
293
|
+
}
|
|
294
|
+
lines.push(deepWorkLine);
|
|
204
295
|
}
|
|
205
296
|
if (weeklyPattern.meetingHeavyDays.length > 0) {
|
|
206
297
|
lines.push(`ミーティング集中日: ${this.formatDays(weeklyPattern.meetingHeavyDays)}`);
|
|
@@ -208,6 +299,14 @@ export class WorkingCadenceService {
|
|
|
208
299
|
if (weeklyReview?.enabled) {
|
|
209
300
|
lines.push(`週次レビュー: ${DAY_MAP[weeklyReview.day] || weeklyReview.day}曜 ${weeklyReview.time}`);
|
|
210
301
|
}
|
|
302
|
+
// Add focusTime summary if available
|
|
303
|
+
if (focusTimeStats && focusTimeStats.focusTimeBlocks.length > 0) {
|
|
304
|
+
const totalFocusMinutes = focusTimeStats.focusTimeBlocks.reduce((sum, block) => sum + block.duration, 0);
|
|
305
|
+
const hours = Math.floor(totalFocusMinutes / 60);
|
|
306
|
+
const minutes = Math.round(totalFocusMinutes % 60);
|
|
307
|
+
const durationStr = minutes > 0 ? `${hours}時間${minutes}分` : `${hours}時間`;
|
|
308
|
+
lines.push(`Focus Time: 週合計${durationStr}`);
|
|
309
|
+
}
|
|
211
310
|
return lines.join('\n');
|
|
212
311
|
}
|
|
213
312
|
timeToMinutes(time) {
|
|
@@ -233,5 +332,44 @@ export class WorkingCadenceService {
|
|
|
233
332
|
summary: '',
|
|
234
333
|
};
|
|
235
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Analyze focus time events from calendar
|
|
337
|
+
* Filters events with eventType='focusTime' and calculates total focus time per day
|
|
338
|
+
* @param events Calendar events to analyze
|
|
339
|
+
* @returns Object containing focus time blocks with day and duration
|
|
340
|
+
*/
|
|
341
|
+
analyzeFocusTimeEvents(events) {
|
|
342
|
+
// Filter events with eventType='focusTime'
|
|
343
|
+
const focusTimeEvents = events.filter((e) => e.eventType === 'focusTime');
|
|
344
|
+
// Calculate total focus time per day
|
|
345
|
+
const focusTimeByDay = new Map();
|
|
346
|
+
for (const event of focusTimeEvents) {
|
|
347
|
+
const day = new Date(event.start).toLocaleDateString('en-US', { weekday: 'long' });
|
|
348
|
+
const duration = (new Date(event.end).getTime() - new Date(event.start).getTime()) / (1000 * 60); // minutes
|
|
349
|
+
focusTimeByDay.set(day, (focusTimeByDay.get(day) || 0) + duration);
|
|
350
|
+
}
|
|
351
|
+
// Return focus time statistics
|
|
352
|
+
const focusTimeBlocks = Array.from(focusTimeByDay.entries()).map(([day, duration]) => ({
|
|
353
|
+
day,
|
|
354
|
+
duration,
|
|
355
|
+
}));
|
|
356
|
+
return { focusTimeBlocks };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Enhance deep work detection by combining config settings with focus time analysis
|
|
360
|
+
* Days with >=4h (240 minutes) of focusTime events are considered deep work days
|
|
361
|
+
* @param configDeepWorkDays Deep work days from configuration
|
|
362
|
+
* @param focusTimeBlocks Focus time blocks from calendar analysis
|
|
363
|
+
* @returns Combined list of deep work days (unique values)
|
|
364
|
+
*/
|
|
365
|
+
enhanceDeepWorkDetection(configDeepWorkDays, focusTimeBlocks) {
|
|
366
|
+
// Days with >=4h (240 minutes) of focusTime events are considered deep work days
|
|
367
|
+
const focusTimeDays = focusTimeBlocks
|
|
368
|
+
.filter((block) => block.duration >= 240)
|
|
369
|
+
.map((block) => block.day);
|
|
370
|
+
// Combine config.deepWorkDays with focusTime analysis (remove duplicates)
|
|
371
|
+
const allDeepWorkDays = new Set([...configDeepWorkDays, ...focusTimeDays]);
|
|
372
|
+
return Array.from(allDeepWorkDays);
|
|
373
|
+
}
|
|
236
374
|
}
|
|
237
375
|
//# sourceMappingURL=working-cadence.js.map
|